# file: simtk_utils.py

import os
import sys
import re
from pyplusplus.module_builder.call_policies import *
import pyplusplus
from pyplusplus import module_builder
from pygccxml import declarations

def create_module_builder(includes):
    "Initial module_builder creation is the same for all simtk modules"
    # Find the path to the gccxml program on this computer
    gccxml_executable = find_program(['gccxml'], paths=['C:/Program Files/gccxml_sherm/bin', '/usr/local/bin'])
    if (not gccxml_executable) or (not os.path.exists(gccxml_executable)):
        raise IOError("gccxml executable not found. Is it on your PATH?")
    # Find the path to the simtk include files - TODO
    simtk_include_dir = find_path(['SimTKcommon.h'], 
            paths=['ENV SimTK_SDK', 'C:/Program Files/SimTK', '/usr/local/SimTK'], 
            path_suffixes=['include'])
    if (not simtk_include_dir) or (not os.path.exists(simtk_include_dir)):
        raise IOError("SimTK include directory not found.  Is SimTK_SDK env var set?")
    python_include_dir = find_path(['Python.h'], 
            paths=['C:/Python26', '/usr/local', '/usr'], 
            path_suffixes=['include'])
    if (not python_include_dir) or (not os.path.exists(python_include_dir)):
        raise IOError("Python include directory not found.")
    # Create pyplusplus module_builder object
    #~ WARNING: SimTK::Mat<6, 6, double, 6, 1>::Mat(double const & e0, double const & e1, double const & e2, double const & e3, double const & e4, double const & e5, double const & e6, double const & e7, double const & e8, double const & e9, double const & e10, double const & e11) [constructor]^M
    #~ > warning W1007: The function has more than 10 arguments ( 12 ). You^M
    #~ > should adjust BOOST_PYTHON_MAX_ARITY macro. For more information see:^M
    #~ > http://www.boost.org/libs/python/doc/v2/configuration.html
    mb = module_builder.module_builder_t(files = includes
           , gccxml_path=gccxml_executable
           , include_paths=[simtk_include_dir, python_include_dir]
           , indexing_suite_version=2
           # "_HAS_TR1=0" prevents crazy errors with VC9 express
           , define_symbols=["_HAS_TR1=0", "BOOST_PYTHON_MAX_ARITY=22"]
         )
    mb.BOOST_PYTHON_MAX_ARITY = 22 # Prevents warnings on 10-22 argument methods
    return mb

def perform_usual_wrapping_tasks(mb):
    exclude_nonpublic_functions(mb)
    wrap_call_policies(mb)
    respect_explicit_constructors(mb)
    exclude_underbar_methods(mb)
    
def create_brief_alias(class_name):
    # remove leading and trailing space characters
    alias = class_name.strip()
    # strings need a little less ambiguity
    alias = alias.replace(r'std::string', 'std_string')
    alias = alias.replace(r'SimTK::String', 'simtk_String')
    # Vec3
    alias = alias.replace(r'Vec<3, double, 1>', 'Vec3')
    alias = alias.replace(r'Vec_3__double__1_', 'Vec3')
    # Strip off any leading namespace
    alias = re.match(r'([^<]*::)?(.*)', alias).group(2)
    # Recursively alias arrays
    m = re.match(r'Array_<(.*),([^,]*)>', alias)
    if m:
        alias1 = create_brief_alias(m.group(1))
        alias2 = create_brief_alias(m.group(2))
        alias = "Array_%s" % alias1
        if alias2 != "unsigned_int":
            alias = "%sBy%s" % (alias, alias2)
    # replace spaces with underscores
    alias = alias.replace(' ', '_')
    alias = alias.replace(',', '_')
    alias = alias.replace('<', '_')
    alias = alias.replace('>', '_')
    alias = alias.replace('*', 'Ptr')
    return alias

def shorten_array_aliases(mb):
    for array in mb.classes(lambda c: c.name.startswith('Array_<')):
        array.alias = create_brief_alias(array.name)

def exclude_underbar_methods(mb):
    #1>WARNING: SimTK::String * std::vector<SimTK::String, std::allocator<SimTK::String> >::_Ufill(SimTK::String * _Ptr, size_t _Count, SimTK::String const & _Val) [member function]
    #1>> compilation error W1050: The function returns "SimTK::String *" type.
    #1>> You have to specify a call policies.Be sure to take a look on `Py++`
    #1>> defined call policies
    for fn in mb.member_functions(lambda f: f.name.startswith('_')):
        fn.exclude()
    for cls in mb.classes(lambda c: c.name.startswith('_')):
        cls.exclude()
    mb.variables(lambda var: var.name.startswith('_')).exclude()

def exclude_nonpublic_functions(mb):
    #1>WARNING: SimTK::DuMMForceFieldSubsystem [class]
    #1>> warning W1031: `Py++` will generate class wrapper - user asked to
    #1>> expose non - public member function "defineIncompleteAtomClass"
    for fn in mb.member_functions(lambda f: f.access_type != declarations.ACCESS_TYPES.PUBLIC):
        fn.exclude()
    #1>WARNING: SimTK::VectorBase<SimTK::negator<double> > [class]
    #1>> warning W1031: `Py++` will generate class wrapper - user asked to
    #1>> expose non - public member function "VectorBase"
    for fn in mb.constructors(lambda f: f.access_type != declarations.ACCESS_TYPES.PUBLIC):
        fn.exclude()

def exclude_matrixhelpers(mb):
    mb.classes(lambda c: c.name.startswith('MatrixHelper<')).exclude()
    #~ WARNING: SimTK::MatrixCharacter [class]
    #~ > warning W1031: `Py++` will generate class wrapper - user asked to
    #~ > expose non - public member function "MatrixCharacter"
    mb.class_('MatrixCharacter').exclude()
    mb.class_('MatrixCommitment').exclude()
    #~ mb.classes(lambda c: c.name.startswith('VectorBase<')).exclude()
    #~ WARNING: SimTK::VectorBase<SimTK::Vec<3, double, 1> > [class]^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:^M
    #~ > SimTK::Vector_<SimTK::Vec<3, double, 1>^M
    #~ > >::Vector_(SimTK::VectorBase<SimTK::Vec<3, double, 1> > const & src)^M
    #~ > [constructor]
    for ctor in mb.constructors():
        if not ctor.argument_types:
            continue
        if ctor.argument_types[0].decl_string.startswith("SimTK::VectorBase<"):
            ctor.exclude()

def expose_free_functions(module_builder):
    mb = module_builder
    # compile error
    # mb.free_functions('crossMat').exclude()
    
def wrap_ostream_methods(mb):
    "Turn C++ operator<<() into python __str__ method"
    # TODO - this method takes a lot of time
    simtk = mb.namespace('SimTK')
    for op in simtk.free_operators('operator<<', arg_types=[None, None]):
        if not 'stream' in op.argument_types[0].decl_string:
            continue
        type = op.argument_types[1]
        base_type = type
        if declarations.is_reference(base_type):
            base_type = base_type.base
        if declarations.is_const(base_type):
            base_type = base_type.base
        cls_name = base_type.decl_string
        cls_name = cls_name.replace('::SimTK::', '', 1);
        # Link errors...
        if cls_name in ['AtomicInteger', # link err
                        'SimbodyMatterSubsystem', # link err
                        # 'Xml::Node', # no such class
                        ]: 
            continue
        try:
            # Recursively find inner classes
            # e.g. 'Xml::Attribute'
            if cls_name.startswith('Xml::'):             
                cls = simtk
                for name in cls_name.split('::'):
                    cls = cls.class_(name)
            else:
                cls = simtk.class_(cls_name)
        except:
            print """
                    WARNING: unrecognized class name '%s'
                    while trying to wrap python __str__() method 
                    from C++ operator<<() method.
                  """ % cls_name
            continue
        # Use built-in boost.python mechanism for wrapping __str__
        cls.add_registration_code('def( bp::self_ns::str(bp::self) )')
    
def exclude_strange_classes(mb):
    for prefix in ['AddOp', 
                   'AndOpType', 
                   'ArrayIndexPackTypeHelper', 
                   'ArrayIndexTraits',
                   'CNT<',
                   'DvdOp',
                   'Is64BitHelper',
                   'IsFloatingType',
                   'IsIntegralType',
                   'IsVoidType',
                   # 'MatrixBase',
                   'MulOp',
                   'NTraits',
                   'Narrowest',
                   'NiceTypeName',
                   'OrOpType',
                   'PIMPLHandle',
                   'RTraits',
                   # 'RowVectorBase',
                   'SubOp',
                   'ThisNeg',
                   # 'VectorBase',
                   'Wider',
                   'Widest',
                   'XorOpType',
                   'ArrayView',]:
        for cls in mb.classes():
            if cls.alias.startswith(prefix):
                cls.exclude()
    # Remove constructors that refer to VectorBase
    simtk = mb.namespace('SimTK')
    for cls in simtk.classes(lambda c: c.name.startswith('Vector_'), allow_empty=True):
        for ctor in cls.constructors(arg_types=[None]):
            arg_t = ctor.argument_types[0]
            if arg_t.decl_string.startswith("VectorBase"):
                print ctor
                ctor.exclude()
    
def disambiguate_typedef_aliases(module_builder):
    #~ RuntimeError: `Py++` is going to write different content to the same file(c:\Documents and Settings\cmbruns\My Documents\svn\simtk_python\src\common\generated_code\AddOp.pypp.hpp).
    mb = module_builder
    for poor_alias in ('AddOp', 'DvdOp', 'ThisNeg', 'MulOpNonConforming', 'SubOp'):
        ind = 1
        for cls in mb.classes(lambda c: c.alias == poor_alias):
            cls.alias = "%s%d" % (poor_alias, ind) # e.g. "AddOp2"
            ind = ind + 1
    # Due to compile error on MulOp4, don't expose MulOp classes
    mb.classes(lambda c: c.alias == 'MulOp').exclude()
    mb.classes(lambda c: c.alias == 'Base').exclude()
    
def wrap_properties(mb):
    "Convert get/set/upd methods to python properties"
    # All properties must be related to a "get" method
    troubleProperties = []
    for get_fn in mb.member_functions(is_simple_get_method):
        propertyRoot = get_fn.name[3:]; # e.g. "Foo" from "getFoo"
        if propertyRoot in troubleProperties:
            continue
        propertyName = propertyRoot[0].lower() + propertyRoot[1:]; # "foo"
        parent_cls = get_fn.parent
        # is there a corresponding "set" method?
        setName = 'set' + propertyRoot; # e.g. "setFoo"
        sets = []
        for set_fn in parent_cls.member_functions(setName, arg_types=[None], allow_empty=True):
            if is_simple_set_method(set_fn):
                sets.append(set_fn)
        if sets:
            # create read/write property using get/set methods
            parent_cls.add_property(propertyName, fget=get_fn, fset=sets[0])
            continue
        # No set method, perhaps there is a upd
        updName = 'upd' + propertyRoot; # e.g. "updFoo"
        upds = []
        for upd_fn in parent_cls.member_functions(updName, arg_types=[], allow_empty=True):
            if is_simple_upd_method(upd_fn):
                upds.append(upd_fn)
        if upds:
            # Create setter method using upds
            upd_fn = upds[0]
            val_type = upd_fn.return_type
            set_fn_name = "set_property_%s" % propertyRoot
            get_fn_name = get_fn.decl_string
            # TODO - make mock_set_fn work as set function object
            mock_set_fn = pyplusplus.decl_wrappers.calldef_wrapper.free_function_t()
            mock_set_fn.name = set_fn_name
            mock_set_fn.parent = mb.namespace('SimTK').parent # top namespace
            mock_set_fn.return_type = declarations.reference_t(declarations.declarated_t(parent_cls))
            mock_set_fn.call_policies = return_self()
            parent_cls.add_declaration_code("""
            static %s& %s(%s& obj, const %s val) {
                obj.%s() = val;
                return obj;
            }
            """ % (parent_cls.decl_string, set_fn_name, parent_cls.decl_string, val_type.decl_string, updName))
            parent_cls.add_property(propertyName, fget=get_fn, fset=mock_set_fn)
            continue
        # No set or upd method, so create a read-only property
        parent_cls.add_property(propertyName, fget=get_fn, fset=None)
        
def is_simple_get_method(fn):
    # perhaps I already didn't like this method
    if fn.ignore:
        return False
    # getters take no arguments
    if fn.arguments:
        return False
    # getters are const methods
    if not fn.has_const:
        return False
    # getters return a value
    if declarations.is_void( fn.return_type ):
        return False
    # getters start with "get"
    if not fn.name.startswith('get'):
        return False
    # getters have names longer than "get()"
    if 3 >= len(fn.name):
        return False # just "get()" is not enough
    # only public methods please
    if fn.access_type != declarations.ACCESS_TYPES.PUBLIC:
        return False
    return True

def is_simple_upd_method(fn):
    if fn.ignore:
        return False
    if fn.arguments:
        return False
    if fn.has_const:
        return False
    if declarations.is_void( fn.return_type ):
        return False
    if not declarations.is_reference( fn.return_type ):
        return False
    if not fn.name.startswith('upd'):
        return False
    if 3 >= len(fn.name):
        return False
    if fn.access_type != declarations.ACCESS_TYPES.PUBLIC:
        return False
    return True

def is_simple_calc_method(fn):
    if not fn.name.startswith('calc'):
        return False
    if fn.access_type != declarations.ACCESS_TYPES.PUBLIC:
        return False
    if not 0 == len(fn.argument_types):
        return False
    return True

def is_simple_set_method(fn):
    if fn.ignore:
        return False
    if fn.has_const:
        return False
    if not fn.name.startswith('set'):
        return False
    if fn.access_type != declarations.ACCESS_TYPES.PUBLIC:
        return False
    # set() actually takes an argument
    if not 1 == len(fn.argument_types):
        return False
    return True

def exclude_rep_classes(mb):
    # for cls in mb.classes(lambda c: c.name.endswith('Rep')): # no matches
    #     cls.exclude()
    # mb.classes(lambda c: c.name.endswith('Impl')).exclude() # no matches
    for cls in mb.classes():
        if cls.name.endswith('Implementation'): cls.exclude()
        if cls.name.endswith('Impl'): cls.exclude()
        if cls.name.endswith('Rep'): cls.exclude()
    for fn in mb.member_functions():
        n = fn.name
        if n.startswith('get') or n.startswith('upd') or n.startswith('construct'):
            if n.endswith('Rep'): fn.exclude()
            if n.endswith('Guts'): fn.exclude()
            if n.endswith('Impl'): fn.exclude()
            if n.endswith('Implementation'): fn.exclude()
    # Compile errors for constructors that take a pointer to an impl
    for ctor in mb.constructors(arg_types=[None]):
        arg_t = ctor.argument_types[0]
        if (not declarations.is_pointer(arg_t)) and (not declarations.is_reference(arg_t)):
            continue
        arg_t = arg_t.base
        if declarations.is_const(arg_t):
            continue
        if arg_t.decl_string.endswith('Implementation'):
            ctor.exclude()
            continue
        if arg_t.decl_string.endswith('Guts'):
            ctor.exclude()
            continue
        if arg_t.decl_string.endswith('Rep'):
            ctor.exclude()
            continue
        if arg_t.decl_string.endswith('Impl'):
            ctor.exclude()
            continue
    
def respect_explicit_constructors(mb):
    mb.constructors( lambda c: c.explicit == True ).allow_implicit_conversion = False
    
def expose_indexes(mb):
    # for index in mb.classes(lambda c: c.name.endswith('Index') or c.name.endswith('Ex')):
        # index.include()
        # runtime import error
        #~ from simbios._simbody import *
        #~ KeyError: 'max_size'
        # max_size = index.member_function('max_size')
        # max_size.exclude()
        # max_size.has_static = False
    # Better str and repr methods
    for fn in mb.member_functions('max_size'):
        fn.exclude()
    for index in mb.classes(lambda c: c.name.endswith('Index') or c.name.endswith('Ex')):
        try:
            index.member_function('invalidate')
        except:
            continue
        a = index.alias
        d = index.decl_string
        index.add_declaration_code("""
            std::string %s_str_wrapper(const %s& ix) {
                std::ostringstream val;
                val << (int) ix;
                return val.str();
            }
            std::string %s_repr_wrapper(const %s& ix) {
                std::ostringstream val;
                val << "%s(";
                val << (int) ix;
                val << ")";
                return val.str();
            }
        """ % (a,d,a,d,a) )
        index.add_registration_code('def("__str__", &%s_str_wrapper)' % a)
        index.add_registration_code('def("__repr__", &%s_repr_wrapper)' % a)

def exclude_operators(module_builder):
    mb = module_builder
    for op in mb.free_operators():
        if op.name == 'operator<<':
            op.exclude()
    for cls in mb.classes():
        #~ WARNING: SimTK::Enumeration<SimTK::Stage> SimTK::EnumerationSet<SimTK::Stage>::iterator::operator*() [member operator]
        #~ > compilation error W1014: "operator*" is not supported. See
        #~ > Boost.Python documentation:
        #~ > http://www.boost.org/libs/python/doc/v2/operators.html#introduction.
        cls.member_operators('operator*', arg_types=[], allow_empty=True).exclude()
        cls.member_operators('operator&', arg_types=[], allow_empty=True).exclude()
        #~ WARNING: SimTK::EnumerationSet<SimTK::Stage>::iterator SimTK::EnumerationSet<SimTK::Stage>::iterator::operator++() [member operator]
        #~ > compilation error W1014: "operator++" is not supported. See
        #~ > Boost.Python documentation:
        #~ > http://www.boost.org/libs/python/doc/v2/operators.html#introduction.
        cls.member_operators('operator++', allow_empty=True).exclude()
        cls.member_operators('operator--', allow_empty=True).exclude()
        # WARNING: std::ostream & SimTK::operator<<(std::ostream & o, SimTK::Stage g) [free operator]
        # > compilation error W1014: "operator<<" is not supported. See
        # > Boost.Python documentation:
        # > http://www.boost.org/libs/python/doc/v2/operators.html#introduction.
        cls.member_operators('operator<<', allow_empty=True).exclude()
        # Assignment operators are probably uncool also
        cls.member_operators('operator=', allow_empty=True).exclude()
        # even their named counterparts
        cls.member_functions('deepAssign', allow_empty=True).exclude()
        cls.member_functions('shallowAssign', allow_empty=True).exclude()
        #1>WARNING: SimTK::Xml::Attribute * SimTK::Xml::attribute_iterator::operator->() [member operator]
        #1>> compilation error W1014: "operator->" is not supported. See
        #1>> Boost.Python documentation:
        cls.member_operators('operator->', allow_empty=True).exclude()
    
def is_state_archaeologist(fn, module_builder):
    "Returns True for methods that extract information from a state"
    n = len(fn.argument_types)
    if n < 1:
        return False
    if not (fn.name.startswith('get') or fn.name.startswith('upd')):
        return False
    arg1 = declarations.declarated_t(fn.argument_types[0])
    if not arg1:
        return False
    try:
        state = module_builder.class_('State')
    except:
        return False
    state_cls = declarations.declarated_t(state)
    state_ref_cls = declarations.reference_t(state_cls)
    if not declarations.is_same(state_ref_cls, arg1):
        return False
    return True

def wrap_one_call_policy(fn, module_builder):
    rt = fn.return_type
    parent_ref = declarations.reference_t(declarations.declarated_t(fn.parent))
    # If return type is not a reference, I have no opinion about the call policies
    if not declarations.is_reference(rt):
        return
    # Need type without reference for next type checks
    nonref_rt = rt.base
    if declarations.is_arithmetic(nonref_rt) or declarations.is_enum(nonref_rt):
        # returning const& double can cause compile trouble if return_internal_reference is used
        if declarations.is_const(nonref_rt):
            fn.call_policies = return_value_policy(copy_const_reference)
            return
        # int& might need to be copy_non_const_reference...
        else:
            fn.call_policies = return_value_policy(copy_non_const_reference)
            return
    if is_state_archaeologist(fn, module_builder):
        fn.call_policies = return_internal_reference(1) # Notice the "1", first argument
        return
    # Returning reference to this same class looks like return_self() [does this always work?]
    if declarations.is_same(parent_ref, rt):
        fn.call_policies = return_self()
        return
    # Everything else probably returns an internal reference
    fn.call_policies = return_internal_reference()
    return

def wrap_call_policies(module_builder):
    "Set function and operator call policies to sensible defaults"
    mb = module_builder
    for fn in mb.member_functions():
        wrap_one_call_policy(fn, module_builder)
    for op in mb.member_operators():
        wrap_one_call_policy(op, module_builder)

def wrap_arrayviewcommon(array, module_builder):
    "Wrap methods common to ArrayView_<whatever> and ArrayViewConst_<whatever>"
    # array.include()
    # WARNING: SimTK::EventId const * SimTK::ArrayViewConst_<SimTK::EventId, unsigned int>::cbegin() const [member function]
    # > compilation error W1050: The function returns "SimTK::EventId const *"
    # > type. You have to specify a call policies.Be sure to take a look on
    # > `Py++` defined call policies
    array.member_functions('cbegin').call_policies = return_internal_reference()
    array.member_functions('cend').call_policies = return_internal_reference()
    array.member_functions('cdata').call_policies = return_internal_reference()    
    array.member_functions('begin').call_policies = return_internal_reference()
    array.member_functions('end').call_policies = return_internal_reference()
    array.member_functions('data').call_policies = return_internal_reference()

def wrap_arrayviewconst(array, module_builder):
    "Wrap ArrayViewConst_<whatever> methods"
    wrap_arrayviewcommon(array, module_builder)
    # WARNING: SimTK::ArrayViewConst_<SimTK::EventId, unsigned int> [class]
    # > warning W1031: `Py++` will generate class wrapper - user asked to
    # > expose non - public member function "pallocated"
    array.member_function('pallocated').exclude()
    array.member_function('psize').exclude()
    array.member_function('setData').exclude()
    array.member_function('setSize').exclude()
    array.member_function('incrSize').exclude()
    array.member_function('decrSize').exclude()
    array.member_function('setAllocated').exclude()
    array.member_function('ullSize').exclude()
    array.member_function('ullCapacity').exclude()
    array.member_function('ullMaxSize').exclude()
    array.member_function('indexName').exclude()
    #~ WARNING: SimTK::ArrayViewConst_<std::string, unsigned int>::pData [variable]
    #~ > compilation error W1036: `Py++` can not expose pointer to Python
    #~ > immutable member variables. This could be changed in future.
    array.variable('pData').exclude()

def wrap_arrayview(array, module_builder):
    "Wrap ArrayView_<whatever> methods"
    wrap_arrayviewcommon(array, module_builder)
    # WARNING: SimTK::ArrayViewConst_<SimTK::EventId, unsigned int> [class]
    # > warning W1047: There are two or more classes that use same
    # > alias("CBase"). Duplicated aliases causes few problems, but the main
    # > one is that some of the classes will not be exposed to Python.Other
    # > classes : SimTK::ArrayViewConst_<SimTK::System::EventTriggerInfo,
    # > unsigned int> [class]
    change_typedef_alias(array.typedef('CBase'))
    #~ WARNING: SimTK::Array_<SimTK::System::EventTriggerInfo, unsigned int> & SimTK::ArrayView_<SimTK::System::EventTriggerInfo, unsigned int>::operator ::SimTK::Array_<SimTK::System::EventTriggerInfo, unsigned int> &() [casting operator]
    #~ > warning W1016: `Py++` does not exports non-const casting operators
    #~ > with user defined type as return value. This could be changed in
    #~ > future.
    array.casting_operators(allow_empty=True).exclude()
    #~ WARNING: int & SimTK::Array_<int, unsigned int>::at(unsigned int i) [member function]
    #~ > warning W1008: The function returns non-const reference to "Python
    #~ > immutable" type. The value cannot be modified from Python.
    array.member_functions('front', allow_empty=True).call_policies = return_internal_reference()
    array.member_functions('back', allow_empty=True).call_policies = return_internal_reference()
    # in response to compile errors...
    array.member_functions('fill', allow_empty=True).exclude()
    array.member_functions('at', allow_empty=True).exclude()
    array.member_functions('updElt', allow_empty=True).exclude()
    # Wrap ArrayViewConst_<... also
    if array.name.startswith('ArrayView_<'):
        const_name = array.name.replace('ArrayView_', 'ArrayViewConst_', 1)
        wrap_arrayviewconst(module_builder.class_(const_name), module_builder)

def wrap_array(array, module_builder):
    "Wrap Array_<whatever> methods"
    wrap_arrayview(array, module_builder)
    array.member_functions('raw_push_back').call_policies = return_internal_reference()
    array.member_functions('erase').call_policies = return_internal_reference()
    array.member_functions('eraseFast').call_policies = return_internal_reference()
    array.member_functions('insert').call_policies = return_internal_reference()
    array.member_functions('growWithGap').call_policies = return_internal_reference()
    array.member_functions('insertGapAt').call_policies = return_internal_reference()
    array.member_functions('allocN').call_policies = return_internal_reference()
    change_typedef_alias(array.typedef('Base'))
    # in response to compile errors...
    array.member_functions('deallocate', allow_empty=True).exclude()
    array.member_functions('adoptData', allow_empty=True).exclude()
    array.member_functions('shareData', allow_empty=True).exclude()
    # More compile errors when int or double is contained
    match = re.search(r'^(SimTK::)?Array_<(SimTK::)?(.+\S)\s*, (SimTK::)?([^,]+)\s*>$', array.demangled)
    if not match:
        raise ValueError('Could not parse array name %s' % (array.demangled) )
    elem_type_string = match.group(3)
    index_type_string = match.group(5)
    # print array.decl_string, "#", elem_type_string, "#", index_type_string
    # elem_type_string = re.search('<(SimTK::)?([^,]+),', array.demangled).group(2)
    # index_type_string = re.search('<.*,\s*(SimTK::)?([^,]+)\s*>$', array.demangled).group(2)
    # print index_type_string
    if elem_type_string in ['double', 'int', 'Event::Trigger'] \
        or elem_type_string.endswith('*'):
        array.member_functions('getElt', allow_empty=True).exclude()
        array.member_functions('erase', allow_empty=True).exclude()
        array.member_functions('eraseFast', allow_empty=True).exclude()
        array.member_functions('insert', allow_empty=True).exclude()
        array.member_functions('front', allow_empty=True).exclude()
        array.member_functions('back', allow_empty=True).exclude()
        array.member_functions('begin', allow_empty=True).exclude()
        array.member_functions('end', allow_empty=True).exclude()
        array.member_functions('cbegin', allow_empty=True).exclude()
        array.member_functions('cend', allow_empty=True).exclude()    
        array.member_functions('cdata', allow_empty=True).exclude()    
        array.member_functions('data', allow_empty=True).exclude()    
        array.member_functions('raw_push_back', allow_empty=True).exclude()
    if elem_type_string.startswith('Array_<') \
            or elem_type_string in ['std::string',] \
            or index_type_string.endswith('Ix') \
            or elem_type_string.startswith('Vec<'):
        array.member_functions('rend', allow_empty=True).exclude()
        array.member_functions('rbegin', allow_empty=True).exclude()
        array.member_functions('crend', allow_empty=True).exclude()
        array.member_functions('crbegin', allow_empty=True).exclude()
    # python indexing_suite v2
    if array.name.startswith('Array_<'):
        array.include_files.append("simtk_indexing_helpers.hpp")
        array.add_registration_code("""
            // Enable python-style indexing of SimTK::Array_<> (slices, negative indices, etc.)
            def(bp::indexing::container_suite<
                    %s, 
                    bp::indexing::all_methods, 
                    simtk_array_algorithms< %s, %s > >())
            """ % (array.decl_string, elem_type_string, index_type_string) )
        # String output
        # array.add_registration_code("def( bp::self_ns::str(bp::self) )")
    # Automatic conversion from python sequence to array
    array.include_files.append("to_simtk_sequence_conversion.hpp")
    array.add_registration_code("""
            // Enable conversion of a python sequence to a SimTK::Array_<>,
            // particularly for use in method arguments
            simtkarray_from_pysequence< %s >();
        """ % array.decl_string, 
        works_on_instance=False)

def change_typedef_alias(typedef):
    "Change alias of redundant inner typedef to a more specific name"
    td = typedef
    td.type.declaration.alias = "%s_%s"%(td.parent.alias, td.alias)

def define_py_sequence_methods(vec):
    """
    Adds pythonic sequence methods to a wrapped simtk class.
    
    Argument 'vec' is a pyplusplus module_builder _class object.
    """
    # 1) __len__
    vec.add_registration_code('def("__len__", &SimTK::%s::size)' % vec.name)
    # 2) __getitem__
    vec.member_operators('operator[]').exclude(); # don't use C++ indexing
    vec.add_declaration_code("""
        static int py_index_to_c_index(SimTK::%s const & v, int pyIndex) {
            int cIndex = pyIndex;
            if (cIndex < 0)
                cIndex = v.size() + cIndex;
            if ( (cIndex >= v.size()) || (cIndex < 0) ) {
                PyErr_SetString(PyExc_IndexError, "Index out of range");
                bp::throw_error_already_set();
            }
            return cIndex;
        }
        static SimTK::Real %s_getitem(SimTK::%s const & v, int i) {
            return v[py_index_to_c_index(v, i)];
        }
    """ % (vec.name, vec.alias, vec.name) )
    vec.add_registration_code('def("__getitem__", &%s_getitem)' % vec.alias)
    # 3) __setitem__
    vec.add_declaration_code("""
        static void %s_setitem(SimTK::%s& v, int i, SimTK::Real value) {
            v[py_index_to_c_index(v, i)] = value;
        }    
    """ % (vec.alias, vec.name) )
    vec.add_registration_code('def("__setitem__", &%s_setitem)' % vec.alias)
    
def find_path(names, paths=None, path_suffixes=None, found_path=None):
    """
    Find the directory containing a file.
    Intended to be similar to cmake find_path command.
    """
    if found_path:
        return found_path
    for path in paths:
        if not os.path.exists(path):
            continue
        # User might have used "ENV var" syntax
        m = re.match(r'^ENV (.*)$', path)
        if m:
            var = m.group(1)
            if var in os.environ:
                path = os.environ[var]
                print path
            else:
                continue # environment variable not found
        for file in os.listdir(path):
            if file in names:
                return path
        for suffix in path_suffixes:
            path2 = os.path.join(path, suffix)
            for file in os.listdir(path2):
                if file in names:
                    return path2                
    return found_path # probably None, if we got this far
    
def find_program(names, paths=None, use_default_path=True, program_path=None):
    """
    Find an executable program on this computer.
    Intended to be similar to cmake find_program command.
    
    If program_path is set to a non-None value, it is returned unmodified
    """
    if program_path: 
        return program_path
    # Search PATH first, then user specified folders
    if use_default_path:
        for path in os.environ['PATH'].split(';'): # TODO- is ';' windows only?
            program_path = find_program_in_directory(names, path)
            if program_path:
                return program_path
        for path in os.environ['PATH'].split(':'): # linux
            program_path = find_program_in_directory(names, path)
            if program_path:
                return program_path
    # Search user specified folders
    for path in paths:
            program_path = find_program_in_directory(names, path)
            if program_path:
                return program_path
    return program_path

def find_program_in_directory(names, path):
    "Helper method for find_program subroutine"
    if not os.path.exists(path):
        return None
    for file in os.listdir(path):
        if file in names:
            return os.path.join(path, file)
        if sys.platform == "win32":
            if re.search(r'(?i)\.exe$', file):
                short_file = file[:-4]
                if short_file in names:
                    return os.path.join(path, file)

