#!/usr/bin/python
# {{{1 GPL License

# This file is part of gringo - a grounder for logic programs.
# Copyright (C) 2013  Roland Kaminski

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# {{{1 Preamble

import os
from os.path import join

# {{{1 Auxiliary functions

def find_files(env, path):
    oldcwd = os.getcwd()
    try:
        os.chdir(Dir('#').abspath)
        sources = []
        for root, dirnames, filenames in os.walk(path):
            for filename in filenames:
                if filename.endswith(".cc") or filename.endswith(".cpp"):
                    sources.append(os.path.join(root, filename))
                if filename.endswith(".yy"):
                    target = os.path.join(root, filename[:-3],  "grammar.cc")
                    source = "#"+os.path.join(root, filename)
                    sources.append(target)
                    env.Bison(target, source)
                if filename.endswith(".xh"):
                    target = os.path.join(root, filename[:-3] + ".hh")
                    source = "#"+os.path.join(root, filename)
                    env.Re2c(target, source)
        return sources
    finally:
        os.chdir(oldcwd)

def shared(env, sources):
    return [env.SharedObject(x) for x in sources]

def bison_emit(target, source, env):
    path = os.path.split(str(target[0]))[0];
    target += [os.path.join(path, "grammar.hh"), os.path.join(path, "grammar.out")]
    return target, source

def CheckBison(context):
    context.Message('Checking for bison 2.5... ')
    (result, output) = context.TryAction("${BISON} ${SOURCE} -o ${TARGET}", '%require "2.5"\n%%\nstart:', ".y")
    context.Result(result)
    return result

def CheckRe2c(context):
    context.Message('Checking for re2c... ')
    (result, output) = context.TryAction("${RE2C} ${SOURCE}", '', ".x")
    context.Result(result)
    return result

def CheckNeedRT(context):
    context.Message('Checking if need library rt... ')
    srcCode = """
    #include <tbb/compat/condition_variable>
    int main(int argc, char **argv)
    {
        tbb::interface5::unique_lock<tbb::mutex> lock;
        tbb::tick_count::interval_t i;
        tbb::interface5::condition_variable cv;
        cv.wait_for(lock, i);
        return 0;
    }
    """
    result = not context.TryLink(srcCode, '.cc')
    context.Result(result)
    return result

def CheckMyFun(context, name, code, header):
    source = header + "\nint main() {\n" + code + "\nreturn 0; }"
    context.Message('Checking for C++ function ' + name + '()... ')
    result = context.TryLink(source, '.cc')
    context.Result(result)
    return result

# {{{1 Basic environment

Import('env')

bison_action = Action("${BISON} -r all --report-file=${str(TARGET)[:-3]}.out -o ${TARGET} ${SOURCE} ${test}")

bison_builder = Builder(
    action = bison_action,
    emitter = bison_emit,
    suffix = '.cc',
    src_suffix = '.yy'
    )

re2c_action = Action("${RE2C} -o ${TARGET} ${SOURCE}")

re2c_builder = Builder(
    action = re2c_action,
    suffix = '.hh',
    src_suffix = '.xh'
    )

env['ENV']['PATH'] = os.environ['PATH']
if 'LD_LIBRARY_PATH' in os.environ: env['ENV']['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
env['BUILDERS']['Bison'] = bison_builder
env['BUILDERS']['Re2c']  = re2c_builder

# {{{1 Gringo specific configuration

conf = Configure(env, custom_tests = {'CheckBison' : CheckBison, 'CheckRe2c' : CheckRe2c, 'CheckMyFun' : CheckMyFun}, log_file = join("build", GetOption('build_dir') + ".log"))
DEFS = {}
failure = False

if not conf.CheckBison():
    print 'error: no usable bison version found'
    failure = True

if not conf.CheckRe2c():
    print 'error: no usable re2c version found'
    failure = True

if not conf.CheckCXX():
    print 'error: no usable C++ compiler found'
    Exit(1)

if (env['WITH_PYTHON'] is not None or env["WITH_LUA"] is not None) and not conf.CheckSHCXX():
    print 'error: no usable (shared) C++ compiler found'
    Exit(1)

if env['WITH_PYTHON']:
    if not conf.CheckLibWithHeader(env['WITH_PYTHON'], 'Python.h', 'C++'):
        print 'error: python library not found'
        failure = True
    else:
        DEFS["WITH_PYTHON"] = 1

if env['WITH_LUA']:
    if not conf.CheckLibWithHeader(env['WITH_LUA'], 'lua.hpp', 'C++'):
        print 'error: lua library not found'
        failure = True
    else:
        DEFS["WITH_LUA"] = 1

if not conf.CheckMyFun('snprintf', 'char buf[256]; snprintf (buf,256,"");', '#include <cstdio>'):
    if conf.CheckMyFun('__builtin_snprintf', 'char buf[256]; __builtin_snprintf (buf,256,"");', '#include <cstdio>'):
        DEFS['snprintf']='__builtin_snprintf'

if not conf.CheckMyFun('vsnprintf', 'char buf[256]; va_list args; vsnprintf (buf,256,"", args);', "#include <cstdio>\n#include <cstdarg>"):
    if conf.CheckMyFun('__builtin_vsnprintf', 'char buf[256]; va_list args; __builtin_vsnprintf (buf,256,"", args);', "#include <cstdio>\n#include <cstdarg>"):
        DEFS['vsnprintf']='__builtin_vsnprintf'

if not conf.CheckMyFun('std::to_string', 'std::to_string(10);', "#include <string>"):
    DEFS['MISSING_STD_TO_STRING']=1

env = conf.Finish()
env.PrependUnique(LIBPATH=[Dir('.')])
env.Append(CPPDEFINES=DEFS)

# {{{1 Clasp specific configuration

claspEnv  = env.Clone()
claspConf = Configure(claspEnv, custom_tests = {'CheckNeedRT' : CheckNeedRT}, log_file = join("build", GetOption('build_dir') + ".log"))
DEFS = {}

DEFS["WITH_THREADS"] = 0
if env['WITH_TBB']:
    if not claspConf.CheckLibWithHeader(env['WITH_TBB'], 'tbb/tbb.h', 'C++'):
        print 'error: tbb library not found'
        failure = True
    else:
        DEFS["WITH_THREADS"] = 1

        if claspConf.CheckNeedRT():
            if not claspConf.CheckLibWithHeader('rt', 'time.h', 'C++'):
                print 'error: rt library not found'
                failure = True

claspEnv = claspConf.Finish()
claspEnv.Append(CPPDEFINES=DEFS)

# {{{1 Test specific configuration

if env['WITH_CPPUNIT']:
    testEnv  = claspEnv.Clone()
    testConf = Configure(testEnv, custom_tests = {'CheckBison' : CheckBison, 'CheckRe2c' : CheckRe2c}, log_file = join("build", GetOption('build_dir') + ".log"))
    if not testConf.CheckLibWithHeader(env['WITH_CPPUNIT'], 'cppunit/TestFixture.h', 'C++'):
        print 'error: cppunit library not found'
        failure = True
    testEnv  = testConf.Finish()

# {{{1 Check configuration

if failure: Exit(1)

# {{{1 Opts: Library

LIBOPTS_SOURCES = find_files(env, 'libprogram_opts/src')
LIBOPTS_HEADERS = [Dir('#libprogram_opts'), Dir('#libprogram_opts/src')]

optsEnv = env.Clone()
optsEnv.Append(CPPPATH = LIBOPTS_HEADERS)

optsLib  = optsEnv.StaticLibrary('libprogram_opts', LIBOPTS_SOURCES)
optsLibS = optsEnv.StaticLibrary('libprogram_opts_shared', shared(optsEnv, LIBOPTS_SOURCES))

# {{{1 Clasp: Library

LIBCLASP_SOURCES = find_files(env, 'libclasp/src')
LIBCLASP_HEADERS = [Dir('#libclasp'), Dir('#libclasp/src'), Dir('#libprogram_opts')]

claspEnv.Append(CPPPATH = LIBCLASP_HEADERS)

claspLib  = claspEnv.StaticLibrary('libclasp', LIBCLASP_SOURCES)
claspLibS = claspEnv.StaticLibrary('libclasp_shared', shared(claspEnv, LIBCLASP_SOURCES))

# {{{1 Gringo: Library

LIBGRINGO_SOURCES = find_files(env, 'libgringo/src')
LIBGRINGO_HEADERS = [Dir('#libgringo'), 'libgringo/src']

gringoEnv = env.Clone()
gringoEnv.Append(CPPPATH = LIBGRINGO_HEADERS + LIBOPTS_HEADERS)

gringoLib  = gringoEnv.StaticLibrary('libgringo', LIBGRINGO_SOURCES)
gringoLibS = gringoEnv.StaticLibrary('libgringo_shared', shared(gringoEnv, LIBGRINGO_SOURCES))

# {{{1 Gringo: Program

GRINGO_SOURCES = find_files(env, 'app/gringo')

gringoProgramEnv = gringoEnv.Clone()
gringoProgramEnv.Prepend(LIBS=[ gringoLib, optsLib ])

gringoProgram = gringoProgramEnv.Program('gringo', GRINGO_SOURCES)
gringoProgramEnv.Alias('gringo', gringoProgram)

if not env.GetOption('clean'):
    Default(gringoProgram)

# {{{1 Clingo: Program

CLINGO_SOURCES = find_files(env, 'app/clingo/src') + find_files(env, 'app/shared/src')

clingoProgramEnv = claspEnv.Clone()
clingoProgramEnv.Prepend(LIBS=[ gringoLib, claspLib, optsLib ])
clingoProgramEnv.Append(CPPPATH = [Dir('#app/shared/include')] + LIBGRINGO_HEADERS + LIBCLASP_HEADERS + LIBOPTS_HEADERS)

clingoProgram  = clingoProgramEnv.Program('clingo', CLINGO_SOURCES)
clingoProgramEnv.Alias('clingo', clingoProgram)

if not env.GetOption('clean'):
    Default(clingoProgram)

# {{{1 PyClingo + LuaClingo

sharedLibS = None
if env["WITH_PYTHON"] or env["WITH_LUA"]:
    SHARED_SOURCES = find_files(env, 'app/shared/src')

    sharedEnv = claspEnv.Clone()
    sharedEnv.Append(CPPPATH = [Dir('#app/shared/include'), LIBGRINGO_HEADERS])

    sharedLibS = sharedEnv.StaticLibrary('libshared_shared', shared(sharedEnv, SHARED_SOURCES))

if env["WITH_PYTHON"]:
    PYCLINGO_SOURCES = find_files(env, 'app/pyclingo/src')

    pyclingoEnv = sharedEnv.Clone()
    pyclingoEnv["LIBPREFIX"] = ""
    pyclingoEnv.Prepend(LIBS   = [sharedLibS, gringoLibS, claspLibS, optsLibS])

    pyclingo = pyclingoEnv.SharedLibrary('python/gringo', PYCLINGO_SOURCES)
    pyclingoEnv.Alias('pyclingo', pyclingo)
    if not env.GetOption('clean'):
        Default(pyclingo)

if env["WITH_LUA"]:
    LUACLINGO_SOURCES = find_files(env, 'app/luaclingo/src')

    luaclingoEnv = sharedEnv.Clone()
    luaclingoEnv["LIBPREFIX"] = ""
    luaclingoEnv.Prepend(LIBS   = [sharedLibS, gringoLibS, claspLibS, optsLibS])

    luaclingo = luaclingoEnv.SharedLibrary('lua/gringo', LUACLINGO_SOURCES)
    luaclingoEnv.Alias('luaclingo', luaclingo)
    if not env.GetOption('clean'):
        Default(luaclingo)

# {{{1 Gringo: Tests

if env['WITH_CPPUNIT']:
    TEST_LIBGRINGO_SOURCES  = find_files(env, 'libgringo/tests')

    gringoTestEnv           = testEnv.Clone()
    gringoTestEnv.Append(CPPPATH = LIBGRINGO_HEADERS + LIBCLASP_HEADERS)
    gringoTestEnv.Prepend(LIBS   = [gringoLib, claspLib])

    testGringoProgram = gringoTestEnv.Program('test_libgringo', TEST_LIBGRINGO_SOURCES)
    testGringoAlias   = gringoTestEnv.Alias('test', [testGringoProgram], testGringoProgram[0].path + (" " + GetOption("test_case") if GetOption("test_case") else ""))
    AlwaysBuild(testGringoAlias)

# {{{1 Clingo: Tests

clingoTestCommand = env.Command('clingo-test', clingoProgram, '/bin/zsh app/clingo/tests/run.sh $SOURCE' + (" -- -t8" if env["WITH_TBB"] else ""))
clingoTest        = env.Alias('test-clingo', [clingoTestCommand])
env.AlwaysBuild(clingoTest)

# {{{1 Clingo: Configure

clingoConfigure = env.Alias('configure', [])

# {{{1 Ctags

ctagsCommand = env.Command('ctags', [], 'ctags --c++-kinds=+p --fields=+imaS --extra=+q -R libgringo app')
ctagsAlias   = env.Alias('tags', [ctagsCommand])
env.AlwaysBuild(ctagsCommand)

