#! /usr/bin/env python
# encoding: utf-8

import platform
import sys
from distutils import sysconfig as _sysconfig

import Utils
import Options


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


# these variables are mandatory ('/' are converted automatically)
top = '.'
out = 'build'

# TODO extensive testing with different configuration (should work with Python >= 2.3)

def set_options(opt):
    myplatform = Utils.detect_platform()
    
    if myplatform.startswith('linux'):
        opt.add_option('--no-lsb',
                action = 'store_true',
                help = 'Prevent building LSB (Linux Standard Base) bootloader.',
                default = False,
                dest = 'nolsb')
        opt.add_option('--lsbcc-path',
                action = 'store',
                help = 'Path to lsbcc. By default PATH is searched for lsbcc otherwise is tried file /opt/lsb/bin/lsbcc. [Default: lsbcc]',
                default = 'lsbcc',
                dest = 'lsbcc_path')
        opt.add_option('--lsb-target-version',
                action = 'store',
                help = 'Specify LSB target version [Default: 4.0]',
                default = '4.0',
                dest = 'lsb_version')

    opt.tool_options('compiler_cc')
    opt.tool_options('python')



def configure(conf):

    def achitecture():
        return '32bit'

    conf.env.NAME = 'default'

    opt = Options.options
    myplatform = Utils.detect_platform()

    # on 64bit Mac function platform.architecture() returns 64bit even
    # for 32bit Python. This is the workaround for this.
    if myplatform == 'darwin' and sys.maxint <= 3**32:
        myarch = '32bit'
    else:
        myarch = platform.architecture()[0] # 32bit or 64bit
    conf.env.MYARCH = myarch

    Utils.pprint('CYAN', '%s-%s detected' % (platform.system(), myarch))

    if myplatform == 'darwin' and myarch == '64bit':
        Utils.pprint('CYAN', 'WARNING: Building bootloader for Python 64-bit on Mac OSX')
        Utils.pprint('CYAN', 'For 32b-bit bootloader prepend the python command with:')
        Utils.pprint('CYAN', 'VERSIONER_PYTHON_PREFER_32_BIT=yes arch -i386 python')

    if myplatform.startswith('linux') and not opt.nolsb:
        Utils.pprint('CYAN', 'Building LSB bootloader.')

    ### C compiler

    if myplatform.startswith('win'):
        try:
            # mingw (gcc for Windows)
            conf.check_tool('gcc')
        except:
            try:
                # Newest detected MSVC version is used by default.
                # msvc 7.1 (Visual Studio 2003)
                # msvc 8.0 (Visual Studio 2005)
                # msvc 9.0 (Visual Studio 2008)
                #conf.env['MSVC_VERSIONS'] = ['msvc 7.1', 'msvc 8.0', 'msvc 9.0']
                if myarch == '32bit':
                    conf.env['MSVC_TARGETS'] = ['x86', 'ia32']
                elif myarch == '64bit':
                    conf.env['MSVC_TARGETS'] = ['x64', 'x86_amd64', 'intel64', 'em64t']
                conf.check_tool('msvc')
                conf.print_all_msvc_detected()
                # Do not embed manifest file.
                # Manifest file will be added in the phase of packaging python application.
                conf.env.MSVC_MANIFEST = False
                # Path to Python development file pythonXY.lib is not detected correctly
                # by waf 1.5 on Windows in virtualenv.
                if _sysconfig.get_config_var('LIBDIR'):
                    conf.env.append_value('LINKFLAGS', '/LIBPATH:' + _sysconfig.get_config_var('LIBDIR'))
            except:
                Utils.pprint('RED', 'GCC (MinGW) or MSVC compiler not found.')
                exit(1)
        else:
            # When using GCC, deactivate declarations after statements
            # (not supported by Visual Studio)
            conf.env.append_value('CCFLAGS', '-Wdeclaration-after-statement')
            conf.env.append_value('CCFLAGS', '-Werror')

    else:
        conf.check_tool('compiler_cc')


    conf.check_tool('python')
    conf.check_python_headers()

    # Do not link with libpython and libpthread.
    for lib in conf.env.LIB_PYEMBED:
        if lib.startswith('python') or lib.startswith('pthread'):
            conf.env.LIB_PYEMBED.remove(lib)


    ### build LSB (Linux Standard Base) boot loader
    if myplatform.startswith('linux') and not opt.nolsb:
        try:
            # custom lsbcc path
            if opt.lsbcc_path != 'lsbcc':
                conf.env.LSBCC = conf.find_program(opt.lsbcc_path, mandatory=True)
            # default values
            else:
                conf.env.LSBCC = conf.find_program(opt.lsbcc_path)
                if not conf.env.LSBCC:
                    conf.env.LSBCC = conf.find_program('/opt/lsb/bin/lsbcc',
                            mandatory=True)
        except:
            Utils.pprint('RED', 'LSB (Linux Standard Base) tools >= 4.0 are required.')
            Utils.pprint('RED', 'Try --no-lsb option if not interested in building LSB binary.')
            exit(2)
        # lsbcc as CC compiler
        conf.env.append_value('CCFLAGS', '--lsb-cc=%s' % conf.env.CC[0])
        conf.env.append_value('LINKFLAGS', '--lsb-cc=%s' % conf.env.CC[0])
        conf.env.CC = conf.env.LSBCC
        conf.env.LINK_CC = conf.env.LSBCC
        ## check LSBCC flags
        # --lsb-besteffort - binary will work on platforms without LSB stuff
        # --lsb-besteffort - available in LSB build tools >= 4.0
        conf.check_cc(ccflags='--lsb-besteffort',
                msg='Checking for LSB build tools >= 4.0',
                errmsg='LSB >= 4.0 is required', mandatory=True)
        conf.env.append_value('CCFLAGS', '--lsb-besteffort')
        conf.env.append_value('LINKFLAGS', '--lsb-besteffort')
        # binary compatibility with a specific LSB version
        # LSB 4.0 can generate binaries compatible with 3.0, 3.1, 3.2, 4.0
        # however because of using function 'mkdtemp', loader requires
        # using target version 4.0
        lsb_target_flag = '--lsb-target-version=%s' % opt.lsb_version
        conf.env.append_value('CCFLAGS', lsb_target_flag)
        conf.env.append_value('LINKFLAGS', lsb_target_flag)


    ### Defines, Includes

    if myplatform.startswith('lin'):
        # make sure we don't use declarations after statement (break Visual Studio)
        conf.env.append_value('CCFLAGS', '-Wdeclaration-after-statement')
        conf.env.append_value('CCFLAGS', '-Werror')

    if myplatform.startswith('win'):
        conf.env.append_value('CCDEFINES', 'WIN32')
        conf.env.append_value('CPPPATH', '../zlib')

    if myplatform.startswith('sun'):
        conf.env.append_value('CCDEFINES', 'SUNOS')

    if myplatform.startswith('aix'):
        conf.env.append_value('CCDEFINES', 'AIX')

    conf.env.append_value('CPPPATH', '../common')


    ### Libraries

    if myplatform.startswith('win'):
        conf.check_cc(lib='user32', mandatory=True)
        conf.check_cc(lib='comctl32', mandatory=True)
        conf.check_cc(lib='kernel32', mandatory=True)
        conf.check_cc(lib='ws2_32', mandatory=True)

    else:
        conf.check_cc(lib='z', mandatory=True)
        if conf.check_cc(function_name='readlink', header_name='unistd.h'):
            conf.env.append_value('CCFLAGS', '-DHAVE_READLINK')


    ### ccflags

    if myplatform.startswith('win') and conf.env.CC_NAME == 'gcc':
        # Disables console - MinGW option
        conf.check_cc(ccflags='-mwindows', mandatory=True,
                msg='Checking for flags -mwindows')
        # Use Visual C++ compatible alignment
        conf.check_cc(ccflags='-mms-bitfields', mandatory=True,
                msg='Checking for flags -mms-bitfields')
        conf.env.append_value('CCFLAGS', '-mms-bitfields')

    elif myplatform.startswith('win') and conf.env.CC_NAME == 'msvc':
        if myarch == '32bit':
            conf.env.append_value('LINKFLAGS', '/MACHINE:X86')
        elif myarch == '64bit':
            conf.env.append_value('LINKFLAGS', '/MACHINE:X64')
            # enable 64bit porting warnings
            conf.env.append_value('CCFLAGS', '/Wp64')
        # We use SEH exceptions in winmain.c; make sure they are activated.
        conf.env.append_value('CCFLAGS', '/EHa')
    
    # Compile with 64bit gcc 32bit binaries or vice versa.
    if conf.env.CC_NAME == 'gcc':
        if myarch == '32bit' and conf.check_cc(ccflags='-m32', msg='Checking for flags -m32'):
            conf.env.append_value('CCFLAGS', '-m32')
        elif myarch == '64bit':
            conf.env.append_value('CCFLAGS', '-m64')

    # Ensure proper architecture flags on Mac OS X.
    # TODO Add support for universal binaries.
    if myplatform.startswith('darwin'):

        available_archs = {'32bit': 'i386', '64bit': 'x86_64'}
        mac_arch = available_archs[myarch]

        conf.env.append_value('CCFLAGS', '-arch')
        conf.env.append_value('CCFLAGS', mac_arch)
        conf.env.append_value('CXXFLAGS', '-arch')
        conf.env.append_value('CXXFLAGS', mac_arch)
        conf.env.append_value('LINKFLAGS', '-arch')
        conf.env.append_value('LINKFLAGS', mac_arch)

    # On linux link only with needed libraries.
    # -Wl,--as-needed is on some platforms detected during configure but
    # fails during build. (Mac OS X, Solaris, AIX)
    if myplatform.startswith('linux') and conf.check_cc(ccflags='-Wl,--as-needed',
            msg='Checking for flags -Wl,--as-needed'):
        conf.env.append_value('LINKFLAGS', '-Wl,--as-needed')


    ### Other stuff

    if myplatform.startswith('darwin'):
        conf.env.MACOSX_DEPLOYMENT_TARGET = '10.4'

    if myplatform.startswith('win'):
        # RC file - icon
        conf.check_tool('winres')

    ### DEBUG and RELEASE environments

    rel = conf.env.copy()
    dbg = conf.env.copy()
    rel.set_variant('release') # separate subfolder for building
    dbg.set_variant('debug') # separate subfolder for building
    rel.detach() # detach environment from default
    dbg.detach()

    ## setup DEBUG environment
    dbg.set_variant('debug') # separate subfolder for building
    conf.set_env_name('debug', dbg)
    conf.setenv('debug')
    conf.env.append_value('CCDEFINES', '_DEBUG LAUNCH_DEBUG'.split())
    conf.env.append_value('CCFLAGS', conf.env.CCFLAGS_DEBUG)
    dbgw = conf.env.copy() # WINDOWED DEBUG environment
    dbgw.set_variant('debugw') # separate subfolder for building
    dbgw.detach()

    ## setup windowed DEBUG environment
    conf.set_env_name('debugw', dbgw)
    conf.setenv('debugw')
    conf.env.append_value('CCDEFINES', 'WINDOWED')
    # disables console - mingw option
    if myplatform.startswith('win') and conf.env.CC_NAME == 'gcc':
            conf.env.append_value('LINKFLAGS', '-mwindows')
    elif myplatform.startswith('darwin'):
        conf.env.append_value('CCFLAGS', '-I/Developer/Headers/FlatCarbon')
        conf.env.append_value('LINKFLAGS', '-framework')
        conf.env.append_value('LINKFLAGS', 'Carbon')


    ## setup RELEASE environment
    conf.set_env_name('release', rel)
    conf.setenv('release')
    conf.env.append_value('CCDEFINES', 'NDEBUG')
    conf.env.append_value('CCFLAGS', conf.env.CCFLAGS_RELEASE)
    relw = conf.env.copy() # WINDOWED RELEASE environment
    relw.set_variant('releasew') # separate subfolder for building
    relw.detach()

    ## setup windowed RELEASE environment
    conf.set_env_name('releasew', relw)
    conf.setenv('releasew')
    conf.env.append_value('CCDEFINES', 'WINDOWED')
    # disables console
    if myplatform.startswith('win') and conf.env.CC_NAME == 'gcc':
            conf.env.append_value('LINKFLAGS', '-mwindows')
    elif myplatform.startswith('darwin'):
        conf.env.append_value('LINKFLAGS', '-framework')
        conf.env.append_value('LINKFLAGS', 'ApplicationServices')


def build(bld):
    # Force to run with 1 job (no parallel building).
    # There are reported build failures on multicore CPUs
    # with some msvc versions.
    Options.options.jobs = 1

    myplatform = Utils.detect_platform()
    install_path = '../../support/loader/' + platform.system() + "-" + bld.env.MYARCH
    targets = dict(release='run', debug='run_d', releasew='runw', debugw='runw_d')

    if myplatform.startswith('win'):

        # static lib zlib

        for key in targets.keys():
            bld(
                features = 'cc cstaticlib',
                source = bld.path.ant_glob('zlib/*.c'),
                target = 'staticlib_zlib',
                env = bld.env_of_name(key),
            )

        # console

        for key in ('release', 'debug'):
            bld(
                features = 'cc cprogram pyembed',
                source = bld.path.ant_glob('windows/utils.c windows/run.rc common/*.c'),
                target = targets[key],
                install_path = install_path,
                uselib_local = 'staticlib_zlib',
                uselib = 'USER32 COMCTL32 KERNEL32 WS2_32',
                env = bld.env_of_name(key).copy(),
            )

        # windowed

        for key in ('releasew', 'debugw'):
            bld(
                features = 'cc cprogram pyembed',
                source = bld.path.ant_glob('windows/utils.c windows/runw.rc common/*.c'), # uses different RC file (icon)
                target = targets[key],
                install_path = install_path,
                uselib_local = 'staticlib_zlib',
                uselib = 'USER32 COMCTL32 KERNEL32 WS2_32',
                env = bld.env_of_name(key).copy(),
            )

        ## inprocsrvr console

        if bld.env.CC_NAME == 'msvc':
            linkflags_c = bld.env.CPPFLAGS_CONSOLE
            linkflags_w = bld.env.CPPFLAGS_WINDOWS
        else:
            linkflags_c = ''
            linkflags_w = ''

        for key, value in dict(release='inprocsrvr', debug='inprocsrvr_d').items():
            bld(
                features = 'cc cshlib',
                source = bld.path.ant_glob('common/launch.c windows/dllmain.c'),
                target = value,
                install_path = install_path,
                uselib_local = 'staticlib_zlib',
                uselib = 'USER32 COMCTL32 KERNEL32 WS2_32',
                env = bld.env_of_name(key).copy(),
                linkflags = linkflags_c
            )

        ## inprocsrvr windowed

        for key, value in dict(releasew='inprocsrvrw', debugw='inprocsrvrw_d').items():
            bld(
                features = 'cc cshlib',
                source = bld.path.ant_glob('common/launch.c windows/dllmain.c'),
                target = value,
                install_path = install_path,
                uselib_local = 'staticlib_zlib',
                uselib = 'USER32 COMCTL32 KERNEL32 WS2_32',
                env = bld.env_of_name(key).copy(),
                linkflags = linkflags_w
            )

    else: # linux, darwin (MacOSX)

        for key, value in targets.items():
            bld(
                features = 'cc cprogram pyembed',
                source = bld.path.ant_glob('linux/*.c common/*.c'),
                target = value,
                install_path = install_path,
                uselib = 'Z', # zlib
                env = bld.env_of_name(key).copy(),
            )
