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

namespace Machinations.UserComponents
{
    public class CardInstance : UcInstanceBase
    {
        private Cards m_cards;
        private int m_currentCard;
        private int m_instanceNumber ;
        private UserComponent m_dummyComponent;
        private UserComponent m_card;
        private bool m_outOfCards;

        public CardInstance(Cards cards, int instanceNumber): this(cards)
        {
            cards.Instances.Add(this);
            if (cards.Instances.Count == 1)
                m_currentCard = 1;
            m_instanceNumber = instanceNumber;
            m_name = cards.Name + m_instanceNumber;
            foreach (var inOut in cards.InnerInOuts)
            {
                var myInOut = new OuterInOut(m_cards, inOut.Position, instanceNumber, inOut.InOutNumber, m_cards, inOut.Input);
                m_outerInOuts.Add(myInOut);
            }
        }

        public CardInstance(Cards cards) : base()
        {
            m_cards = cards;
            m_graph = m_cards.Graph;
        }

        public override int CurrentCard
        {
            get { return m_currentCard; }
            set
            {
                m_currentCard = value;
                ReloadCard(true);
                OnPropertyChanged(nameof(Card));
                //ComponentChanged.Invoke();
            }
        }

        public UserComponent Card
        {
            get
            {
                if(m_card == null)
                    ReloadCard(false);
                return m_card;
            }
        }

        public void ReloadCard(bool inform)
        {
            if (m_currentCard == 0)
                m_card = DummyComponent;
            else
            {
                m_card = m_cards.GetCardInstance(m_currentCard);
                if (m_card == null)
                {
                    m_card = DummyComponent;
                    if (!m_outOfCards)
                    {
                        m_outOfCards = true;
                        CardsEnded();
                    }
                }
                else
                    m_outOfCards = false;
            }
            if(inform)
                ComponentChanged.Invoke();
        }

        public UserComponent DummyComponent
        {
            get
            {
                if (m_dummyComponent == null)
                {
                    CreateDummyComponent();
                }
                return m_dummyComponent;
            }
        }

        private void CreateDummyComponent()
        {
            m_dummyComponent = new UserComponent();
            m_outerInOuts.ForEach(outer =>
            {
                m_dummyComponent.CreateInOut(outer.Position, outer.Input, outer.InOutNumber);
            });
        }

        public Cards Cards
        {
            get { return m_cards; }
            set { m_cards = value; }
        }

        public override UserComponentGraph InternalGraph { get { return Card.InternalGraph; } }

        public override IEnumerable<GraphElement> PresentedElements
        {
            get { return Card.PresentedElements; }
        }

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

        protected override IEnumerable<InnerInOut> MyInnerInOuts { get { return Card.InnerInOuts; } }

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


        public override IUserComponentSchema GetUcScheme()
        {
            return m_cards;
        }

        public override bool IsSimilar(IUserComponentInstance model)
        {
            if (this.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 override void UpdateOuters()
        {
            Dictionary<int, OuterInOut> outers = m_outerInOuts.ToDictionary(outer => outer.InOutNumber);
            foreach (var inner in Cards.InnerInOuts)
            {
                OuterInOut outer;
                if (!outers.TryGetValue(inner.InOutNumber, out outer))
                {
                    outer = new OuterInOut(this.Cards, inner.Position, 0, inner.InOutNumber, m_cards, inner.Input) { Name = inner.Name };
                    m_outerInOuts.Add(outer);
                }
                else
                    outer.UpdateFields(inner);
            }
            HashSet<int> innerNumbers = new HashSet<int>(Cards.InnerInOuts.Select(inner => inner.InOutNumber));
            m_outerInOuts.RemoveAll(outer => !innerNumbers.Contains(outer.InOutNumber));
            CreateDummyComponent();
        }

        public override void AppendToGraph(Graph graph)
        {
            m_graph = graph;
            graph.AddElement(this);
            if (!graph.UserComponents.Contains(m_cards))
            {
                graph.UserComponents.Add(m_cards);
                m_cards.Graph = graph;
            }
            m_outerInOuts.ForEach(graph.AddElement);
        }

        public override void AddLoadedOuterInOut(OuterInOut inOut)
        {
            m_outerInOuts.Add(inOut);
            inOut.UserComponent = Cards;
            //inOut.InstanceNumber = m_instanceNumber;
        }

        public override XmlElement GenerateXml(XmlDocument document, XmlElement element = null)
        {
            if (element == null)
            {
                element = document.CreateElement("CardInstance");
            }
            element = base.GenerateXml(document, element);
            element.SetAttribute("name", m_cards.Name);
            element.SetAttribute("created", m_cards.Created.ToString());
            element.SetAttribute("instanceNumber", m_instanceNumber.ToString());
            element.SetAttribute("currentCard", m_currentCard.ToString());
            element.SetAttribute("CardsId", m_cards.UcId.ToString());
            return element;
        }

        public override void ReadXml(XmlElement element)
        {
            base.ReadXml(element);
            m_instanceNumber = int.Parse(element.GetAttribute("instanceNumber"));
            m_currentCard = int.Parse(element.GetAttribute("currentCard"));
            m_cards.Instances.Add(this);
            m_name = m_cards.Name + m_instanceNumber;
        }

        public void TakeCard()
        {
            if(m_card != DummyComponent)
                m_cards.TakeCard(m_card);
        }

        public void OnCardChanged()
        {
            Zip();
            var prepared = new HashSet<GraphElement>(m_graph.Elements);
            ReloadCard(true);
            UnZip();
            foreach (var element in m_graph.Elements.Where(e => !prepared.Contains(e)).OfType<IPreparable>())
            {
                element.Prepare(true);
            }
            ComponentChanged.Invoke();
        }

        private void CardsEnded()
        {
            //jako source.Satisfy
            m_outputs.ForEach(output =>
            {
                ResourceConnection res = output as ResourceConnection;
                if (res != null && !res.Inhibited) res.Produce(res.Color, res.Color);
                StateConnection sta = output as StateConnection;
                sta?.Fire();
            });
        }
    }
}
