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

namespace Machinations.UserComponents
{
    public class Cards : IUserComponentSchema, IDisposable
    {
        private readonly List<UserComponent> m_cards;
        private readonly Dictionary<string, XmlElement> m_cardTemplates;
        private CardTemplate m_currentCard;
        private string m_currentCardUnchangedName;
        private readonly XmlDocument m_xMlDocument;

        private string m_name;
        //private readonly List<IInOut> m_inAndOuts;
        private readonly List<InnerInOut> m_innerInOuts;
        private readonly List<IUserComponentInstance> m_instances;
        private List<string> m_order;
        private int m_nextInOutNumber;
        public event Action OnCardsChanged;

        //private int m_cardShift;
        //private int m_nextCardShift;
        private bool m_needChangeCard;
        private readonly Dictionary<UserComponent, bool> m_takenCards;

        /// <summary>
        /// pokud je true, je balicek zabalen, nema vygenerovane karty a vraci templaty. Její instance jsou zipple
        /// </summary>                                                                          
        private bool m_zipped;

        private Graph m_graph;

        //public event Action CardChanged;
        private DateTime m_created;

        private bool m_shuffle;



        public Cards()
        {
            //m_inAndOuts = new List<IInOut>();
            m_innerInOuts = new List<InnerInOut>();
            m_cards = new List<UserComponent>();
            m_cardTemplates = new Dictionary<string, XmlElement>();
            m_instances = new List<IUserComponentInstance>();
            //m_currentCard = new CardTemplate(m_inAndOuts, this);
            m_xMlDocument = new XmlDocument();
            m_nextInOutNumber = 0;
            m_order = new List<string>();
            m_shuffle = true;
            m_takenCards = new Dictionary<UserComponent, bool>();
        }

        public Cards(string fileName) : this()
        {
            XmlDocument document = m_xMlDocument;
            document.Load(fileName);
            XmlElement element = document.DocumentElement;
            ReadXml(element);
            ChooseActiveTemplate(m_cardTemplates.Keys.First());
        }

        public Cards(XmlElement element) : this()
        {
            ReadXml(element);
            ChooseActiveTemplate(m_cardTemplates.Keys.First());
        }


        public Graph Graph
        {
            get
            {
                return m_graph;
                //return m_currentCard.InternalGraph; 
            }
            set
            {
                m_graph = value;
                ((MachinationsGraph) m_graph).CardsChange += ApplyCardTake;
            }
        }

        public CardTemplate CurrentCard
        {
            get { return m_currentCard; }
        }

        public string Name
        {
            get { return m_name; }
            set { m_name = value; }
        }

        public int UcId { get; set; }

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

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

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

        public List<string> TemplateNames
        {
            get
            {
                return m_cardTemplates.Keys.ToList();
            }
        }

        public IEnumerable<string> ActualTemplateNames
        {
            get
            {
                var unchanged = TemplateNames;
                unchanged.Remove(m_currentCardUnchangedName);
                unchanged.Add(CurrentCard.Name);
                return unchanged;
            }
        } 

        public int CardsCount
        {
            get { return m_cards.Count; }
        }

        //public int CardShift
        //{
        //    get { return m_cardShift; }
        //    set {
        //        if (m_cardShift != value)
        //        {
        //            bool increasing = value < m_cardShift;
        //            m_cardShift = value;
        //            CardChanged(increasing);
        //        }
        //    }
        //}

        public Dictionary<UserComponent, bool> TakenCards
        {
            get { return m_takenCards; }
        }

        public bool Shuffle
        {
            get { return m_shuffle; }
            set { m_shuffle = value; }
        }

        public List<string> Order
        {
            get { return m_order; }
            set { m_order = value; }
        }

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

        //public void AppendToGraph(Graph graph)

        //{

        //    m_graph = graph;

        //    graph.UserComponents.Add(this);

        //    //m_inAndOuts.ForEach(graph.AddElement);

        //}

        public IUserComponentInstance GetNewUcInstance(IUserComponentInstance previousInstance = null)
        {
            if (previousInstance?.Type != UserComponentType.Card)
                return new CardInstance(this, GetNewInstanceNumber());
            var pi = (CardInstance) previousInstance;
            return new CardInstance(this, pi.InstanceNumber);
        }

        public IUserComponentInstance GetInstance(int instanceNumber)
        {
            return m_instances.Single(instance => instance.InstanceNumber == instanceNumber);
        }

        public void UpdateOuters()
        {
            GenerateCards();
            m_instances.ForEach(i => i.UpdateOuters());
        }

        public IEnumerable<IUserComponentInstance> GetInstances()
        {
            return m_instances;
        }

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

        }

        public XmlElement GenerateXmlTemplate(XmlDocument document)
        {
            var element = document.CreateElement(MachinationsVariables.CardsTemplate);
            element.SetAttribute("name", Name);
            element.SetAttribute("created", m_created.ToString());
            element.SetAttribute("inOutVersion", m_nextInOutNumber.ToString());
            element.SetAttribute("shuffle", m_shuffle.ToString());
            var inOutsElement = document.CreateElement("InOutsElement");
            m_innerInOuts.ForEach(inOut => inOutsElement.AppendChild(inOut.GenerateXml(document)));
            element.AppendChild(inOutsElement);
            SaveCurrentTemplate();
            m_cardTemplates.Values.ToList().ForEach(card =>
            {
                //pokud byl vytvoren jinym dokumentm
                XmlNode importNode = document.ImportNode(card, true);
                element.AppendChild(importNode);
            });
            var keys = m_cardTemplates.Keys.ToList();
            var order = m_order.Select(o => keys.IndexOf(o));
            element.SetAttribute("order", string.Join(",", order));
            return element;
        }

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

        public void ReadXml(XmlElement element)
        {
            Name = element.GetAttribute("name");
            m_created = element.HasAttribute("created")
                ? DateTime.Parse(element.GetAttribute("created"))
                : DateTime.Now;
            if (element.HasAttribute("inOutCounter"))
            {
                m_nextInOutNumber = int.Parse(element.GetAttribute("inOutCounter"));
            }
            else if (element.HasAttribute("inOutVersion"))
            {
                m_nextInOutNumber = int.Parse(element.GetAttribute("inOutVersion"));
            }
            else m_nextInOutNumber = 0;
            m_shuffle = !element.HasAttribute("shuffle") || bool.Parse(element.GetAttribute("shuffle"));
            var inOutsElement = element.ChildNodes[0];
            foreach (XmlElement childNode in inOutsElement.ChildNodes)
            {
                m_innerInOuts.Add((InnerInOut) ProgramControlUtils.ReadNodeXml(childNode));
            }
            bool first = true;
            List<string> orderedNames = new List<string>();
            foreach (XmlElement cardElement in element.ChildNodes)
            {
                if (first)
                {
                    first = false;
                    continue;
                }
                string name = cardElement.GetAttribute("name");
                m_cardTemplates.Add(name, cardElement);
                orderedNames.Add(name);
            }
            if (element.HasAttribute("order") && element.GetAttribute("order") != "")
            {
                var ordered = element.GetAttribute("order").Split(',');
                m_order = ordered.Select(o => orderedNames[int.Parse(o)]).ToList();
            }
            m_innerInOuts.ForEach(inOut => inOut.UserComponent = this);
            GenerateCards();
        }

        public void GenerateCards()
        {
            m_cards.Clear();
            m_takenCards.Clear();

            if (m_shuffle)
            {
                foreach (var element in m_cardTemplates.Values)
                {
                    var density = int.Parse(element.GetAttribute("density"));
                    for (int i = 0; i < density; i++)
                    {
                        m_cards.Add(new UserComponent(element));
                    }
                }
                m_cards.Shuffle();
                m_order = m_cards.Select(c => c.Name).ToList();
            }
            else
            {
                m_order.ForEach(i => m_cards.Add(new UserComponent(m_cardTemplates[i])));
            }
            m_cards.ForEach(c => m_takenCards.Add(c, false));
        }


        public UserComponentType Type
        {
            get { return UserComponentType.Card; }
        }

        public List<IUserComponentInstance> Instances
        {
            get { return m_instances; }
        }

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

        public InnerInOut CreateInOut(Point position, bool? input = null, int? inOutNumber = null)
        {
            var inOut = new InnerInOut(this, position, inOutNumber ?? m_nextInOutNumber);
            m_nextInOutNumber++;
            m_innerInOuts.Add(inOut);
            m_currentCard.GetInternalGraph(0, true).AddElement(inOut);
            return inOut;
        }

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

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

        public void UnZip()
        {
            m_zipped = false;
            m_cards.ForEach(card => TakenCards[card] = false);
            m_needChangeCard = false;
            SortInstances();
            m_instances.ForEach(instance => instance.UnZip());
        }

        public void Zip()
        {
            m_zipped = true;
            m_cards.ForEach(card => TakenCards[card] = false);
            m_needChangeCard = false;
            m_instances.ForEach(instance =>
            {
                instance.Zip();
                (instance as CardInstance)?.ReloadCard(true);
            });
        }

        public string CreateCardTemplate(bool choose)
        {
            string newName = "Card";
            int number = m_cardTemplates.Count;
            while (TemplateNames.Contains(newName + number))
            {
                number++;
            }
            var newTemplate = new CardTemplate(m_innerInOuts, this) {Name = newName + number};
            m_cardTemplates.Add(newName + number, newTemplate.GenerateXmlTemplate(m_xMlDocument));
            if (choose)
            {
                ChooseActiveTemplate(newName + number);
            }
            return newName + number;
        }

        public void RemoveCardTemplate(string name)
        {
            if (m_currentCard.Name.Equals(name))
            {
                ChooseActiveTemplate(m_cardTemplates.Keys.First(n => !n.Equals(name)));
            }
            m_cardTemplates.Remove(name);
        }

        public void ChooseActiveTemplate(string name)
        {
            if (m_currentCard != null)
            {
                if (name.Equals(m_currentCardUnchangedName))
                    return;
                SaveCurrentTemplate();
            }
            m_currentCard = new CardTemplate(m_innerInOuts, m_cardTemplates[name], this);
            m_currentCardUnchangedName = m_currentCard.Name;
        }

        private void SaveCurrentTemplate()
        {
            var previousDensity = int.Parse(m_cardTemplates[m_currentCardUnchangedName].GetAttribute("density"));
            var diff = previousDensity - m_currentCard.Density;
            if (diff > 0)
            {
                m_order.Reverse();
                for (int i = 0; i < diff; i++)
                {
                    m_order.Remove(m_currentCardUnchangedName);
                }
                m_order.Reverse();
            }
            else if (diff < 0)
            {
                for (int i = 0; i < -diff; i++)
                {
                    m_order.Add(m_currentCardUnchangedName);
                }
            }
            if (m_currentCard.Name != m_currentCardUnchangedName)
            {
                m_cardTemplates.Remove(m_currentCardUnchangedName);
                for (int i = 0; i < m_order.Count; i++)
                {
                    if (m_order[i].Equals(m_currentCardUnchangedName))
                    {
                        m_order[i] = m_currentCard.Name;
                    }
                }
            }
            m_cardTemplates[m_currentCard.Name] = m_currentCard.GenerateXmlTemplate(m_xMlDocument);
        }

        public bool TrySetCurrentCardName(string value)
        {
            if (!TemplateNames.Contains(value))
            {
                CurrentCard.Name = value;
                return true;
            }
            return false;
        }

        [CanBeNull]
        public UserComponent GetCardInstance(int currentCard)
        {
            var nonTakenCards = m_cards.Where(c => !TakenCards[c]).ToArray();
            if (currentCard > nonTakenCards.Length)
            {
                return null;
            }
            return nonTakenCards[currentCard - 1];
        }

        public void TakeCard(UserComponent card)
        {
            m_takenCards[card] = true;
            m_needChangeCard = true;
        }

        public void ApplyCardTake()
        {
            if (m_needChangeCard)
            {
                m_needChangeCard = false;
                CardChanged(true);
            }
        }

        public void Dispose()
        {
            ((MachinationsGraph) m_graph).CardsChange -= ApplyCardTake;
        }

        private int GetNewInstanceNumber()
        {
            int i = 0;
            HashSet<int> existing = new HashSet<int>(m_instances.Select(instances => instances.InstanceNumber));
            while (existing.Contains(i))
            {
                i++;
            }
            return i;
        }

        private void SortInstances()
        {
            m_instances.Sort((x, y) => x.CurrentCard.CompareTo(y.CurrentCard));
        }

        /// <summary>
        /// rekne instancim,aby si vymenily karty. Instance jsou serazene podle toho, kam ukazuji, a je potreba, aby se při směru dolu zacinalo nejhlubsi instanci a pri smeru vzhuru instanci nahore
        /// </summary>
        /// <param name="goingDeeper"></param>
        public void CardChanged(bool goingDeeper)
        {
            if (!goingDeeper)
                Instances.ForEach(i => (i as CardInstance)?.OnCardChanged());
            else
                for (int i = Instances.Count - 1; i >= 0; i--)
                {
                    (Instances[i] as CardInstance)?.OnCardChanged();
                }
            OnCardsChanged?.Invoke();
        }

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

        public bool IsZipped
        {
            get { return m_zipped; }
        }

        public IEnumerable<IUserComponentSchema> GetInnerComponents()
        {
            var innerComponents = new List<IUserComponentSchema>();
            foreach (var element in m_cardTemplates.Values)
            {
                var unusedComponent = new UserComponent(element);
                innerComponents.AddRange(unusedComponent.GetInnerComponents()); 
            }
           return innerComponents.Distinct();
        }
    }
}