# file: generate_simbody_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 pyplusplus import function_transformers as FT
from doxygen_doc_extractor import doxygen_doc_extractor

def generate_simbody_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(['simbody/wrap_simbody.h',])

    # Delegate wrapping details to other subroutines, to keep this subrouting from growing too large
    mb.namespace('SimTK').include()
    perform_usual_wrapping_tasks(mb)
    disambiguate_typedef_aliases(mb)    
    expose_multibodysystem(mb)
    expose_variables(mb)
    expose_matter(mb)
    expose_subsystems(mb)
    expose_constraint(mb)
    expose_mobilizedbody(mb)
    expose_contactgeometry(mb)
    expose_generalforcesubsystem(mb)
    expose_force(mb)
    expose_body(mb)
    expose_study(mb)
    expose_motion(mb)
    expose_vtkeventreporter(mb)
    expose_collisiondetectionalgorithm(mb)
    expose_simbody_arrays(mb)
    convert_sequences_to_vectors(mb)
    expose_indexes(mb)
    wrap_trianglemesh(mb)
    exclude_compliantcontactsubsystem(mb)
    wrap_ostream_methods(mb)
    shorten_array_aliases(mb)
    expose_free_functions(mb)
    exclude_rep_classes(mb)
    exclude_operators(mb)
    exclude_strange_classes(mb)
    wrap_properties(mb)
    
    # Don't rewrap anything already wrapped by simtk.common etc.
    # 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/')
    mb.register_module_dependency('simtkmath/generated_code/')
    
    extractor = doxygen_doc_extractor()

    mb.build_code_creator(module_name='_simbody', doc_extractor=extractor)
    mb.split_module(os.path.join(os.path.abspath('.'), 'simbody', 'generated_code'))
    
    # 4>src\simbody\generated_code\std_vector_Vec3.pypp.cpp(7) : fatal error C1083: Cannot open include file: '_Vec_less__3_comma__double_comma__1__greater___value_traits.pypp.hpp': No such file or directory
    missing_file = os.path.join(os.path.abspath('.'), 'simbody', 'generated_code', '_Vec_less__3_comma__double_comma__1__greater___value_traits.pypp.hpp')
    if not os.path.exists(missing_file):
        open(missing_file, "w").close()
    missing_file = os.path.join(os.path.abspath('.'), 'simbody', 'generated_code', '_std_vector_int__value_traits.pypp.hpp')
    if not os.path.exists(missing_file):
        open(missing_file, "w").close()
    
    # If all succeeds, record this accomplishment by touching a particular file
    open(os.path.join(os.path.abspath('.'), 'simbody', 'generated_code', 'generate_simbody.stamp'), "w").close()

def convert_sequences_to_vectors(mb):
    """
    Permit automatic conversion of python sequences to certain std::vectors in method arguments.
    Particularly those used in constructors of FunctionBased Mobilizers.
    """
    for vec_name in 'vector<int>', 'vector< std::vector<int> >', 'vector<SimTK::Function_<double> const* >':
        vec = mb.class_(vec_name)
        # print vec.decl_string
        # Automatic conversion from python sequence to array
        vec.include_files.append("to_simtk_sequence_conversion.hpp")
        vec.add_registration_code("""
                // Enable conversion of a python sequence to a std::vector,
                // particularly for use in method arguments
                stdvector_from_pysequence< %s >();
            """ % vec.decl_string, 
            works_on_instance=False)
    
def exclude_compliantcontactsubsystem(mb):
    ccs = mb.class_('CompliantContactSubsystem')
    # 1>CompliantContactSubsystem.pypp.obj : error LNK2019: unresolved external symbol "__declspec(dllimport) public: void __thiscall SimTK::CompliantContactSubsystem::calcContactPatchDetails    
    ccs.exclude()

def wrap_trianglemesh(mb):
    geom = mb.namespace('SimTK').class_('ContactGeometry')
    mesh = geom.class_('TriangleMesh')
    # WARNING: SimTK::Vec3 SimTK::ContactGeometry::TriangleMesh::findNearestPoint(SimTK::Vec3 const & position, bool & inside, SimTK::UnitVec3 & normal) const [member function]
    # > execution error W1009: The function takes as argument (name=inside,
    # > 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.
    # mesh.member_function('findNearestPoint', arg_types=[None,None,None]).add_transformation(
    #     FT.output('inside'), FT.output('normal'))
    mesh.member_function('findNearestPoint', arg_types=[None,None,None,None]).add_transformation(
        FT.output('inside'), FT.output('face'), FT.output('uv'))
    # mesh.member_function('intersectsRay', arg_types=[None,None,None,None]).add_transformation(
    #     FT.inout('distance'), FT.inout('normal'))
    mesh.member_function('intersectsRay', arg_types=[None,None,None,None,None]).add_transformation(
        FT.inout('distance'), FT.inout('face'),  FT.inout('uv'))
    mesh.member_function('findVertexEdges').add_transformation(
        FT.output('edges'))
    geom.member_functions('findNearestPoint', arg_types=[None,None,None]).add_transformation(
        FT.output('inside'), FT.output('normal'))
    geom.member_functions('intersectsRay', arg_types=[None,None,None,None]).add_transformation(
        FT.inout('distance'), FT.inout('normal'))
    geom.member_functions('getBoundingSphere', arg_types=[None,None]).add_transformation(
        FT.output('center'), FT.output('radius'))
    
def expose_motion(mb):
    motion = mb.class_('Motion')
    steady = motion.class_('Steady')
    # link error
    steady.member_function('getDefaultRate').exclude()
    
def expose_study(mb):
    # link errors
    mb.class_('MultibodyDynamicsStudy').exclude()
    
def expose_collisiondetectionalgorithm(mb):
    c = mb.class_('CollisionDetectionAlgorithm')
    # return type compile error: return a pointer which can be NULL
    c.member_function('getAlgorithm').exclude()

def expose_simbody_arrays(module_builder):
    "Polish up all Array_(whatever) classes"
    mb = module_builder
    for array in mb.classes(lambda c: c.name.startswith('Array_')):
        wrap_array(array, mb)
    # compile error due to array of abstract class
    mb.class_('Array_< SimTK::Function_< double >, unsigned int >').exclude()
    #~ Array__less__SimTK_scope_Markers_scope_Marker_comma__SimTK_scope_Markers_scope_MarkerIx__greater_.pypp.cpp
    #~ c:\program files\simtk\include\SimTKcommon/internal/Array.h(3033) : error C2512: 'SimTK::Markers::Marker' : no appropriate default constructor available
    mb.class_('Array_< SimTK::Markers::Marker, SimTK::Markers::MarkerIx >').exclude()
    mb.classes(lambda c: c.name.startswith('ArrayView')).exclude()
        
def expose_vtkeventreporter(mb):
    v = mb.class_('VTKEventReporter')
    v.include()
    v.held_type = 'std::auto_ptr< %s >' % v.decl_string
    parent_ptr_type = 'std::auto_ptr< %s >' % v.bases[0].related_class.decl_string
    mb.add_registration_code(
        'bp::implicitly_convertible<%s, %s >();' % (v.held_type, parent_ptr_type) )
    # compile errors
    vr = mb.class_('VTKVisualizer')
    for fn_name in ['getVtkRenderer', 'updVtkRenderer', 'getVtkRenderWindow', 'updVtkRenderWindow',]:
        vr.member_function(fn_name).exclude()

def expose_body(mb):
    body = mb.class_('Body')
    body.include()
    # Link errors
    body.class_('Linear').exclude()
    try:
        body.class_('Particle').exclude()
    except:
        pass

def expose_force(mb):
    force = mb.class_('Force')
    force.include()
    # link errors
    force.class_('Thermostat').member_function('excludeMobilizedBody').exclude()
    force.class_('Thermostat').member_function('initializeSystemToBathTemperature').exclude()
    force.class_('Thermostat').member_function('setSystemToTemperature').exclude()

def expose_generalforcesubsystem(mb):
    mb.class_('GeneralForceSubsystem').include()
    
def expose_contactgeometry(mb):
    mb.class_('OBBTreeNode').constructors().exclude()
    mb.class_('OBBTreeNode').no_init = True

def expose_mobilizedbody(mb):
    mobilized_body = mb.class_('MobilizedBody')
    mobilized_body.include()
    mobilized_body.class_('Ground').include()
    # Temporarily disable Function based to get past compile error
    # mobilized_body.class_('FunctionBased').exclude()
    # Link errors - not implemented?
    mobilized_body.member_function('isAccelerationAlwaysZero').exclude()
    mobilized_body.member_function('isVelocityAlwaysZero').exclude()
    mobilized_body.member_function('setDefaultMotionType').exclude()
    mobilized_body.member_function('setMotionType').exclude()
    function_based = mobilized_body.class_('FunctionBased')
    # Constructors all take arrays of Function pointers, which is trouble
    # Eliminate constructors that take an argument of SimTK::Array_<Function_<Real> >
    function_based.no_init = True
    for ctor in function_based.constructors():
        if len(ctor.argument_types) >= 4:
            if 'SimTK::Array_' in ctor.argument_types[3].decl_string:
                ctor.exclude()
        if len(ctor.argument_types) >= 6:
            if 'SimTK::Array_' in ctor.argument_types[5].decl_string:
                ctor.exclude()
    #function_based.constructors().exclude()
    ## Wrap constructors with functions that take std::vector< std::auto_ptr<Function> > arguments
    #mobilized_body.include_files.append("boost/python/stl_iterator.hpp")
    #fbs = function_based.decl_string
    #function_based.add_declaration_code("""//
        #static boost::shared_ptr< %s > constructFunctionBasedMobilizer1(
            #MobilizedBody& parent, const Transform& inbFrame, 
            #const Body& body, const Transform& outbFrame, int nmobilities,
            #const bp::object& functions, // <== container that needs special handling
            #const bp::object& coordIndices,
            #MobilizedBody::Direction direction=MobilizedBody::Forward)
        #{
            #// convert "functions" argument to an std::vector, transferring ownership of elements
            #bp::stl_input_iterator< std::auto_ptr< SimTK::Function > > begin_fns(functions), end_fns, f;
            #std::vector< SimTK::Function const * > fn_ptrs;
            #for (f = begin_fns; f != end_fns; ++f)
                #// Odd. "f->release()" won't compile, but "(*f).release()" will...
                #fn_ptrs.push_back((*f).release());
            #// convert coordIndices to vector of vectors
            #bp::stl_input_iterator< bp::object > begin_indices(coordIndices), end_indices, i;
            #std::vector< std::vector<int> > coords;
            #for (i = begin_indices; i != end_indices; ++i) {
                #bp::stl_input_iterator< int > begin_coords(*i), end_coords, c;
                #std::vector<int> vi;
                #for (c = begin_coords; c != end_coords; ++c)
                    #vi.push_back(*c);
                #coords.push_back(vi);
            #}
            #// call the real constructor
            #return boost::shared_ptr< %s >(new %s(
                #parent, inbFrame, body, outbFrame, nmobilities, 
                #fn_ptrs, coords, direction));
        #}""" % (fbs, fbs, fbs) )
    #function_based.add_registration_code(
        #'def("__init__", bp::make_constructor(constructFunctionBasedMobilizer1))', 
        #tail=False)

def expose_constraint(mb):
    constraint = mb.class_('Constraint')
    constraint.include()
    #~ WARNING: void SimTK::Constraint::getNumConstraintEquationsInUse(SimTK::State const & arg0, int & mp, int & mv, int
    #~ & ma) const [member function]
    #~ > execution error W1009: The function takes as argument (name=mp, 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.
    constraint.member_function('getNumConstraintEquationsInUse').add_transformation(
        FT.output('mp'), FT.output('mv'), FT.output('ma'))
    # Link errors
    ball = constraint.class_('Ball')
    ball.member_function('getBallReactionForceOnBody2').exclude()
    ball.member_function('getBallReactionForceOnBody1').exclude()
    ball.member_function('getPointOnBody2').exclude()
    ball.member_function('getPointOnBody1').exclude()
    constant_angle = constraint.class_('ConstantAngle')
    constant_angle.member_function('getAngle').exclude()
    constant_angle.member_function('getBaseAxis').exclude()
    constant_angle.member_function('getFollowerAxis').exclude()
    constant_angle.member_function('getTorqueOnFollowerBody').exclude()
    constant_orientation = constraint.class_('ConstantOrientation')
    constant_orientation.member_function('getBaseRotation').exclude()
    constant_orientation.member_function('getFollowerRotation').exclude()
    constant_orientation.member_function('getTorqueOnFollowerBody').exclude()
    constant_speed = constraint.class_('ConstantSpeed')
    constant_speed.member_function('getGeneralizedForce').exclude()
    constraint.class_('NoSlip1D').member_function('getContactPoint').exclude()
    constraint.class_('NoSlip1D').member_function('getDirection').exclude()
    constraint.class_('NoSlip1D').member_function('getForceAtContactPoint').exclude()
    constraint.class_('PointInPlane').member_function('getFollowerPoint').exclude()
    constraint.class_('PointInPlane').member_function('getForceOnFollowerPoint').exclude()
    constraint.class_('PointInPlane').member_function('getPlaneHeight').exclude()
    constraint.class_('PointInPlane').member_function('getPlaneNormal').exclude()
    constraint.class_('Rod').member_function('getPointOnBody2').exclude()
    constraint.class_('Rod').member_function('getPointOnBody1').exclude()
    constraint.class_('Rod').member_function('getRodLength').exclude()
    constraint.class_('Rod').member_function('getRodTension').exclude()
    constraint.class_('Weld').member_function('getAxisDisplayLength').exclude()
    constraint.class_('Weld').member_function('getFrameOnBody2').exclude()
    constraint.class_('Weld').member_function('getFrameOnBody1').exclude()
    constraint.class_('Weld').member_function('getWeldReactionOnBody2').exclude()
    constraint.class_('Weld').member_function('getWeldReactionOnBody1').exclude()
    constraint.class_('Weld').member_function('setAxisDisplayLength').exclude()
    constraint.class_('PointOnLine').member_function('getLineDirection').exclude()
    constraint.class_('PointOnLine').member_function('getForceOnFollowerPoint').exclude()
    constraint.class_('PointOnLine').member_function('getFollowerPoint').exclude()
    constraint.class_('PointOnLine').member_function('getPointOnLine').exclude()
    constraint.class_('ConstantAcceleration').member_function('getGeneralizedForce').exclude()
    # Create more flexible constructor for CoordinateCoupler
    coordcoupler = constraint.class_('CoordinateCoupler')
    # must add includes to parent, since parent controls the source file
    constraint.include_files.append("boost/python/stl_iterator.hpp")
    ccs = coordcoupler.decl_string
    coordcoupler.add_declaration_code("""//
        static boost::shared_ptr< %s > constructCoordinateCoupler1(
            SimbodyMatterSubsystem  & matter,
            const std::auto_ptr<Function>& function,
            const bp::object& coordBody,
            const bp::object& coordIndex)        
        {
            // convert "coordBody" argument to an std::vector
            bp::stl_input_iterator< MobilizedBodyIndex > begin_body(coordBody), end_body, b;
            std::vector< MobilizedBodyIndex > vbodies;
            for (b = begin_body; b != end_body; ++b)
                vbodies.push_back(*b);
            bp::stl_input_iterator< MobilizerQIndex > begin_index(coordIndex), end_index, i;
            std::vector< MobilizerQIndex > vindexes;
            for (i = begin_index; i != end_index; ++i)
                vindexes.push_back(*i);
            // call the real constructor
            std::auto_ptr<Function>& ncfn = const_cast<std::auto_ptr<Function>&>(function);
            return boost::shared_ptr< %s >(new %s(
                matter, ncfn.release(),
                vbodies, vindexes
                ));
        }""" % (ccs, ccs, ccs) )
    coordcoupler.add_registration_code(
        'def("__init__", bp::make_constructor(constructCoordinateCoupler1))', 
        tail=False)


def expose_matter(mb):
    matter = mb.class_('SimbodyMatterSubsystem')
    matter.include()
    # Link errors
    matter.member_function('calcAccConstraintErr').exclude()
    matter.member_function('calcAccelerationFromUDot').exclude()
    matter.member_function('calcDynamicsFromForces').exclude()
    matter.member_function('calcDynamicsFromForcesAndMotions').exclude()
    matter.member_function('calcG').exclude()
    matter.member_function('calcGV').exclude()
    matter.member_function('calcGt').exclude()
    matter.member_function('calcGtV').exclude()
    matter.member_function('calcM').exclude()
    matter.member_function('calcMInv').exclude()
    matter.member_function('calcPtV').exclude()
    matter.member_function('calcResidualForce').exclude()
    matter.member_function('getTotalMultAlloc').exclude()
    matter.member_function('getNMobilities').exclude()
    matter.member_function('getNConstraints').exclude()
    matter.member_function('getNBodies').exclude()
    # more link errors
    smsr = mb.class_('SimbodyMatterSubtreeResults')
    smsr.member_function('clear').exclude()
    
def expose_subsystems(mb):
    mb.class_('GeneralContactSubsystem').include()
    mb.class_('ForceSubsystem').include()
    mb.class_('DecorationSubsystem').include()
    # mb.class_('ContactSet').include()
    mbsys_ref = declarations.reference_t(declarations.declarated_t(mb.class_('MultibodySystem')))
    for subsys in mb.classes(lambda c: c.name.endswith('Subsystem')):
        for ctor in subsys.constructors(arg_types=[mbsys_ref], allow_empty=True):
            ctor.call_policies = with_custodian_and_ward(1, 2)

def expose_variables(mb):
    #~ WARNING: SimTK::Plugin::m_handle [variable]
    #~ > compilation error W1036: `Py++` can not expose pointer to Python
    #~ > immutable member variables. This could be changed in future.
    mb.variables('m_handle').exclude()
    mb.variables('atomic').exclude()

def expose_multibodysystem(mb):
    mbsys = mb.class_('MultibodySystem')
    mbsys.include()
    # setBlahSubsystem(blah) takes ownership of Blah
    for fn in mbsys.member_functions(lambda f: f.name.startswith('set') and f.name.endswith('Subsystem')):
        fn.call_policies = with_custodian_and_ward(2, 1) # TODO check order
    mbsys.member_function('addForceSubsystem').call_policies = with_custodian_and_ward(2, 1) # TODO check order
    #~ WARNING: SimTK::MultibodySystem [class]
    #~ > warning W1031: `Py++` will generate class wrapper - user asked to
    #~ > expose non - public member function "MultibodySystem"
    # (presumably private constructor "explicit MultibodySystem(MultibodySystemRep*);"
    for ctor in mbsys.constructors(arg_types=[None]):
        if declarations.is_pointer(ctor.argument_types[0]):
            ctor.exclude()
    # Constructor taking matter subsystem takes ownership
    matter_ref = declarations.reference_t(declarations.declarated_t(mb.class_('SimbodyMatterSubsystem')))
    mbsys.constructor(arg_types=[matter_ref]).call_policies = with_custodian_and_ward(2, 1)

if __name__ == "__main__":
    generate_simbody_source()
