# file: generate_simtkvecs_source.py

import os
import sys
import re
from pygccxml import declarations
from pyplusplus import module_builder
from pyplusplus.module_builder.call_policies import *
from pyplusplus import function_transformers as FT
from simtk_wrap_utils import *
from doxygen_doc_extractor import doxygen_doc_extractor
import time

def generate_simtkvecs_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
    """
    print "creating module builder at %s ..." % time.asctime()
    mb = create_module_builder(includes=['simtkvecs/wrap_simtkvecs.h', 'simtkvecs/instantiations.h']) 
    print "Finished creating module builder at %s." % time.asctime()
    # mb = create_module_builder(includes=['simtkvecs/wrap_simtkvecs.h']) 
    
    # Delegate wrapping details to other subroutines, to keep this one from getting too big
    mb.decls().exclude() # e.g. no instantiate() methods
    print "1 %s." % time.asctime()
    mb.namespace('SimTK').include()
    print "2 %s." % time.asctime()
    enumerate_vectors(mb)
    print "3 %s." % time.asctime()
    expose_vec_classes(mb)
    print "4 %s." % time.asctime()
    perform_usual_wrapping_tasks(mb)
    print "5 %s." % time.asctime()
    wrap_mat33(mb)
    print "6 %s." % time.asctime()
    expose_mats(mb)
    expose_negator(mb)
    expose_vec3(mb)
    expose_unitvec(mb)
    expose_rotation(mb)
    print "11 %s." % time.asctime()
    expose_transform(mb)
    print "12 %s." % time.asctime()
    wrap_ostream_methods(mb)
    print "13 %s." % time.asctime()
    exclude_symmat_ctors(mb)
    print "14 %s." % time.asctime()
    exclude_lapack(mb)
    print "15 %s." % time.asctime()
    exclude_cnts(mb)
    print "16 %s." % time.asctime()
    exclude_arrayview(mb)
    expose_simtk_string(mb)
    expose_global_variables(mb)
    wrap_outer_products(mb)
    wrap_row3(mb)
    print "21 %s." % time.asctime()
    wrap_arrays(mb)
    wrap_matrixes(mb)
    wrap_smallmats(mb)
    shorten_array_aliases(mb)
    wrap_conjugate(mb)
    print "26 %s." % time.asctime()
    wrap_matrix_outline(mb)
    wrap_vector(mb)
    exclude_long_doubles_and_such(mb)
    exclude_strange_classes(mb)
    wrap_properties(mb)
    print "Finished custom wrapping code at %s." % time.asctime()
    
    # Don't rewrap anything already wrapped by std module
    # See http://www.language-binding.net/pyplusplus/documentation/multi_module_development.html
    mb.register_module_dependency('external/std/generated_code/')
    print "Finished loading dependencies at %s." % time.asctime()
    
    extractor = doxygen_doc_extractor()

    mb.build_code_creator(module_name='_vecs', doc_extractor=extractor)
    print "Finished building code creator at %s." % time.asctime()
    
    mb.split_module(os.path.join(os.path.abspath('.'), 'simtkvecs', 'generated_code'))
    print "Finished writing source files at %s." % time.asctime()
    # If all succeeds, record this accomplishment by touching a particular file
    open(os.path.join(os.path.abspath('.'), 'simtkvecs', 'generated_code', 'generate_simtkvecs.stamp'), "w").close()

# Cach lists of classes for performance
vec3s = None
vec4s = None
row3s = None
unitvec3s = None
mat33s = None
unitrow3s = None
symmat3s = None
def enumerate_vectors(mb):
    global vec3s, vec4s, row3s, unitvec3s, mat33s, unitrow3s, symmat3s
    vec3s = list(iter_vec3s(mb))
    mat33s = list(iter_mat33s(mb))
    row3s = list(iter_vec3s(mb, row_or_vec='Row'))
    unitvec3s = list(iter_unitvecs(mb))
    unitrow3s = list(iter_unitvecs(mb, 'Row'))
    symmat3s = list(iter_symmat3s(mb))
    vec4s = list(iter_vec4s(mb))

def wrap_row3(mb):
    # Cache mat33s for performance
    # mat33s = list(iter_mat33s(mb))
    for row in row3s:
        # Row * Mat33
        for mat in mat33s:
            row.add_registration_code("def( bp::self * bp::other< %s >() )" % mat.decl_string)
        for rot_name in 'Rotation_<double>', 'InverseRotation_<double>':
            rot = mb.class_(rot_name)
            row.add_registration_code("def( bp::self * bp::other< %s >() )" % rot.decl_string)            

def iter_unitvecs(mb, row_or_vec='Vec', strides=[1,3]):
    # No negator version of UnitVecs, as operator- produces new UnitVecs (for now...)
    for stride in strides:
        uvec = mb.class_('Unit%s<double, %s>' % (row_or_vec, stride) )
        yield uvec

def iter_vecs(mb, row_or_vec='Vec', size=3, strides=[1,3,4,6,7]):
    for elt in 'double', 'SimTK::negator<double>':
        for stride in strides:    
            vec = mb.class_('%s<%s, %s, %s>' % (row_or_vec, size, elt, stride) )
            yield vec

def iter_vec2s(mb, row_or_vec='Vec', strides=[1,2,3]):
    for vec in iter_vecs(mb, mb, row_or_vec=row_or_vec, size=2, strides=strides):
        yield vec

def iter_vec3s(mb, row_or_vec='Vec', strides=[1,3,4,6,7]):
    for vec in iter_vecs(mb, row_or_vec=row_or_vec, size=3, strides=strides):
        yield vec

def iter_vec4s(mb, row_or_vec='Vec', strides=[1,4,5]):
    for vec in iter_vecs(mb, row_or_vec=row_or_vec, size=4, strides=strides):
        yield vec

def iter_vec6s(mb, row_or_vec='Vec', strides=[1,6,7]):
    for vec in iter_vecs(mb, row_or_vec=row_or_vec, size=6, strides=strides):
        yield vec

def wrap_outer_products(mb):
    # Cache classes for efficiency
    # vec3s = iter_vecs(mb)
    # unitvecs = iter_unitvecs(mb)
    # Motivating python runtime error:
    #   TypeError: unsupported operand type(s) for *: 'UnitRow3' and 'Vec3'
    for urow in unitrow3s:
        for vec3 in vec3s:
            urow.add_registration_code("""
                def( bp::self * bp::other< %s >() )
                """ % vec3.decl_string )
        for uvec in unitvec3s:
            urow.add_registration_code("""
                def( bp::self * bp::other< %s >() )
                """ % uvec.decl_string )
    for row in row3s:
        # rhs Vec3s seem to be handled correctly by instantiator() method trick
        # leaving UnitVec rhs to be instantiated
        for uvec in unitvec3s:
            row.add_registration_code("""
                def( bp::self * bp::other< %s >() )
                """ % uvec.decl_string )

def wrap_vector(mb):
    # Interconvert tuples with Vectors
    # mb.add_declaration_code('#include "convert_simtk_vector.hpp"', tail=False)
    # mb.add_registration_code("register_simtk_vector_conversion();", tail=False)
    expose_vector(mb.class_('Vector_<double>'))
    
def expose_global_variables(mb):
    simtk = mb.namespace('SimTK')
    # global variables that are references to int or double cause compile error
    for var in mb.variables():
        if declarations.is_reference(var.type):
            if declarations.is_arithmetic(var.type.base):
                var.exclude()
       
def wrap_matrix_outline(mb):
    mo = mb.class_('MatrixOutline')
    mo.member_function('getMinimumSize').add_transformation(
        FT.output('m'), FT.output('n'))
    
def exclude_long_doubles_and_such(mb):
    #WARNING: bool SimTK::canStoreInNonnegativeInt(long long unsigned int u) [free function]
    #> execution error W1010: The function introduces registration order
    #> problem. For more information about the problem read "registration
    #> order" document.Problematic functions list:           bool
    #> SimTK::canStoreInNonnegativeInt(bool arg0) [free function]
    simtk = mb.namespace('SimTK')
    bad_words = ['long ', 'short ', 'float', 'unsigned ', 'signed ']
    for fn in simtk.free_functions():
        for arg in fn.argument_types:
            for annoy in bad_words:
                if annoy in arg.decl_string:
                    fn.exclude()
    for fn in simtk.free_operators():
        for arg in fn.argument_types:
            for annoy in bad_words:
                if annoy in arg.decl_string:
                    fn.exclude()

def wrap_conjugate(mb):
    mb.class_('conjugate<double>').alias = "conjugate_double"
    mb.class_('conjugate<float>').exclude()
    mb.class_('conjugate<long double>').exclude()

def wrap_arrays(mb):
    for array in mb.classes(lambda c: c.name.startswith('Array_')):
        wrap_array(array, mb)

def exclude_arrayview(mb):
    mb.classes(lambda c: c.name.startswith('ArrayView')).exclude()    
    mb.member_functions('getSubArray').exclude()
    mb.member_functions('updSubArray').exclude()

def exclude_cnts(mb):
    mb.classes(lambda c: c.name.startswith('CNT<')).exclude()

def exclude_symmat_ctors(mb):
    #~ WARNING: SimTK::SymMat<6,double,1> [class declaration]
    #~ > execution error W1040: The declaration is unexposed, but there are
    #~ > other declarations, which refer to it. This could cause "no to_python
    #~ > converter found" run time error. Declarations:    SimTK::Mat<6, 6,
    #~ > double, 1, 6>::Mat(SimTK::SymMat<6,double,1> const & src)
    #~ > [constructor]   SimTK::Mat<6, 6, double, 6,
    #~ > 1>::Mat(SimTK::SymMat<6,double,1> const & src) [constructor]
    simtk = mb.namespace('SimTK')
    for ctor in simtk.constructors(arg_types=[None]):
        arg_type = ctor.argument_types[0]
        if 'SymMat' in arg_type.decl_string:
            ctor.exclude()

def expose_vec_classes(mb):
    """
    Because for this module, we are not yet taking everything in the simtk namespace.
    """
    simtk = mb.namespace('SimTK')
    # drop1 leads to unwanted expansion of classes that must be instantiated
    simtk.member_functions('drop1').exclude()
    # positional transposes also expand set of classes that must be instantiated
    simtk.member_functions('positionalTranspose').exclude()
    simtk.member_functions('updPositionalTranspose').exclude()

def iter_mats( mb, m=3, n=3, strides=((3,1), (1,3), (6,1), (1,6)) ):
    for elt in 'double', 'SimTK::negator<double>':
        for stride1, stride2 in strides:
            mat = mb.class_( 'Mat<%d, %d, %s, %d, %d>' % (m, n, elt, stride1, stride2) )
            yield mat
            
def iter_mat22s( mb, strides=((2,1), (1,2)) ):
    for mat in iter_mats(mb, m=2, n=2, strides=strides):
        yield mat
        
def iter_mat33s( mb, strides=((3,1), (1,3), (6,1), (1,6)) ):
    for mat in iter_mats(mb, m=3, n=3, strides=strides):
        yield mat
       
def iter_symmat3s( mb, strides=[1] ):
    for elt in 'double', 'SimTK::negator<double>':
        yield mb.class_('SymMat<3, %s ,1>' % elt)
      
def iter_mat44s( mb, strides=((4,1), (1,4)) ):
    for mat in iter_mats(mb, m=4, n=4, strides=strides):
        yield mat
        
def iter_mat66s( mb, strides=((6,1), (1,6)) ):
    for mat in iter_mats(mb, m=6, n=6, strides=strides):
        yield mat
        
def wrap_mat33(mb):
    # Profiler shows that iterating again and again is slow, so cache the classes
    # mat33s = list(iter_mat33s(mb))
    # vec3s = list(iter_vec3s(mb))
    # unitvecs = list(iter_unitvecs(mb))
    for mat in mat33s:
        # Mat33 has some long named inner classes that cause problems for epydoc generator
        mat.classes(allow_empty=True).exclude()
        # Addition and subtraction
        # (but you cannot add Mat33s to SymMat3s...)
        for mat2 in mat33s:
            mat.add_registration_code("def( bp::self + bp::other< %s >() )" % mat2.decl_string)
            mat.add_registration_code("def( bp::self - bp::other< %s >() )" % mat2.decl_string)
        # Matrix vector multiplication
        for vec3 in vec3s:
            mat.add_registration_code("def( bp::self * bp::other< %s >() )" % vec3.decl_string)            
        for vec3 in unitvec3s:
            mat.add_registration_code("def( bp::self * bp::other< %s >() )" % vec3.decl_string)
    for mat in symmat3s:
        # Matrix vector multiplication
        for vec3 in vec3s:
            mat.add_registration_code("def( bp::self * bp::other< %s >() )" % vec3.decl_string)            
        for vec3 in unitvec3s:
            mat.add_registration_code("def( bp::self * bp::other< %s >() )" % vec3.decl_string)

chainable_method_re = re.compile(r'^(add|set|upd)')
def is_chainable_method(fn):
    """
    Member functions that return a reference to the parent class,
    and begin with names like "add" or "set" (e.g. addBodyDecoration)
    probably return a reference to self.
    """
    if not chainable_method_re.match(fn.name):
        return False
    rt = fn.return_type
    if not rt:
        return False
    parent_ref = declarations.reference_t(declarations.declarated_t(fn.parent))
    return declarations.is_same(parent_ref, rt)

def wrap_chainable_methods(module_builder):
    module_builder.member_functions(is_chainable_method).call_policies = return_self()
    
def wrap_getters(module_builder):
    """
    Remember to run this BEFORE wrap_chainable_methods
    """
    mb = module_builder
    module_builder.member_functions(is_object_getter_method).call_policies = return_internal_reference()
    
getter_method_re = re.compile(r'^(get|upd)')
def is_object_getter_method(fn):
    if not getter_method_re.match(fn.name):
        return False
    rt = fn.return_type
    if not rt:
        return False
    return declarations.is_reference(rt)

def expose_unitvec(module_builder):
    mb = module_builder
    #~ WARNING: SimTK::UnitVec<double,3> [class declaration]
    #~ > execution error W1040: The declaration is unexposed, but there are
    #~ > other declarations, which refer to it. This could cause "no to_python
    #~ > converter found" run time error. Declarations:
    #~ > SimTK::UnitVec<double,3> const & SimTK::InverseTransform_<double>::x()
    #~ > const [member function]   SimTK::UnitVec<double,3> const &
    #~ > SimTK::InverseTransform_<double>::y() const [member function]
    #~ > SimTK::UnitVec<double,3> const & SimTK::InverseTransform_<double>::z()
    #~ > const [member function]
    # mb.class_('UnitVec<double,1>').include()
    # mb.class_('UnitRow<double,1>').include()
    # mb.class_('UnitVec<double,1>').include()
    # mb.class_('UnitRow<double,1>').include()
    # mb.class_('UnitVec<double,1>').exclude()
    # mb.class_('UnitRow<double,1>').exclude()
    # Attempt to debug screwiness with Constraint::PointOnLine constructor
    # by excluding UntiVec1
    #for cls_name in ['UnitVec<double,1>', 'UnitRow<double,1>']:
        #cls = mb.class_(cls_name)
        #cls.include()

def expose_rotation(module_builder):
    mb = module_builder
    #~ WARNING: SimTK::Rotation_<double> [struct]
    #~ > execution error W1040: The declaration is unexposed, but there are
    #~ > other declarations, which refer to it. This could cause "no to_python
    #~ > converter found" run time error. Declarations:
    #~ > SimTK::Rotation_<double> const &
    rot = mb.class_('Rotation_<double>')
    rot.include()
    invrot = mb.class_('InverseRotation_<double>')
    invrot.include()
    mb.enumeration('BodyOrSpaceType').include()
    mb.class_('CoordinateAxis').include()
    #~ WARNING: SimTK::CoordinateAxis::CoordinateAxis(SimTK::CoordinateAxis::XTypeAxis const & arg0) [constructor]
    #~ > compilation error W1005: `Py++` cannot expose function that takes as
    #~ > argument/returns instance of non-public class. Generated code will not
    #~ > compile.
    mb.class_('CoordinateAxis').constructors().exclude()
    #~ WARNING: SimTK::CoordinateAxis SimTK::CoordinateAxis::crossProduct(SimTK::CoordinateAxis const & axis2, int & sign) const [member function]
    #~ > execution error W1009: The function takes as argument (name=sign,
    #~ > 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.
    mb.class_('CoordinateAxis').member_function('crossProduct').add_transformation(FT.output('sign'))
    #~ WARNING: SimTK::Quaternion_<double> [struct]
    #~ > execution error W1040: The declaration is unexposed, but there are
    #~ > other declarations, which refer to it. This could cause "no to_python
    #~ > converter found" run time error. Declarations:
    #~ > SimTK::Rotation_<double>::Rotation_(SimTK::Quaternion_<double> const &
    #~ > q) [constructor]   SimTK::Quaternion_<double>
    #~ > SimTK::Rotation_<double>::convertRotationToQuaternion() const [member
    #~ > function]   SimTK::Rotation_<double> & SimTK::Rotation_<double>::setRo
    #~ > tationFromQuaternion(SimTK::Quaternion_<double> const & q) [member
    #~ > function]
    quat = mb.class_('Quaternion_<double>')
    quat.include()
    # compile error...
    quat.member_function('normalizeThis').call_policies = return_self()
    #~ WARNING: SimTK::Vec<4, double, 1> [class]
    #~ > execution error W1040: The declaration is unexposed, but there are
    #~ > other declarations, which refer to it. This could cause "no to_python
    #~ > converter found" run time error. Declarations:
    #~ > SimTK::Quaternion_<double>::Quaternion_(SimTK::Vec<4, double, 1> const
    #~ > & q) [constructor]
    #~ > SimTK::Quaternion_<double>::Quaternion_(SimTK::Vec<4, double, 1> const
    #~ > & v, bool arg1) [constructor]   SimTK::Vec<4, double, 1> const &
    #~ > SimTK::Quaternion_<double>::asVec4() const [member function]
    #~ > SimTK::Vec<4, double, 1>
    #~ > SimTK::Quaternion_<double>::convertQuaternionToAngleAxis() const
    #~ > [member function]   void
    # quat.member_functions('setQuaternionFromAngleAxis').exclude()
    # quat.member_functions('convertQuaternionToAngleAxis').exclude()
    # quat.member_functions('asVec4').exclude()
    # quat.constructors(arg_types=[None, 'bool']).exclude() # TODO this might be too harsh
    # quat.constructors(arg_types=[None]).exclude() # TODO this might be too harsh
    #~ WARNING: SimTK::Mat<4,3,double,4,1> [class declaration]
    #~ > execution error W1040: The declaration is unexposed, but there are
    #~ > other declarations, which refer to it. This could cause "no to_python
    #~ > converter found" run time error. Declarations:    static
    #~ > SimTK::Mat<4,3,double,4,1> SimTK::Rotation_<double>::calcUnnormalizedN
    #~ > DotForQuaternion(SimTK::Vec<4, double, 1> const & qdot) [member
    #~ > function]   static SimTK::Mat<4,3,double,4,1>
    #~ > SimTK::Rotation_<double>::calcUnnormalizedNForQuaternion(SimTK::Vec<4,
    #~ > double, 1> const & q) [member function]
    # rot.member_functions('calcUnnormalizedNDotForQuaternion').exclude()
    # rot.member_functions('calcUnnormalizedNForQuaternion').exclude()
    # rot.member_functions('calcUnnormalizedNInvForQuaternion').exclude()
    # rot.member_functions('convertRotationToBodyFixedXY').exclude()
    # rot.member_functions('convertTwoAxesRotationToTwoAngles').exclude()
    # rot.member_functions('setRotationToBodyFixedXY').exclude()
    # rot.member_functions('convertRotationToAngleAxis').exclude()
    # rot.member_functions('convertQuaternionDotToAngVel').exclude()
    # rot.member_functions('convertAngVelToQuaternionDot').exclude()
    # rot.member_functions('convertAngVelDotToQuaternionDotDot').exclude()
    #~ WARNING: SimTK::Mat<3, 3, double, 3, 1> [class]
    #~ > execution error W1040: The declaration is unexposed, but there are
    #~ > other declarations, which refer to it. This could cause "no to_python
    #~ > converter found" run time error. Declarations:
    #~ > SimTK::Rotation_<double>::Rotation_(SimTK::Mat<3, 3, double, 3, 1>
    #~ > const & m, bool arg1) [constructor]
    #~ > SimTK::Rotation_<double>::Rotation_(SimTK::Mat<3, 3, double, 3, 1>
    #~ > const & m) [constructor]   SimTK::Mat<3, 3, double, 3, 1> const &
    #~ > SimTK::Rotation_<double>::asMat33() const [member function]   static
    #~ > SimTK::Mat<3, 3, double, 3, 1>
    #~ > SimTK::Rotation_<double>::calcNDotForBodyXYZInBodyFrame(SimTK::Vec<3,
    #~ > double, 1> const & q, SimTK::Vec<3, double, 1> const & qdot) [member
    #~ > function]   static SimTK::Mat<3, 3, double, 3, 1>
    # rot.constructors(arg_types=[None, 'bool']).exclude()
    # rot.constructors(arg_types=[None]).exclude()
    # rot.member_functions('asMat33').exclude()
    # rot.member_functions('calcNDotForBodyXYZInBodyFrame').exclude()
    # rot.member_functions('calcNForBodyXYZInBodyFrame').exclude()
    # rot.member_functions('calcNInvForBodyXYZInBodyFrame').exclude()
    # rot.member_functions('setRotationFromApproximateMat33').exclude()
    # rot.member_functions('setRotationFromMat33TrustMe').exclude()
    # rot.member_functions('toMat33').exclude()
    # rot.member_functions('reexpressSymMat33').exclude()
    # invrot.member_functions('asMat33').exclude()
    # invrot.member_functions('toMat33').exclude()
    # invrot.member_functions('reexpressSymMat33').exclude()
    rot.member_functions('calcUnnormalizedNForQuaternion').exclude() # To avoid instantiating Mat43
    rot.member_functions('calcUnnormalizedNDotForQuaternion').exclude()
    rot.member_functions('calcUnnormalizedNInvForQuaternion').exclude()
    # Rotation * vec3
    for vec3 in vec3s:
        rot.add_registration_code("def( bp::self * bp::other< %s >() )" % vec3.decl_string)      
        invrot.add_registration_code("def( bp::self * bp::other< %s >() )" % vec3.decl_string)      
    for vec3 in unitvec3s:
        rot.add_registration_code("def( bp::self * bp::other< %s >() )" % vec3.decl_string)
        invrot.add_registration_code("def( bp::self * bp::other< %s >() )" % vec3.decl_string)
    
def expose_transform(module_builder):
    mb = module_builder
    #~ WARNING: SimTK::Transform_<double> [struct]
    #~ > execution error W1040: The declaration is unexposed, but there are
    #~ > other declarations, which refer to it. This could cause "no to_python
    #~ > converter found" run time error. Declarations:    SimTK::Transform
    #~ > const & SimTK::DecorativeGeometry::getTransform() const [member
    #~ > function]   SimTK::DecorativeGeometry &
    #~ > SimTK::DecorativeGeometry::setTransform(SimTK::Transform const & X_BG)
    #~ > [member function]
    xform = mb.class_('Transform_<double>')
    xform.include()
    invxform = mb.class_('InverseTransform_<double>')
    invxform.include()
    #~ WARNING: SimTK::Mat<3,4,double,3,1> [class declaration]
    #~ > execution error W1040: The declaration is unexposed, but there are
    #~ > other declarations, which refer to it. This could cause "no to_python
    #~ > converter found" run time error. Declarations:
    #~ > SimTK::Mat<3,4,double,3,1> SimTK::InverseTransform_<double>::toMat34()
    #~ > const [member function]   SimTK::Mat<3,4,double,3,1> const &
    #~ > SimTK::Transform_<double>::asMat34() const [member function]
    #~ > SimTK::Mat<3,4,double,3,1> SimTK::Transform_<double>::toMat34() const
    #~ > [member function]
    # invxform.member_functions('toMat34').exclude()
    # xform.member_functions('toMat34').exclude()
    # xform.member_functions('asMat34').exclude()
    # invxform.member_functions('toMat44').exclude()
    # xform.member_functions('toMat44').exclude()
    xform.member_function('asMat34').exclude() # avoid instantiating Mat34
    xform.member_function('toMat34').exclude()
    invxform.member_function('toMat34').exclude()
    # Cache right-hand-sides for efficiency
    # vec3s = list(iter_vecs(mb))
    # unitvecs = list(iter_unitvecs(mb))
    # vec4s = list(iter_vecs(mb, size=4, strides=[1,4,5]))
    # Multiply operators
    for x in xform, invxform:
        # Transform * Transform
        for x2 in xform, invxform:
            x.add_registration_code("def( bp::self * bp::other< %s >() )"
                % x2.decl_string )
        # Transform * Vec
        for vec in vec3s:
            x.add_registration_code("def( bp::self * bp::other< %s >() )"
                % vec.decl_string )
        for vec in unitvec3s:
            x.add_registration_code("def( bp::self * bp::other< %s >() )"
                % vec.decl_string )
        for vec in vec4s:
            x.add_registration_code("def( bp::self * bp::other< %s >() )"
                % vec.decl_string )

def expose_negator(module_builder):
    "Wrap negator class, which is needed by vec3 wrapping"
    mb = module_builder
    negator_double = mb.class_('negator<double>')
    negator_double.alias = 'negator_double' # use a nicer name
    negator_double.include() # wrap it
    mb.class_('negator<float>').exclude()
    mb.class_('negator<long double>').exclude()
    for cls in mb.classes(lambda c: c.name.startswith('negator<')):
        # Notice below that I place a warning issued by pyplusplus, followed by 
        # code to stifle the warning.
        #
        # WARNING: SimTK::negator<double> const * SimTK::negator<double>::getData() const [member function]
        # > compilation error W1050: The function returns "SimTK::negator<double>
        # > const *" type. You have to specify a call policies.Be sure to take a
        # > look on `Py++` defined call policies
        cls.member_function('getData').call_policies = return_internal_reference()
        cls.member_function('updData').call_policies = return_internal_reference()
        #
        # WARNING: double & SimTK::negator<double>::operator-() [member operator]
        # > warning W1008: The function returns non-const reference to "Python
        # > immutable" type. The value cannot be modified from Python.
        cls.member_operators('operator-', arg_types=[]).call_policies = return_internal_reference()
        # compile errors about undefined call policies
        cls.member_functions('real').call_policies = return_internal_reference()
        cls.member_functions('imag').call_policies = return_internal_reference()
    # clampInPlace() returns a reference to the second argument
    simtk = mb.namespace('SimTK')
    # but boost.python mysteriously has trouble compiling the char version
    # simtk.free_functions('clampInPlace', arg_types=[None,None,None]).call_policies = return_internal_reference(2)
    simtk.free_functions('clampInPlace', arg_types=[None,None,None]).exclude()

def expose_smallvec(vec):
    vec.include() # wrap it
    # Below are error messages, in comments, followed by operations used to correct the problem
    # WARNING: double & SimTK::Vec<3, double, 1>::operator()(int i) [member operator]
    # > warning W1008: The function returns non-const reference to "Python
    # > immutable" type. The value cannot be modified from Python.
    # vec.member_operators('operator()').call_policies = return_internal_reference()
    # confusing compile errors too...
    vec.member_operators('operator()').exclude()
    # WARNING: SimTK::Vec<2,SimTK::negator<double>,1> [class declaration]
    # > execution error W1040: The declaration is unexposed, but there are
    # > other declarations, which refer to it. This could cause "no to_python
    # > converter found" run time error. Declarations:
    # > SimTK::Vec<2,SimTK::negator<double>,1> SimTK::Vec<3,
    # > SimTK::negator<double>, 1>::drop1(int p) const [member function]
    vec.member_function('drop1').exclude() # avoid having to implement Vec(n-1)
    # default parameter=getDefaultTolerance() causes compile trouble, so exclude isNumericallyEqual()
    vec.member_function('isNumericallyEqual').exclude()
    # compile errors about undefined call policies
    vec.member_functions('real').call_policies = return_internal_reference()
    vec.member_functions('imag').call_policies = return_internal_reference()
    vec.member_functions('updCastAwayNegatorIfAny').call_policies = return_internal_reference()
    vec.member_functions('updNegate').call_policies = return_internal_reference()
    vec.member_functions('updPositionalTranspose').call_policies = return_internal_reference()
    vec.member_functions('updTranspose').call_policies = return_internal_reference()
    vec.member_functions('updAs').call_policies = return_internal_reference()
    # Pythonify sequence operations
    # define_py_sequence_methods(vec)
    # Addition and subtraction
    t = vec.demangled
    vec.add_declaration_code("""
        typedef %s vec_type;
        static vec_type vec_subtract(const vec_type& v1, const vec_type& v2) {return v1 - v2;}
        static vec_type vec_add(const vec_type& v1, const vec_type& v2) {return v1 + v2;}
    """ % (t))
    # vec.add_registration_code('def("__sub__", &vec_subtract)')
    # vec.add_registration_code('def("__add__", &vec_add)')
    # equality operator
    vec.add_declaration_code("""
        static bool are_vecs_equal(const vec_type& v1, const vec_type& v2) {return v1 == v2;}
        static bool are_vecs_unequal(const vec_type& v1, const vec_type& v2) {return v1 != v2;}
    """)
    vec.add_registration_code('def("__eq__", &are_vecs_equal)')
    vec.add_registration_code('def("__ne__", &are_vecs_unequal)')
    # use indexing suite to allow slicing etc.
    vec.include_files.append("simtk_indexing_helpers.hpp")
    vec.add_registration_code("""
        def(bp::indexing::container_suite<
                %s, 
                bp::indexing::all_methods, 
                list_algorithms<SimTKVec_container_traits<%s, %s::TElement, int> > >())
        """ % (t, t, t) )
    # absolute value - python arithmetic    
    vec.add_registration_code('def("__abs__", &%s::norm)' % t)
    # Use built-in boost.python mechanism for wrapping __str__
    vec.add_registration_code('def( bp::self_ns::str(bp::self) )')
    # Fudge repr by pretending all is Vec3 or Row3, regardless of stride, negator
    m = re.match(r'.*(Vec|Row)<\s*(\d+)\s*,\s*(\S.*\S)\s*,\s*(\d+)\s*>', vec.decl_string)
    vec_or_row = m.group(1)
    size = m.group(2)
    elt = m.group(3)
    stride = m.group(4)
    alias = vec_or_row + size
    repr_alias = alias
    if stride != "1":
        alias += "_%s" % stride
    if 'negator' in elt:
        alias = "Neg" + alias
    # vec.alias = alias # covered in instantiate.h
    vec.add_declaration_code("""
        std::string %s_repr_string(const %s& vec) {
            std::ostringstream s;
            s << "%s(";
            for(int i = 0; i < %s; ++i) {
                if (i > 0) s << ", ";
                s << vec[i];
            }
            s << ")";
            return s.str();
        }
    """ % (alias, vec.decl_string, repr_alias, size) )
    vec.add_registration_code('def("__repr__", %s_repr_string)' % alias)
    # Get rid of inner classes like "Result"
    for cls in vec.classes(allow_empty=True):
        cls.exclude()

def expose_vec3(module_builder):
    "Massage module_builder to make wrapping of Vec3 nice"
    mb = module_builder
    # My custom Vec3 <=> tuple conversion code
    mb.add_declaration_code('#include "convert_simtk_vec3.hpp"', tail=False)
    mb.add_registration_code("register_simtk_vec3_conversion();", tail=False)
    # Many templated classes must be defined at the same time, since
    # they make reference to one another.
    # e.g. 'Vec<3, double, 1>', 'Row<3, negator<double>, 1>', etc.
    simtk = mb.namespace('SimTK')
    for vec in simtk.classes(lambda c: c.name.startswith('Vec<') or c.name.startswith('Row<')):
        expose_smallvec(vec)
    # exit(1)
    
def expose_vector(vec):
    """
    For Vector_<double> and related classes
    """
    vec.include()
    vec.member_functions('begin', allow_empty=True).exclude() # avoid VectorIterator
    vec.member_functions('end', allow_empty=True).exclude() # avoid VectorIterator
    vec.casting_operators(allow_empty=True).exclude()
    vec.member_operators('operator()', allow_empty=True).exclude()
    vec.member_functions('index', allow_empty=True).exclude()
    vec.member_functions('updIndex', allow_empty=True).exclude()
    vec.member_functions('resize', allow_empty=True).exclude()
    vec.member_functions('resizeKeep', allow_empty=True).exclude()
    # Indexing
    # use indexing suite to allow slicing etc.
    t = vec.demangled
    elem_type_string = re.search('<(.+)>', t).group(1)
    vec.include_files.append("simtk_indexing_helpers.hpp")
    vec.add_registration_code("""
        def(bp::indexing::container_suite<
                %s, 
                bp::indexing::all_methods, 
                list_algorithms<SimTKVec_container_traits<%s, %s, int> > >())
        """ % (t, t, elem_type_string) )
    # String output
    vec.add_registration_code("def( bp::self_ns::str(bp::self) )")
    # Conversion of python sequences to Vectors, for method arguments
    vec.include_files.append("to_simtk_sequence_conversion.hpp")
    vec.add_registration_code("""
            // Enable conversion of a python sequence to a SimTK::Vector_<>,
            // particularly for use in method arguments
            simtkvector_from_pysequence< %s >();
        """ % vec.decl_string,
        works_on_instance=False)

def wrap_smallmats(mb):
    for mat in mb.classes( lambda c: c.name.startswith('Mat<')):
        # String output
        mat.add_registration_code("def( bp::self_ns::str(bp::self) )")

def wrap_matrixes(mb):    
    for mat in mb.classes( lambda c: c.name.startswith('Matrix_<')):
        # String output
        mat.add_registration_code("def( bp::self_ns::str(bp::self) )")

def expose_mats(module_builder):
    mb = module_builder
    for mat in mb.classes( lambda c: c.name.startswith('Mat')):
        # Zero argument methods
        # mat.member_functions('diag', arg_types=[], allow_empty=True).call_policies = return_internal_reference()
        # One argument methods
        # mat.member_operators('operator()', allow_empty=True).call_policies = return_internal_reference()
        # compile errors...
        mat.member_functions('elt', allow_empty=True).exclude()
        mat.member_functions('getElt', allow_empty=True).exclude()
        mat.member_functions('updElt', allow_empty=True).exclude()
        mat.member_functions('getAnyElt', allow_empty=True).exclude()
        mat.member_operators('operator()', allow_empty=True).exclude()
        mat.member_functions('lockNRows', allow_empty=True).exclude()
        mat.member_functions('lockNCols', allow_empty=True).exclude()
        mat.member_functions('unlockNRows', allow_empty=True).exclude()
        mat.member_functions('unlockNCols', allow_empty=True).exclude()
        # getContiguousScalarData() returns double*, probably not worth the trouble
        mat.member_functions('getContiguousScalarData', allow_empty=True).exclude()
        mat.member_functions('updContiguousScalarData', allow_empty=True).exclude()
    # avoid too many additional classes
    mb.member_functions('dropRow').exclude()
    mb.member_functions('dropCol').exclude()
    mb.member_functions('dropRowCol').exclude()
    # Default parameter trouble
    mb.member_functions('isNumericallyEqual').exclude()
    mb.member_functions('isNumericallySymmetric').exclude()

def exclude_lapack(mb):
    #~ WARNING: static void SimTK::Lapack::getrf(int m, int n, double * a, int lda, int * ipiv, int & info) [member function]^M
    #~ > execution error W1009: The function takes as argument (name=info,^M
    #~ > pos=5) non-const reference to Python immutable type - function could^M
    #~ > not be called from Python. Take a look on "Function Transformation"^M
    #~ > functionality and define the transformation.
    #~ ^M
    #~ WARNING: static void SimTK::Lapack::getrf(int m, int n, float * a, int lda, int * ipiv, int & info) [member function]^M
    #~ > execution error W1009: The function takes as argument (name=info,^M
    #~ > pos=5) non-const reference to Python immutable type - function could^M
    #~ > not be called from Python. Take a look on "Function Transformation"^M
    #~ > functionality and define the transformation.
    mb.namespace('SimTK').class_('Lapack').exclude()

def expose_simtk_string(module_builder):
    mb = module_builder
    string = mb.class_('String')
    ref_type = declarations.reference_t(declarations.declarated_t(string))
    string.include()
    string.member_functions(lambda fn: fn.return_type == ref_type).call_policies = return_self()
    # some methods are not implemented
    string.member_functions('replaceAllChar').exclude()
    string.member_functions('toLower').exclude()
    string.member_functions('toUpper').exclude()
    string.member_functions('trimWhiteSpace').exclude()
    # Equality operator
    #~ string.add_declaration_code("""
        #~ bool simtk_string_equals(SimTK::String const & s, char const * rhs) {
            #~ return strcmp((const char *)s, rhs) == 0;
        #~ }
    #~ """)
    # string.add_registration_code('def("__eq__", &simtk_string_equals)')
    
    # Too many constructors
    # For example, cannot wrap both "int" and "bool" constructors
    unwantedTypes = set(['char'
                       , 'bool'
                       , 'long int'
                       , 'long long int'
                       , 'unsigned int'
                       , 'long unsigned int'
                       , 'long long unsigned int'])
    for ctor in string.constructors():
        if 1 <= len(ctor.argument_types):
            arg_type = ctor.argument_types[0]
            if arg_type.decl_string in unwantedTypes:
                ctor.exclude()
                
doProfile = False
if doProfile:
    import cProfile
    cProfile.run("generate_simtkvecs_source()", "generate_simtkvecs_profile.log")
    exit(0)

if __name__ == "__main__":
    generate_simtkvecs_source()
