""" 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. """

__all__ = []

import os, re
import panda3d, pandac
from panda3d.dtoolconfig import *

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")

# Copied from interrogateMakerPythonNative.cxx.
CLASS_REMAP = {
    "Loader"       : "PandaLoader",
    "LMatrix4f"    : "Mat4",
    "LMatrix3f"    : "Mat3",
    "LVecBase4f"   : "VBase4",
    "LVector4f"    : "Vec4",
    "LPoint4f"     : "Point4",
    "LVecBase3f"   : "VBase3",
    "LVector3f"    : "Vec3",
    "LPoint3f"     : "Point3",
    "LVecBase2f"   : "VBase2",
    "LVector2f"    : "Vec2",
    "LPoint2f"     : "Point2",
    "LQuaternionf" : "Quat",
    "LMatrix4d"    : "Mat4D",
    "LMatrix3d"    : "Mat3D",
    "LVecBase4d"   : "VBase4D",
    "LVector4d"    : "Vec4D",
    "LPoint4d"     : "Point4D",
    "LVecBase3d"   : "VBase3D",
    "LVector3d"    : "Vec3D",
    "LPoint3d"     : "Point3D",
    "LVecBase2d"   : "VBase2D",
    "LVector2d"    : "Vec2D",
    "LPoint2d"     : "Point2D",
    "LQuaterniond" : "QuatD",
    "Plane"        : "PlaneBase",
    "Planef"       : "Plane",
    "Planed"       : "PlaneD",
    "Frustum"      : "FrustumBase",
    "Frustumf"     : "Frustum",
    "Frustumd"     : "FrustumD" }

libraries = {}
for m, lib in panda3d.modules.items():
    if not isinstance(lib, str):
        for l in lib:
            libraries[l.replace("lib", "")] = m
    else:
        libraries[lib.replace("lib", "")] = m

def comment(code):
    lines = code.split("\n")
    newlines = []
    indent = 0
    reading_desc = False

    for line in lines:
        if line.startswith("////"):
            continue

        line = line.rstrip()
        strline = line.lstrip('/ \t')
        if reading_desc:
            newlines.append(line[min(indent, len(line) - len(strline)):])
        else:
            # A "Description:" text starts the description.
            if strline.startswith("Description"):
                strline = strline[11:].lstrip(': \t')
                indent = len(line) - len(strline)
                reading_desc = True
                newlines.append(strline)

    newcode = "\n".join(newlines)
    if len(newcode) > 0:
        return "/** " + newcode + " */"
    else:
        return ""

def translateFunctionName(name):
    name = CLASS_REMAP.get(name, 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 translated_type_name(type):
    typename = interrogate_type_name(type)
    for old, new in CLASS_REMAP.items():
        if typename == old:
            typename = new
        else:
            typename = re.sub("\\b%s\\b" % old, new, typename)

    typename = typename.replace("< ", "").replace(" >", "")
    return typename

def translateTypeSpec(name):
    name = name.strip("* ")
    name = name.replace("BitMask< unsigned int, 32 >", "BitMask32")
    name = name.replace("atomic ", "")
    name = name.replace("< ", "").replace(" >", "")
    for old, new in CLASS_REMAP.items():
        if name == old:
            return new
        name = re.sub("\\b%s\\b" % old, new, name)
    return name

def processFunction(handle, function, isConstructor = False):
    for i_wrapper in xrange(interrogate_function_number_of_python_wrappers(function)):
        wrapper = interrogate_function_python_wrapper(function, i_wrapper)
        if interrogate_wrapper_has_comment(wrapper):
            print >>handle, comment(interrogate_wrapper_comment(wrapper))
        
        if not isConstructor:
            if not interrogate_wrapper_number_of_parameters(wrapper) > 0 or not interrogate_wrapper_parameter_is_this(wrapper, 0):
                print >>handle, "static",
            
            if interrogate_wrapper_has_return_value(wrapper):
                print >>handle, translateTypeSpec(translated_type_name(interrogate_wrapper_return_type(wrapper))),
            else:
                pass#print >>handle, "void",
        print >>handle, translateFunctionName(interrogate_function_name(function)) + "(",
        
        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 >>handle, ",",
                print >>handle, translateTypeSpec(translated_type_name(interrogate_wrapper_parameter_type(wrapper, i_param))),
                if interrogate_wrapper_parameter_has_name(wrapper, i_param):
                    print >>handle, interrogate_wrapper_parameter_name(wrapper, i_param),
                first = False
        
        print >>handle, ");"

def processType(handle, type):
    typename = translated_type_name(type)
    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 >>handle, comment(interrogate_type_comment(type))
    
    if interrogate_type_is_enum(type):
        print >>handle, "enum %s {" % typename
        for i_value in range(interrogate_type_number_of_enum_values(type)):
            print >>handle, translateFunctionName(interrogate_type_enum_value_name(type, i_value)), "=", interrogate_type_enum_value(type, i_value), ","
    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" % typename
        
        if len(derivations) > 0:
            print >>handle, "%s %s : public %s {" % (classtype, typename, ", public ".join(derivations))
        else:
            print >>handle, "%s %s {" % (classtype, typename)
        print >>handle, "public:"
    
    for i_ntype in xrange(interrogate_type_number_of_nested_types(type)):
        processType(handle, interrogate_type_get_nested_type(type, i_ntype))
    
    for i_method in xrange(interrogate_type_number_of_constructors(type)):
        processFunction(handle, interrogate_type_get_constructor(type, i_method), True)
    
    for i_method in xrange(interrogate_type_number_of_methods(type)):
        processFunction(handle, interrogate_type_get_method(type, i_method))
    
    for i_method in xrange(interrogate_type_number_of_make_seqs(type)):
        print >>handle, "list", translateFunctionName(interrogate_make_seq_seq_name(interrogate_type_get_make_seq(type, i_method))), "();"
    
    print >>handle, "};"

if __name__ == "__main__":
    handle = open("pandadoc.hpp", "w")
    
    print >>handle, comment("Panda3D modules that are implemented in C++.")
    print >>handle, "namespace panda3d {}"
    
    # Determine the path to the interrogatedb files
    interrogate_add_search_directory(os.path.join(os.path.dirname(pandac.__file__), "..", "..", "etc"))
    interrogate_add_search_directory(os.path.join(os.path.dirname(pandac.__file__), "input"))

    try:
        panda3d.__load__()
    except ImportError, msg:
        print msg

    lastpkg = None
    for i_type in xrange(interrogate_number_of_global_types()):
        type = interrogate_get_global_type(i_type)

        if interrogate_type_has_module_name(type):
            package = libraries[interrogate_type_module_name(type)]
            if lastpkg != package:
                print >>handle, "}"
                print >>handle, "namespace panda3d.%s {" % package
                lastpkg = package

            processType(handle, type)
        else:
            print "Type %s has no module name" % typename
    
    if lastpkg is not None:
        print >>handle, "}"
    handle.close()

