using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using PXP.Crawler.API.Designers;
using PXP.Crawler.API.Designers.Controls;
using PXP.Crawler.API.Designers.Forms;
using PXP.Crawler.API.Properties;
using NodePinDirPXP.Crawler.API.Utils;
using PXP.Crawler.API.Utils;
using ScintillaNET;

namespace PXP.Crawler.API.Model
{
    
    public class Node : MarshalByRefObject, IDisposable, IDesigner<Node>
    {
        #region Declarations

        public enum ActionState : int
        {
            NotInitialized = 0,
            Stopped,
            Running,
            Finished
        }

        #endregion

        #region Variables

        protected internal Project project;
        protected internal Program program;
        internal ActionState state = ActionState.NotInitialized;
        protected internal Designer<Node> designer;
        internal DesignerData designerData = new DesignerData();
        internal int id = -1;
        internal string category = "General";

        protected internal SortedDictionary<string, NodeConnectionProperty> inPropConns =
            new SortedDictionary<string, NodeConnectionProperty>();

        protected internal SortedDictionary<string, SortedDictionary<int, NodeConnectionProperty>> outPropConns =
            new SortedDictionary<string, SortedDictionary<int, NodeConnectionProperty>>();

        protected internal NodeForm nodeForm = null;
        internal string description = "";

        protected internal bool designerSizing = false;

        protected internal string nodeName = "";

        #endregion

        #region Constructor / Destructor

        public Node(Program program)
        {
            this.program = program;
            this.project = program.project;

            NodeInfo nInfo = GetType().GetCustomAttribute<NodeInfo>(false);
            if (nInfo != null)
            {
                Name = nInfo.name;

            }
            else
            {
                Name = "Dummy";
            }
        }

        public virtual void Dispose()
        {
            if (program != null)
            {

                foreach (NodeConnectionProperty inConn in inPropConns.Values)
                {
                    program.connections.Remove(inConn.id);
                }
                inPropConns.Clear();

                foreach (SortedDictionary<int, NodeConnectionProperty> outConns in outPropConns.Values)
                {
                    foreach (NodeConnectionProperty outConn in outConns.Values)
                    {
                        program.connections.Remove(outConn.id);
                    }
                    outConns.Clear();
                }

                outPropConns.Clear();
            }
        }

        #endregion

        #region Properties

        [Category("Node")]
        public string NodeName
        {
            get
            {
                return nodeName;
            }
            set
            {
                nodeName = value;                    
            }             
        }

        [Browsable(false)]
        public object Tag { get; set; }

        [Category("Node")]
        public virtual string Description
        {
            get
            {
                return description;
            }
            set
            {
                description = value;
                if (designer != null)
                {
                    designer.control.Parent.Invalidate(true);
                }
            }
        }

        [Browsable(false)]
        public int ID
        {
            get { return id; }
            set { id = value; }
        }

        [Browsable(false)]
        public Designer<Node> Designer
        {
            get
            {
                return designer;
            }
        }

        [Browsable(false)]
        public string Category
        {
            get { return category; }
        }

        [Browsable(false)]
        public DesignerData DesignerData
        {
            get
            {
                return designerData;
            }
            set
            {
                designerData = value;
            }
        }

        [Browsable(false)]
        public Program Program
        {
            get { return program; }
        }


        [ReadOnly(true)]
        [Browsable(false)]
        public virtual string Name { get; set; }

        [Browsable(false)]
        public virtual ActionState State
        {
            get { return state; }
        }

        #endregion

        #region Methods

        protected internal virtual void OnDesignerBeginSizing()
        {
            
        }

        protected internal virtual void OnDesignerEndSizing()
        {
            
        }

        public virtual void OnInputPinConnected(string name)
        {
            
        }
        
        public virtual void OnInputPinDisconnected(string name)
        {

        }

        public static Node CreteFromXml(Program program, XmlElement nodeEl)
        {
            string nodeCat = Misc.GetChildElementXml(nodeEl, "node_cat", "General");                        
            string nodeTypeName = Misc.GetChildElementXml(nodeEl, "node_type", "Node");
            Node node = null;

            NodeInfo nInfo = PluginManager.Current.FindNodeInfo(nodeTypeName);
            if (nInfo == null)
            {
                if (PluginManager.Current.NodeTypes.ContainsKey(nodeCat))
                {
                    SortedDictionary<string, NodeInfo> catNodeInfos = PluginManager.Current.NodeTypes[nodeCat];
                    if (catNodeInfos.ContainsKey(nodeTypeName))
                    {
                        nInfo = catNodeInfos[nodeTypeName];

                    }
                }
            }

            if (nInfo != null)
            {
                ConstructorInfo nConstInfo = nInfo.nodeType.GetConstructor(new Type[] { typeof(Program) });
                node = (Node)nConstInfo.Invoke(new object[] { program });                
            }

            if (node != null)
            {node.id = Conv.StringToInt(Misc.GetChildElementXml(nodeEl, "id"), -1);
                node.Name = Misc.GetChildElementXml(nodeEl, "name");
                node.nodeName = Misc.GetChildElementXml(nodeEl, "node_name");

                node.Description = Misc.GetChildElementXml(nodeEl, "descr");

                node.designerData.SetXml(nodeEl);

                node.designerData.SetNodePinStates(node);

                Type nType = node.GetType();

                XmlElement propsDataEl = nodeEl["props_data"];
                foreach (XmlElement propDataEl in propsDataEl.ChildNodes)
                {
                    string propName = propDataEl.GetAttribute("name");
                    string propValue = propDataEl.GetAttribute("value");

                    PropertyInfo nPI = nType.GetProperty(propName);
                    if (nPI != null)
                    {
                        if (nPI.CanWrite)
                        {
                            object cValue = null;
                            bool setValue = false;
                            if (nPI.PropertyType.IsEnum)
                            {
                                cValue = Enum.Parse(nPI.PropertyType, propValue);
                                setValue = cValue != null;
                            }
                            else
                            {
                                setValue = true;
                                string nPTypeName = nPI.PropertyType.Name;
                                double cvd = 0;
                                switch (nPTypeName)
                                {
                                    case "Int16":
                                    case "Int32":
                                    case "Int64":
                                        if (double.TryParse(propValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out cvd))
                                        {
                                            long cvl = (long)cvd;
                                            cValue = Convert.ChangeType(cvl, nPI.PropertyType);
                                        }
                                        break;
                                    case "Single":
                                    case "Double":
                                        if (double.TryParse(propValue, NumberStyles.Float, CultureInfo.InvariantCulture, out cvd))
                                        {
                                            cValue = Convert.ChangeType(cvd, nPI.PropertyType);
                                        }
                                        break;
                                    case "String":
                                        cValue = propValue;
                                        break;
                                    case "Boolean":
                                        cValue = Boolean.Parse(propValue);
                                        break;
                                    default:
                                        setValue = false;
                                        break;
                                }                                
                            }
                            if (setValue)
                            {
                                nPI.SetValue(node, cValue);
                            }
                        }
                    }
                }
            }

            return node;
        }


        public void DisconnectOutProperty(string propertyName)
        {
            if (outPropConns.ContainsKey(propertyName))
            {
                SortedDictionary<int, NodeConnectionProperty> outPrConns = outPropConns[propertyName];
                foreach (NodeConnectionProperty outPrConn in outPrConns.Values)
                {
                    bool isNullable = (!outPrConn.sourcePropInfo.PropertyType.IsValueType ||
                                       Nullable.GetUnderlyingType(outPrConn.sourcePropInfo.PropertyType) != null);
                    if (isNullable)
                    {
                        outPrConn.targetPropInfo.SetValue(outPrConn.target, null);
                    }

                    outPrConn.sourceDesignerPin.RemoveConnPin(outPrConn.targetDesignerPin);
                    outPrConn.targetDesignerPin.RemoveConnPin(outPrConn.sourceDesignerPin);
                    
                    outPrConn.target.OnInputPinDisconnected(outPrConn.targetPropInfo.Name);
                    program.connections.Remove(outPrConn.id);                    
                }
                outPrConns.Clear();
                outPropConns.Remove(propertyName);

            }
        }


        public void DisconnectInProperty(string propertyName)
        {
            if (inPropConns.ContainsKey(propertyName))
            {
                NodeConnectionProperty npConn = inPropConns[propertyName];
                
                bool isNullable = (!npConn.targetPropInfo.PropertyType.IsValueType ||
                    Nullable.GetUnderlyingType(npConn.targetPropInfo.PropertyType) != null);
                if (isNullable)
                {
                    npConn.targetPropInfo.SetValue(npConn.target, null);
                }
                program.connections.Remove(inPropConns[propertyName].id);
                inPropConns.Remove(propertyName);

                npConn.sourceDesignerPin.RemoveConnPin(npConn.targetDesignerPin);
                npConn.targetDesignerPin.RemoveConnPin(npConn.sourceDesignerPin);

                OnInputPinDisconnected(propertyName);
            }
        }

        public void NotifyPropertyChange(string propertyName)
        {
            if (outPropConns.ContainsKey(propertyName))
            {
                SortedDictionary<int, NodeConnectionProperty> noConns = outPropConns[propertyName];
                foreach (NodeConnectionProperty noConn in noConns.Values)
                {
                    noConn.Update();
                }
            }
        }

        public virtual void CreateDesigner()
        {
            if (designer == null)
            {
                designer = new NodeDesigner(this);
                NodeInfo nInfo = GetType().GetCustomAttribute<NodeInfo>(false);
                NodeDesignerControl ndc = (NodeDesignerControl) designer.control;
                ndc.lockSize = nInfo.lockSize;
                ndc.lockWidth = nInfo.lockWidth;
                ndc.lockHeight = nInfo.lockHeight;
            }
        }


        public virtual void DestroyDesigner()
        {
            if (designer != null)
            {
                if (designer.control != null)
                {
                    designer.control.Dispose();
                }
                designer = null;
            }
        }

        #endregion

        #region Serialize

        public virtual void PutXml(XmlNode parent)
        {
            XmlElement nodeEl = Misc.CreateAppendXmlElement(parent, "node");


            NodeInfo nInfo = GetType().GetCustomAttribute<NodeInfo>(false);

            Misc.CreateAppendXmlElement(nodeEl, "node_cat", nInfo.Category);
            Misc.CreateAppendXmlElement(nodeEl, "node_type", this.GetType().FullName);

            Misc.CreateAppendXmlElement(nodeEl, "id", id.ToString());
            Misc.CreateAppendXmlElement(nodeEl, "name", Name.ToString());
            Misc.CreateAppendXmlElement(nodeEl, "nodee_name", Name.ToString());
            
            Misc.CreateAppendXmlElement(nodeEl, "descr", Description.ToString());

            designerData.GetNodePinStates(this);

            designerData.PutXml(nodeEl);

            XmlElement propsData = Misc.CreateAppendXmlElement(nodeEl, "props_data");
            foreach (PropertyInfo pi in GetType().GetProperties())
            {
                if (pi.CanRead && pi.CanWrite)
                {
                    NodeSerializeProperty nsp = pi.GetCustomAttribute<NodeSerializeProperty>();
                    if (nsp != null)
                    {
                        XmlElement prdEl = Misc.CreateAppendXmlElement(propsData, "prop_data");
                        object pValObj = pi.GetValue(this);
                        if (pValObj == null)
                            pValObj = "";
                        prdEl.SetAttribute("name", pi.Name);
                        prdEl.SetAttribute("value", pValObj.ToString());
                    }                    
                }
            }
        }

        #endregion
    }

    
}
