## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
from __future__ import print_function
import os, os.path
import sys
import shutil
import types
import warnings

from waflib import TaskGen, Task, Options, Build, Utils
from waflib.Errors import WafError
import wutils

try:
    set
except NameError:
    from sets import Set as set # Python 2.3 fallback

all_contrib_modules = []
for dirname in os.listdir('contrib'):
    if dirname.startswith('.') or dirname == 'CVS':
        continue
    path = os.path.join('contrib', dirname)
    if not os.path.isdir(path):
        continue
    if os.path.exists(os.path.join(path, 'wscript')):
        all_contrib_modules.append(dirname)
all_contrib_modules.sort()

def get_required_boost_libs(conf):
    for module in all_contrib_modules:
        conf.recurse (module, name="required_boost_libs", mandatory=False)

def options(opt):
    for module in all_contrib_modules:
        opt.recurse(module, mandatory=False)

def configure(conf):
    for module in all_contrib_modules:
        conf.recurse(module, mandatory=False)

    ## Used to link the 'test-runner' program with all of ns-3 code
    conf.env['NS3_CONTRIBUTED_MODULES'] = ['ns3-' + module.split('/')[-1] for module in all_contrib_modules]


# we need the 'ns3module' waf "feature" to be created because code
# elsewhere looks for it to find the ns3 module objects.

def create_ns3_module(bld, name, dependencies=(), test=False):
    static = bool(bld.env.ENABLE_STATIC_NS3)
    # Create a separate library for this module.
    if static:
        module = bld(features='cxx cxxstlib ns3module')
    else:
        module = bld(features='cxx cxxshlib ns3module')
    module.target = '%s/lib/ns%s-%s%s' % (bld.srcnode.path_from(module.path), wutils.VERSION,
                                       name, bld.env.BUILD_SUFFIX)
    linkflags = []
    cxxflags = []
    ccflags = []
    if not static:
        cxxflags = module.env['shlib_CXXFLAGS']
        ccflags = module.env['shlib_CXXFLAGS']
        # Turn on the link flags for shared libraries if we have the
        # proper compiler and platform.
        if module.env['CXX_NAME'] in ['gcc', 'icc'] and module.env['WL_SONAME_SUPPORTED']:
            # Get the module library name without any relative paths
            # at its beginning because all of the libraries will end
            # up in the same directory.
            module_library_name = module.env.cshlib_PATTERN % (os.path.basename(module.target),)
            linkflags = '-Wl,--soname=' + module_library_name
    cxxdefines = ["NS3_MODULE_COMPILATION"]
    ccdefines = ["NS3_MODULE_COMPILATION"]

    module.env.append_value('CXXFLAGS', cxxflags)
    module.env.append_value('CCFLAGS', ccflags)
    module.env.append_value('LINKFLAGS', linkflags)
    module.env.append_value('CXXDEFINES', cxxdefines)
    module.env.append_value('CCDEFINES', ccdefines)

    module.is_static = static
    module.vnum = wutils.VNUM
    # Add the proper path to the module's name.
    # Set the libraries this module depends on.  
    module.module_deps = list(dependencies)

    module.install_path = "${LIBDIR}"

    module.name = "ns3-" + name
    module.dependencies = dependencies
    # Initially create an empty value for this because the pcfile
    # writing task assumes every module has a uselib attribute.
    module.uselib = ''
    module.use = ['ns3-' + dep for dep in dependencies]
    module.test = test
    module.is_ns3_module = True
    module.ns3_dir_location = bld.path.path_from(bld.srcnode)

    module.env.append_value("INCLUDES", '#')

    module.pcfilegen = bld(features='ns3pcfile')
    module.pcfilegen.module = module.name
    
    return module

def create_ns3_module_test_library(bld, name):
    # Create an ns3 module for the test library that depends only on
    # the module being tested.
    library_name = name + "-test"
    library = bld.create_ns3_module(library_name, [name], test=True)
    library.features += " ns3testlib"

    # Modify attributes for the test library that are different from a
    # normal module.
    del library.is_ns3_module
    library.is_ns3_module_test_library = True
    library.module_name = 'ns3-' + name

    # Add this module and test library to the list.
    bld.env.append_value('NS3_MODULES_WITH_TEST_LIBRARIES', [(library.module_name, library.name)])

    # Set the include path from the build directory to modules. 
    relative_path_from_build_to_here = bld.path.path_from(bld.bldnode)
    include_flag = '-I' + relative_path_from_build_to_here
    library.env.append_value('CXXFLAGS', include_flag)
    library.env.append_value('CCFLAGS',  include_flag)

    return library

def create_obj(bld, *args):
    warnings.warn("(in %s) Use bld(...) call now, instead of bld.create_obj(...)" % str(bld.path),
                  DeprecationWarning, stacklevel=2)
    return bld(*args)


def ns3_python_bindings(bld):
    # this method is called from a module wscript, so remember bld.path is not bindings/python!
    module_abs_src_path = bld.path.abspath()
    module = os.path.basename(module_abs_src_path)
    env = bld.env
    env.append_value("MODULAR_BINDINGS_MODULES", "ns3-"+module)

    if Options.options.apiscan:
        return

    if not env['ENABLE_PYTHON_BINDINGS']:
        return

    bindings_dir = bld.path.find_dir("bindings")
    if bindings_dir is None or not os.path.exists(bindings_dir.abspath()):
        warnings.warn("(in %s) Requested to build modular python bindings, but apidefs dir not found "
                      "=> skipped the bindings." % str(bld.path),
                      Warning, stacklevel=2)
        return

    if ("ns3-%s" % (module,)) not in env.NS3_ENABLED_MODULES:
        #print "bindings for module %s which is not enabled, skip" % module
        return

    env.append_value('PYTHON_MODULES_BUILT', module)
    try:
        apidefs = env['PYTHON_BINDINGS_APIDEFS'].replace("-", "_")
    except AttributeError:
        # we likely got an empty list for env['PYTHON_BINDINGS_APIDEFS']
        return

    #debug = ('PYBINDGEN_DEBUG' in os.environ)
    debug = True # XXX
    source = [bld.srcnode.find_resource('bindings/python/ns3modulegen-modular.py'),
              bld.path.find_resource("bindings/modulegen__%s.py" % apidefs)]

    modulegen_customizations = bindings_dir.find_resource("modulegen_customizations.py")
    if modulegen_customizations is not None:
        source.append(modulegen_customizations)

    modulegen_local = bld.path.find_resource("bindings/modulegen_local.py")
    # the local customization file may or not exist
    if modulegen_local is not None:
        source.append("bindings/modulegen_local.py")

    module_py_name = module.replace('-', '_')
    module_target_dir = bld.srcnode.find_dir("bindings/python/ns").path_from(bld.path)

    # if bindings/<module>.py exists, it becomes the module frontend, and the C extension befomes _<module>
    if bld.path.find_resource("bindings/%s.py" % (module_py_name,)) is not None:
        bld(features='copy',
            source=("bindings/%s.py" % (module_py_name,)),
            target=('%s/%s.py' % (module_target_dir, module_py_name)))
        extension_name = '_%s' % (module_py_name,)
        bld.install_files('${PYTHONARCHDIR}/ns', ["bindings/%s.py" % (module_py_name,)])
    else:
        extension_name = module_py_name

    target = ['bindings/ns3module.cc', 'bindings/ns3module.h', 'bindings/ns3modulegen.log']
    #if not debug:
    #    target.append('ns3modulegen.log')

    argv = ['NS3_ENABLED_FEATURES=${FEATURES}',
            'GCC_RTTI_ABI_COMPLETE=${GCC_RTTI_ABI_COMPLETE}',
            '${PYTHON}']
    #if debug:
    #    argv.extend(["-m", "pdb"])
    
    argv.extend(['${SRC[0]}', module_abs_src_path, apidefs, extension_name, '${TGT[0]}'])

    argv.extend(['2>', '${TGT[2]}']) # 2> ns3modulegen.log

    features = []
    for (name, caption, was_enabled, reason_not_enabled) in env['NS3_OPTIONAL_FEATURES']:
        if was_enabled:
            features.append(name)

    bindgen = bld(features='command', source=source, target=target, command=argv)
    bindgen.env['FEATURES'] = ','.join(features)
    bindgen.dep_vars = ['FEATURES', "GCC_RTTI_ABI_COMPLETE"]
    bindgen.before = ['cxxprogram', 'cxxshlib', 'cxxstlib']
    bindgen.after = 'gen_ns3_module_header'
    bindgen.name = "pybindgen(ns3 module %s)" % module
    bindgen.install_path = None

    # generate the extension module
    pymod = bld(features='cxx cxxshlib pyext')
    pymod.source = ['bindings/ns3module.cc']
    pymod.target = '%s/%s' % (module_target_dir, extension_name)
    pymod.name = 'ns3module_%s' % module
    pymod.use = ["%s" % mod for mod in pymod.env['NS3_ENABLED_MODULES']] #  Should be '"ns3-"+module', but see bug 1117
    if pymod.env['ENABLE_STATIC_NS3']:
        if sys.platform == 'darwin':
            pymod.env.append_value('LINKFLAGS', '-Wl,-all_load')
            for mod in pymod.usel:
                #mod = mod.split("--lib")[0]
                pymod.env.append_value('LINKFLAGS', '-l' + mod)
        else:
            pymod.env.append_value('LINKFLAGS', '-Wl,--whole-archive,-Bstatic')
            for mod in pymod.use:
                #mod = mod.split("--lib")[0]
                pymod.env.append_value('LINKFLAGS', '-l' + mod)
            pymod.env.append_value('LINKFLAGS', '-Wl,-Bdynamic,--no-whole-archive')
    defines = list(pymod.env['DEFINES'])
    defines.extend(['NS_DEPRECATED=', 'NS3_DEPRECATED_H'])
    if Utils.unversioned_sys_platform() == 'win32':
        try:
            defines.remove('_DEBUG') # causes undefined symbols on win32
        except ValueError:
            pass
    pymod.env['DEFINES'] = defines
    pymod.includes = '# bindings'
    pymod.install_path = '${PYTHONARCHDIR}/ns'

    # Workaround to a WAF bug, remove this when ns-3 upgrades to WAF > 1.6.10
    # https://www.nsnam.org/bugzilla/show_bug.cgi?id=1335
    # http://code.google.com/p/waf/issues/detail?id=1098
    if Utils.unversioned_sys_platform() == 'darwin':
        pymod.mac_bundle = True

    return pymod

def build(bld):
    bld.create_ns3_module = types.MethodType(create_ns3_module, bld)
    bld.create_ns3_module_test_library = types.MethodType(create_ns3_module_test_library, bld)
    bld.create_obj = types.MethodType(create_obj, bld)
    bld.ns3_python_bindings = types.MethodType(ns3_python_bindings, bld)
    
    # Remove these modules from the list of all modules.
    for not_built in bld.env['MODULES_NOT_BUILT']:

        if not_built in all_contrib_modules:
            all_contrib_modules.remove(not_built)

    bld.recurse(list(all_contrib_modules))

    for module in all_contrib_modules:
        modheader = bld(features='ns3moduleheader')
        modheader.module = module.split('/')[-1]

