# Copyright 2021 by Saithalavi M, saithalavi@gmail.com
# All rights reserved.
# This file is part of the Nessaid CLI Framework, nessaid_cli python package
# and is released under the "MIT License Agreement". Please see the LICENSE
# file included as part of this package.
#

import inspect
import textwrap

from nessaid_cli.cli import NessaidCli
from nessaid_cli.compiler import compile


class NessaidCmd(NessaidCli):
    """
    """
    """
    Base Cmd class

    The class can be initialized with cli_hook_prefix argument whose default value is do_
    All the functions starting with the prefix will be treated as Cmd command handlers
    The input will be matched as per the grammar specification given as the docstring and
    the function. The global grammar definitions should go as the derived class's docstring
    """

    def generate_grammar_name(self, hookmethod):
        """Generate the grammar name used in the grammar

        Returns the grammar name autogenerated from the Cmd functions.

        :param hookmethod: The method name with which the associated grammar is used
        :returns: The name of the generated grammar block
        :rtype: str
        """

        return hookmethod.__name__

    def generate_root_grammar_name(self):
        """Generate the root grammar name used in the Cmd instance

        Returns the root grammar name autogenerated from the Cmd functions.

        :returns: The name of the root grammar specification used in the Cmd instance
        :rtype: str
        """

        return self.__class__.__name__.split(".")[0]

    def format_grammar(self, grammar_text):
        formatted = ""
        if grammar_text:
            grammar_text = grammar_text.replace("\r\n", "\n")
            grammar_text = grammar_text.replace("\r", "\n")
            formatted = textwrap.dedent(grammar_text)
        return formatted

    def __init__(self, loop=None, prompt=None, cli_hook_prefix="do_", cli_nargs=3,
                 stdin=None, stdout=None, stderr=None, completekey='tab', use_rawinput=True, show_grammar=False):
        """Creates a Cmd instance

        :param loop: the event loop used to run the Cmd loop.
        :param prompt: The prombp string for the Cmd instance
        :param cli_hook_prefix: The prefix of the methods in the class to be converted to Cmd commands
        :param cli_nargs: Number of arguments the generated Cmd handlers should have.
        :param show_grammar: print the generated grammar before the Cmd prompt.
        """

        self._stdin = stdin
        self._stdout = stdout
        self._stderr = stderr

        self.execute_line = self.exec_line
        self.execute_args = self.exec_args
        grammar_text = self.__doc__ if self.__doc__ else ""
        grammar_hooks = [getattr(self, f) for f in dir(self) if f.startswith(cli_hook_prefix) and callable(getattr(self, f))]
        grammar_alternatives = []

        for hook in grammar_hooks:
            if hook.__doc__:
                argspec = inspect.getfullargspec(hook)
                argnames = argspec.args[1:]
                gramar_name = self.generate_grammar_name(hook)
                grammar_alternatives.append(gramar_name)
                argstring = ", ".join(["$arg_" + str(n + 1) for n in range(cli_nargs)])
                grammar_name_line = "\n\n    {grammar_name}[{argstring}]:\n".format(
                    grammar_name=gramar_name, argstring=argstring)
                if argnames:
                    grammar_name_line += "      <<\n"
                    for arg in argnames:
                        grammar_name_line += "        ${argname} = \"\";\n".format(argname=arg)
                    grammar_name_line += "      >>\n"
                hook_grammar = grammar_name_line
                hook_grammar +=  "      ("
                hook_grammar += hook.__doc__.rstrip() + "\n"
                hook_grammar +=  "      )\n"
                hook_grammar +=  "      <<call {grammar_name}({grammar_args});>>".format(
                    grammar_name=self.get_cli_hook(gramar_name),
                    grammar_args=", ".join(["$" + arg for arg in argnames]))
                hook_grammar += "\n      ;"

                grammar_text += hook_grammar

        if grammar_alternatives:
            root_grammar_name = self.generate_root_grammar_name()
            root_grammar = "\n\n    {grammar_name}[{argstring}]:\n".format(grammar_name=root_grammar_name, argstring=argstring)
            root_grammar += "      " + "\n      |\n      ".join(grammar_alternatives)
            root_grammar += "\n      ;\n"

            grammar_text += root_grammar
            grammar_text = self.format_grammar(grammar_text)

            if show_grammar:
                self.print("# Generated CLI grammar:")
                self.print(grammar_text)

            grammar_set = compile(grammar_text)
            super().__init__(grammar_set, prompt=prompt, loop=loop,
                 stdin=stdin, stdout=stdout, stderr=stderr, completekey=completekey, use_rawinput=use_rawinput)

    def do__exit(self):
        """
        "exit" | "quit" | "end"
        """
        self.exit_loop()

    async def cmdloop(self, intro=None):
        """Runs the cmd loop until exit

        :returns: None
        :rtype: None
        """

        return await super().cmdloop(grammarname=self.generate_root_grammar_name(), intro=intro)

    @classmethod
    async def execute_args(cls, *args):
        cmd = cls(prompt="# ", show_grammar=False)
        return await cmd.exec_args(*args)

    @classmethod
    async def execute_line(cls, line):
        cmd = cls(prompt="# ", show_grammar=False)
        return await cmd.exec_line(line)

    async def exec_args(self, *args):
        modified_args = []
        chars_to_check = [" "]
        for arg in args:
            if not (arg.startswith('"') and arg.endswith('"')):
                need_quoting = False
                for c in chars_to_check:
                    if arg.strip():
                        if c in arg:
                            need_quoting = True
                            break
                if need_quoting:
                    modified_args.append('"' + arg + '"')
                    continue
            modified_args.append(arg)

        args = modified_args
        line = " ".join(args)
        return await self.exec_line(line)


    async def exec_line(self, line):
        try:
            grammar = self.generate_root_grammar_name()
        except Exception as e:
            self.error("Exception getting root grammar:")
            return 1
        try:
            self.enter_grammar(self.generate_root_grammar_name())
        except Exception as e:
            self.error("Exception entering grammar:", grammar, "Error:", e)
            return 2
        try:
            await self.cli_exec_init()
            await super().exec_line(line)
        except Exception as e:
            self.error("Exception executing grammar:", grammar, "input:", line, "Error:", e)
            return 3
        finally:
            self.exit_grammar()
        return 0

