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

namespace Machinations.Status
{
    /// <summary>
    /// Handles saving and retreiving statuses for one graph
    /// </summary>
    public class StatusHandler
    {
        private readonly MachinationsGraph m_graph;
        private Dictionary<object, List<IElementStatus>> m_memory;
        private List<IElementStatus> m_graphMemory; 
        private int m_lastRecord;
        private bool m_shouldCreateStatus;

        public Action LastRecordChanged;

        public StatusHandler(MachinationsGraph graph)
        {
            m_graph = graph;
            m_graph.StepChanged += () => 
            {
                if (LastRecord == m_graph.Step - 1)
                {
                    m_shouldCreateStatus = true;
                }
            };
            m_graph.TurnFinished += () =>
            {
                if (m_shouldCreateStatus)
                {
                    m_shouldCreateStatus = false;
                    CreateStatus();
                }
            };
            m_memory = new Dictionary<object, List<IElementStatus>>();
            Reset();
        }

        public int LastRecord
        {
            get { return m_lastRecord; }
            private set
            {
                if (m_lastRecord != value)
                {
                    m_lastRecord = value;
                    LastRecordChanged?.Invoke();
                }
            }
        }

        public void Reset()
        {
            m_memory.Clear();
            LastRecord = 0;
        }

        public void CreateFirstRecord()
        {
            m_graphMemory = new List<IElementStatus>() {new GraphStatus(m_graph)};
            foreach (var graphElement in SchemasWithStatuses().Concat<object>(ElementsWithStatuses()))
            {
                IElementStatus status = TypesWithStatuses.GetStatus((dynamic) graphElement, 0);
                if (!m_memory.ContainsKey(graphElement))
                {
                    m_memory.Add(graphElement, new List<IElementStatus>());
                    m_memory[graphElement].Add(status);
                }
                    
                
            }
            LastRecord = 0;
        }

        public void CreateStatus()
        {
            if(m_graphMemory.Last().NeedNewStatus())
                m_graphMemory.Add(new GraphStatus(m_graph));
            foreach (var graphElement in SchemasWithStatuses().Concat<object>(ElementsWithStatuses()))
            {
                bool needStatus = false;
                if (!m_memory.ContainsKey(graphElement))
                {
                    m_memory.Add(graphElement, new List<IElementStatus>());
                    needStatus = true;
                }
                else
                {
                    needStatus = m_memory[graphElement].Last().NeedNewStatus();
                }
                if (needStatus)
                {
                    m_memory[graphElement].Add(TypesWithStatuses.GetStatus((dynamic)graphElement, m_graph.Step));
                }
            }
            LastRecord = m_graph.Step;
        }

        public void RestoreStatus(int step)
        {
            if(step > m_lastRecord)throw new ArgumentException();
            RestoreIfNeeded(m_graphMemory.Last(status => status.Step <= step));
            foreach (var schema in SchemasWithStatuses())
            {
                RestoreIfNeeded(m_memory[schema].Last(status => status.Step <= step));
            }
            foreach (var graphElement in ElementsWithStatuses())
            {
                if(graphElement is StateConnection && 
                    (!m_memory.ContainsKey(graphElement) || !m_memory[graphElement].Any(status => status.Step <= step)))
                {
                    if (((StateConnection) graphElement).Label.IsCondition())
                        ((StateConnection)graphElement).State = ((StateConnection)graphElement).State;
                }
                else
                                        RestoreIfNeeded(m_memory[graphElement].Last(status => status.Step <= step));
            }
        }

        public void RemoveStatusesAfter(int step)
        {
            m_graphMemory.RemoveAll(status => status.Step > step);
            foreach (KeyValuePair<object, List<IElementStatus>> pair in m_memory)
            {
                pair.Value.RemoveAll(status => status.Step > step);
            }
            m_memory = m_memory.Where(pair => pair.Value.Count > 0).ToDictionary(pair => pair.Key, pair => pair.Value);
            LastRecord = step;
        }



        private IEnumerable<GraphElement> ElementsWithStatuses()
        {
            return m_graph.Elements.Where(element => TypesWithStatuses.ElementTypes.Contains(element.GetType()));
        }

        private IEnumerable<IUserComponentSchema> SchemasWithStatuses()
        {
            return m_graph.UserComponents.Where(uc => TypesWithStatuses.TemplateTypes.Contains(uc.GetType()));
        }

        private void RestoreIfNeeded(IElementStatus lastStatus)
        {
            if(lastStatus.NeedNewStatus())
                lastStatus.Restore();
        }
    }
}
