using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using DevExpress.Utils.Drawing;
using PXP.Crawler.API.Designers;
using PXP.Crawler.API.Designers.Controls;
using PXP.Crawler.API.Utils;

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

        internal Project project;
        internal SortedDictionary<int, Node> nodes = new SortedDictionary<int, Node>();
        internal SortedDictionary<int, NodeConnection> connections = new SortedDictionary<int, NodeConnection>();
        internal int id = -1;
        internal int currentNodeID = 0;
        internal int currentConnID = 0;

        internal ProgramDesigner designer;

        #endregion

        #region Constructor / Destructor
        
        public Program(Project project)
        {
            Name = "New Program";            
            this.project = project;            
            designer = new ProgramDesigner(this);
        }
        
        public virtual void Dispose()
        {
            DestroyDesigner();
            foreach (Node cn in nodes.Values)
            {
                cn.Dispose();
            }
            nodes.Clear();
        }

        #endregion

        #region Properties

        public string Name { get; set; }

        public int ID
        {
            get
            {
                return id;
            }
            set
            {
                id = value;               
            }
        }

        public Project Project
        {
            get
            {
                return project;
            }
        }

        #endregion

        #region Methods

        public void Log(string msg, string type = "info")
        {
            if (project != null)
                project.Log(msg, type, this);
        }

        public Node FindNodeByID(int nodeID)
        {
            if (nodes.ContainsKey(nodeID))
                return nodes[nodeID];
            return null;
        }

        public void RemoveNodePinPropertyConnections(Node node, string propName)
        {
            foreach (NodeConnection nConn in connections.Values)
            {
                
            }
        }

        public List<NodeConnection> FindConnectionByNode(Node node)
        {
            List<NodeConnection> conns = new List<NodeConnection>();
            foreach (var nConn in connections.Values)
            {
                if (nConn.source == node)
                {
                    conns.Add(nConn);
                }
                else if (nConn.target == node)
                {
                    conns.Add(nConn);
                }                    
            }
            return conns;
        }

        internal void SetXml(XmlElement projEl)
        {
            currentNodeID = 0;

            id = Conv.StringToInt(Misc.GetChildElementXml(projEl, "id"), id);
            Name = Misc.GetChildElementXml(projEl, "name");

            XmlElement nodesEl = projEl["nodes"];

            foreach (XmlElement nodeEl in nodesEl.ChildNodes)
            {
                Node nNode = Node.CreteFromXml(this, nodeEl);
                if (nNode != null)
                {
                    nodes.Add(nNode.id, nNode);
                    if (nNode.id >= currentNodeID)
                    {
                        currentNodeID = nNode.id + 1;
                    }                    
                }
            }

            currentConnID = 0;

            XmlElement connsEl = projEl["connections"];
            foreach (XmlElement connEl in connsEl.ChildNodes)
            {
                NodeConnection conn = NodeConnection.CreateFromXml(this, connEl);
                if (conn != null)
                {
                    if (conn.id >= currentConnID)
                        currentConnID = conn.id + 1;
                    if (conn is NodeConnectionProperty)
                    {
                        if (((NodeConnectionProperty) conn).targetPropInfo == null)
                        {
                            connections.Remove(conn.id);
                        }
                    }
                }
                //conn.Update();
            }

            List<int> remConnIDs = new List<int>();


            bool hasTarget = false;
            foreach (NodeConnection nConn in connections.Values)
            {
                hasTarget = true;
                if (nConn is NodeConnectionProperty)
                {
                    NodeConnectionProperty npConn = (NodeConnectionProperty) nConn;

                    hasTarget = npConn.targetPropInfo != null && npConn.sourcePropInfo != null;

                    if (hasTarget)
                    {
                        if (!npConn.source.outPropConns[npConn.sourcePropInfo.Name].ContainsKey(npConn.id))
                        {
                            npConn.source.outPropConns[npConn.sourcePropInfo.Name].Add(nConn.id, (NodeConnectionProperty)nConn);
                        }

                        if (!npConn.target.inPropConns.ContainsKey(npConn.targetPropInfo.Name))
                        {
                            npConn.target.inPropConns.Add(npConn.targetPropInfo.Name, (NodeConnectionProperty)nConn);
                            npConn.target.OnInputPinConnected(npConn.targetPropInfo.Name);
                        }                        
                    }

                }

                if (hasTarget)
                {
                    nConn.Update();
                }
                else
                {
                    remConnIDs.Add(nConn.id);
                }
            }

            foreach (int rcID in remConnIDs)
            {
                NodeConnection nc = connections[rcID];
                if (nc is NodeConnectionProperty)
                {
                    NodeConnectionProperty ncp = (NodeConnectionProperty) nc;
                    string spName = "";
                    if (ncp.sourcePropInfo != null)
                        spName = ncp.sourcePropInfo.Name;
                    if (ncp.source.outPropConns.ContainsKey(spName))
                    {
                        if (ncp.source.outPropConns[ncp.targetPropInfo.Name].ContainsKey(ncp.id))
                        {
                            ncp.source.outPropConns[ncp.targetPropInfo.Name].Remove(ncp.id);
                        }                        
                    }
                }
                connections.Remove(rcID);
                nc.UpdateNodeReferences();
            }
        }

        public string GetXml()
        {
            StringBuilder sb = new StringBuilder();
            TextWriter tw = new StringWriter(sb, CultureInfo.InvariantCulture);
            XmlWriterSettings xws = new XmlWriterSettings()
            {
                Indent = true,
                Encoding = Encoding.UTF8,
                NewLineHandling = NewLineHandling.Entitize
            };
            XmlWriter xw = XmlTextWriter.Create(tw, xws);
            XmlDocument xDoc = new XmlDocument();
            PutXml(xDoc);
            xDoc.WriteTo(xw);
            xw.Close();
            xw.Dispose();
            tw.Dispose();

            return sb.ToString();
        }

        internal void PutXml(XmlNode parent)
        {
            XmlElement progEl = Misc.CreateAppendXmlElement(parent, "program");
            parent.AppendChild(progEl);

            Misc.CreateAppendXmlElement(progEl, "id", id.ToString());
            Misc.CreateAppendXmlElement(progEl, "name", Name);

            XmlElement nodesEl = Misc.CreateAppendXmlElement(progEl, "nodes");

            foreach (int nodeKey in nodes.Keys)
            {
                Node cNode = nodes[nodeKey];
                cNode.id = nodeKey;

                cNode.PutXml(nodesEl);
            }

            XmlElement connsEl = Misc.CreateAppendXmlElement(progEl, "connections");
            foreach (NodeConnection conn in connections.Values)
            {
                XmlElement connElement = Misc.CreateAppendXmlElement(connsEl, "connection");
                conn.PutXml(connElement);
            }
        }

        public int GetNewConnectionID()
        {
            int ncid = currentConnID;
            currentConnID++;
            return ncid;
        }

        public int GetNewNodeID()
        {
            int nnid = currentNodeID;
            currentNodeID++;
            return nnid;
        }

        #endregion

        #region Designer

        public ProgramDesigner ProgramDesigner
        {
            get
            {
                return designer;
            }
        }

        public Designer<Program> Designer
        {
            get
            {
                return designer;
            }            
        }

        public DesignerData DesignerData { get; set; }

        public void CreateDesigner()
        {
            foreach (Node n in nodes.Values)
            {
                n.CreateDesigner();
            }
        }

        public void DestroyDesigner()
        {
            foreach (Node n in nodes.Values)
            {
                n.DestroyDesigner();
            }

            designer = null;
        }

        #endregion

    }
}
