using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ImageBlockz.Core;
using ImageBlockz.Api;

namespace ImageBlockz.Gui
{
    /// <summary>
    /// This class provides integrade between <see cref="Sequence"/> and graphical layout in <see cref="SequenceGui"/>.
    /// </summary>
    class BlockzModel
    {
        private Sequence sequence = new Sequence();

        private IOperation inputOperation;
        private List<OperationsPair> operationsPairsList = new List<OperationsPair>();

        private bool noValidate = false;

        /// <summary>
        /// Instance of this class can contain two operation - Filter and Output.
        /// </summary>
        private class OperationsPair
        {
            /// <summary>
            /// Creates instance of <see cref="OperationsPair"/> without parameters.
            /// </summary>
            public OperationsPair()
            {
            }

            /// <summary>
            /// Creates instance of <see cref="OperationsPair"/> with parameters filter and output.
            /// </summary>
            /// <param name="filter"><see cref="IOperation"/> filter.</param>
            /// <param name="output"><see cref="Ioperation"/> output.</param>
            public OperationsPair(IOperation filter, IOperation output)
            {
                this.Filter = filter;
                this.Output = output;
            }

            /// <summary>
            /// Gets or sets Filter operation from/to instance of OperationsPair
            /// </summary>
            public IOperation Filter
            {
                get;
                set;
            }

            /// <summary>
            /// Gets or sets Output operation from/to instance of OperationsPair
            /// </summary>
            public IOperation Output
            {
                get;
                set;
            }
        }

        /// <summary>
        /// Clear All operation in Sequence and OperationsPairs list.
        /// </summary>
        public void Clear()
        {
            sequence.RemoveAll();
            operationsPairsList.Clear();
        }
        /// <summary>
        /// Gets count of items in List of OperationPairs
        /// </summary>
        public int PairsCount
        {
            get
            {
                return operationsPairsList.Count;
            }
        }

        /// <summary>
        /// Gets or sets input operation.
        /// </summary>
        public IOperation InputOperation
        {
            get
            {
                return inputOperation;
            }
            set
            {
                inputOperation = value;
                GenerateSequence();
            }
        }

        /// <summary>
        /// Turn off validation.
        /// </summary>
        public bool NoValidate
        {
            set
            {
                noValidate = value;
            }
        }

        /// <summary>
        /// This Method returns Filter <see cref="IOperation"/> from List of OperationsPairs on exact index.
        /// </summary>
        /// <param name="index">Index of operation in the list.</param>
        /// <returns><see cref="IOperation"/></returns>
        public IOperation GetFilterOperation(int index)
        {
            return operationsPairsList[index].Filter;
        }

        /// <summary>
        /// This Method add Filter <see cref="IOperation"> to the List of OperationsPairs.
        /// </summary>
        /// <param name="operation">Filter <see cref="IOperation"/></param>
        public void AddFilterOperation(IOperation operation)
        {
            operationsPairsList.Add(new OperationsPair(operation, null));
            GenerateSequence();
        }

        /// <summary>
        /// This Method returns Output <see cref="IOperation"/> from List of OperationsPairs on exact index.
        /// </summary>
        /// <param name="index">Index of operation in the list.</param>
        /// <returns><see cref="IOperation"/></returns>
        public IOperation GetOutputOperation(int index)
        {
            return operationsPairsList[index].Output;
        }

        /// <summary>
        /// This Method add Output <see cref="IOperation"/> to the List of OperationsPairs.
        /// </summary>
        /// <param name="operation">Filter <see cref="IOperation"/></param>
        public void AddOutputOperation(IOperation operation, int index)
        {
            operationsPairsList[index].Output = operation;
            GenerateSequence();
        }

        /// <summary>
        /// Gets or Sets <see cref="Sequence"/>.
        /// </summary>
        public Sequence Sequence
        {
            get
            {
                return sequence;
            }
            set
            {
                sequence = value;
                GenerateModel();
            }
        }

        private OperationsPair GetCurrentPair(IOperation operation)
        {
            foreach (OperationsPair pair in operationsPairsList)
            {
                if (pair.Output == operation || pair.Filter == operation)
                {
                    return pair;
                }
            }

            return null;
        }

        private Sequence GenerateSequence()
        {
            sequence.RemoveAll();

            if (inputOperation != null)
            {
                sequence.Add(inputOperation);
            }

            foreach (OperationsPair pair in operationsPairsList)
            {
                sequence.Add(pair.Filter);

                if (pair.Output != null)
                {
                    sequence.Add(pair.Output);
                }
            }

            return sequence;
        }

        private void GenerateModel()
        {
            if (ValidateSequence() == false)
            {
                return;
            }

            inputOperation = sequence.GetOperation(0);

            operationsPairsList.Clear();

            for (int i = 1; i < sequence.Counter(); i++)
            {
                OperationsPair pair = new OperationsPair();
                pair.Filter = sequence.GetOperation(i);

                if (sequence.Counter() > i + 1)
                {
                    if (sequence.GetOperation(i + 1).GetOperationType() == OperationType.Output)
                    {
                        pair.Output = sequence.GetOperation(i + 1);
                        i++;
                    }
                }
                operationsPairsList.Add(pair);
            }
        }

        private bool ValidateSequence()
        {        
            if (sequence.Counter() == 0)
            {
                System.Windows.Forms.MessageBox.Show("Loaded sequence is not valid. Sequence is empty.");
                return false;
            }
            else if (sequence.GetOperation(0).GetOperationType() != OperationType.Input)
            {
                System.Windows.Forms.MessageBox.Show("Loaded sequence is not valid. Input missing.");
                return false;
            }

            if (sequence.Counter() == 1)
            {
                return true;
            }
            else if (sequence.GetOperation(1).GetOperationType() == OperationType.Output)
            {
                System.Windows.Forms.MessageBox.Show("Loaded sequence is not valid. Bad placed Output.");
                return false;
            }


            for (int i = 1; i < sequence.Counter(); i++)
            {
                IOperation oper = sequence.GetOperation(i);

                if (oper.GetOperationType() == OperationType.Input)
                {
                    System.Windows.Forms.MessageBox.Show("Loaded sequence is not valid. Bad placed Input.");
                    return false;
                }
                else if (oper.GetOperationType() == OperationType.Output)
                {
                    OperationType lastOperationType = sequence.GetOperation(i - 1).GetOperationType();

                    if (lastOperationType != OperationType.Filter)
                    {
                        System.Windows.Forms.MessageBox.Show("Loaded sequence is not valid. Filter missing.");
                        return false;
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// This method moves operation up in the model.
        /// </summary>
        /// <param name="operation"><see cref="IOperation"/></param>
        public void MoveUpBlock(IOperation operation)
        {
            OperationsPair tempPair = GetCurrentPair(operation);

            int index = operationsPairsList.IndexOf(tempPair); ;
            if (index < 1)
            {
                return;
            }

            if (operation.GetOperationType() == OperationType.Filter)
            {
                operationsPairsList.Insert(index - 1, tempPair);
                operationsPairsList.RemoveAt(index + 1);
            }
            else
            {
                IOperation tempOutput = operationsPairsList[index - 1].Output;

                operationsPairsList[index - 1].Output = operationsPairsList[index].Output;
                operationsPairsList[index].Output = tempOutput;
            }

            GenerateSequence();
        }


        /// <summary>
        /// This method moves operation down in the model.
        /// </summary>
        /// <param name="operation"><see cref="IOperation"/></param>
        public void MoveDownBlock(IOperation operation)
        {
            OperationsPair tempPair = GetCurrentPair(operation);

            int index = operationsPairsList.IndexOf(tempPair);
            int count = operationsPairsList.Count;

            if (index > count - 2)
            {
                return;
            }

            if (operation.GetOperationType() == OperationType.Filter)
            {
                operationsPairsList.Insert(index + 2, tempPair);
                operationsPairsList.RemoveAt(index);
            }
            else
            {
                IOperation tempOutput = operationsPairsList[index + 1].Output;

                operationsPairsList[index + 1].Output = operationsPairsList[index].Output;
                operationsPairsList[index].Output = tempOutput;
            }

            GenerateSequence();
        }


        /// <summary>
        /// This method delete operation from the model the model.
        /// </summary>
        /// <param name="operation"><see cref="IOperation"/></param>
        public void DeleteBlock(IOperation operation)
        {
            OperationsPair pair = GetCurrentPair(operation);

            if (operation.GetOperationType() == OperationType.Filter)
            {
                operationsPairsList.Remove(pair);
            }
            else if (operation.GetOperationType() == OperationType.Output)
            {
                pair.Output = null;
            }

            GenerateSequence();
        }
    }
}
