import inspect
import GPS
from gs_utils.internal.utils import dump_menu, run_test_driver, timeout


class Inspect(object):
    """An inspector for a given module"""

    module_header = """Scripting API reference for `%(module)s`
==========================================

.. automodule:: %(module)s

.. inheritance-diagram GPS    # DISABLED, add "::" to enable

"""

    functions_header = """
Functions
---------

"""

    function_stub = ".. autofunction:: %(name)s\n"

    classes_header = """
Classes
-------

"""
    class_stub = """
:class:`%(module)s.%(name)s`
%(underscore)s

.. autoclass:: %(name)s()
%(members)s
%(inheritance)s
"""

    method_stub = """
   .. automethod:: %(name)s
"""

    data_stub = """

   .. autoattribute:: %(name)s

"""

    exceptions_header = """
Exceptions
-------

"""

    def __init__(self, module):
        self.module = module
        self.func = []
        self.classes = []
        self.excepts = []

        for obj_name, obj in module.__dict__.items():
            if obj_name.startswith("__") and obj_name not in ["__init__"]:
                pass
            elif inspect.ismodule(obj):
                pass
            elif inspect.isfunction(obj) or inspect.isroutine(obj):
                self.func.append(obj_name)
            elif isinstance(obj, Exception):
                self.excepts.append(obj_name)
            elif inspect.isclass(obj):
                self.classes.append((obj_name, obj))

        self.func.sort()
        self.excepts.sort()
        self.classes.sort()

    def __methods(self, cls):
        """Returns the methods and data of the class"""

        methods = []
        data = []

        for name, kind, defined, obj in inspect.classify_class_attrs(cls):
            if defined != cls:
                pass  # Inherited method
            elif name.startswith("_") and name not in ["__init__"]:
                pass
            elif kind in ["method", "static method", "class method"]:
                methods.append(name)
            elif kind in ["property", "data"]:
                data.append(name)
            else:
                print("Unknown kind (%s) for %s.%s" % (kind, defined.__name__, name))

        methods.sort()
        data.sort()
        return (data, methods)

    def generate_rest(self):
        """Generate a REST file for the given module.
        The output should be processed by sphinx.
        """

        n = self.module.__name__
        fd = open("%s.rst" % n, "w", newline="\n")

        fd.write(".. This file is automatically generated, do not edit\n\n")
        fd.write(Inspect.module_header % {"module": n})

        if self.func:
            fd.write(Inspect.functions_header)
            for f in self.func:
                fd.write(Inspect.function_stub % {"name": f, "module": n})

        if self.classes:
            fd.write(Inspect.classes_header)
            for name, c in self.classes:
                # Only show inheritance diagram if base classes are other
                # than just "object"

                inheritance = ""
                mro = inspect.getmro(c)  # first member is always c
                if len(mro) > 2 or (len(mro) == 2 and mro[1].__name__ != "object"):
                    inheritance = "   .. inheritance-diagram:: %s.%s" % (n, name)

                if name in (
                    "FileContext",
                    "AreaContext",
                    "MessageContext",
                    "EntityContext",
                ):
                    # These are for backward compatibility only
                    continue

                fd.write(
                    Inspect.class_stub
                    % {
                        "name": name,
                        "inheritance": inheritance,
                        "members": "",
                        "underscore": "^" * (len(name) + len(n) + 10),
                        "module": n,
                    }
                )

                data, methods = self.__methods(c)

                for d in data:
                    mname = "%s.%s.%s" % (n, name, d)
                    fd.write(
                        Inspect.data_stub
                        % {
                            "name": mname,
                            "base_name": d,
                            "underscore": "*" * (len(d) + 8),
                        }
                    )

                for m in methods:
                    mname = "%s.%s.%s" % (n, name, m)
                    fd.write(
                        Inspect.method_stub
                        % {
                            "name": mname,
                            "base_name": m,
                            "inheritance": "   .. inheritance-diagram:: %s.%s" % (n, c),
                            "underscore": "*" * (len(m) + 8),
                        }
                    )

                if name == "Hook":
                    # Include generated doc for predefined hooks
                    fd.write(
                        Inspect.class_stub
                        % {
                            "name": "Predefined_Hooks",
                            "inheritance": "",
                            "members": "    :members:\n",
                            "underscore": "^" * (len(n) + 10 + 16),
                            "module": n,
                        }
                    )

        if self.excepts:
            fd.write(Inspect.exceptions_header)
            for c in self.excepts:
                fd.write(
                    Inspect.class_stub
                    % {
                        "name": c,
                        "inheritance": ".. inheritance-diagram:: %s.%s" % (n, c),
                        "underscore": "^" * (len(c) + len(n) + 10),
                        "module": n,
                    }
                )


# (re)generate the Python API doc

Inspect(GPS).generate_rest()
Inspect(GPS.Browsers).generate_rest()

# (re)generate the menus doc

toplevel_menus = [
    "File",
    "Edit",
    "Navigate",
    "Find",
    "View",
    "Code",
    "VCS",
    "Build",
    "Analyze",
    "Debug",
    "SPARK",
    "CodePeer",
    "GNATSAS",
    "Window",
    "Help",
]

menu_specialcases = {
    "/SPARK": """This menu is available if the SPARK toolset is installed on your system
and available on your PATH. See :menuselection:`Help --> SPARK -->
Reference --> Using SPARK with GPS` for more details.
""",
    "/CodePeer": """This menu is available if the CodePeer toolset is installed on your
system and available on your PATH. See your CodePeer documentation for
more details.
""",
    "/Help/CodePeer": """This menu is available if the CodePeer toolset is installed on your
system and available on your PATH. See your CodePeer documentation for
more details.
""",
    "/GNATSAS": """This menu is available if the GNATSAS toolset is installed on your
system and available on your PATH. See your GNATSAS documentation for
more details.
""",
    "/Help/GNATSAS": """This menu is available if the GNATSAS toolset is installed on your
system and available on your PATH. See your GNATSAS documentation for
more details.
""",
    "/Help/GNAT Runtime": """This menu is generated automatically, and provides
pointers to the contents of the currently loaded runtime.
""",
    "/Help/GNAT": """This menu is generated automatically, and provides documentation
for the compiler and accompanying tools, along with examples.
""",
    "/Help/GNU Tools": """Manuals for the GNU Tools that come with your compiler.
""",
    "/Help/Ada": """This menu contains pointers to the Ada reference manuals.
""",
    "/File/Open Recent Projects": """Use this menu to load one of the recently open projects.
""",
    "/Build/Bareboard": """Actions to build bareboard code.
""",
    "/Build/Makefile": """Actions to launch Makefile targets - this is generated
automatically when a Makefile is present next to the loaded project.
""",
}

menu_file_header = """
.. This file is automatically generated by generate.py, from the contents
.. of the menu actions.

.. index:: menu; menus

************
The Menu Bar
************

.. image:: menubar.png

GPS provides a standard menu bar giving access to all operations. However,
it is usually easier to access a feature using the various contextual menus
provided throughout GPS: these give direct access to the most relevant
actions in the current context (for example, a project, directory, file, or
entity). Contextual menus pop up when you click the right mouse button or
use the special :kbd:`open contextual menu` key on most keyboards.

You can access the following entries from the menu bar:

"""


def print_menu_header(f, menupath, indent):
    textpath = menupath[1:].replace("/", " --> ")
    f.write(indent + ".. index:: menu; {}\n\n".format(textpath.lower()))
    f.write(indent + "* :menuselection:`{}`\n\n".format(textpath))


def print_entry(f, menupath, indent):
    print("generating menu doc {}".format(menupath))
    print_menu_header(f, menupath, indent)
    for line in GPS.Menu.get(menupath).action.__doc__.splitlines():
        if line:
            f.write(indent + "   " + "{}\n".format(line))
        else:
            f.write("\n")


def print_menu(f, lis, parent_path, indent):
    """Print in file f the menu represented by list lis, at the given indent"""

    if parent_path in menu_specialcases:
        print_menu_header(f, parent_path, indent)
        for line in menu_specialcases[parent_path].splitlines():
            f.write(indent + "   " + line + "\n")
        f.write("\n")

    else:
        lis.append("")
        prev_entry = None
        for entry in lis:
            if entry != "<separator>":
                if type(prev_entry) == str:
                    menupath = parent_path + "/" + prev_entry
                    if type(entry) == str:
                        print_entry(f, menupath, indent)
                    else:
                        print_menu(f, entry, menupath, indent)
                prev_entry = entry


@run_test_driver
def driver():
    while len(dump_menu("/File/Open Recent Projects")) == 0:
        yield timeout(100)

    with open("menus.rst", "w", newline="\n") as f:
        f.write(menu_file_header)

        # Generate the indexes
        for m in toplevel_menus:
            f.write("  * :menuselection:`{}` (see :ref:`The_{}_Menu`)\n\n".format(m, m))

        # Generate the menu contents
        for m in toplevel_menus:
            f.write(".. index:: {}\n\n".format(m.lower()))
            f.write(".. _The_{}_Menu:\n\n".format(m))
            title = "The {} Menu".format(m)
            f.write("{}\n{}\n\n".format(title, "=" * len(title)))
            parent_path = "/{}".format(m)
            menu_list = dump_menu(parent_path) + ["", ""]
            print_menu(f, menu_list[1], parent_path, "")
