import libtbx.load_env
from libtbx.str_utils import show_string
from libtbx.utils import \
  warn_if_unexpected_md5_hexdigest, \
  write_this_is_auto_generated
from libtbx.env_config import darwin_shlinkcom
import string
import sys, os
op = os.path
Import("env_base", "env_etc")
env_etc.boost_dist = libtbx.env.dist_path("boost")
env_etc.boost_include = env_etc.boost_dist
env_etc.boost_adaptbx_dist = libtbx.env.dist_path("boost_adaptbx")
env_etc.boost_adaptbx_include = os.path.dirname(env_etc.boost_adaptbx_dist)

def build_boost_thread():
  if (not libtbx.env.build_options.enable_boost_threads):
    return
  if (sys.platform == "win32"): s = "win32"
  else:                         s = "pthread"
  join = os.path.join
  src = join("libs", "thread", "src", s)
  prefix = "#" + join(os.path.basename(env_etc.boost_dist), src)
  source = []
  for node in sorted(os.listdir(join(env_etc.boost_dist, src))):
    if (not node.lower().endswith(".cpp")): continue
    source.append(join(prefix, node))
  env = env_base.Clone(
    SHLINKFLAGS=env_etc.shlinkflags)
  env.Append(SHCXXFLAGS=["-DBOOST_THREAD_BUILD_DLL"])
  env_etc.include_registry.append(
    env=env,
    paths=[env_etc.boost_include])
  darwin_shlinkcom(env_etc, env,
                   lo="boost/libs/thread/src/libboost_thread.lo",
                   dylib="lib/libboost_thread.dylib")
  env.Repository(os.path.dirname(env_etc.boost_dist))
  env.SharedLibrary(
    target='#lib/boost_thread',
    source=source)

build_boost_thread()

if (not env_etc.no_boost_python):
  env_etc.cxxflags_bpl_defines_base = [
    "-DBOOST_PYTHON_MAX_BASES=2"]
  if (libtbx.env.build_options.boost_python_no_py_signatures):
    env_etc.cxxflags_bpl_defines_base.append(
      "-DBOOST_PYTHON_NO_PY_SIGNATURES")
  env_no_includes_boost_python_ext = env_base.Clone(
    SHLINKFLAGS=env_etc.shlinkflags_bpl,
    SHLIBPREFIX="",
    LIBS=["boost_python"] + env_etc.libs_python + env_etc.libm)
  env_no_includes_boost_python_ext.Append(
    LIBPATH=env_etc.libpath_python)
  env_no_includes_boost_python_ext.Append(
    SHCXXFLAGS=env_etc.cxxflags_bpl_defines_base,
    PCHPDBFLAGS=env_etc.cxxflags_bpl_defines_base)
  env_no_includes_boost_python_ext.Replace(
    SHLIBSUFFIX=env_etc.extension_module_suffix)
  if (env_etc.compiler == "win32_cl" and env_etc.have_manifest_tool):
    env_no_includes_boost_python_ext.Append(
      SHLINKCOM=[
        'mt /nologo /outputresource:"${TARGET};#2"'
        ' /manifest ${TARGET}.manifest'])
  elif (env_etc.mac_os_use_dsymutil):
    # Debugging Boost.Python module C++ code works much more reliably
    # with the DWARF + dSYM format. Hence run dsymutil after building *.so
    env = env_no_includes_boost_python_ext
    env['SHLINKCOM'] = [env['SHLINKCOM'], 'dsymutil $TARGET']

  Export("env_no_includes_boost_python_ext")
  env_pure_boost_python_ext = env_no_includes_boost_python_ext.Clone()
  env_etc.include_registry.set_boost_dir_name(env_etc.boost_dist)
  env_etc.include_registry.append(
    env=env_pure_boost_python_ext,
    paths=[
      "#include",
      env_etc.boost_include,
      env_etc.python_include])
  conf = env_pure_boost_python_ext.Clone().Configure()
  if (not conf.TryCompile("#include <iostream>", extension=".cpp")):
    raise RuntimeError("""\
FATAL:

********************** C++ compiler does not work. **********************

See config.log for details.
""")
  conf.Finish()
  conf = env_pure_boost_python_ext.Clone().Configure()
  if (not conf.TryCompile("#include <Python.h>", extension=".cpp")):
    raise RuntimeError("""\
FATAL:

******************** Python.h include not available. ********************

On some Linux machines you may need to install the "python-dev" package,
e.g. with "yum install python-dev" or "apt-get install python-dev".
""")
  conf.Finish()
  env_boost_python_ext = env_pure_boost_python_ext.Clone()
  env_etc.include_registry.prepend(
    env=env_boost_python_ext,
    paths=[env_etc.libtbx_include])
  Export("env_boost_python_ext")

  # Precompiled headers for the like of Boost Python, standard library, etc.
  if libtbx.env.build_options.precompile_headers:
    if sys.platform == 'win32':
      precompiled_cpp = '#' + os.path.join('boost_adaptbx', 'precompiled.cpp')
      precompiled_h = 'boost_adaptbx/precompiled.h'
      env_no_includes_boost_python_ext['PCH'], _ \
                = env_boost_python_ext.PCH(precompiled_cpp)
      env_no_includes_boost_python_ext['PCHSTOP'] = precompiled_h
      env_no_includes_boost_python_ext.Append(CPPFLAGS=[ '/FI' + precompiled_h ])
    elif env_etc.compiler.endswith('clang'):
      # 1st trick:
      # use Command to execute $SHCXXCOM with an extra option to produce
      # a precompiled header a la clang
      precompiled_h_gch = "#boost_adaptbx/precompiled.h.gch"
      env_pch = env_pure_boost_python_ext.Clone()
      env_pch.Append(SHCXXFLAGS=[ '-x', 'c++-header' ])
      env_pch.Command(target=precompiled_h_gch,
                      source="precompiled.h",
                      action="$SHCXXCOM")
      # 2nd trick
      # use an emitter so that the dependency on the precompiled header
      # is added on-the-fly to the target of SharedLibrary
      # (got the idea from http://scons.org/wiki/GchBuilder)
      explicit_include = [ '-include', 'boost_adaptbx/precompiled.h' ]
      def gcc_style_pch_emitter(target, source, env):
        import SCons.Defaults
        # emitters are not chained, so we need to call the default one
        # otherwise some setup is not done and scons will refuse to link
        # shared libraries from what it thinks are static objects
        SCons.Defaults.SharedObjectEmitter(target, source, env)
        env.Depends(target, precompiled_h_gch)
        return target, source
      for env in (env_pure_boost_python_ext, env_no_includes_boost_python_ext):
        env.Append(SHCXXFLAGS=explicit_include)
        for suffix in ('.cxx', '.cpp', '.cc'):
          env['BUILDERS']['SharedObject'].add_emitter(suffix,
                                                      gcc_style_pch_emitter)

  env_pure_boost_python_ext.SharedLibrary(
    target="#lib/boost_python_hybrid_times_ext",
    source="hybrid_times_ext.cpp")

  env_pure_boost_python_ext.SharedLibrary(
    target="#lib/boost_rational_ext",
    source="rational_ext.cpp")

  env = env_pure_boost_python_ext.Clone()
  env_etc.include_registry.prepend(
    env=env,
    paths=[env_etc.boost_adaptbx_include])
  env.SharedLibrary(
    target="#lib/boost_python_meta_ext",
    source="meta_ext.cpp")
  env.SharedLibrary(
    target="#lib/boost_optional_ext",
    source="optional_ext.cpp")
  env.SharedLibrary(
    target="#lib/std_pair_ext",
    source="std_pair_ext.cpp")
  env.SharedLibrary(
    target="#lib/boost_tuple_ext",
    source="tuple_ext.cpp")
  env.SharedLibrary(
    target="#lib/boost_adaptbx_python_streambuf_test_ext",
    source="tests/python_streambuf_test_ext.cpp")

  env = env_base.Clone()
  env_etc.include_registry.prepend(
    env=env,
    paths=[env_etc.boost_adaptbx_include])
  env.Program(
    target="tests/tst_optional_copy",
    source="tests/tst_optional_copy.cpp")

  import os, os.path
  env = env_base.Clone(LIBS=env_etc.libs_python)
  env.Append(LIBPATH=env_etc.libpath_python)
  env.Append(SHCXXFLAGS=env_etc.cxxflags_bpl_defines_base)
  env.Append(SHCXXFLAGS=["-DBOOST_PYTHON_SOURCE"])
  if (libtbx.env.build_options.boost_python_bool_int_strict):
    env.Append(SHCXXFLAGS=["-DBOOST_PYTHON_BOOL_INT_STRICT"])
  env.Replace(SHLINKFLAGS=env_etc.shlinkflags)
  env.Append(CXXFLAGS=env_etc.cxxflags_bpl_defines_base)
  env.Append(CXXFLAGS="-DBOOST_PYTHON_SOURCE")
  env.Replace(LINKFLAGS=env_etc.shlinkflags)
  env_etc.include_registry.append(
    env=env,
    paths=[env_etc.boost_include, env_etc.python_include])

  darwin_shlinkcom(env_etc, env,
                   lo="boost/libs/python/src/libboost_python.lo",
                   dylib="lib/libboost_python.dylib")

  # fixed list of file names introduced 2009-09-29, due to major changes in
  # boost/libs/python/build/Jamfile.v2 svn rev. 56305 (new Python 3 support)
  bpl_dll_sources = """\
numeric.cpp
list.cpp
long.cpp
dict.cpp
tuple.cpp
str.cpp
slice.cpp
converter/from_python.cpp
converter/registry.cpp
converter/type_id.cpp
object/enum.cpp
object/class.cpp
object/function.cpp
object/inheritance.cpp
object/life_support.cpp
object/pickle_support.cpp
errors.cpp
module.cpp
converter/builtin_converters.cpp
converter/arg_to_python_base.cpp
object/iterator.cpp
object/stl_iterator.cpp
object_protocol.cpp
object_operators.cpp
wrapper.cpp
import.cpp
exec.cpp
object/function_doc_signature.cpp
""".splitlines()
  prefix = "#"+os.path.join(
    os.path.basename(env_etc.boost_dist), "libs", "python", "src")
  bpl_dll_sources = [os.path.join(prefix, path) for path in bpl_dll_sources]
  #
  env.Repository(os.path.dirname(env_etc.boost_dist))
  if (env_etc.static_bpl):
    env.StaticLibrary(target="#lib/boost_python", source=bpl_dll_sources)
  else:
    env.SharedLibrary(target="#lib/boost_python", source=bpl_dll_sources)
  if (bool(int(ARGUMENTS.get("boost_python_tests", "0")))):
    warn_if_unexpected_md5_hexdigest(
      path=libtbx.env.under_dist("boost", "libs/python/test/Jamfile.v2"),
      expected_md5_hexdigests=[
        "c7a3dd81bf730635146f5c908ac982eb", # svn revision 39065M
        "d40aac0029bcd28f6e205ae3b30a1284", # svn revision 40216
        "b79f016d3ec10cf1625a9e006e605428", # svn revision 40714
        "f948983d970fd47e83a3a785bb54520a", # svn revision 41550
        "c3d84006331d534840c42ab956fdfa05", # svn revision 50367M
        "cedfd061d767a27413ef4a75ee0e446f", # svn revision 56310
        "e5507482a1937825e4c9f3ffe555fc59", # svn revision 59331
        "351b57c6a60e484925ca0cb9ed0f8ade", # svn revision 61086
        "4fb583110f7424c341a0dd44ff3b3a2d", # svn revision 69551
        "fdde4c7231c301c3150b0a5605045914", # svn revision 72175
      ])
    bpl_tests = Split("""
staticmethod
shared_ptr
enable_shared_from_this
andreas_beyer
polymorphism
polymorphism2
auto_ptr
minimal
args
numpy
enum
exception_translator
test_pointer_adoption
operators
callbacks
defaults
object
list
long
dict
tuple
str
virtual_functions
back_reference
implicit
data_members
ben_scott1
bienstman1
bienstman2
bienstman3
multi_arg_constructor
extract
opaque
pickle1
pickle2
pickle3
pickle4
nested
docstring
pytype_function
bienstman4
bienstman5
test_builtin_converters,builtin_converters_ext,test_builtin_converters
cltree,cltree,test_cltree
m1,m1,newtest
m2,m2,newtest
iterator,iterator_ext,iterator
input_iterator,input_iterator,iterator
crossmod_exception_a,crossmod_exception_a,crossmod_exception
crossmod_exception_b,crossmod_exception_b,crossmod_exception
vector_indexing_suite
return_arg
keywords,keywords,keywords_test
properties
map_indexing_suite;int_map_indexing_suite;a_map_indexing_suite,map_indexing_suite_ext,map_indexing_suite
injected
slice
const_argument
raw_ctor
pointer_vector
wrapper_held_type
polymorphism2_auto_ptr
stl_iterator
voidptr
crossmod_opaque_a,crossmod_opaque_a,crossmod_opaque
crossmod_opaque_b,crossmod_opaque_b,crossmod_opaque
class
""")
# non-portable tests are not included above:
#   calling_conventions
#   calling_conventions_mf
#
    Import("env_boost_python_ext")
    test_dir = libtbx.env.under_dist("boost", "libs/python/test")
    prefix = "#"+os.path.join(
      os.path.basename(env_etc.boost_dist), "libs", "python", "test")
    all_tst = []
    for bpl_test in bpl_tests:
      flds = bpl_test.split(",")
      assert len(flds) in (1,3)
      if (len(flds) == 1):
        src = bpl_test
        pyd = bpl_test + "_ext"
        tst = bpl_test
      else:
        src = flds[0]
        pyd = flds[1]
        tst = flds[2]
      env = env_pure_boost_python_ext.Clone()
      env.Repository(os.path.dirname(env_etc.boost_dist))
      ok = True
      source = []
      for s in src.split(";"):
        cpp = "%s.cpp" % s
        file_name = os.path.join(test_dir, cpp)
        if (not os.path.isfile(file_name)):
          print "Warning: source file not available:", show_string(file_name)
          ok = False
        else:
          source.append(os.path.join(prefix, cpp))
      if (ok):
        if (not tst in all_tst):
          all_tst.append(tst)
        env.SharedLibrary(target="#lib/%s" % pyd, source=source)
    all_tst = [os.path.join(test_dir, tst+".py") for tst in all_tst]
    #
    if (sys.platform == "linux2"):
      base_lib = libtbx.env.under_build(path="base/lib")
      if (os.path.isdir(base_lib)):
        env_prog = env.Clone()
        env_prog.Append(LIBPATH=[base_lib])
        env_prog.Append(LIBS=[
          "-lpython%d.%d" % sys.version_info[:2],
          "-lpthread",
          "-lutil",
          "-ldl"])
        exe = env_prog.Program(
          target="boost/libs/python/test/exec_dynamic",
          source=[os.path.join(prefix, "exec.cpp")])
        libtbx.env.write_dispatcher_in_bin(
          source_file=exe[0].get_abspath(),
          target_file="boost_libs_python_test_exec_dynamic")
        all_tst.append("$ boost_libs_python_test_exec_dynamic %s"
          % show_string(os.path.join(test_dir, "exec.py")))
    #
    if (os.name != "nt"):
      env.SharedLibrary(
        target="#lib/boost_adaptbx_char_array_ext",
        source=["char_array_ext.cpp"])
      all_tst.append(
        libtbx.env.under_dist("boost_adaptbx", "tests/tst_char_array.py"))
    #
    if (os.name == "nt"):
      boost_python_run_tests = \
        "%s\\boost_python_run_tests.bat" % libtbx.env.build_path
      f = open(boost_python_run_tests, "w")
      for tst in all_tst:
        if (tst.startswith("$ ")):
          print >> f, 'call %s' % tst[2:]
        else:
          print >> f, 'call libtbx.python %s' % show_string(tst)
      f.close()
    else:
      boost_python_run_tests = \
        "%s/boost_python_run_tests.csh" % abs(libtbx.env.build_path)
      f = open(boost_python_run_tests, "w")
      print >> f, "#! /bin/csh -f"
      print >> f, "set verbose"
      for tst in all_tst:
        if (tst.startswith("$ ")):
          print >> f, tst[2:]
        else:
          print >> f, 'libtbx.python "%s"' % show_string(tst)
      f.close()
      os.chmod(boost_python_run_tests, 0755)

  def write_type_id_eq_h():
    unsigned_types = [
      "unsigned short",
      "unsigned",
      "unsigned long",
      "unsigned long long"]
    test_code = """\
#include <typeinfo>
#include <iostream>
#include <stddef.h>

int
main()
{
  std::cout
%s
    << std::endl;
  return 0;
}
""" % "\n".join(["    << static_cast<int>(typeid(%s) == typeid(size_t))" % u
      for u in unsigned_types])
    env = env_base.Clone()
    conf = env.Configure()
    flag, output = conf.TryRun(test_code, extension='.cpp')
    conf.Finish()
    if (not flag):
      see_message_above = \
        'Failure building small C++ program for obtaining unsigned sizes.' \
        ' Please inspect "config.log" for details'
      raise RuntimeError(see_message_above)
    output = output.rstrip()
    if (   len(output) != len(unsigned_types)
        or len(output.replace("0", "").replace("1", "")) != 0
        or output.count("1") != 1):
      raise RuntimeError('Unexpected output: "%s"' % output)
    dir_name = libtbx.env.under_build("include/boost_adaptbx")
    if (not op.isdir(dir_name)):
      os.makedirs(dir_name)
    assert op.isdir(dir_name)
    if (1): # XXX backward compatibility 2009-10-14
      file_name = op.join(dir_name, "boost_python_type_id_eq.h")
      if (op.isfile(file_name)):
        try: os.remove(file_name)
        except OSError: pass
    file_name = op.join(dir_name, "type_id_eq.h")
    f = open(file_name, "w")
    write_this_is_auto_generated(
      f=f,
      file_name_generator="cctbx_project/boost_adaptbx/SConscript")
    guard = "BOOST_ADAPTBX_TYPE_ID_EQ_H"
    print >> f, "#ifndef", guard
    print >> f, "#define", guard
    print >> f
    for u,c in zip(unsigned_types, output):
      if (c == "1"):
        print >> f, "#define BOOST_ADAPTBX_TYPE_ID_SIZE_T_EQ_" \
          + u.replace(" ", "_").upper()
    print >> f
    print >> f, "#endif // GUARD"
    del f
  write_type_id_eq_h()

  if (os.path.isfile(libtbx.env.under_build("lib/libswig_class_example.a"))):
    # this example requires these manual steps:
    #   gunzip -c swig-1.3.24.tar.gz | tar xf -
    #   cd SWIG-1.3.24
    #   ./configure
    #   make
    #   cd Examples/python/class
    #   make
    #   cp _example.so example.py $LIBTBX_BUILD/lib
    #   ar r $LIBTBX_BUILD/lib/libswig_class_example.a example.o
    env = env_pure_boost_python_ext.Clone()
    env.Prepend(LIBS=["swig_class_example"])
    env.SharedLibrary(
      target="#lib/boost_python_swig_args_ext",
      source="swig_args_ext.cpp")

  if (os.path.isfile(libtbx.env.under_dist("boost_adaptbx", "demo.cpp"))):
    env = env_pure_boost_python_ext.Clone()
    env.SharedLibrary(
      target="#lib/demo",
      source="demo.cpp")
