using System;
using System.Collections.Generic;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework;

namespace StardewModdingAPI
{
    /// <summary>A command that can be submitted through the SMAPI console to interact with SMAPI.</summary>
    [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.ConsoleCommands))]
    public class Command
    {
        /*********
        ** Properties
        *********/
        /// <summary>The commands registered with SMAPI.</summary>
        private static readonly IDictionary<string, Command> LegacyCommands = new Dictionary<string, Command>(StringComparer.InvariantCultureIgnoreCase);

        /// <summary>Manages console commands.</summary>
        private static CommandManager CommandManager;

        /// <summary>Manages deprecation warnings.</summary>
        private static DeprecationManager DeprecationManager;

        /// <summary>Tracks the installed mods.</summary>
        private static ModRegistry ModRegistry;


        /*********
        ** Accessors
        *********/
        /// <summary>The event raised when this command is submitted through the console.</summary>
        public event EventHandler<EventArgsCommand> CommandFired;

        /****
        ** Command
        ****/
        /// <summary>The name of the command.</summary>
        public string CommandName;

        /// <summary>A human-readable description of what the command does.</summary>
        public string CommandDesc;

        /// <summary>A human-readable list of accepted arguments.</summary>
        public string[] CommandArgs;

        /// <summary>The actual submitted argument values.</summary>
        public string[] CalledArgs;


        /*********
        ** Public methods
        *********/
        /****
        ** Command
        ****/
        /// <summary>Injects types required for backwards compatibility.</summary>
        /// <param name="commandManager">Manages console commands.</param>
        /// <param name="deprecationManager">Manages deprecation warnings.</param>
        /// <param name="modRegistry">Tracks the installed mods.</param>
        internal static void Shim(CommandManager commandManager, DeprecationManager deprecationManager, ModRegistry modRegistry)
        {
            Command.CommandManager = commandManager;
            Command.DeprecationManager = deprecationManager;
            Command.ModRegistry = modRegistry;
        }

        /// <summary>Construct an instance.</summary>
        /// <param name="name">The name of the command.</param>
        /// <param name="description">A human-readable description of what the command does.</param>
        /// <param name="args">A human-readable list of accepted arguments.</param>
        public Command(string name, string description, string[] args = null)
        {
            this.CommandName = name;
            this.CommandDesc = description;
            if (args == null)
                args = new string[0];
            this.CommandArgs = args;
        }

        /// <summary>Trigger this command.</summary>
        public void Fire()
        {
            if (this.CommandFired == null)
                throw new InvalidOperationException($"Can't run command '{this.CommandName}' because it has no registered handler.");
            this.CommandFired.Invoke(this, new EventArgsCommand(this));
        }


        /****
        ** SMAPI
        ****/
        /// <summary>Parse a command string and invoke it if valid.</summary>
        /// <param name="input">The command to run, including the command name and any arguments.</param>
        /// <param name="monitor">Encapsulates monitoring and logging.</param>
        public static void CallCommand(string input, IMonitor monitor)
        {
            Command.DeprecationManager.Warn("Command.CallCommand", "1.9", DeprecationLevel.Info);
            Command.CommandManager.Trigger(input);
        }

        /// <summary>Register a command with SMAPI.</summary>
        /// <param name="name">The name of the command.</param>
        /// <param name="description">A human-readable description of what the command does.</param>
        /// <param name="args">A human-readable list of accepted arguments.</param>
        public static Command RegisterCommand(string name, string description, string[] args = null)
        {
            name = name?.Trim().ToLower();

            // raise deprecation warning
            Command.DeprecationManager.Warn("Command.RegisterCommand", "1.9", DeprecationLevel.Info);

            // validate
            if (Command.LegacyCommands.ContainsKey(name))
                throw new InvalidOperationException($"The '{name}' command is already registered!");

            // add command
            string modName = Command.ModRegistry.GetModFromStack() ?? "<unknown mod>";
            string documentation = args?.Length > 0
                ? $"{description} - {string.Join(", ", args)}"
                : description;
            Command.CommandManager.Add(modName, name, documentation, Command.Fire);

            // add legacy command
            Command command = new Command(name, description, args);
            Command.LegacyCommands.Add(name, command);
            return command;
        }

        /// <summary>Find a command with the given name.</summary>
        /// <param name="name">The command name to find.</param>
        public static Command FindCommand(string name)
        {
            Command.DeprecationManager.Warn("Command.FindCommand", "1.9", DeprecationLevel.Info);
            if (name == null)
                return null;

            Command command;
            Command.LegacyCommands.TryGetValue(name.Trim(), out command);
            return command;
        }


        /*********
        ** Private methods
        *********/
        /// <summary>Trigger this command.</summary>
        /// <param name="name">The command name.</param>
        /// <param name="args">The command arguments.</param>
        private static void Fire(string name, string[] args)
        {
            Command command;
            if (!Command.LegacyCommands.TryGetValue(name, out command))
                throw new InvalidOperationException($"Can't run command '{name}' because there's no such legacy command.");
            command.Fire();
        }
    }
}