using Hawkeye.VisionBuilder.Workflow.Datatypes;
using Hawkeye.VisionBuilder.Workflow.Links;
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
using Microsoft.Scripting.Hosting;
using Ninject.Infrastructure.Language;
using OpenCvSharp;
using System.Diagnostics;
using System.Reflection;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using Serilog;
using RectangleElement = Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle.RectangleElement;

namespace Hawkeye.VisionBuilder.Workflow
{
    public abstract class BaseOperation
    {
        private static readonly ScriptScope _scope;
        private static readonly ScriptEngine _engine;

        static BaseOperation()
        {
            _engine = IronPython.Hosting.Python.CreateEngine();
            _scope = _engine.CreateScope();
        }
        protected BaseOperation()
        {
            TypeName = MakeOperationName(this.GetType().Name);
            Label = MakeOperationName(this.GetType().Name,false);

            
        }

        public static void SetScriptValue<T>(string name, T value)
        {
            _scope.SetVariable(name,value);
        }

        public static T GetScriptValue<T>(ScriptValue scriptValue)
        {
            return _engine.Execute<T>(scriptValue.Script,_scope);
        }
        [NotForTool]
        public bool CanHaveProcessingError { get; set; } = true;
        [NotForTool]
        public string Status { get; set; }

        protected bool CheckImageExists(Context context)
        {
            if (context.ActiveImage == null)
            {
                Status = "No image available";
                Result = false;
                return false;
            }

            return true;
        }

        protected bool CheckColorful(Context context)
        {
            if (context.ActiveImage.ImageData.Type() != MatType.CV_8UC3)
            {
                Status="Image is not colorful";
                Result = false;
                return false;
            }

            return true;
        }

        protected bool CheckGrayscale(Context context)
        {
            if (context.ActiveImage.ImageData.Type() != MatType.CV_8UC1)
            {
                Status="Image is not grayscale";
                Result = false;
                return false;
            }

            return true;
        }
        [NotForTool]
        public bool NeedsUpdate { get; set; } = true;
        [NotForTool]
        public Guid Id { get; protected set; }= Guid.NewGuid();
        public static string MakeOperationName(string typeName,bool useSpaces=true)
        {
            return Regex.Replace(typeName.Replace("Operation", ""), "(\\B[A-Z]+?(?=[A-Z][^A-Z])|\\B[A-Z]+?(?=[^A-Z]))", (useSpaces?" ":"")+"$1");
        }
        [NotForTool]
        public bool Result { get; set; } = true;
        [NotForTool]
        public string ResultString
        {
            get
            {
                return this.ToString();
            }
        }
        [NotForTool]
        public string TypeName { get; private set; }
        [NotForTool]
        public string Label { get; set; }

        [NotForTool]
        public bool Enabled { get; set; } = true;

        [NotForTool]
        public TimeSpan ExecutionTime { get; set; }
        [NotForTool]
        public string ExecutionTimeString
        {
            get
            {
                return ((int)ExecutionTime.TotalMilliseconds) + " ms";
            }
        }

        public void Interpret(Context context)
        {
            
            
            Result = true;
            Status = "";
            Stopwatch sw = Stopwatch.StartNew();
            InterpretInternal(context);
            sw.Stop();
            ExecutionTime = sw.Elapsed;
            _scope.SetVariable(Label,this);
            
        }

        protected abstract void InterpretInternal(Context context);

        public Dictionary<string, object> GetParameters()
        {

            var props = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(x=>!x.HasAttribute(typeof(NotForToolAttribute)));
            return props.ToDictionary(x => x.Name, x => x.GetValue(this)!);
        }

        public virtual void SetParameters(Dictionary<string, object> parameters)
        {
            foreach (KeyValuePair<string, object> pair in parameters)
            {
                this.GetType().GetProperty(pair.Key,
                    BindingFlags.Public | BindingFlags.Instance )
                    ?.SetValue(this,pair.Value);
            }
        }


        public virtual void SaveXML(XmlWriter writer)
        {
            writer.WriteAttributeString("Id", Id.ToString());
            writer.WriteAttributeString("Label", Label);

            // Save all public properties that are not marked with NotForTool
            var properties = GetParameters();
            foreach (var property in properties)
            {
                if (property.Value != null)
                {
                    writer.WriteStartElement("Property");
                    writer.WriteAttributeString("Name", property.Key);
                    writer.WriteAttributeString("Type", property.Value.GetType().FullName);

                    // Handle different types appropriately
                    if (property.Value is string || property.Value.GetType().IsPrimitive || property.Value.GetType().IsEnum)
                    {
                        writer.WriteString(property.Value.ToString());
                    }
                    else if (property.Value is Guid guid)
                    {
                        writer.WriteString(guid.ToString());
                    }
                    else
                    {
                        // For complex types, convert to string representation
                        writer.WriteString(property.Value.ToString());
                    }

                    writer.WriteEndElement();
                }
            }
        }

        public virtual void LoadXML(XmlReader reader)
        {
            if (reader.GetAttribute("Id") != null)
                Id = new Guid(reader.GetAttribute("Id"));
            if (reader.GetAttribute("Label") != null)
                Label = reader.GetAttribute("Label");

            var properties = new Dictionary<string, object>();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element && reader.Name == "Property")
                {
                    string propertyName = reader.GetAttribute("Name");
                    string propertyType = reader.GetAttribute("Type");
                    string propertyValue = reader.ReadElementContentAsString();

                    if (!string.IsNullOrEmpty(propertyName) && !string.IsNullOrEmpty(propertyValue))
                    {
                        // Convert string value back to appropriate type
                        object convertedValue = ConvertFromString(propertyValue, propertyType);
                        if (convertedValue != null)
                        {
                            properties[propertyName] = convertedValue;
                        }
                    }
                }
                else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == GetOperationElementName())
                {
                    break;
                }
            }

            if (properties.Count > 0)
            {
                SetParameters(properties);
            }
        }

        private string GetOperationElementName()
        {
            return this.GetType().Name;
        }

        private object ConvertFromString(string value, string typeName)
        {
            try
            {
                var type = Type.GetType(typeName);
                if (type == null) return value;

                if (type == typeof(string))
                    return value;
                else if (type == typeof(int))
                    return int.Parse(value);
                else if (type == typeof(double))
                    return double.Parse(value);
                else if (type == typeof(float))
                    return float.Parse(value);
                else if (type == typeof(bool))
                    return bool.Parse(value);
                else if (type == typeof(Guid))
                    return new Guid(value);
                else if (type.IsEnum)
                    return Enum.Parse(type, value);
                else
                    return value; // Fallback to string
            }
            catch
            {
                return value; // Fallback to string if conversion fails
            }
        }


        public override string ToString()
        {
            return Status;
        }

        public virtual void Save(BinaryWriter bw)
        {
            bw.Write(Id.ToString());
            bw.Write(Label);
        }

        public virtual void Load(BinaryReader br)
        {
            Id = new Guid(br.ReadString());
            Label = br.ReadString();
        }

        public virtual void Save(Dictionary<string, object> dict)
        {
            dict[nameof(Id)] = Id.ToString();
            dict[nameof(Label)] = Label;
            dict[nameof(Enabled)] = Enabled;
        }

        public virtual void Load(Dictionary<string, object> dict)
        {
            if (dict.ContainsKey(nameof(Id)))
                Id = new Guid(dict[nameof(Id)].ToString());
            if (dict.ContainsKey(nameof(Label)))
                Label = dict[nameof(Label)].ToString();
            if (dict.ContainsKey(nameof(Enabled)))
                Enabled = Convert.ToBoolean(dict[nameof(Enabled)]);
        }

        public void SetError(string eMessage)
        {
            Log.Information($"Processing error on {Label}:{eMessage}");
            Status=eMessage;
            Result = false;
        }
    }
}