# -*- coding: utf-8 -*-
import os.path, sys, re

# exceptions
from simula_scons.Errors import PkgconfigError, PkgconfigMissing, CommandError, _configError

# import the local 'scons'
import simula_scons as scons

# Here, the main program will be written. The logic must be _visible_, and the
# code should be short and simple.

# import the default env and configure objects created in SConstruct
if "configure" in COMMAND_LINE_TARGETS:
    Import("env", "buildDataHash")
else:
    Import("env", "modules", "configuredPackages", "buildDataHash")

# Set the default env in our local scons utility module.
scons.setDefaultEnv(env)

python_version = "python-%d" % (sys.version_info[0])

# Add non-standard paths with pkg-config
#configuredPackages = {}

if "configure" in COMMAND_LINE_TARGETS:
    # Make sure pkg-config is installed:
    print "Checking for pkg-config...",
    try:
        scons.runCommand("pkg-config", ["--version"])
        print "yes"
    except:
        print "no"
        print " Please install pkg-config"
        Exit()

    # determine which packages that should be disabled:
    disabled_packages = []
    if not env["enablePetsc"]:
        disabled_packages.append("petsc")
        disabled_packages.append("slepc")
    if not env["enableSlepc"]:
        disabled_packages.append("slepc")
    if not env["enableScotch"]:
        disabled_packages.append("scotch")
    if not env["enableUmfpack"]:
        disabled_packages.append("umfpack")
    if not env["enableTrilinos"]:
        disabled_packages.append("trilinos")
    if not env["enableCholmod"]:
        disabled_packages.append("cholmod")
    if not env["enableMtl4"]:
        disabled_packages.append("mtl4")
    if not env["enableParmetis"]:
        disabled_packages.append("parmetis")
    if not env["enableGmp"]:
        disabled_packages.append("gmp")
    if not env["enableZlib"]:
        disabled_packages.append("zlib")
    if not env["enableCgal"]:
        disabled_packages.append("cgal")
    if not env["enableLapack"]:
        disabled_packages.append("lapack")

    # Find modules in the project and dependencies:
    # only modules and configuredPackages will be used in this code:
    modules, dependencies, alldependencies, configuredPackages, packcfgObjs = \
        scons.getModulesAndDependencies(disabled_packages=disabled_packages)

    # we will not use the 'dependencies' and 'alldependencies' here.
    # TODO: Consider removing it from the API.
    dependencies = None
    alldependencies = None
    # See if dependencies can be resolved with PkgConfig.
    # for d in alldependencies.keys():
    #    try:
    #       packcfg = scons.pkgconfig.PkgConfig(d, env=env)
    #       configuredPackages[d] = scons.Customize.Dependency(cppPath=packcfg.includeDirs(), \
    #                               libPath=packcfg.libDirs(), libs=packcfg.libs(),\
    #                               version=packcfg.version())
    #    except:
    #       pass

    # resolve modules according to detected dependencies.
    # if a dependency can not be found, the module will not be built:
    #modules = scons.resolveModuleDependencies(modules, configuredPackages)
    modules, env = scons.resolveModuleDependencies(modules, configuredPackages, packcfgObjs, sconsEnv=env)

    if env["PLATFORM"] == "darwin":
        env = scons.Customize.darwinCxx(env)
    elif env["PLATFORM"].startswith("win"):
        env = scons.Customize.winCxx(env)

    if env["enablePython"]:
        # Make sure we can find both SWIG and Python header files (Python.h):
        try:
            out, err = scons.runCommand("swig", "-version")
        except Exception, err:
            print "*** Unable to find SWIG."
            print "*** Install SWIG or disable Python wrappers with enablePython=no"
            Exit(1)

        # Determine swig version
        swig_version = re.match(r"SWIG Version (.+)", out, re.M).group(1).strip()

        # Try to import ufc
        try:
            import ufc
        except:
            print "*** Unable to import UFC. Install latest UFC"
            Exit(1)

        # Check version of swig UFC was compiled with
        try:
            assert [int(s) for s in ufc.__swigversion__.split('.')] == \
                [int (s) for s in swig_version.split('.')]
        except AttributeError:
            print """*** Old UFC installation detected. Install latest UFC."""
            Exit(1)
        except AssertionError:
            print """*** UFC compiled with different version of SWIG.
    Please install SWIG version %s or recompile UFC with present SWIG.""" % \
            ufc.__swigversion__
            Exit(1)

        # Check for python development files
        py_ver = "%s.%s" % (sys.version_info[0],sys.version_info[1])
        if env["PLATFORM"].startswith("win"):
            py_inc = os.path.join(sys.prefix,"include")
        else:
            py_inc = os.path.join(sys.prefix,"include","python"+py_ver)
        if not os.path.isfile(os.path.join(py_inc, "Python.h")):
            print "*** Unable to find Python development files on your system."
            print "*** Perhaps you need to install the package python%s-dev?" % py_ver
            Exit(1)
        print "Enabling compilation of Python wrappers"
    else:
        print "Disabling compilation of Python wrappers"

# Using the SWIG option '-dirvtable' causes leaks with director call-backs
if env["enablePython"]:
    #swigFlags = "-python -c++ -shadow -O -I%s" % os.path.join("include","swig")
    #swigFlags = "-python -c++ -shadow -modern -modernargs -fastdispatch -fvirtual -dirvtable -nosafecstrings -noproxydel \
    #             -fastproxy -fastinit -fastunpack -fastquery -nobuildnone -I%s" % os.path.join("include","swig")
    swigFlags = "-python -c++ -shadow -modern -modernargs -fastdispatch \
                 -fvirtual -nosafecstrings -noproxydel -fastproxy -fastinit \
                 -fastunpack -fastquery -nobuildnone -I%s" % os.path.join("include","swig")
    swigFlags = swigFlags.split()

    # This swig environment
    swigEnv = env.Clone()
    swigEnv["SWIGFLAGS"] = swigFlags
    swigEnv["SHLIBPREFIX"] = "_"
    if env["PLATFORM"] == "darwin":
        swigEnv = scons.Customize.darwinSwig(swigEnv)
    elif env["PLATFORM"].startswith("win"):
        swigEnv = scons.Customize.winSwig(swigEnv)

    cFileBldr = swigEnv["BUILDERS"]["CFile"]
    cxxFileBldr = swigEnv["BUILDERS"]["CXXFile"]

    for bldr in cFileBldr, cxxFileBldr:
        bldr.add_emitter(".i", scons.Customize.swigEmitter)

    swigEnv.Prepend(SCANNERS=scons.Customize.swigscanner)

#### XXX
#### Messy code
#### TODO
#### Fix and move to a better location.
####
# build rpath for paths for all modules, stored in the Module objects.
#
# Used for building test-binaries (aka AppSources)
rpathdirs=[Dir("#%s/%s" % (env["projectname"],m.path)).abspath  for m in modules.values()]
#### End TODO

for modName, mod in modules.items():
    libs, libPath, compileOpts, linkOpts, cppPath, frameworks = [], [], [], [], [], []
    swiglibs, swigframeworks, swiglinkOpts, swiglibPath, swigcppPath = [], [], [], [], []

    for d in mod.dependencies:
        if d in modules:
            # Internal dependency
            libs.append(d)
            libPath.insert(0, modules[d].path)
        elif d in configuredPackages:
            # External (configured) dependency
            dep = configuredPackages[d]
            libs += dep.libs[0]          # The libs
            frameworks += list(dep.libs[1])    # The frameworks (Darwin)
            libPath  += dep.libPath
            compileOpts += dep.compileOpts
            linkOpts += dep.linkOpts
            cppPath  += dep.cppPath

            # on Darwin, automatically add all regular external deps to
            # swig deps. Strictly speaking, it is only required if the
            # external dep consist of shared libraries.
            scons.addToDependencies(mod.swigDependencies,d)

    for d in mod.swigDependencies:
        if d in modules:
            # not sure what to do with that... Or maybe not relevant
            print "Internal module %s as swigDependency in module %s is undefined" % (modules[d].modName,modName)
        elif d in configuredPackages:
            swigdep = configuredPackages[d]
            swiglibs += swigdep.libs[0]
            swigframeworks += list(swigdep.libs[1])
            swiglibPath += swigdep.libPath
            swigcppPath += swigdep.cppPath

    if mod.libSources:
        # Register shared library targets in the module

        modEnv = env.Clone(CXXFLAGS=mod.cxxFlags, LINKFLAGS=mod.linkFlags)
        # Prepend the CPPPATH so that directories containing headers are searched before those
        # they are to be installed into, otherwise SCons might think it necessary to install
        # them first

        # Add flags for code coverage
        if env["enableCodeCoverage"]:
            modEnv.Append(CPPFLAGS=' -fprofile-arcs -ftest-coverage',
                          LINKFLAGS=' -fprofile-arcs -ftest-coverage',
                          LIBS=['gcov'])

        modEnv.Prepend(CPPPATH=[Dir("#").abspath] + cppPath, LIBPATH=libPath)
        modEnv.Append(LIBS=libs)
        modEnv.Append(CXXFLAGS=compileOpts)
        modEnv.Append(LINKFLAGS=linkOpts)
        if env["PLATFORM"] == "darwin":
            modEnv.Append(FRAMEWORKS=frameworks)
        shlib = modEnv.VersionedSharedLibrary(os.path.join(mod.path, modName),
                env["PACKAGE_VERSION"],
                [os.path.join(mod.path, s) for s in mod.libSources])
        # The builder returns a node list
        buildDataHash["shlibs"].append(shlib[0])

    if mod.swigSources and env["enablePython"]:
        # Register swig wrapper targets in the module
        mod.cxxFlags += ["-fno-strict-aliasing"]

        modEnv = swigEnv.Clone(CXXFLAGS=mod.cxxFlags + compileOpts,
                               LINKFLAGS=mod.linkFlags + linkOpts)
        modEnv.Append(SWIGFLAGS=mod.swigFlags + ["-I%s" % i for i in swigcppPath])

        # Add python dependency (always required in python wrappers)
        pyPkg = configuredPackages[python_version]
        cppPath += pyPkg.cppPath + swigcppPath
        libPath += pyPkg.libPath + swiglibPath
        libs += pyPkg.libs[0] + swiglibs
        frameworks += pyPkg.libs[1] + swigframeworks

        # Add the directories of the module and its dependencies to swig's include path, so
        # that it can resolve included headers
        modEnv.Prepend(CPPPATH=[Dir("#").abspath] + cppPath, \
                LIBPATH=[mod.path] + swiglibPath, LIBS=swiglibs)

        if mod.libSources:
            modEnv.Append(LIBS=modName)
        else:
            # This is a standalone swig'ed module
            modEnv.Append(LIBPATH=libPath, LIBS=libs)
        if env["PLATFORM"] == "darwin":
            modEnv.Append(FRAMEWORKS=frameworks)

        # Setting the name of the extension module of to "cpp", not modName
        swigModName = "cpp"
        # FIXME: What happens if scons.cfg defines several source files? Shouldn't we
        # have several target files then? Or do we asssume only one CXX file is built?
        wrapCxx, wrapPy = modEnv.CXXFile(target=os.path.join(mod.path, "swig", swigModName), source=[os.path.join(mod.path, s) for s in mod.swigSources])
        swig = modEnv.SharedLibrary(target=os.path.join(mod.path, "swig", swigModName), source=wrapCxx)[0]
        # The builder returns a node list
        buildDataHash["extModules"].append(swig)
        buildDataHash["pythonModules"].append(wrapPy)

    for progName, progSources in mod.progSources.items():
        # Register program targets in the module

        modEnv = env.Clone(CXXFLAGS=mod.cxxFlags, LINKFLAGS=mod.linkFlags)

        # Prepend the CPPPATH so that directories containing headers are searched before those
        # they are to be installed into, otherwise SCons might think it necessary to install
        # them first

        # disable building of apps' on macosx as those only cause problems...
        #if env["PLATFORM"] == "darwin":
        #    continue

        modEnv.Prepend(CPPPATH=[Dir("#").abspath] + cppPath)
        if mod.libSources and env["PLATFORM"] != "darwin":
            # Link against module library if defined
            modEnv.Append(LIBS=modName, LIBPATH=mod.path, RPATH=rpathdirs)
        elif mod.libSources and env["PLATFORM"] == "darwin":
            # Do not use rpath on darwin, as it doesn't work
            modEnv.Append(LIBS=[modName] + libs, LIBPATH=[mod.path] + libPath)
            modEnv.Append(FRAMEWORKS=frameworks)

        prog = modEnv.Program(target=os.path.join(mod.path, progName), source=\
                [os.path.join(mod.path, s) for s in progSources])
        # The builder returns a node list
        buildDataHash["progs"].append(prog[0])

    # Register headers to be installed for the module
    buildDataHash["headers"] += [File(h, mod.path) for h in mod.libHeaders]
    ## Need to detect the 'dolfin.h' header file, and treat differently
    #for h in mod.libHeaders:
    #    if h.endswith("dolfin.h"):
    #        buildDataHash["main_header"] = File(h, mod.path)
# The 'dolfin.h' header file is treated differently
buildDataHash["dolfin_header"] = File("dolfin.h", '.')

# set pythonScripts we want to install
buildDataHash["pythonScripts"] += scons.globFiles(Dir("#/app").srcnode().abspath, "*.py")
# set python packagedirs we want to install (in site-packages)
buildDataHash["pythonPackageDirs"] += [Dir("#site-packages")]

if env["enablePython"]:
    # set SWIG interface files we want to install
    buildDataHash["swigfiles"] += scons.globFiles(Dir("#/%s/swig" % env["projectname"]).srcnode().abspath, "*.i")
    buildDataHash["swigfiles"] += scons.globFiles(Dir("#/%s/swig/import" % env["projectname"]).srcnode().abspath, "*.i")

# read SConscript in data.
data = env.SConscript(os.path.join("#data", "SConscript"), exports=["env"])
# set information about data we want to install
buildDataHash["data"].extend([File(os.path.join("#data", f)) for f in data])

## read SConscript for tests
#tests = env.SConscript(os.path.join("#test", "SConscript"), exports=["env", "modules", "configuredPackages"])
#buildDataHash["tests"].extend([File(os.path.join("#test", f)) for f in tests["pytests"]])
## join the cpptests with regular progs.
#buildDataHash["progs"].append([t for t in tests["cpptests"]])

# read the SConscript for tests if enabled
buildDataHash["tests"] = []
if env["enableTests"]:
    print "Checking for cppunit...",
    try:
        cppunitCfg = scons.pkgconfig.PkgConfig('cppunit', env=env)
    except Exception:
        print 'no'
        print "*** Unable to find the Unit Testing Library for C++"
        print "*** Install CppUnit (libcppunit-dev) or disable testing with enableTests=no"
        Exit(1)
    buildDataHash["tests"] += env.SConscript(os.path.join("#test", "SConscript"),
                                   exports=["env", "modules", "configuredPackages", "cppunitCfg"])

# read SConscript for docs if enabled
buildDataHash["docs"] = []
if env["enableDocs"]:
    buildDataHash["docs"] += env.SConscript(os.path.join("#doc", "SConscript"), exports=["env"])

# read the SConscript for demos if enabled (or if tests are enabled)
buildDataHash["demos"] = []
if env["enableDemos"] or env["enableTests"]:
    buildDataHash["demos"] += env.SConscript(os.path.join("#demo", "SConscript"),
                                   exports=["env", "modules", "configuredPackages"])

# read the SConscript for benchmarks if enabled
buildDataHash["benchmarks"] = []
if env["enableBenchmarks"]:
    buildDataHash["benchmarks"] += \
        env.SConscript(os.path.join("#bench", "SConscript"),
                       exports=["env", "modules", "configuredPackages"])

# generate a builder for the pycc pkg-config file.
# TODO: The template-based generator can maybe be replaced with the
# "builtin" generator (in simula-scons/_module
# But look out for strange names... The default-name used by that
# functionality is projectname_module -> dolfin_dolfin.pc...
# sticking to the "old-style" (but renowed) for now:
replacements = {}
# set the dolfin-version. Bad place to have it, consider moving this!
replacements['PACKAGE_VERSION'] = env["PACKAGE_VERSION"]
# set the dependencies and compiler and link flags:
requires = ""
cxxflags = ""
linkflags = ""
for mod in modules.values():
    requires += " " + " ".join(mod.dependencies)
    cxxflags += " " + " ".join(mod.cxxFlags)
    linkflags += " " + " ".join(mod.linkFlags)
replacements['PACKAGE_REQUIRES'] = requires
replacements['PACKAGE_CXXFLAGS'] = cxxflags
replacements['PACKAGE_LINKFLAGS'] = linkflags
# set CXX compiler:
replacements['CXX'] = env["CXX"]

scons.pkgconfig.generate(env, replace=replacements)
buildDataHash["pkgconfig"] = env.PkgConfigGenerator("%s.pc.in" % (env["projectname"]))
# the <projectname>.pc file needs to be regenerated if there are changes to the
# configured packages, installation prefix, any compiler/link flags,
# or the compiler
pkgconfig_deps = [configuredPackages.keys(), env["prefix"],
                  cxxflags, linkflags, env["CXX"]]
env.Depends(buildDataHash["pkgconfig"][0], Value(pkgconfig_deps))

# We also like to install all generated pkg-config files.
buildDataHash["pkgconfig"] += scons.globFiles(Dir("#/scons/pkgconfig/").srcnode().abspath, "*.pc")

# Return the big data dictionary. Actual targets will be run in SConstruct
# based on this information. Also return configuration stuff that should be
# pickled.
Return("buildDataHash", "modules", "configuredPackages")

# vim:ft=python sw=2 ts=2
