
import inspect
import GPS
from gps_utils.internal.utils import dump_menu


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__.iteritems():
            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 = file("%s.rst" % n, "w")

        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', '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/GNAT Runtime': """This menu is generated automatically, and provides
pointers to the contents of the currently loaded runtime.
""",
}

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():
        f.write(indent + '   ' + "{}\n".format(line))


def print_menu(f, l, parent_path, indent):
    """Print in file f the menu represented by list l, 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:
        l.append('')
        prev_entry = None
        for entry in l:
            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


with open('menus.rst', 'wb') 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, '')

GPS.exit()
