using System;
using System.Collections.Generic;
using System.Linq;
using Quadarax.Foundation.Core.Console;
using Quadarax.Foundation.Core.QConsole.Argument;
using Quadarax.Foundation.Core.QConsole.Value;
using Quadarax.Foundation.Core.Value;

namespace Quadarax.Foundation.Core.QConsole.Command.Base
{
    public abstract class AbstractListCommand : AbstractCommand
    {
        #region *** Private Fields ***
        private readonly IList<ISelectionEntry> _items = new List<ISelectionEntry>();
        #endregion

        #region *** Properties ***
        protected IEnumerable<ISelectionEntry> Items => _items;
        #endregion

        #region *** Constructors ***
        protected AbstractListCommand(Engine engine) : base(engine)
        {
           
        }
        #endregion

        #region *** Public Operations ***
        public void SetModel(ISelectionEntry[] modelItems, bool isItemAppends = false)
        {
            if (modelItems == null)
                throw new ArgumentNullException(nameof(modelItems));
            if (!isItemAppends)
                ClearModel();
            foreach (var item in modelItems)
                _items.Add(item);
            var asAppend = isItemAppends ? "as appended" : "";
            WriteDebugInfo($"Command '{Name}' model set {asAppend} with {_items.Count} items.");
        }

        public void ClearModel()
        {
            _items.Clear();
            WriteDebugInfo($"Command '{Name}' model was clear.");
        }

        public ISelectionEntry GetItem(int ordinal)
        {
            return _items[ordinal];
        }
        #endregion

        #region *** Protected overrides ***
        protected override Result EndExecute(Result incommingResult)
        {
            
            if (_items == null)
                throw new Exception($"Command '{Name}' must have set model first in OnExecute.");
            
            using (var writer = new ConsoleWriter(ConsoleColor.Yellow))
            {
                using (var writerGreen = new ConsoleWriter(ConsoleColor.Green))
                {
                    SetupPaging();
                    var isEndOfPaging = false;
                    
                    writerGreen.Write("Paging mode: ");
                    writer.WriteLine(IsPagingMode() ? "ENABLED" : "Disabled");
                    writer.WriteLine();


                    while (!isEndOfPaging)
                    {
                        WriteDebugInfo(
                            $"Command '{Name}': IsPagingMode={IsPagingMode()}; GetCurrentPage={GetCurrentPage()}; GetItemsPerPage={GetItemsPerPage()};");

                        int nCnt = 0;
                        isEndOfPaging = !IsPagingMode();
                        var ordinal = (GetCurrentPage() - 1) * GetItemsPerPage();
                        foreach (var item in _items.Skip(GetCurrentPage() - 1).Take(GetItemsPerPage()))
                        {
                            writer.Write($"{ordinal + 1}. :");
                            OnWriteEntry(writerGreen, item);
                            ordinal++;
                            nCnt++;
                        }

                        writer.WriteLine();
                        if (IsPagingMode()) 
                            writer.WriteLine($"List shows {nCnt}/{_items.Count} items.");
                        else
                            writer.WriteLine($"List shows {nCnt} items.");
                        
                        if (IsPagingMode())
                        {
                            writer.WriteLine($"Showing page {GetCurrentPage()}/{GetPagesCount()}.");
                            using (var writerCyan = new ConsoleWriter(ConsoleColor.Cyan))
                            {
                                using (var writerWhite = new ConsoleWriter(ConsoleColor.White))
                                {
                                    var isSelected = false;
                                    while (!isSelected)
                                    {
                                        writerWhite.WriteLine(
                                            $"Type special character [{QConsole.Defaults.RoleCharacters.InputIncrementPageCharacter}/{QConsole.Defaults.RoleCharacters.InputDecrementPageCharacter}/{QConsole.Defaults.RoleCharacters.InputFirstPageCharacter}/{QConsole.Defaults.RoleCharacters.InputLastPageCharacter}] or direct page value or ENTER to skip.");
                                        writerCyan.Write($"[{GetFirstPage()}-{GetLastPage()}]>");
                                        var pageString = writerCyan.ReadLine().Trim();
                                        try
                                        {
                                            isSelected = true;
                                            if (pageString == QConsole.Defaults.RoleCharacters
                                                    .InputIncrementPageCharacter)
                                            {
                                                //increment page
                                                if (CheckPage(GetCurrentPage() + 1))
                                                    SetCurrentPage(GetCurrentPage() + 1);
                                            }
                                            else if (pageString == QConsole.Defaults.RoleCharacters
                                                         .InputDecrementPageCharacter)
                                            {
                                                //decrement page
                                                if (CheckPage(GetCurrentPage() - 1))
                                                    SetCurrentPage(GetCurrentPage() - 1);
                                            }
                                            else if (pageString == QConsole.Defaults.RoleCharacters
                                                         .InputFirstPageCharacter)
                                            {
                                                //first page
                                                SetCurrentPage(GetFirstPage());
                                            }
                                            else if (pageString ==
                                                     QConsole.Defaults.RoleCharacters.InputLastPageCharacter)
                                            {
                                                //last page
                                                SetCurrentPage(GetLastPage());
                                            }
                                            else if (pageString ==
                                                     QConsole.Defaults.RoleCharacters.InputExitPageCharacter)
                                            {
                                                //exit page
                                                isEndOfPaging = true;
                                                continue;
                                            }
                                            else
                                            {
                                                //direct page
                                                var pageNo = int.Parse(pageString);
                                                SetCurrentPage(pageNo);
                                            }
                                        }
                                        catch (Exception e)
                                        {
                                            using (var writerRed = new ConsoleWriter(ConsoleColor.Red))
                                            {
                                                writerRed.Write("Error:").WriteLine(e.Message);
                                            }

                                            isSelected = false;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return incommingResult;
        }

        
        protected abstract void OnWriteEntry(ConsoleWriter writer, ISelectionEntry entry);

        protected override IEnumerable<AbstractArgument> OnSetupArguments()
        {
            var arguments = base.OnSetupArguments().ToList();
            arguments.AddRange(new[]
            {
                new FlagArgument(Constants.Arguments.Lists.PageMode.Code,
                    Constants.Arguments.Lists.PageMode.Description,
                    Constants.Arguments.Lists.PageMode.Hint, false),
                new NamedArgument(Constants.Arguments.Lists.ItemsPerPage.Code,
                    Constants.Arguments.Lists.ItemsPerPage.Description,
                    Constants.Arguments.Lists.ItemsPerPage.Hint,
                    TypeValuesEnum.Integer,
                    QConsole.Defaults.Arguments.ItemsPerPage.ToString(), false),
                new NamedArgument(Constants.Arguments.Lists.Page.Code,
                    Constants.Arguments.Lists.Page.Description,
                    Constants.Arguments.Lists.Page.Hint,
                    TypeValuesEnum.Integer,
                    "1", false),
            });
            return arguments;
        }

        protected void EnableDefaultPagingMode()
        {
            var argPageMode = GetArgument(Constants.Arguments.Lists.PageMode.Code);
            argPageMode.DefaultValue.Set(true);
            argPageMode.SetValueAsDefault();
        }
        protected void DisableDefaultPagingMode()
        {
            var argPageMode = GetArgument(Constants.Arguments.Lists.PageMode.Code);
            argPageMode.DefaultValue.Set(false);
            argPageMode.SetValueAsDefault();
        }
        #endregion

        #region *** Private Operations ***

        private bool IsPagingMode()
        {
            var argPageMode = GetArgument(Constants.Arguments.Lists.PageMode.Code);
            return (bool) argPageMode.Value.Get();
        }

        private int GetItemsPerPage()
        {
            if (!IsPagingMode())
                return _items.Count;
            var argPageItems = GetArgument(Constants.Arguments.Lists.ItemsPerPage.Code);
            return (int) argPageItems.Value.Get();
        }

        private int GetCurrentPage()
        {
            var argPage = GetArgument(Constants.Arguments.Lists.Page.Code);
            return (int) argPage.Value.Get();
        }

        private void SetCurrentPage(int page)
        {
            if (!CheckPage(page))
                throw new Exception($"Page value must be inside interval <{GetFirstPage()};{GetLastPage()}>.");
            var argPage = GetArgument(Constants.Arguments.Lists.Page.Code);
            argPage.Value.Set(page);
        }

        private bool CheckPage(int page)
        {
            return !(page < GetFirstPage() || page > GetLastPage());
        }

        private int GetFirstPage()
        {
            return 1;
        }
        private int GetLastPage()
        {
            return GetPagesCount();
        }

        private int GetPagesCount()
        {
            return (_items.Count / GetItemsPerPage()) + 1;
        }
        private void SetupPaging()
        {
            if (!IsPagingMode())
            {
                var argPageItems = GetArgument(Constants.Arguments.Lists.ItemsPerPage.Code);
                argPageItems.Value.Set(_items.Count);
                SetCurrentPage(1);
            }
        }
        #endregion

    }
}
