using System;
using System.Collections.Generic;
using System.Linq;
using Quadarax.Foundation.Common.Console;
using Quadarax.Foundation.Common.Value;
using Quadarax.Foundation.QConsole.Argument;
using Quadarax.Foundation.QConsole.Context;
using Quadarax.Foundation.QConsole.Extensions;

namespace Quadarax.Foundation.QConsole.Command.Base
{
    public abstract class AbstractCommand
    {
        #region *** Private Fields ***
        private Engine _engine;
        private IDictionary<string, AbstractArgument> _arguments;
        protected ICommandContext _context;
        #endregion

        #region *** Properties ***
        public abstract string Name { get; }
        public abstract string Description { get; }
        public AbstractArgument[] Arguments => _arguments.Values.ToArray();

        protected Engine Engine => _engine;
        #endregion

        #region  *** Constructor ***

        public AbstractCommand(Engine engine)
        {
            _engine = engine ?? throw new ArgumentNullException(nameof(engine));
            _arguments = new Dictionary<string, AbstractArgument>();
            _context = _engine.Context.CreateCommandContext();
            WriteDebugInfo($"Context for command '{Name}' created: {_context}");
            var args = SetupArguments();
            foreach (var arg in args)
                _arguments.Add(arg.Code, arg);

            Init();

            WriteDebugInfo($"Command '{Name}' has {_arguments.Count} arguments. Initialized.");
        }
        #endregion

        #region *** Public operations ***
        public Result Execute(string input)
        {
            ConsoleWriter writer = null;
            try
            {
                writer = new ConsoleWriter(ConsoleColor.Green);
                foreach (var arg in Arguments)
                    arg.SetValueAsDefault();
                ParseInput(input);

                if (_engine.Configuration.ShowStatusOnEveryCommand)
                    ShowContextStatus(writer);

                var result = OnExecute();
                result = EndExecute(result);
                
                return result;
            }
            catch (Exception e)
            {
                return new Result(e);
            }
            finally
            {
                writer?.Dispose();
            }
        }

        public override string ToString()
        {
            return "Command: " + Name;
        }
        #endregion

        #region *** Private operations ***

        private void Init()
        {
            OnCommandInit();
        }
        private void ParseInput(string input)
        {
            //var args = input.Split(new[] {Engine.Configuration.CharacterNamedArgumentSeparator}, StringSplitOptions.RemoveEmptyEntries);
            var args = Split(input, Engine.Configuration.CharacterTextValueBraceSeparator, new[]
            {
                Engine.Configuration.CharacterNamedArgumentSeparator,
                Engine.Configuration.CharacterOrdinalArgumentSeparator
            });


            for (int i = 1; i < args.Length; i++)
            {
                var arg = args[i];
                var index = i - 1;
                WriteDebugInfo($"Parsing argument [index={index}] by value string '{arg}' in progress..");
                AbstractArgument argument = null;
                if (arg.StartsWith(Engine.Configuration.CharacterNamedArgumentSeparator))
                {
                    //Named
                    WriteDebugInfo("Detects argument as named.");
                    var parsed = ParseNamedArgument(arg);
                    if (!ContainsArgument(parsed.Item1))
                        throw new Exception(
                            $"Invalid argument '{Engine.Configuration.CharacterNamedArgumentSeparator}{parsed.Item1}' for this command.");
                    argument = GetArgument(parsed.Item1);
                    if (argument.IsPositional())
                        if (!((OrdinalArgument)argument).IsCodeAllowed)
                            throw new Exception($"Usage of ordinal argument '{argument.Code}' as named argument is not allowed!");
                    if (argument.IsFlag())
                        ((FlagArgument)argument).Set();
                    else
                        argument.Value.Set(parsed.Item2);
                }
                else
                {
                    //Ordinal
                    WriteDebugInfo("Detects argument as ordinal.");
                    argument = GetArgument(index);
                    if (argument == null)
                        throw new Exception(
                            $"Invalid ordinal argument '{arg}' at position {index} for this command.");

                    argument.Value.Set(arg);
                    WriteDebugInfo($"Ordinal argument '{argument.Code}' is set to value '{argument.Value.AsString()}'.");
                }
            }
        }

        private Tuple<string, string> ParseNamedArgument(string argument)
        {
            try
            {
                var argParts = argument.Split(new[] {Engine.Configuration.CharacterNamedArgumentValueSeparator}, StringSplitOptions.RemoveEmptyEntries);
                WriteDebugInfo($"Splitting arg-parts '{argument}' to parts (count={argParts.Length}): {string.Join(",",argParts)}");
                var argName = argParts[0].Replace(Engine.Configuration.CharacterNamedArgumentSeparator,string.Empty);
                var argValue = argParts.Length > 1 ? argParts[1] : string.Empty;
                WriteDebugInfo($"Named argument '{argument}' parsed as name='{argName}'; value='{argValue}'.");
                return new Tuple<string, string>(argName, argValue);
            }
            catch (Exception e)
            {
                WriteDebugInfo($"Error during parsing named argument '{argument}'.");
                throw new Exception($"Argument '{argument}' parsing error.", e);
            }
            
        }
        private string[] Split(string input, string valueBracket, string[] separators)
        {
            var result = new List<string>();
            var trimedInput = input.Trim();
            if (string.IsNullOrEmpty(trimedInput))
                return result.ToArray();

            var cValueBracked = valueBracket.ToCharArray().First();
            var cSeparators = separators.Select(x => x.ToCharArray().First());

            var nIndexStart = 0;
            var nIndexEnd = 0;
            var isInsideBracked = false;
            int offset = 0;

            for (offset = 0; offset < trimedInput.Length; offset++)
            {
                var current = trimedInput[offset];
                // current | isInside | Result
                //   1          1          0
                //   1          0          1
                //   0          1          1
                //   0          0          0
                isInsideBracked = current == cValueBracked ^ isInsideBracked;
                if (isInsideBracked)
                    continue;
                if (cSeparators.Contains(current))
                {
                    nIndexEnd = offset;
                    result.Add(trimedInput.Substring(nIndexStart,nIndexEnd - nIndexStart));
                    nIndexStart = nIndexEnd;
                    isInsideBracked = false;
                }
            }

            if (offset>nIndexStart)
                result.Add(trimedInput.Substring(nIndexStart,offset - nIndexStart));

            return result.Where(x => !string.IsNullOrEmpty(x.Trim())).Select(x=>x.Trim()).ToArray();
        }
        private IEnumerable<AbstractArgument> SetupArguments()
        {
            return OnSetupArguments();
        }
        protected void WriteDebugInfo(string text)
        {
            using (var debug = new DebugConsoleWriter())
            {
                debug.WriteDebugLine(text);
            }
        }

        protected void WriteInfo(string text)
        {
            using (var writer = new ConsoleWriter(ConsoleColor.Green))
                writer.WriteLine(text);
        }

        protected void WriteCaption(string text)
        {
            using (var writer = new ConsoleWriter(ConsoleColor.White))
                writer.WriteLine(text);
        }

        protected void WriteWarning(string text)
        {
            using (var writer = new ConsoleWriter(ConsoleColor.Yellow))
                writer.WriteLine(text);
        }

        private void ShowContextStatus(ConsoleWriter writer)
        {
            using (var writerBlue = new ConsoleWriter(ConsoleColor.Blue))
            {
                using (var writerYellow = new ConsoleWriter(ConsoleColor.Magenta))
                {
                    writer.WriteLine("Engine context:");
                    writerBlue.Write(_engine.Context.ToString()).Write(" : ");
                    writerYellow.WriteLine(_engine.Context.Status);
                    writer.WriteLine("Command context:");
                    writerBlue.Write(_context.ToString()).Write(" : ");
                    writerYellow.WriteLine(_context.Status);
                }
            }
        }

        #endregion

        #region *** Protected virtuals ***
        protected virtual void OnCommandInit()
        {
            WriteDebugInfo("Command called OnCommandInit.");
        }
        protected virtual IEnumerable<AbstractArgument> OnSetupArguments()
        {
            return new AbstractArgument[0];
            //{
            //    new FlagArgument(Constants.Arguments.General.DebugMode.Code, Constants.Arguments.General.DebugMode.Description, Constants.Arguments.General.DebugMode.Hint, true), 
            //};
        }

        
        protected abstract Result OnExecute();

        protected virtual Result EndExecute(Result incommingResult)
        {
            return incommingResult;
        }
        #endregion

        #region *** Protected operations ***
        protected AbstractArgument GetArgument(string code)
        {
            if (!_arguments.ContainsKey(code)) 
                throw new Exception($"Argument '{code}' is not defined.");
            return _arguments[code];
        }
        protected AbstractArgument GetArgument(int ordinalPosition)
        {
            return _arguments.Values.Where(x => x.IsPositional()).Skip(ordinalPosition).FirstOrDefault();
        }

        protected bool ContainsArgument(string code)
        {
            return _arguments.ContainsKey(code);
        }

        protected TContext GetContext<TContext>() where TContext : class, ICommandContext
        {
            return (TContext)_context;
        }
        #endregion

    }
}
