# file: generate_simtkmath_source.py
# Run this script from the above src directory

import os
import sys
import re
from pyplusplus import module_builder
from pyplusplus.module_builder.call_policies import *
from simtk_wrap_utils import find_path, find_program
from simtk_wrap_utils import *
from doxygen_doc_extractor import doxygen_doc_extractor
from pyplusplus import function_transformers as FT

def generate_simtkmath_source():
    """
    Use pyplusplus to generate boost.python source code for simtk commmon module.
    See pyplusplus documentation at 
        http://www.language-binding.net/pyplusplus/pyplusplus.html
    """
    mb = create_module_builder(['simtkmath/wrap_simtkmath.h',])

    # Delegate wrapping details to other subroutines, to keep this one from getting too big
    mb.namespace('SimTK').include()
    perform_usual_wrapping_tasks(mb)
    expose_integrator(mb)
    expose_timestepper(mb)
    expose_indexes(mb)
    expose_cpodes(mb)
    expose_differentiator(mb)
    exclude_gcvsplutil(mb)
    wrap_optimizersystem(mb)
    wrap_function(mb)
    wrap_ostream_methods(mb)
    exclude_rep_classes(mb)
    wrap_properties(mb)
    expose_free_functions(mb) # crossMat compile error
    
    # Don't rewrap anything already wrapped by simtk.common
    # See http://www.language-binding.net/pyplusplus/documentation/multi_module_development.html
    mb.register_module_dependency('external/std/generated_code/')
    mb.register_module_dependency('simtkvecs/generated_code/')
    mb.register_module_dependency('simtkcommon/generated_code/')
    
    extractor = doxygen_doc_extractor()

    mb.build_code_creator(module_name='_math', doc_extractor=extractor)
    mb.split_module(os.path.join(os.path.abspath('.'), 'simtkmath', 'generated_code'))
    # mb.write_module(os.path.join(os.path.abspath('.'), 'simtkmath', 'generated_code', 'test.cpp'))
    # If all succeeds, record this accomplishment by touching a particular file
    open(os.path.join(os.path.abspath('.'), 'simtkmath', 'generated_code', 'generate_simtkmath.stamp'), "w").close()

def wrap_function(mb):
    # Function based mobilizers need to take ownership of Function pointers, so change held_type
    function = mb.class_('Function_<double>')
    function.alias = 'Function'
    # function.no_init = True
    function.noncopyable = True
    function.held_type = 'std::auto_ptr< %s >' % function.wrapper_alias
    function_ptr_type = 'std::auto_ptr< %s >' % function.decl_string
    function.add_registration_code(
        'bp::implicitly_convertible<%s, %s >();' % (function.held_type, function_ptr_type),
        works_on_instance=False)
    # Inner classes such as Function::Constant, Function::Linear, Function::Polynomial
    for fn2 in function.classes():
        fn2.held_type = 'std::auto_ptr< %s::%s >' % (function.wrapper_alias, fn2.wrapper_alias)
        fn2_ptr_type = 'std::auto_ptr< %s >' % fn2.decl_string
        fn2.add_registration_code(
            'bp::implicitly_convertible< %s, %s >();' % (fn2.held_type, fn2_ptr_type),
            works_on_instance=False)
        fn2.add_registration_code(
            'bp::implicitly_convertible< %s, %s >();' % (fn2.held_type, function_ptr_type), # parent
            works_on_instance=False)

def wrap_arrays(mb):
    #WARNING: int * SimTK::Array_<int, unsigned int>::insertGapAt(int * p, unsigned int n, char const * methodName) [member function]
    #> compilation error W1050: The function returns "int *" type. You have
    #> to specify a call policies.Be sure to take a look on `Py++` defined
    #> call policies
    for array in mb.classes(lambda c: c.name.startswith('Array_')):
        wrap_array(array, mb)

def wrap_optimizersystem(mb):
    opt_sys = mb.class_('OptimizerSystem')
    #WARNING: int SimTK::OptimizerSystem::objectiveFunc(SimTK::Vector const & parameters, bool const new_parameters, SimTK::Real & f) const [member function]
    #> execution error W1009: The function takes as argument (name=f, pos=2)
    #> non-const reference to Python immutable type - function could not be
    #> called from Python. Take a look on "Function Transformation"
    #> functionality and define the transformation.
    #
    opt_sys.member_function('objectiveFunc').add_transformation(FT.output('f'))
    opt_sys.member_function('gradientFunc').add_transformation(FT.output('gradient'))
    opt_sys.member_function('constraintFunc').add_transformation(FT.output('constraints'))
    opt_sys.member_function('constraintJacobian').add_transformation(FT.output('jac'))
    opt_sys.member_function('hessian').add_transformation(FT.output('gradient'))
    #WARNING: void SimTK::OptimizerSystem::getParameterLimits(double * * lower, double * * upper) const [member function]
    #> warning W1051: The function takes as argument (name=lower, pos=0)
    #> "double * *" type. You have to specify a call policies or to use
    #> "Function Transformation" functionality.
    opt_sys.member_function('getParameterLimits').exclude()

def exclude_gcvsplutil(mb):
    #WARNING: static void SimTK::GCVSPLUtil::gcvspl(SimTK::Vector const & x, SimTK::Vector const & y, SimTK::Vector const & wx, SimTK::Real wy, int m, int md, SimTK::Real val, SimTK::Vector & c, SimTK::Vector & wk, int & ier) [member function]
    #> execution error W1009: The function takes as argument (name=ier,
    #> pos=9) non-const reference to Python immutable type - function could
    #> not be called from Python. Take a look on "Function Transformation"
    #> functionality and define the transformation.
    # Plus there is no documentation for this class.
    mb.class_('GCVSPLUtil').exclude()
    
def exclude_crossmat(mb):
    # compile error
    #6>c:\program files\simtk\include\SimTKcommon/internal/SmallMatrixMixed.h(630) : error C2665: 'SimTK::Row<N,ELT,STRIDE>::Row' : none of the 13 overloads could convert all the argument types
    #6>        with
    #6>        [
    #6>            N=3,
    #6>            ELT=SimTK::negator<double>,
    #6>            STRIDE=1
    #6>        ]
    #6>        c:\program files\simtk\include\SimTKcommon/internal/Row.h(308): could be 'SimTK::Row<N,ELT,STRIDE>::Row(const SimTK::negator<NUMBER> &,const SimTK::negator<NUMBER> &,const SimTK::negator<NUMBER> &)'
    #6>        with
    #6>        [
    #6>            N=3,
    #6>            ELT=SimTK::negator<double>,
    #6>            STRIDE=1,
    #6>            NUMBER=double
    #6>        ]
    #6>        while trying to match the argument list '(SimTK::negator<NUMBER>, const double, const SimTK::negator<NUMBER>)'
    #6>        with
    #6>        [
    #6>            NUMBER=double
    #6>        ]
    #6>        src\simtkmath\generated_code\_simtkmath_free_functions.pypp.cpp(28) : see reference to function template instantiation 'SimTK::Mat<M,N,ELT,CS,RS> SimTK::crossMat<SimTK::negator<NUMBER>,1>(const SimTK::Vec<3,ELT,STRIDE> &)' being compiled
    #6>        with
    #6>        [
    #6>            M=3,
    #6>            N=3,
    #6>            ELT=SimTK::negator<double>,
    #6>            CS=3,
    #6>            RS=1,
    #6>            NUMBER=double,
    #6>            STRIDE=1
    #6>        ]
    #6>c:\program files\simtk\include\SimTKcommon/internal/SmallMatrixMixed.h(631) : error C2665: 'SimTK::Row<N,ELT,STRIDE>::Row' : none of the 13 overloads could convert all the argument types
    #6>        with
    #6>        [
    #6>            N=3,
    #6>            ELT=SimTK::negator<double>,
    #6>            STRIDE=1
    #6>        ]
    #6>        c:\program files\simtk\include\SimTKcommon/internal/Row.h(308): could be 'SimTK::Row<N,ELT,STRIDE>::Row(const SimTK::negator<NUMBER> &,const SimTK::negator<NUMBER> &,const SimTK::negator<NUMBER> &)'
    #6>        with
    #6>        [
    #6>            N=3,
    #6>            ELT=SimTK::negator<double>,
    #6>            STRIDE=1,
    #6>            NUMBER=double
    #6>        ]
    #6>        while trying to match the argument list '(const SimTK::negator<NUMBER>, SimTK::negator<NUMBER>, const double)'
    #6>        with
    #6>        [
    #6>            NUMBER=double
    #6>        ]
    #6>c:\program files\simtk\include\SimTKcommon/internal/SmallMatrixMixed.h(632) : error C2665: 'SimTK::Row<N,ELT,STRIDE>::Row' : none of the 13 overloads could convert all the argument types
    #6>        with
    #6>        [
    #6>            N=3,
    #6>            ELT=SimTK::negator<double>,
    #6>            STRIDE=1
    #6>        ]
    #6>        c:\program files\simtk\include\SimTKcommon/internal/Row.h(308): could be 'SimTK::Row<N,ELT,STRIDE>::Row(const SimTK::negator<NUMBER> &,const SimTK::negator<NUMBER> &,const SimTK::negator<NUMBER> &)'
    #6>        with
    #6>        [
    #6>            N=3,
    #6>            ELT=SimTK::negator<double>,
    #6>            STRIDE=1,
    #6>            NUMBER=double
    #6>        ]
    #6>        while trying to match the argument list '(const double, const SimTK::negator<NUMBER>, SimTK::negator<NUMBER>)'
    #6>        with
    #6>        [
    #6>            NUMBER=double
    #6>        ]    
    mb.free_functions('crossMat').exclude()

def expose_differentiator(mb):
    #~ WARNING: SimTK::Differentiator::GradientFunction [class]^M
    #~ > warning W1031: `Py++` will generate class wrapper - user asked to^M
    #~ > expose non - public member function "GradientFunction"
    diff = mb.class_('Differentiator')
    # diff.exclude()
    #~ for cls in diff.classes():
        #~ if cls.name.endswith('Function'):
            #~ cls.constructors().exclude()
            #~ cls.no_init = True
            #~ cls.exclude() # protected destructor compile error
    #~ diff.constructors(arg_types=[None]).exclude()
    #~ diff.no_init = True
    #
    #WARNING: int SimTK::Differentiator::ScalarFunction::f(SimTK::Real x, SimTK::Real & fx) const [member function]
    #> execution error W1009: The function takes as argument (name=fx, pos=1)
    #> non-const reference to Python immutable type - function could not be
    #> called from Python. Take a look on "Function Transformation"
    #> functionality and define the transformation.
    scalar = diff.class_('ScalarFunction')
    scalar.member_function('f').add_transformation(FT.output('fx'))
    scalar.no_init = True
    scalar.noncopyable = True
    # scalar.exclude() # I give up; compile errors on protected destructor
    gradient = diff.class_('GradientFunction')
    gradient.member_function('f').add_transformation(FT.output('fy'))
    gradient.no_init = True
    gradient.noncopyable = True
    # gradient.exclude() # I give up; compile errors on protected destructor
    diff.member_function('calcDerivative', arg_types=[None, None, None, None]).add_transformation(FT.output('dfdy'))
    function = diff.class_('Function')
    function.no_init = True
    function.noncopyable = True
    # function.exclude()
    diff.exclude()
    
def expose_cpodes(mb):
    #~ WARNING: char * SimTK::CPodes::getReturnFlagName(int flag) [member function]^M
    #~ > compilation error W1050: The function returns "char *" type. You have^M
    #~ > to specify a call policies.Be sure to take a look on `Py++` defined^M
    #~ > call policies
    cpodes = mb.class_('CPodes')
    cpodes.member_function('getReturnFlagName').exclude()
    cpodes.member_function('dlsGetReturnFlagName').exclude()
    #~ WARNING: void SimTK::CPodes::registerQuadratureFunc(int (*)( ::SimTK::CPodesSystem const &,::SimTK::Real,::SimTK::Vector const &,::SimTK::Vector & ) * arg0) [member function]^M
    #~ > compilation error W1004: Boost.Python library can not expose function,^M
    #~ > which takes as argument/returns pointer to function.  See^M
    #~ > http://www.boost.org/libs/python/doc/v2/faq.html#funcptr for more^M
    #~ > information.
    for fn in cpodes.member_functions():
        if fn.name.startswith('register') and fn.name.endswith('Func'):
            fn.exclude()
    #~ WARNING: _iobuf [struct]^M
    #~ > execution error W1040: The declaration is unexposed, but there are^M
    #~ > other declarations, which refer to it. This could cause "no to_python^M
    #~ > converter found" run time error. Declarations:    int^M
    #~ > SimTK::CPodes::setErrFile(FILE * errfp) [member function]
    cpodes.member_function('setErrFile').exclude()

def expose_integrator(mb):
    mb.class_('Integrator').include()
    sys_ref = declarations.reference_t(declarations.const_t
            (declarations.declarated_t(mb.class_('System'))))
    for integ_name in ['VerletIntegrator', 'RungeKuttaMersonIntegrator']:
        integ = mb.class_(integ_name)
        integ.include()
        integ.constructor(arg_types=[sys_ref]).call_policies = with_custodian_and_ward(1, 2)

def expose_timestepper(mb):
    t = mb.class_('TimeStepper')
    t.include()
    sys_ref = declarations.reference_t(declarations.const_t
            (declarations.declarated_t(mb.class_('System'))))
    integ_ref = declarations.reference_t(declarations.declarated_t(mb.class_('Integrator')))
    t.constructor(arg_types=[sys_ref]).call_policies = with_custodian_and_ward(1, 2)
    t.constructor(arg_types=[sys_ref, integ_ref]).call_policies = with_custodian_and_ward(1, 2, 
            with_custodian_and_ward(1, 3))
    t.member_function('setIntegrator').call_policies = with_custodian_and_ward(1, 2)

if __name__ == "__main__":
    generate_simtkmath_source()
