import os, sys, re, pdb
import distutils.sysconfig as sysconfig
import distutils.util
import platform
import SCons.SConf as SConf
import config

# taken from scons wiki
def CheckPKGConfig(context, version):
    context.Message( 'Checking for pkg-config version > %s... ' % version)
    ret = context.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
    context.Result( ret )
    return ret

def CheckFramework(context, name):
    ret = 0
    if (platform.system().lower() == 'darwin'):
        context.Message( '\nLooking for framework %s... ' % name )
        lastFRAMEWORKS = context.env['FRAMEWORKS']
        context.env.Append(FRAMEWORKS = [name])
        ret = context.TryLink("""
              int main(int argc, char **argv) {
                return 0;
              }
              """, '.c')
        if not ret:
            context.env.Replace(FRAMEWORKS = lastFRAMEWORKS
)

    return ret

def CheckFink(context):
    context.Message( 'Looking for fink... ')
    prog = context.env.WhereIs('fink')
    if prog:
        ret = 1
        prefix = prog.rsplit(os.sep, 2)[0]
        context.env.Append(LIBPATH = [prefix + os.sep +'lib'],
                           CPPPATH = [prefix + os.sep +'include'])
        context.Message( 'Adding fink lib and include path')
    else:
        ret = 0
        
    context.Result(ret)    
    return int(ret)

def CheckMacports(context):
    context.Message( 'Looking for macports... ')
    prog = context.env.WhereIs('port')
    if prog:
        ret = 1
        prefix = prog.rsplit(os.sep, 2)[0]
        context.env.Append(LIBPATH = [prefix + os.sep + 'lib'],
                           CPPPATH = [prefix + os.sep + 'include'])
        context.Message( 'Adding port lib and include path')
    else:
        ret = 0
        
    context.Result(ret)    
    return int(ret)

# TODO: We should use the scons one instead
def CheckLib(context, name):
    context.Message( 'Looking for lib %s... ' % name )
    lastLIBS = context.env['LIBS']
    context.env.Append(LIBS = [name])
    ret = context.TryLink("""
              int main(int argc, char **argv) {
                return 0;
              }
              """,'.c')
    if not ret:
        context.env.Replace(LIBS = lastLIBS)

    return ret

def ConfigPKG(context, name):
    context.Message( '\nUsing pkg-config for %s... ' % name )
    ret = context.TryAction('pkg-config --exists \'%s\'' % name)[0]
    context.Result(  ret )
    if ret:
        context.env.ParseConfig('pkg-config --cflags --libs \'%s\'' % name)
    return int(ret)

def CheckPKG(context, name):
    context.Message( 'Checking for %s... ' % name )
    if platform.system().lower() == 'windows':
        return 0 
    ret = 1
    if not CheckFramework(context, name):
        if not ConfigPKG(context, name.lower()):
            ret = CheckLib(context, name) 

    context.Result(ret)
    return int(ret)


## Configure colors for pretty builds
colors = {}
colors['cyan']   = '\033[96m'
colors['purple'] = '\033[95m'
colors['blue']   = '\033[94m'
colors['green']  = '\033[92m'
colors['yellow'] = '\033[93m'
colors['red']    = '\033[91m'
colors['end']    = '\033[0m'

#If the output is not a terminal, remove the colors
if not sys.stdout.isatty():
   for key, value in colors.iteritems():
      colors[key] = ''

def error(msg):
   print "%s%s%s" % (colors['red'], msg, colors['end'])
   sys.exit(1)

def warn(msg):
   print "%s%s%s" % (colors['yellow'], msg, colors['end'])

compile_source_message = '%sCompiling %s==> %s$SOURCE%s' % \
   (colors['blue'], colors['purple'], colors['yellow'], colors['end'])

compile_shared_source_message = '%sCompiling shared %s==> %s$SOURCE%s' % \
   (colors['blue'], colors['purple'], colors['yellow'], colors['end'])

compile_python_source_message = '%sCompiling python module %s==> %s$SOURCE%s' % \
   (colors['blue'], colors['purple'], colors['yellow'], colors['end'])

link_program_message = '%sLinking Program %s==> %s$TARGET%s' % \
   (colors['red'], colors['purple'], colors['yellow'], colors['end'])

link_library_message = '%sLinking Static Library %s==> %s$TARGET%s' % \
   (colors['red'], colors['purple'], colors['yellow'], colors['end'])

ranlib_library_message = '%sRanlib Library %s==> %s$TARGET%s' % \
   (colors['red'], colors['purple'], colors['yellow'], colors['end'])

link_shared_library_message = '%sLinking Shared Library %s==> %s$TARGET%s' % \
   (colors['red'], colors['purple'], colors['yellow'], colors['end'])

link_python_module_message = '%sLinking Native Python module %s==> %s${TARGET}%s' % \
   (colors['red'], colors['purple'], colors['yellow'], colors['end'])

java_library_message = '%sCreating Java Archive %s==> %s$TARGET%s' % \
   (colors['red'], colors['purple'], colors['yellow'], colors['end'])

def install_colors(args):
    """ Installs colors into an environment """
    args.update(dict( CXXCOMSTR = compile_source_message,
                      CCCOMSTR = compile_source_message,
                      SHCCCOMSTR = compile_shared_source_message,
                      SHCXXCOMSTR = compile_shared_source_message,
                      ARCOMSTR = link_library_message,
                      RANLIBCOMSTR = ranlib_library_message,
                      SHLINKCOMSTR = link_shared_library_message,
                      LINKCOMSTR = link_program_message,
                      JARCOMSTR = java_library_message,
                      JAVACCOMSTR = compile_source_message,))

import optparse

### This workaround is because scons does not provide access to the
### parser, and by setting Help() we are unable to generate the option
### listing from AddOption
my_parser = optparse.OptionParser()

import SCons.Script.Main as Main
import SCons.Script as Script

def add_option(arg, option, *args, **kwargs):
    opt = "--%s" % option
    Main.AddOption(opt, *args, **kwargs)
    my_parser.add_option(opt, *args, **kwargs)

    arg[option] = Main.GetOption(option)

def generate_help(vars, env):
    Script.Help("AFF4 build system configuration.\n\nFollowing are compile time options:\n")
    Script.Help(my_parser.format_help())
    Script.Help("\nThe following variables can be used on the command line:\n")
    Script.Help(vars.GenerateHelpText(env))

HEADERS = {}

def check_size(conf, types):
    global _DEFAULTS

    for t in types:
        name = "SIZEOF_" + t.replace(" ","_").upper()
        HEADERS[name] = conf.CheckTypeSize(
            t, size = _DEFAULTS[t][0])

def check_type(conf, types):
    header = None
    for t in types:
        if ':' in t:
            t, header = t.split(':')
        define = "HAVE_" + t.upper().replace(".","_")

        result = 0
        if conf.CheckType(t, includes="#include <%s>\n" % header):
            result = 1

        HEADERS[define] = result

def check_build(conf, message, define, prog):
    """ Build and links prog and adds define if that succeeds """
    context = SConf.CheckContext(conf)
    context.Message("Checking for %s ..." % message)
    if context.TryLink(prog, ".c"):
        HEADERS[define] = 1
        context.Message("yes\n")
    else:
        context.Message("no\n")

def check(type, conf, headers, extra_include =''):
    for header in headers:
        if ":" in header:
            define, header = header.split(':')
        else:
            if "/" in header:
                tmp = header.split("/")[-1]
            else:
                tmp = header

            define = "HAVE_" + tmp.upper().replace(".","_")

        global HEADERS
        result = 0
        if type == 'header':
            #pdb.set_trace()
            if conf.CheckCHeader(header): result = 1
            HEADERS[define] = result
        elif type == 'func':
            if conf.CheckFunc(header, header=extra_include): result = 1
            HEADERS[define] = result
        elif type == 'lib':
            if conf.CheckLib(header): result =1
            HEADERS[define] = result

## Build the config.h file
def config_h_build(target, source, env):
    config_h_defines = env.Dictionary()
    config_h_defines.update(env.config.__dict__)
    warn("Generating %s" % (target[0].path))

    for a_target, a_source in zip(target, source):
        config_h = file(str(a_target), "w")
        config_h_in = file(str(a_source), "r")
        config_h.write(config_h_in.read() % config_h_defines)
        config_h_in.close()

    keys = HEADERS.keys()
    keys.sort()

    for k in keys:
        if HEADERS[k]:
            config_h.write("#define %s %s\n" % (k,HEADERS[k]))
        else:
            config_h.write("/** %s unset */\n" % k)

    config_h.close()

import SCons.Environment

class ExtendedEnvironment(SCons.Environment.Environment):
    """ Implementation from Richard Levitte email to
    org.tigris.scons.dev dated Jan 26, 2006 7:05:10 am."""
    python_cppflags = "-I"+sysconfig.get_python_inc()

    def PythonModule(self, libname, lib_objs=[], **kwargs):
        """ This builds a python module which is almost a library but
        is sometimes named differently.

        We have two modes - a cross compile mode where we do our best
        to guess the flags. In the native mode we can get the required
        flags directly from distutils.
        """
        if config.MINGW_XCOMPILE:
            shlib_suffix = ".pyd"
            cppflags = "-I%s" % config.XCOMPILE_PYTHON_PATH
            shlink_flags = ['']

        else:
            platform = self.subst('$PLATFORM')
            shlib_pre_action = None
            shlib_suffix = distutils.util.split_quoted(
                sysconfig.get_config_var('SO'))
            shlib_post_action = None
            cppflags = distutils.util.split_quoted(self.python_cppflags)
            shlink_flags = str(self['LINKFLAGS']).split()

        install_dest = distutils.util.split_quoted(
            os.path.join(
                sysconfig.get_config_var('BINLIBDEST'),os.path.dirname(libname)))

        flags = distutils.util.split_quoted(
            sysconfig.get_config_var('LDSHARED'))

        ## For some stupid reason they include the compiler in LDSHARED
        shlink_flags.extend([x for x in flags if 'gcc' not in x])

        shlink_flags.append(sysconfig.get_config_var('LOCALMODLIBS'))

        ## TODO cross compile mode
        kwargs['LIBPREFIX'] = ''
        kwargs['CPPFLAGS'] = cppflags
        kwargs['SHLIBSUFFIX'] = shlib_suffix
        kwargs['SHLINKFLAGS'] = shlink_flags

        if not self.config.V:
            kwargs['SHCCCOMSTR'] = compile_python_source_message
            kwargs['SHLINKCOMSTR'] = link_python_module_message

        lib = self.SharedLibrary(libname,lib_objs,
                                 **kwargs)

        ## Install it to the right spot
        self.Install(install_dest, lib)
        self.Alias('install', install_dest)

        return lib

    def VersionedSharedLibrary(self, libname, libversion, lib_objs=[]):
        """ This creates a version library similar to libtool.

        We name the library with the appropriate soname.
        """
        platform = self.subst('$PLATFORM')
        shlib_pre_action = None
        shlib_suffix = self.subst('$SHLIBSUFFIX')
        shlib_post_action = None
        shlink_flags = SCons.Util.CLVar(self.subst('$SHLINKFLAGS'))

        if platform == 'posix':
            shlib_post_action = [ 'rm -f $TARGET', 'ln -s ${SOURCE.file} $TARGET' ]
            shlib_post_action_output_re = [ '%s\\.[0-9\\.]*$' % re.escape(shlib_suffix), shlib_suffix ]
            shlib_suffix += '.' + libversion
            shlink_flags += [ '-Wl,-Bsymbolic', '-Wl,-soname=${LIBPREFIX}%s%s' % (
                    libname, shlib_suffix) ]
        elif platform == 'aix':
            shlib_pre_action = [ "nm -Pg $SOURCES > ${TARGET}.tmp1", "grep ' [BDT] ' < ${TARGET}.tmp1 > ${TARGET}.tmp2", "cut -f1 -d' ' < ${TARGET}.tmp2 > ${TARGET}", "rm -f ${TARGET}.tmp[12]" ]
            shlib_pre_action_output_re = [ '$', '.exp' ]
            shlib_post_action = [ 'rm -f $TARGET', 'ln -s $SOURCE $TARGET' ]
            shlib_post_action_output_re = [ '%s\\.[0-9\\.]*' % re.escape(shlib_suffix), shlib_suffix ]
            shlib_suffix += '.' + libversion
            shlink_flags += ['-G', '-bE:${TARGET}.exp', '-bM:SRE']
        elif platform == 'cygwin':
            shlink_flags += [ '-Wl,-Bsymbolic', '-Wl,--out-implib,${TARGET.base}.a' ]
        elif platform == 'darwin':
            shlib_suffix = '.' + libversion + shlib_suffix
            shlink_flags += [ '-dynamiclib', '-current-version %s' % libversion ]

        lib = self.SharedLibrary(libname,lib_objs,
                                 SHLIBSUFFIX=shlib_suffix,
                                 SHLINKFLAGS=shlink_flags)

        if shlib_pre_action:
            shlib_pre_action_output = re.sub(shlib_pre_action_output_re[0], shlib_pre_action_output_re[1], str(lib[0]))
            self.Command(shlib_pre_action_output, [ lib_objs ], shlib_pre_action)
            self.Depends(lib, shlib_pre_action_output)

        if shlib_post_action:
            shlib_post_action_output = re.sub(shlib_post_action_output_re[0], shlib_post_action_output_re[1], str(lib[0]))
            self.Command(shlib_post_action_output, lib, shlib_post_action)

        return lib

    def InstallVersionedSharedLibrary(self, destination, lib):
        platform = self.subst('$PLATFORM')
        shlib_suffix = self.subst('$SHLIBSUFFIX')
        shlib_install_pre_action = None
        shlib_install_post_action = None

        if platform == 'posix':
            shlib_post_action = [ 'rm -f $TARGET', 'ln -s ${SOURCE.file} $TARGET' ]
            shlib_post_action_output_re = [ '%s\\.[0-9\\.]*$' % re.escape(shlib_suffix), shlib_suffix ]
            shlib_install_post_action = shlib_post_action
            shlib_install_post_action_output_re = shlib_post_action_output_re

        ilib = self.Install(destination,lib)

        if shlib_install_pre_action:
            shlib_install_pre_action_output = re.sub(shlib_install_pre_action_output_re[0], shlib_install_pre_action_output_re[1], str(ilib[0]))
            self.Command(shlib_install_pre_action_output, ilib, shlib_install_pre_action)
            self.Depends(shlib_install_pre_action_output, ilib)

        if shlib_install_post_action:
            shlib_install_post_action_output = re.sub(shlib_install_post_action_output_re[0], shlib_install_post_action_output_re[1], str(ilib[0]))
            self.Command(shlib_install_post_action_output, ilib, shlib_install_post_action)


import subprocess

def pkg_config(pkg, type):
    try:
        result = subprocess.Popen(["%s-config" % pkg, "--%s" % type],
                                  stdout=subprocess.PIPE).communicate()[0]
    except:
        error("Unable to run %s-config - do you have the dev package installed?" % pkg)

    return result.strip()


# Sensible default for common types on common platforms.
_DEFAULTS = {
    'char': [1,],
    'short' : [2,],
    'int' : [4, 2],
    'long' : [4, 8],
    'long long' : [8, 4],
    # Normally, there is no need to check unsigned types, because they are
    # guaranteed to be of the same size than their signed counterpart.
    'unsigned char': [1,],
    'unsigned short' : [2,],
    'unsigned int' : [4, 2],
    'unsigned long' : [4, 8],
    'unsigned long long' : [8, 4],
    'float' : [4,],
    'double' : [8,],
    'long double' : [12,],
    'size_t' : [4,],
}

def CheckTypeSize(context, type, includes = None, language = 'C', size = None):
    """This check can be used to get the size of a given type, or to check whether
    the type is of expected size.

    Arguments:
        - type : str
            the type to check
        - includes : sequence
            list of headers to include in the test code before testing the type
        - language : str
            'C' or 'C++'
        - size : int
            if given, will test wether the type has the given number of bytes.
            If not given, will test against a list of sizes (all sizes between
            0 and 16 bytes are tested).

        Returns:
                status : int
                        0 if the check failed, or the found size of the type if the check succeeded."""
    minsz = 0
    maxsz = 16

    if includes:
        src = "\n".join([r"#include <%s>\n" % i for i in includes])
    else:
        src = ""

    if language == 'C':
        ext = '.c'
    elif language == 'C++':
        ext = '.cpp'
    else:
        raise NotImplementedError("%s is not a recognized language" % language)

    # test code taken from autoconf: this is a pretty clever hack to find that
    # a type is of a given size using only compilation. This speeds things up
    # quite a bit compared to straightforward code using TryRun
    src += r"""
typedef %s scons_check_type;

int main()
{
    static int test_array[1 - 2 * !(((long int) (sizeof(scons_check_type))) <= %d)];
    test_array[0] = 0;

    return 0;
}
"""

    if size:
        # Only check if the given size is the right one
        context.Message('Checking %s is %d bytes... ' % (type, size))
        st = context.TryCompile(src % (type, size), ext)
        context.Result(st)

        if st:
            return size
        else:
            return 0
    else:
        # Only check if the given size is the right one
        context.Message('Checking size of %s ... ' % type)

        # Try sensible defaults first
        try:
            szrange = _DEFAULTS[type]
        except KeyError:
            szrange = []
        szrange.extend(xrange(minsz, maxsz))
        st = 0

        # Actual test
        for sz in szrange:
            st = context.TryCompile(src % (type, sz), ext)
            if st:
                break

        if st:
            context.Result('%d' % sz)
            return sz
        else:
            context.Result('Failed !')
            return 0

#For example, to check wether long is 4 bytes on your platform, you can do:
#config.CheckTypeSize('long', size = 4).
## Now check the sizes
