using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Xml;
using Machinations.FromFlash.graph;
using Machinations.FromFlash.machinations.model;

namespace Machinations.UserComponents
{
    public class UserComponent : UcInstanceBase, IUserComponentSchema//, IEquatable<UserComponent>
    {
        private readonly UserComponentGraph m_internalGraph;
        protected readonly List<InnerInOut> m_innerInOuts;
        private int m_nextInOutNumber;
        private DateTime m_created;

        public UserComponent():base()
        {
            m_innerInOuts = new List<InnerInOut>();
            m_internalGraph = new UserComponentGraph(this);//{Grammar = new MachinationsGrammar()};
            m_nextInOutNumber = 0;
        }

        public UserComponent(string fileName):this()
        {
            XmlDocument document = new XmlDocument();
            document.Load(fileName);
            XmlElement element = document.DocumentElement;
            ReadXmlTemplate(element);
            //UpdateOuters();
        }

        public UserComponent(XmlElement element):this()
        {
            ReadXmlTemplate(element);
            //UpdateOuters();
        }

        protected UserComponent(List<InnerInOut> inAndOuts):this()
        {
            m_innerInOuts = inAndOuts;
            inAndOuts.ForEach(inOut => m_internalGraph.AddElement(inOut));
        }

        public int UcId { get; set; }

        public string FileName
        {
            get { return NameDate +".xml"; }
        }

        public string NameDate
        {
            get { return Name + Created.ToString("MMdd_hhmmss"); }
        }

        public DateTime Created
        {
            get { return m_created; }
            set { m_created = value; }
        }

        public override UserComponentGraph InternalGraph
        {
            get { return m_internalGraph; }
            //set { m_internalGraph = value; }
        }

        public override IEnumerable<GraphElement> PresentedElements
        {
            get
            {
                List<GraphElement> hidden = new List<GraphElement>();
                m_innerInOuts.ForEach(inner =>
                {
                    hidden.Add(inner);
                    hidden.AddRange(inner.InnerConnections);
                    hidden.AddRange(inner.InnerConnections.SelectMany(innerC => innerC.Inputs));
                });
                return m_internalGraph.Elements.Except(hidden);
            }
        }

        public override int CurrentCard { get; set; }

        public List<IInOut> InAndOuts
        {
            get { return m_inOuts; }
        }

        public IEnumerable<InnerInOut> InnerInOuts
        {
            get { return m_innerInOuts; }
        }

        protected override IEnumerable<InnerInOut> MyInnerInOuts { get { return m_innerInOuts; } }

        public override int InstanceNumber { get { return 0; } }

        public override UserComponentType Type { get {return UserComponentType.UserComponent;} }

        public override bool Inhibited
        {
            get { return base.Inhibited; }
            set
            {
                base.Inhibited = value;
                foreach (var presentedElement in PresentedElements)
                {
                    presentedElement.Inhibited = value;
                }
            }
        }


        /// <summary>
        /// uklada sebe sama do samostatneho filu
        /// </summary>
        public void SaveToComponentsFolder()                   
        {
            XmlDocument document = new XmlDocument();
            document.AppendChild(GenerateXmlTemplate(document));
            document.Save(MachinationsVariables.ComponentsDirectory + "\\" + FileName);
        }

        public UserComponentGraph GetInternalGraph(int i, bool templateOnly)
        {
            if(i != 0)
                throw new ArgumentException();
            return m_internalGraph;
        }

        public override IUserComponentSchema GetUcScheme()
        {
            return this;
        }

        public override bool IsSimilar(IUserComponentInstance model)
        {
            if (m_outerInOuts.Count != model.OuterInOuts.Count())
                return false;
            Dictionary<int, OuterInOut> others = model.OuterInOuts.ToDictionary(outer => outer.InOutNumber);
            foreach (var myOuter in m_outerInOuts)
            {
                OuterInOut other;
                if (!others.TryGetValue(myOuter.InOutNumber, out other) || !myOuter.IsSimilar(other))
                {
                    return false;
                }
            }
            return true;
        }

        public virtual XmlElement GenerateXmlTemplate(XmlDocument document)
        {
            var element = document.CreateElement(MachinationsVariables.ComponentTemplate);
            element.SetAttribute("name", Name);
            element.SetAttribute("created", m_created.ToString());
            element.SetAttribute("inOutCounter", m_nextInOutNumber.ToString());
            m_internalGraph.FillXmlElement(element);
            return element;
        }

        //pro nacitani XmlTemplatu, ne opak GenerateXml

        public void ReadXmlTemplate(XmlElement element)
        {
            Name = element.GetAttribute("name");
            m_created = element.HasAttribute("created") ? DateTime.Parse(element.GetAttribute("created")) : DateTime.Now;
            m_nextInOutNumber = element.HasAttribute("inOutCounter")
                ? int.Parse(element.GetAttribute("inOutCounter"))
                : 0;
            if (element.HasAttribute("InOutVersion"))
                m_nextInOutNumber = int.Parse(element.GetAttribute("inOutCounter"));
            m_internalGraph.Clear();
            
            ProgramControlUtils.LoadGraph(true, m_internalGraph, element);
            AddInOutsAfterLoad();
        }

        /// <summary>
        /// uloží instanci UC. Obsah se ale bere z templatu
        /// </summary>
        public override XmlElement GenerateXml(XmlDocument document, XmlElement element = null)
        {
            if (element == null)
            {
                element = document.CreateElement("UserComponent");
            }
            element.SetAttribute("name", Name);
            element.SetAttribute("created", m_created.ToString());
            element = base.GenerateXml(document, element);
            return element;
        }

        protected virtual void AddInOutsAfterLoad()
        {
            foreach (var innerInOut in m_internalGraph.Elements.OfType<InnerInOut>())
            {
                innerInOut.UserComponent = this;
                m_innerInOuts.Add(innerInOut);
            }
        }

        //public void ReadXmlInstanceInfo(XmlElement element)
        //{
        //    m_position.X = element.HasAttribute("x") ? double.Parse(element.GetAttribute("x"), CultureInfo.InvariantCulture) : 0;
        //    m_position.Y = element.HasAttribute("y") ? double.Parse(element.GetAttribute("y"), CultureInfo.InvariantCulture) : 0;
        //    m_radius = element.HasAttribute("radius") ? int.Parse(element.GetAttribute("radius")) : 80;
        //}

        public new IUserComponentSchema Clone()
        {
            string fileName = null;
            if (File.Exists(MachinationsVariables.ComponentsDirectory + "\\" + FileName))
                fileName = FileName;
            else if (File.Exists(MachinationsVariables.ComponentsDirectory + "\\" + Name + ".xml"))
            {
                fileName = Name + ".xml";
            }
            if (fileName != null)
            {
                try
                {
                    return new UserComponent(MachinationsVariables.ComponentsDirectory + "\\" + fileName);
                }
                catch (FileNotFoundException)
                {
                    MessageBox.Show("Komponentu se nepodařilo načíst");
                    throw;
                }
            }
            if (Name.Equals(MachinationsVariables.UserComponentInitName))
            {
                return new UserComponent() {Name = MachinationsVariables.UserComponentInitName };
            }
            throw new NotImplementedException();
        }

        
        public override void AppendToGraph(Graph graph)
        {
            //PresentedElements.ToList().ForEach(element => graph.Elements.Add(element));
            m_graph = graph;
            graph.AddElement(this);
            graph.UserComponents.Add(this);
            m_outerInOuts.ForEach(graph.AddElement);
            //UnZip();
        }

        public IUserComponentInstance GetNewUcInstance(IUserComponentInstance previousInstance = null)
        {
            return this;
        }

        public IUserComponentInstance GetInstance(int instanceNumber)
        {
            return this;
        }

        public override void UpdateOuters()
        {
            Dictionary<int, OuterInOut> outers = m_outerInOuts.ToDictionary(outer => outer.InOutNumber);
            m_innerInOuts.ForEach(inner =>
            {
                OuterInOut outer;
                if (!outers.TryGetValue(inner.InOutNumber, out outer))
                {
                    outer = new OuterInOut(this, inner.Position, 0, inner.InOutNumber, this, inner.Input) {Name = inner.Name};
                    m_outerInOuts.Add(outer);
                }
                else
                    outer.UpdateFields(inner);
            });
            HashSet<int> innerNumbers = new HashSet<int>(m_innerInOuts.Select(inner => inner.InOutNumber));
            m_outerInOuts.RemoveAll(outer => !innerNumbers.Contains(outer.InOutNumber));
        }

        public IEnumerable<IUserComponentInstance> GetInstances()
        {
            return new[] {this};
        }

        public void GenerateNewVersion()
        {
            m_created = DateTime.Parse(DateTime.Now.ToString());
        }


        public override void AddLoadedOuterInOut(OuterInOut inOut)
        {
            m_outerInOuts.Add(inOut);
            inOut.UserComponent = this;
        }

        public override void AddOutput(MachinationsConnection connection)
        {
            throw new NotImplementedException("Neni jasny co by měl Output komponenty dělat");
        }

        public override void RemoveOutput(MachinationsConnection connection)
        {
            //throw new NotImplementedException();
        }

        //private void CreateMergedInOuts()
        //{
        //    Dictionary<int, OuterInOut> outers = m_outerInOuts.ToDictionary(o=> o.InOutNumber);
        //    m_innerInOuts.ForEach(inner =>
        //    {
        //        OuterInOut outer = outers[inner.InOutNumber];
        //        m_inOuts.Add(new MergedInOut(inner, outer));
        //    });
        //}

        //private void ZipOrUnzip()
        //{
        //    //m_inOuts.ForEach(io => io.CorrectMerged());
        //    ZipUnzipGraphElements();
        //    if (m_zip)
        //        m_outerInOuts.ForEach(inOut => m_graph.AddElement(inOut));
        //    else
        //        m_outerInOuts.ForEach(inOut => m_graph.RemoveElement(inOut));
        //    m_inOuts.ForEach(inAndOut =>
        //    {
        //        inAndOut.ProcessConnections(IsZipped, Graph);
        //    });
        //}

        public virtual InnerInOut CreateInOut(Point position, bool? input = null, int? inOutNumber = null)
        {
            var inOut = input == null ?
                new InnerInOut(this, position, inOutNumber ?? m_nextInOutNumber):
                new InnerInOut(this, position, inOutNumber ?? m_nextInOutNumber, input.Value);
            m_nextInOutNumber++;
            m_innerInOuts.Add(inOut);
            m_internalGraph.AddElement(inOut);
            return inOut;
        }

        public void RemoveInOut(InnerInOut inOut)
        {
            m_innerInOuts.Remove(inOut);
        }

        public void DeleteXmlTemplate()
        {
            File.Delete(MachinationsVariables.ComponentsDirectory + "\\" + FileName);
        }

        public bool SameSchema(IUserComponentSchema otherSchema)
        {
            return m_name.Equals(otherSchema.Name) &&
                Created.ToString("MMdd_hhmmss").Equals(otherSchema.Created.ToString("MMdd_hhmmss")) &&
                   otherSchema.Type == UserComponentType.UserComponent;
        }

        public IEnumerable<IUserComponentSchema> GetInnerComponents()
        {
            return m_internalGraph.UserComponents;
        }
    }
}