using Hylasoft.Opc.Common;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Hylasoft.Opc.Cli
{
    internal class Repl
    {
        private readonly IOpcClient _client;
        private Node _currentNode;
        private bool _keepAlive = true;

        public Repl(IOpcClient client)
        {
            _client = client;
        }

        /// <summary>
        /// Starts the REPL routing
        /// </summary>
        public void Start()
        {
            while (_keepAlive)
            {
                try
                {
                    Console.WriteLine();
                    Console.Write(_currentNode.Tag + ": ");
                    var line = Console.ReadLine();
                    var command = CreateCommand(line);
                    RunCommand(command);
                }
                catch (BadCommandException)
                {
                    Console.WriteLine("Invalid command or arguments");
                    Console.WriteLine();
                    RunCommand(new Command(SupportedCommands.Help));
                }
                catch (Exception e)
                {
                    Console.WriteLine("An error occurred running the last command:");
                    Console.WriteLine(e.Message);
                }
            }
            // ReSharper disable once FunctionNeverReturns
        }

        private void RunCommand(Command command)
        {
            switch (command.Cmd)
            {
                case SupportedCommands.Help:
                    ShowHelp();
                    break;
                case SupportedCommands.Read:
                    Read(command.Args);
                    break;
                case SupportedCommands.Write:
                    Write(command.Args);
                    break;
                case SupportedCommands.Up:
                    _currentNode = _currentNode.Parent;
                    break;
                case SupportedCommands.Monitor:
                    Monitor(command.Args);
                    break;
                case SupportedCommands.Exit:
                    _client.Dispose();
                    _keepAlive = false;
                    break;
                default:
                    throw new BadCommandException();
            }
        }

        #region commands

        private void Write(IList<string> args)
        {
            if (args.Count < 2)
                throw new BadCommandException();
            var tag = args[0];
            var val = args[1];
            var type = _client.GetDataType(GenerateRelativeTag(tag));
            switch (type.Name)
            {
                case "Int32":
                    var val32 = Convert.ToInt32(val);
                    _client.Write<int>(GenerateRelativeTag(tag), val32);
                    break;
                case "Int16":
                    var val16 = Convert.ToInt16(val);
                    _client.Write<short>(GenerateRelativeTag(tag), val16);
                    break;
                case "UInt16":
                    var valuint16 = Convert.ToUInt16(val);
                    _client.Write<ushort>(GenerateRelativeTag(tag), valuint16);
                    break;
                case "UInt32":
                    var valuint32 = Convert.ToUInt32(val);
                    _client.Write<uint>(GenerateRelativeTag(tag), valuint32);
                    break;
                case "Boolean":
                    var valBool = Convert.ToBoolean(val);
                    _client.Write<bool>(GenerateRelativeTag(tag), valBool);
                    break;
                case "Int64":
                    var val64 = Convert.ToInt64(val);
                    _client.Write<long>(GenerateRelativeTag(tag), val64);
                    break;
                case "UInt64":
                    var valuint64 = Convert.ToUInt64(val);
                    _client.Write<ulong>(GenerateRelativeTag(tag), valuint64);
                    break;
                default:
                    _client.Write<object>(GenerateRelativeTag(tag), val);
                    break;
            }
        }

        private void Monitor(IList<string> args)
        {
            if (!args.Any())
                throw new BadCommandException();
            var stopped = false;
            _client.Monitor<object>(GenerateRelativeTag(args[0]), (o, stop) =>
            {
                // ReSharper disable once AccessToModifiedClosure
                if (stopped)
                    stop();
                else
                    Console.WriteLine("Value changed: " + o);
            });
            Console.WriteLine("Started monitoring. Press any key to interrupt.");
            Console.ReadKey(true);
            stopped = true;
        }

        private void Read(IList<string> args)
        {
            if (!args.Any())
                throw new BadCommandException();
            var value = _client.Read<object>(GenerateRelativeTag(args[0]));
            Console.WriteLine(value);
        }

        private static void ShowHelp()
        {
            Console.WriteLine("Supported commands:");
            Console.WriteLine("  ls: Display the subnodes");
            Console.WriteLine("  cd [tag]: Visit a children node");
            Console.WriteLine("  read [tag]: Read the node");
            Console.WriteLine("  write [tag] [value]: Write value on node");
            Console.WriteLine("  root: Go to root node");
            Console.WriteLine("  up: Go up one folder");
            Console.WriteLine("  monitor [node]: monitor the node");
            Console.WriteLine("subnodes are separated by '.' The tag is relative to the current folder");
        }

        #endregion

        private static Command CreateCommand(string line)
        {
            try
            {
                var cmd = CliUtils.SplitArguments(line);
                var args = cmd.Skip(1).ToList();
                SupportedCommands selectedCommand;
                if (!Enum.TryParse(cmd[0], true, out selectedCommand))
                    selectedCommand = SupportedCommands.Help;
                return new Command(selectedCommand, args);
            }
            catch (Exception e)
            {
                throw new BadCommandException(e.Message, e);
            }
        }

        private string GenerateRelativeTag(string relativeTag)
        {
            return relativeTag;
        }
    }
}