from pyplusplus import module_builder
from pyplusplus.module_builder.call_policies import *
from pygccxml import declarations
from pyplusplus import function_transformers as FT
from simtk_wrap_utils import *
from doxygen_doc_extractor import doxygen_doc_extractor

def wrap_std():
    mb = create_module_builder(['external/std/wrap_std.hpp',])

    wrap_string(mb)
    wrap_ostream(mb)
    wrap_istream(mb)
    wrap_complex(mb)
    wrap_vectors(mb)
    wrap_sets(mb)
    wrap_maps(mb)
    perform_usual_wrapping_tasks(mb)
    exclude_operators(mb)
    
    extractor = doxygen_doc_extractor()

    mb.build_code_creator(module_name='_std', doc_extractor=extractor)
    mb.split_module(os.path.join('external', 'std', 'generated_code'))
    # If all succeeds, record this accomplishment by touching a particular file
    open(os.path.join(os.path.abspath('.'), 'external', 'std', 'generated_code', 'generate_std.stamp'), "w").close()

def wrap_maps(mb):
    mb.class_('map<std::string, double>').include()
    mb.class_('map<std::string, std::string>').include()

def wrap_sets(mb):
    mb.class_('set<int>').include()
    
def wrap_vectors(mb):
    mb.class_('vector<std::string>').include()
    mb.class_('vector<int>').include()
    mb.class_('vector<double>').include()
    mb.class_('vector<std::vector<double> >').include()
    mb.class_('pair<int, int>').include()
    mb.class_('vector<std::pair<int, int> >').include()

def wrap_complex(mb):
    mb.class_('complex<long double>').exclude()
    mb.class_('complex<float>').exclude()
    complex = mb.class_('complex<double>')
    complex.include()
    for ctor in complex.constructors():
        for arg_t in ctor.argument_types:
            # strange unparseable constructor that appears in linux
            if arg_t.decl_string == 'complex double':
                ctor.exclude()
                complex.no_init = True
            if "float" in arg_t.decl_string:
                ctor.exclude()

def wrap_istream(mb):
    std = mb.namespace('std')
    istream_typedef = std.typedef('istream')
    istream = istream_typedef.type.declaration
    istream.include()
    #1>WARNING: std::basic_istream<char,std::char_traits<char> > [struct]
    #1>> warning W1028: `Py++` will generate class wrapper - class contains
    #1>> definition of nested class "_Sentry_base", which requires wrapper
    #1>> class
    istream.classes().exclude()
    # compile errors/warnings
    istream.member_functions().exclude()
    istream.member_operators().exclude()

def wrap_string(mb):
    std = mb.namespace('std')
    string_typedef = std.typedef('string')
    string = string_typedef.type.declaration
    string.include()
    # Many methods return a reference to self
    string_t = declarations.declarated_t(string)
    string_ref_t = declarations.reference_t(declarations.declarated_t(string))
    for fn in string.member_functions(lambda f: f.return_type == string_ref_t):
        fn.call_policies = return_self()
    # Underscore methods should not be exposed
    for fn in string.member_functions(lambda f: f.name.startswith('_')):
        fn.exclude()
    for var in string.variables(lambda v: v.name.startswith('_')):
        var.exclude()
    # for union in string.unions(lambda u: u.name.startswith('_')):
    #     union.exclude()
    # One "at" method returns a non-const char&, and should be removed
    for fn in string.member_functions("at"):
        t = fn.return_type.base
        if not declarations.is_const(t):
            fn.exclude()
    # Compile troubles for template arguments with "_Elem" and others
    # hack...
    string.add_declaration_code("#define _Elem char") # msvc
    string.add_declaration_code("#define _CharT char") # linux gcc
    string.add_declaration_code("#define _Traits std::char_traits<char>") # linux gcc and msvc
    string.add_declaration_code("#define _Ax std::allocator<char>") # msvc
    string.add_declaration_code("#define _Alloc std::allocator<char>") # linux gcc
    # string.copy method generates reasonable sounding compiler warning
    string.member_function('copy').exclude()
    # Use built-in boost.python mechanism for wrapping __str__
    string.add_registration_code('def( bp::self_ns::str(bp::self) )')
    # Rather, use custom auto conversion (doesn't that already exist?)
    # mb.add_declaration_code('#include "convert_string.hpp"')
    # mb.add_registration_code('register_std_string_conversion();')
    # compile errors
    string.member_functions('erase').exclude()
    string.member_functions('insert').exclude()
    string.member_functions('replace').exclude()

def wrap_ostream(mb):
    std = mb.namespace('std')
    ostream_typedef = std.typedef('ostream')
    ostream = ostream_typedef.type.declaration
    ostream.include()
    ostream.member_function('flush').call_policies = return_self()
    ostream.member_function('put').call_policies = return_self()
    ostream.member_functions('seekp').call_policies = return_self()
    ostream.member_function('write').call_policies = return_self()
    std.variable('cout').include()
    std.variable('cerr').include()
    ostream_t = declarations.declarated_t(ostream)
    ostream_ref_t = declarations.reference_t(ostream_t)
    for endl in std.free_functions('endl'):
        if endl.return_type == ostream_ref_t:
            endl.include()
            endl.call_policies = return_internal_reference(1)
    # operator<<
    # We cannot use "def( bp::self << long() )" because self is an lvalue
    for typename in ['long', 'double', 'bool']:
        ostream.add_registration_code("""//
            {
                std::ostream& (std::ostream::*lshift_op)( %s ) = &std::ostream::operator<<;
                ostream_exposer.def("__lshift__", lshift_op, bp::return_self<>() );
            }
        """ % (typename), works_on_instance=False )
    # trouble with os << string
    ostream.add_declaration_code("""//
        std::ostream& lshift_ostream_string_wrapper(std::ostream& os, const std::string& str) {
            return os << str;
        }
        std::ostream& lshift_ostream_ostream_wrapper(std::ostream& os, std::ostream& os2) {
            return os << os2;
        }
    """)
    ostream.add_registration_code('def("__lshift__", &lshift_ostream_string_wrapper, bp::return_self<>())')
    ostream.add_registration_code('def("__lshift__", &lshift_ostream_ostream_wrapper, bp::return_self<>())')

if __name__ == "__main__":
    wrap_std()

