""" This script generates a pandadoc.hpp file representing the Python
wrappers that can be parsed by doxygen to generate the Python documentation.
You need to run this before invoking Doxyfile.python.

It requires a valid makepanda installation with interrogatedb .in
files in the lib/pandac/input directory. """

from __future__ import print_function

__all__ = []

import os, sys
from distutils import sysconfig
import panda3d, pandac
from panda3d.interrogatedb import *


if 'interrogate_element_is_sequence' not in globals():
    def interrogate_element_is_sequence(element):
        return False

if 'interrogate_element_is_mapping' not in globals():
    def interrogate_element_is_mapping(element):
        return False


LICENSE = """PANDA 3D SOFTWARE
Copyright (c) Carnegie Mellon University.  All rights reserved.
All use of this software is subject to the terms of the revised BSD
license.  You should have received a copy of this license along
with this source code in a file named \"LICENSE.\"""".split("\n")

MAINPAGE = """@mainpage Panda3D Python API Reference
Welcome to the Panda3D API reference.

Use the links at the top of this page to browse through the list of modules or
the list of classes.

This reference is automatically generated from comments in the source code.
"""


def comment(code):
    if not code:
        return ""

    comment = ''

    empty_line = False
    for line in code.splitlines(False):
        line = line.strip('\t\n /')
        if line:
            if empty_line:
                # New paragraph.
                comment += '\n\n'
                empty_line = False
            elif comment:
                comment += '\n'
            comment += '/// ' + line
        else:
            empty_line = True

    if comment:
        return comment
    else:
        return ''

def block_comment(code):
    code = code.strip()

    if not code.startswith('///<') and '@verbatim' not in code:
        code = code.replace('<', '\\<').replace('>', '\\>')

    if not code or code[0] != '/':
        # Not really a comment; get rid of it.
        return ""

    return code

def translateFunctionName(name):
    if name.startswith("__"):
        return name

    new = ""
    for i in name.split("_"):
        if new == "":
            new += i
        elif i == "":
            pass
        elif len(i) == 1:
            new += i[0].upper()
        else:
            new += i[0].upper() + i[1:]
    return new

def translateTypeName(name, mangle=True):
    # Equivalent to C++ classNameFromCppName
    class_name = ""
    bad_chars = "!@#$%^&*()<>,.-=+~{}? "
    next_cap = False
    first_char = mangle

    for chr in name:
        if (chr == '_' or chr == ' ') and mangle:
            next_cap = True
        elif chr in bad_chars:
            if not mangle:
                class_name += '_'
        elif next_cap or first_char:
            class_name += chr.upper()
            next_cap = False
            first_char = False
        else:
            class_name += chr

    return class_name

def translated_type_name(type, scoped=True):
    while interrogate_type_is_wrapped(type):
        if interrogate_type_is_const(type):
            return 'const ' + translated_type_name(interrogate_type_wrapped_type(type))
        else:
            type = interrogate_type_wrapped_type(type)

    typename = interrogate_type_name(type)
    if typename in ("PyObject", "_object"):
        return "object"
    elif typename == "PN_stdfloat":
        return "float"
    elif typename == "size_t":
        return "int"

    if interrogate_type_is_atomic(type):
        token = interrogate_type_atomic_token(type)
        if token == 7:
            return 'str'
        elif token == 8:
            return 'long'
        elif token == 9:
            return 'NoneType'
        else:
            return typename

    if not typename.endswith('_t'):
        # Hack: don't mangle size_t etc.
        typename = translateTypeName(typename)

    if scoped and interrogate_type_is_nested(type):
        return translated_type_name(interrogate_type_outer_class(type)) + '::' + typename
    else:
        return typename


def processElement(handle, element):
    if interrogate_element_has_comment(element):
        print(comment(interrogate_element_comment(element)), file=handle)
    elif interrogate_element_has_getter(element):
        # If the property has no comment, use the comment of the getter.
        getter = interrogate_element_getter(element)
        if interrogate_function_has_comment(getter):
            print(block_comment(interrogate_function_comment(getter)), file=handle)

    if interrogate_element_is_mapping(element) or \
       interrogate_element_is_sequence(element):
        suffix = "[]"
    else:
        suffix = ""

    print(translated_type_name(interrogate_element_type(element)), end=' ', file=handle)
    print(interrogate_element_name(element) + suffix + ';', file=handle)


def processFunction(handle, function, isConstructor = False):
    for i_wrapper in range(interrogate_function_number_of_python_wrappers(function)):
        wrapper = interrogate_function_python_wrapper(function, i_wrapper)
        if interrogate_wrapper_has_comment(wrapper):
            print(block_comment(interrogate_wrapper_comment(wrapper)), file=handle)

        if not isConstructor:
            if interrogate_function_is_method(function):
                if not interrogate_wrapper_number_of_parameters(wrapper) > 0 or not interrogate_wrapper_parameter_is_this(wrapper, 0):
                    print("static", end=' ', file=handle)

            if interrogate_wrapper_has_return_value(wrapper):
                print(translated_type_name(interrogate_wrapper_return_type(wrapper)), end=' ', file=handle)
            else:
                pass#print >>handle, "void",

            print(translateFunctionName(interrogate_function_name(function)) + "(", end=' ', file=handle)
        else:
            print("__init__(", end=' ', file=handle)

        first = True
        for i_param in range(interrogate_wrapper_number_of_parameters(wrapper)):
            if not interrogate_wrapper_parameter_is_this(wrapper, i_param):
                if not first:
                    print(",", end=' ', file=handle)
                print(translated_type_name(interrogate_wrapper_parameter_type(wrapper, i_param)), end=' ', file=handle)
                if interrogate_wrapper_parameter_has_name(wrapper, i_param):
                    print(interrogate_wrapper_parameter_name(wrapper, i_param), end=' ', file=handle)
                first = False

        print(");", file=handle)


def processType(handle, type):
    typename = translated_type_name(type, scoped=False)
    derivations = [ translated_type_name(interrogate_type_get_derivation(type, n)) for n in range(interrogate_type_number_of_derivations(type)) ]

    if interrogate_type_has_comment(type):
        print(block_comment(interrogate_type_comment(type)), file=handle)

    if interrogate_type_is_enum(type):
        print("enum %s {" % typename, file=handle)
        for i_value in range(interrogate_type_number_of_enum_values(type)):
            docstring = comment(interrogate_type_enum_value_comment(type, i_value))
            if docstring:
                print(docstring, file=handle)
            print(interrogate_type_enum_value_name(type, i_value), "=", interrogate_type_enum_value(type, i_value), ",", file=handle)

    elif interrogate_type_is_typedef(type):
        wrapped_type = interrogate_type_wrapped_type(type)
        if interrogate_type_is_global(wrapped_type):
            wrapped_type_name = translated_type_name(wrapped_type)
            print("typedef %s %s;" % (wrapped_type_name, typename), file=handle)
        return
    else:
        if interrogate_type_is_struct(type):
            classtype = "struct"
        elif interrogate_type_is_class(type):
            classtype = "class"
        elif interrogate_type_is_union(type):
            classtype = "union"
        else:
            print("I don't know what type %s is" % interrogate_type_true_name(type))
            return

        if len(derivations) > 0:
            print("%s %s : public %s {" % (classtype, typename, ", public ".join(derivations)), file=handle)
        else:
            print("%s %s {" % (classtype, typename), file=handle)
        print("public:", file=handle)

    for i_ntype in range(interrogate_type_number_of_nested_types(type)):
        processType(handle, interrogate_type_get_nested_type(type, i_ntype))

    for i_method in range(interrogate_type_number_of_constructors(type)):
        processFunction(handle, interrogate_type_get_constructor(type, i_method), True)

    for i_method in range(interrogate_type_number_of_methods(type)):
        processFunction(handle, interrogate_type_get_method(type, i_method))

    for i_method in range(interrogate_type_number_of_make_seqs(type)):
        print("list", translateFunctionName(interrogate_make_seq_seq_name(interrogate_type_get_make_seq(type, i_method))), "();", file=handle)

    for i_element in range(interrogate_type_number_of_elements(type)):
        processElement(handle, interrogate_type_get_element(type, i_element))

    print("};", file=handle)

def processModule(handle, package):
    print("Processing module %s" % (package))
    print("namespace %s {" % package, file=handle)

    if package != "core":
        print("using namespace core;", file=handle)

    for i_type in range(interrogate_number_of_global_types()):
        type = interrogate_get_global_type(i_type)

        if interrogate_type_has_module_name(type):
            module_name = interrogate_type_module_name(type)
            if "panda3d." + package == module_name:
                processType(handle, type)
        else:
            print("Type %s has no module name" % typename)

    for i_func in range(interrogate_number_of_global_functions()):
        func = interrogate_get_global_function(i_func)

        if interrogate_function_has_module_name(func):
            module_name = interrogate_function_module_name(func)
            if "panda3d." + package == module_name:
                processFunction(handle, func)
        else:
            print("Type %s has no module name" % typename)

    print("}", file=handle)


if __name__ == "__main__":
    handle = open("pandadoc.hpp", "w")

    mainpage = MAINPAGE.strip()
    if mainpage:
        print("/**\n * " + mainpage.replace('\n', '\n * ') + '\n */', file=handle)

    print(comment("Panda3D modules that are implemented in C++."), file=handle)
    print("namespace panda3d {", file=handle)

    # Determine the path to the interrogatedb files
    pandac = os.path.dirname(pandac.__file__)
    interrogate_add_search_directory(os.path.join(pandac, "..", "..", "etc"))
    interrogate_add_search_directory(os.path.join(pandac, "input"))

    import panda3d.core
    processModule(handle, "core")

    # Determine the suffix for the extension modules.
    if sys.version_info >= (3, 0):
        import _imp
        ext_suffix = _imp.extension_suffixes()[0]
    elif sys.platform == "win32":
        ext_suffix = ".pyd"
    else:
        ext_suffix = ".so"

    for lib in os.listdir(os.path.dirname(panda3d.__file__)):
        if lib.endswith(ext_suffix) and not lib.startswith('core.'):
            module_name = lib[:-len(ext_suffix)]
            __import__("panda3d." + module_name)
            processModule(handle, module_name)

    print("}", file=handle)
    handle.close()

    print("Wrote output to pandadoc.hpp.  You can now run:")
    print()
    print("  doxygen built/direct/directscripts/Doxyfile.python")
