import os
import importlib
import pathlib
import inspect
import warnings
import pkgutil
from shutil import rmtree
from jinja2 import Environment, FileSystemLoader


def find_api_modules(module):
    """
    Return a list of module names that are submodules of the given module and
    declare the variable `__all__`

    :param module: module to explore
    :type module: module
    :return: list of module names
    :rtype: list
    """
    ret = [module.__name__]
    for m in pkgutil.walk_packages(module.__path__, module.__name__ + "."):
        try:
            m = importlib.import_module(m.name)
        except Exception as e:
            warnings.warn("cannot import {}: {}".format(m.name, e))
            continue
        if hasattr(m, "__all__"):
            ret.append(m.__name__)
    ret.sort()
    return ret


def read_modules_list(file_name):
    """Read list of modules to be documented from a file. The file is expected
    to containing a list of module names (one name per line). Empty lines and
    lines starting with '#' are ignored.

    :param file_name: path to the file
    :type file_name: str
    :return: list of module names
    :rtype: list
    """
    ret = []
    with open(file_name) as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith("#"):
                ret.append(line)
    return ret


def build_doc_info(modulenames):
    """
    Inspect the given modules to compile info on them needed by `create_rst`

    :param modulenames: list of module names to be documented
    :return: info dictionary filled with information from the inspected
        modules. The keys are the module names and the values are dictionaries
        with info on classes, sfunctions, etc for the given module
    :rtype: dict
    """
    info = {}

    # sort module names
    modulenames = sorted([s.strip() for s in modulenames])

    for name in modulenames:
        try:
            parent, base = name.rsplit(".", 1)
        except ValueError:
            parent, base = None, name

        info[name] = dict(
            modulename=name,
            basename=base,
            exceptions=[],
            functions=[],
            classes=[],
            other=[],
            submodules=[],
        )

        # add functions and classes info
        m = importlib.import_module(name)
        all_ = getattr(m, "__all__", None)
        if all_ is None:
            warnings.warn("'__all__' not defined for {}".format(name))
            continue
        exceptions = info[name]["exceptions"]
        functions = info[name]["functions"]
        classes = info[name]["classes"]
        other = info[name]["other"]
        for n in all_:
            o = getattr(m, n)
            if isinstance(o, Exception):
                exceptions.append(n)
            elif inspect.isfunction(o):
                functions.append(n)
            elif inspect.isclass(o):
                classes.append(n)
            else:
                other.append(n)

    # add module to its parent's submodules info (if parent is documented)
    # Done in a second loop to ensure that info contains all modules already
    for name in modulenames:
        try:
            parent, base = name.rsplit(".", 1)
        except ValueError:
            parent, base = None, name
        if parent in info:
            info[parent]["submodules"].append(name)

    return info


def create_rst(info, api_dir, template_dir):
    """Write rst files for documenting the api

    :param info: dict containing info on modules to be documented. As
        generated by `build_doc_info`
    :param api_dir: path to the dir where the rst fileswill be created.
        WARNING: the directory will be overwritten.
    :param template_dir: path where the template files are located
    """

    env = Environment(loader=FileSystemLoader(template_dir))
    module_template = env.get_template("api_module.rst")
    class_template = env.get_template("api_class.rst")
    index_template = env.get_template("api_index.rst")

    api_dir = pathlib.Path(api_dir)
    print("INFO: overwriting API dir: '{}'".format(api_dir))
    rmtree(api_dir, ignore_errors=True)
    os.makedirs(api_dir, exist_ok=True)

    all_modules = []
    all_classes = []

    for module_info in info.values():

        module_fname = "{modulename}.rst".format(**module_info)
        all_modules.append((module_info["modulename"], module_fname))
        with open(api_dir / module_fname, "w") as f:
            f.write(module_template.render(**module_info))

        for class_name in module_info["classes"]:
            class_fname = "{modulename}-{classname}.rst".format(
                classname=class_name, **module_info
            )
            all_classes.append((class_name, class_fname))
            with open(api_dir / class_fname, "w") as f:
                f.write(
                    class_template.render(classname=class_name, **module_info)
                )

    # create index
    with open(api_dir / "index.rst", "w") as f:
        f.write(
            index_template.render(
                all_modules=all_modules, all_classes=all_classes
            )
        )


if __name__ == "__main__":

    a = """
    taurus
    taurus.core
    taurus.core.util.codecs
    taurus.core.util.log
    taurus.core.util
    taurus.qt
    taurus.qt.qtgui
    taurus.qt.qtgui.panel
    """

    doc_dir = pathlib.Path(__file__).parent
    nfo = build_doc_info(a.strip().split("\n"))
    import pprint

    pprint.pprint(nfo)
    create_rst(nfo, doc_dir / "source/devel/kkapi", doc_dir)
