# -*- coding: utf-8 -*-
import os.path, sys, re
from os.path import join as pjoin

# 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
Import("env", "configure")

# 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 = {}

# 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(1)

# 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=[])

# 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)

# 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."
    Exit(1)
py_ver = "%s.%s" % (sys.version_info[0],sys.version_info[1])
if env["PLATFORM"].startswith("win"):
    py_inc = pjoin(sys.prefix, "include")
else:
    py_inc = pjoin(sys.prefix, "include", "python" + py_ver)
if not os.path.isfile(pjoin(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)

swigFlags = "-python -c++ -shadow -Isyfi"
swigFlags = swigFlags.split()
out, err = scons.runCommand("swig", "-version")
# Determine swig version, v 1.3.28 and later supports -O flag
m = re.match(r"SWIG Version (.+)", out, re.M)
#if scons.checkVersion(m.group(1), "1.3.28"):
#    swigFlags.append("-O")

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, cxxFileBldr = swigEnv["BUILDERS"]["CFile"], swigEnv["BUILDERS"]["CXXFile"]

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

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

# stencil for return-values:
ret = {"shlibs": [], "extModules": [], "docs": [], "headers": [], \
       "pythonModules": [], "pythonScripts": [], "pkgconfig": [], \
       "pythonPackageDirs": [], "tests": [], \
       "progs": [], "swigfiles": []}

#### 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, 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
            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

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

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

        modEnv = swigEnv.Clone(CXXFLAGS=mod.cxxFlags, LINKFLAGS=mod.linkFlags + linkOpts)
        modEnv.Append(SWIGFLAGS=mod.swigFlags + ["-I%s" % i for i in swigcppPath])
        
        ### uncomment the following line to remove -Werror from the CXXFLAGS:
        #modEnv["CXXFLAGS"] = re.sub("-Werror","",modEnv["CXXFLAGS"])
            
        # 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.lower())
        else:
            # This is a standalone swig'ed module
            modEnv.Append(LIBPATH=libPath, LIBS=libs)
        if env["PLATFORM"] == "darwin":
            modEnv.Append(FRAMEWORKS=frameworks)

        wrapCxx, wrapPy = modEnv.CXXFile(target=pjoin(mod.path, "swig", modName), source=[pjoin(mod.path, s) for s in mod.swigSources])
        swig = modEnv.SharedLibrary(target=pjoin(mod.path, "swig", modName), source=wrapCxx)[0]
        # The builder returns a node list
        ret["extModules"].append(swig)
        ret["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.lower(), 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.lower()] + libs, LIBPATH=[mod.path] + libPath)
            modEnv.Append(FRAMEWORKS=frameworks)
 
        prog = modEnv.Program(target=pjoin(mod.path, progName), source=\
                [pjoin(mod.path, s) for s in progSources])
        # The builder returns a node list
        ret["progs"].append(prog[0])

    # Register headers to be installed for the module
    ret["headers"] += [File(h, mod.path) for h in mod.libHeaders]

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

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

# read SConscript for docs if enabled
ret["docs"] = []
if env["enableDocs"]:
    ret["docs"] += env.SConscript(pjoin("#doc", "SConscript"), exports=["env"])

# read the SConscript for tests if enabled
ret["tests"] = []
if env["enableTests"]:
    ret["tests"] += env.SConscript(pjoin("#tests", "cpp", "SConscript"),
                                   exports=["env", "modules", "configuredPackages"])

# generate a builder for the syfi 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 SyFi version. Bad place to have it, consider moving this!
replacements['SYFILIB_VERSION'] = env["SYFILIB_VERSION"]
# make all dependencies into a list, based on the configured Packages.
replacements['PACKAGE_REQUIRES'] = " ".join(configuredPackages.keys())
# set compiler and link flags:
cxxflags = ""
linkflags = ""
for mod in modules.values():
    cxxflags += " " + " ".join(mod.cxxFlags)
    linkflags += " " + " ".join(mod.linkFlags)
replacements['PACKAGE_CXXFLAGS'] = cxxflags
replacements['PACKAGE_LINKFLAGS'] = linkflags
# set CXX compiler:
replacements['CXX'] = env["CXX"]

scons.pkgconfig.generate(env, replace=replacements)
ret["pkgconfig"] = env.PkgConfigGenerator("%s.pc.in" % (env["projectname"]))
# the syfi.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(ret["pkgconfig"][0], Value(pkgconfig_deps))

# We also like to install all generated pkg-config files. 

ret["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.
Return("ret")

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