#!/usr/bin/env python

# wscript - Build script for waf
# See http://waf.googlecode.com/ for more info

# Environment variables that can be set:
# CFLAGS - Selects compilation options for when the C compiler is used, e.g. '-Wall'. 
# CXXFLAGS - Selects compilation options for when the C++ compiler is used, e.g. '-Wall'. 
# CPPFLAGS - Selects C preprocessor options, e.g. '-DFOO=bar' 
# LINKFLAGS - Extra linker options, e.g. '-L/usr/local -lsome-library' 
# CC - The C compiler that will be used instead of the platform default. Example CC=/usr/bin/gcc-2.95 
# CXX - The C++ compiler that will be used instead of the platform default. Example CXX=/usr/bin/g++-2.95

import os
from optparse import OptionGroup
from platform import system
from subprocess import Popen, PIPE

import Options # Waf module for command line options
from Configure import conf # Waf module for adding configure tools

# the following two variables are used by the target 'waf dist'
VERSION = '2.1'
APPNAME = 'Protokit'

# Constant source directories
INCLUDE = 'include'
COMMON = 'src/common'
UNIX = 'src/unix'
LINUX = 'src/linux'
BSD = 'src/bsd'
WIN32 = 'src/win32'
MANET = 'src/manet'
PYTHON = 'src/python'
JAVA = 'src/java'
WX = 'src/wx'
EXAMPLES = 'examples'

# these variables are mandatory ('/' are converted automatically)
# Base directory for files
srcdir = '.'
# Where to put all compiled files
blddir = 'build'

def set_options(opt):
    '''Sets command line options'''
    # Include command line flags for g++
    opt.tool_options('compiler_cxx')

    # Options for Python for bindings
    opt.tool_options('python')

    misc = OptionGroup(opt.parser, 'Build Options',
            'Options affecting the build process (Call during configure)')
    opt.add_option_group(misc)

    misc.add_option('--debug', action='store_true', help='Build a debug build')
    misc.add_option('--static', action='store_true',
            help='Build a static library (default shared)')

    features = OptionGroup(opt.parser, 'Features',
            'Options that allow you to disable features from the library.  ' +
            'Specify at configure time.')
    opt.add_option_group(features)

    features.add_option('--disable-route', action='store_true', default=False,
            help='Disable the routing classes (ProtoRouteMgr, ProtoRouteTable)')
    features.add_option('--disable-manet', action='store_true', default=False,
            help='Disable the manet classes (ManetMsg, ManetGraph)')
    features.add_option('--disable-wx', action='store_true', default=False,
            help='Disable the wxWidgets application class.')
    features.add_option('--disable-cap', action='store_true', default=False,
            help='Disable the packet capture class (ProtoCap)')
    features.add_option('--disable-detour', action='store_true', default=False,
            help='Disable ProtoDetour')
    features.add_option('--disable-vif', action='store_true', default=False,
            help='Disable ProtoVif')

    bindings = OptionGroup(opt.parser, 'Language Bindings/Other Libraries',
            'Options to build other libraries for Protolib.  Specify at configure time.')
    opt.add_option_group(bindings)

    bindings.add_option('--build-python', action='store_true', default=False,
            help='Build ProtoPipe Python wrapper.')
    bindings.add_option('--build-java', action='store_true', default=False,
            help='Build Java wrapper.')
    bindings.add_option('--build-ns3', action='store_true', default=False,
            help='Build NS3 Library.  Reads NS3_HOME environment variable for location of NS3.')

    examples = OptionGroup(opt.parser, 'Examples',
            'Options to pick which example programs to build.  Specify these at build time.')
    opt.add_option_group(examples)

    examples.add_option('--ex-all', action='store_true', default=False,
            help='Builds all examples')
    examples.add_option('--ex-protoExample', action='store_true', default=False)
    examples.add_option('--ex-vifExample', action='store_true', default=False)
    examples.add_option('--ex-timerTest', action='store_true', default=False)
    examples.add_option('--ex-threadExample', action='store_true', default=False)
    examples.add_option('--ex-pipeExample', action='store_true', default=False)
    examples.add_option('--ex-protoCapExample', action='store_true', default=False)
    examples.add_option('--ex-detourExample', action='store_true', default=False)
    examples.add_option('--ex-graphExample', action='store_true', default=False)
    examples.add_option('--ex-msgExample', action='store_true', default=False)
    examples.add_option('--ex-graphBuilder', action='store_true', default=False)
    examples.add_option('--ex-graphRider', action='store_true', default=False)
    examples.add_option('--ex-simpleTcpExample', action='store_true', default=False)
    examples.add_option('--ex-wxProtoExample', action='store_true', default=False)

def configure(conf):
    '''Performs the configuration checks'''

    if system() in ['Windows','Microsoft']:
        conf.env['MSVC_TARGETS'] = 'x86 amd64'

    # Check for GCC and related tools
    conf.check_tool('compiler_cxx')
	
    ### Set define flags ###
    conf.env.append_unique('CXXDEFINES', '_FILE_OFFSET_BITS=64')

    ## System specific stuff
    if system() in ['Linux', 'Darwin', 'FreeBSD']:
        conf.env.append_unique('CXXDEFINES', 'UNIX')

    if system() == 'Linux':
        conf.env.append_unique('CXXDEFINES', ['LINUX', 'HAVE_SCHED'])
        conf.env.append_unique('system_libs', ['dl', 'rt'])

    if system() == 'Darwin':
        conf.env.append_unique('CXXDEFINES', 'MACOSX')
        #conf.env.append_unique('CXXFLAGS', '-arch i386 -arch x86_64')
        #conf.env.append_unique('LINKFLAGS', '-arch i386 -arch x86_64')
        conf.env.append_unique('system_libs', 'resolv')

    if system() == 'FreeBSD':
        conf.env.append_unique('system_libs', 'pthread')

    if system() in ['Windows','Microsoft']:
        conf.env.append_unique('CXXDEFINES', ['WIN32', '_CRT_SECURE_NO_WARNINGS'])
        conf.env.append_unique('system_libs',
                ['ws2_32', 'iphlpapi', 'user32', 'gdi32'])

    ## Check for different features for define flags

    # What about regular NETSEC
    # if conf.check(header_name='net/security.h' define_name='HAVE_NETSEC'):
    #   conf.env.append_unique('CXXDEFINES', ['NETSEC', 'HAVE_NETSEC']

    # Note: moved this to Linux-only for now - RBA
    #if conf.check(header_name='sched.h'):
    #    conf.env.append_unique('CXXDEFINES', 'HAVE_SCHED')

    if conf.check(header_name='assert.h'):
        conf.env.append_unique('CXXDEFINES', 'HAVE_ASSERT')

    if conf.check(function_name='pselect', header_name='sys/select.h'):
        conf.env.append_unique('CXXDEFINES', 'HAVE_PSELECT')

    if not conf.check(msg='Checking for SCM_RIGHTS', fragment='''
        #include <sys/socket.h>
        #include <sys/un.h>
        int main() {
            int i = SCM_RIGHTS;
            struct sockaddr_un j;
            j.sun_len = 10;
            return 0;
        }'''):
        conf.env.append_unique('CXXDEFINES', 'NO_SCM_RIGHTS')

    # The stuff with the include is becaue sockaddr_in6 is in a different place
    # on windows
    if conf.check(msg='Checking for IPv6', fragment='''
        #include <%s>
        int main() {
            struct sockaddr_in6 *x;
            return 0;
        }''' % ('netinet/in.h', 'ws2tcpip.h')[system() in ['Windows','Microsoft']]):
        conf.env.append_unique('CXXDEFINES', 'HAVE_IPV6')

    ## Choose our build type
    if Options.options.static:
        conf.env['protolib_type'] = 'cstaticlib'
    else:
        conf.env['protolib_type'] = 'cshlib'

    ## Set define flags for optimized/non-optimized build
    if Options.options.debug:
        conf.env.append_unique('CXXDEFINES', 'PROTO_DEBUG')
        if system() in ['Windows','Microsoft']:
            conf.env.append_unique('CXXFLAGS', ['/Od', '/RTC1', '/D_DEBUG', '/ZI'])
        else:
            conf.env.append_unique('CXXFLAGS', ['-g', '-O0'])
    else:
        conf.env.append_unique('CXXDEFINES', 'PROTO_MSG')
        if system() in ['Windows','Microsoft']:
            conf.env.append_unique('CXXFLAGS', ['/Ox', '/DNDEBUG'])
        else:
            conf.env.append_unique('CXXFLAGS', ['-O3'])

    ## Check for system specific libraries
    for lib in conf.env['system_libs']:
        conf.check(lib=lib, mandatory=True)

    ## Do checks for stuff depending on --disable-* flags
    conf.env['disable_route'] = Options.options.disable_route
    conf.env['disable_manet'] = Options.options.disable_manet
    conf.env['disable_cap'] = Options.options.disable_cap
    conf.env['disable_detour'] = Options.options.disable_detour
    conf.env['disable_vif'] = Options.options.disable_vif

    conf.env['disable_wx'] = Options.options.disable_wx
    if not conf.env['disable_wx']:
        conf.check_wx()

    ## Do checks for language bindings
    conf.env['build_python'] = Options.options.build_python
    if conf.env['build_python']:
        conf.check_tool('python')
        conf.check_python_version((2,4))
        conf.check_python_headers()

    conf.env['build_java'] = Options.options.build_java
    if conf.env['build_java']:
        conf.check_tool('java')
        try:
            if system() in ['Windows','Microsoft']:
                cxxflags = ['/I%s\include' % os.environ['JAVA_HOME']]
                cxxflags.append('/I%s\include\win32' % os.environ['JAVA_HOME'])
            else:
                cxxflags = ['-I%s/include' % os.environ['JAVA_HOME']]
                cxxflags.append('-I%s/include/%s' % (
                    os.environ['JAVA_HOME'],
                    system().lower()))
            conf.check(header_name='jni.h', cxxflag=cxxflags, mandatory=True)
            conf.check(header_name='jni_md.h', cxxflag=cxxflags, mandatory=True)
            conf.env.append_unique('CXXFLAGS_JAVA', cxxflags)
        except KeyError:
            conf.fatal('JAVA_HOME environment variable not set.')

    conf.env['build_ns3'] = Options.options.build_ns3
    if conf.env['build_ns3']:
        try:
            conf.check(header_name='ns3/core-module.h',
                    cxxflag='-I%s/build/debug' % os.environ['NS3_HOME'],
                    mandatory=True)
            conf.env['NS3_HOME'] = os.environ['NS3_HOME']
            conf.env.append_unique('CXXDEFINES_NS3', ['SIMULATE', 'NS3'])
        except KeyError:
            conf.fatal('NS3_HOME environment variable not set.')
            
    conf.env['protolib_install'] = True

def build(bld):
    '''Performs the compilation'''
    protokit = bld.new_task_gen(
            # cxx builds c++ and cstaticlib or cshlib builds libraries
            features = 'cxx %s' % bld.env['protolib_type'],
            # waf will turn this into libprotokit.so
            target = 'protokit',
            # for shared libraries, include the .so version number
            vnum = VERSION,
            # where the header files are
            includes = [INCLUDE],
            # for uselib_local
            export_incdirs = [INCLUDE],
            # list of libraries to link against
            uselib = [x.upper() for x in bld.env['system_libs']],
            # Add common source files
            source = ['%s/%s.cpp' % (COMMON, x) for x in [
                'protoAddress', 'protoApp', 'protoBitmask', 'protoChannel',
                'protoDispatcher', 'protoPipe', 'protoPkt', 'protoPktARP',
                'protoPktETH', 'protoPktIP', 'protoPktRTP', 'protoSocket',
                'protoTime', 'protoTimer', 'protoTree', 'protoList',
                'protoGraph', 'protoSpace', 'protoDebug']]
    )
    
    if bld.env['protolib_install'] != True:
        protokit.install_path = None
        
    # Process system specific source files
    system_source = []
    if system() in ['Linux', 'Darwin', 'FreeBSD']:
        system_source.append(UNIX + '/unixVif.cpp')

    if system() == 'Linux':
        system_source.extend(['%s/%s.cpp' % (LINUX, x) for x in
            ['linuxRouteMgr', 'linuxCap', 'linuxDetour']])

    if system() in ['Darwin', 'FreeBSD']:
        system_source.extend(['%s/%s.cpp' % (BSD, x) for x in
            ['bsdRouteMgr', 'bsdDetour']])
        system_source.append(UNIX + '/bpfCap.cpp')

    if system() in ['Windows','Microsoft']:
        system_source.extend(['%s/%s.cpp' % (WIN32, x) for x in
            ['win32RouteMgr', 'win32Vif']])

    # Process all of the --disable-* flags
    if not bld.env['disable_route']:
        protokit.source.extend(['%s/%s.cpp' % (COMMON, x) for x in [
            'protoRouteMgr', 'protoRouteTable']])
        protokit.source.extend([x for x in system_source
            if x.endswith('RouteMgr.cpp')])

    if not bld.env['disable_manet']:
        protokit.source.extend(['%s/%s.cpp' % (MANET, x) for x in [
            'manetGraph', 'manetMsg']])

    if not bld.env['disable_wx']:
        protokit.source.append(WX + '/wxProtoApp.cpp')
        protokit.uselib.append('WX')

    if not bld.env['disable_cap']:
        protokit.source.append(COMMON + '/protoCap.cpp')
        protokit.source.extend(
                [x for x in system_source if x.endswith('Cap.cpp')])

    if not bld.env['disable_detour']:
        protokit.source.extend(
                [x for x in system_source if x.endswith('Detour.cpp')])

    if not bld.env['disable_vif']:
        protokit.source.append(COMMON + '/protoVif.cpp')
        protokit.source.extend(
                [x for x in system_source if x.endswith('Vif.cpp')])

    # Install headers (if not installing in local directory)
    if os.path.abspath(bld.env['PREFIX']) != os.path.abspath('./') and bld.env['protolib_install']:
        bld.install_files('${PREFIX}/include', '%s/*.h' % INCLUDE)

    # Build language bindings
    if bld.env['build_python']:
        bld.new_task_gen(
                features = 'cxx cshlib pyext',
                target = 'protokit',
                name = 'pyprotokit',
                uselib_local = 'protokit',
                source = PYTHON + '/protokit.cpp'
        )

    if bld.env['build_java']:
        bld.new_task_gen(
                features = 'cxx cshlib',
                target = 'ProtolibJni',
                vnum = VERSION,
                uselib = 'JAVA',
                uselib_local = 'protokit',
                includes = JAVA,
                source = JAVA + '/protoPipeJni.cpp'
        )
                
        bld.new_task_gen(
                features='javac seq',
                srcdir=JAVA + '/src',
        )

        bld.new_task_gen(
                features='jar seq',
                basedir=JAVA + '/src',
                destfile='protolib-jni.jar',
        )

    if bld.env['build_ns3']:
        bld.new_task_gen(
                features = 'cxx cshlib',
                target = 'protokitNS3',
                includes = [INCLUDE, bld.env['NS3_HOME']+'/build/debug/'],
                uselib = ['NS3'],
                source = ['%s/%s.cpp' % (COMMON, x) for x in [
                    'protoSimAgent', 'protoSimSocket', 'protoAddress',
                    'protoTimer', 'protoDebug', 'protoTree', 'protoRouteTable',
                    'protoBitmask', 'protoTime']] + ['src/sim/ns3/ns3ProtoSimAgent.cpp']
        )

    # Build examples
    examples = []
    for ex in ['protoExample', 'vifExample', 'timerTest', 'threadExample',
            'pipeExample', 'protoCapExample', 'detourExample', 'graphExample',
            'msgExample', 'graphBuilder', 'graphRider', 'simpleTcpExample',
            'wxProtoExample']:
        if Options.options.ex_all or getattr(Options.options, 'ex_%s' % ex):
            examples.append(ex)

    if os.path.abspath(bld.env['PREFIX']) != os.path.abspath('./'):
        exampleBinPath = None
    else:
        exampleBinPath = '${PREFIX}/bin'
        
    for ex in examples:
        bld.new_task_gen(
                features='cxx cprogram',
                target=ex,
                uselib_local='protokit',
                source=EXAMPLES + '/%s.cpp' % ex,
                install_path=exampleBinPath
                )

@conf
def check_wx(self):
    '''Custom waf function that runs wx-configure to get correct flags for
    compilation and linking; then adds them to uselib.'''
    wx_config = self.find_program('wx-config', mandatory=True)
    proc = Popen([wx_config, '--cxxflags'], stdout=PIPE)
    proc.wait()
    self.env['CXXFLAGS_WX'] = proc.stdout.read().split()

    proc = Popen([wx_config, '--libs'], stdout=PIPE)
    proc.wait()
    self.env['LINKFLAGS_WX'] = proc.stdout.read().split()
