#!/usr/bin/python
# {{{ 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/>.

# }}}

import os
from os.path import join

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


# }}}
# {{{ 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']
env['BUILDERS']['Bison'] = bison_builder
env['BUILDERS']['Re2c']  = re2c_builder

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

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

# }}}
# {{{ 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()

# }}}

if failure: Exit(1)

# {{{ Clasp: Library

libclaspEnv = claspEnv.Clone()
libclaspEnv.Append(CPPPATH = [Dir('#libclasp'), Dir('#libclasp/src'), Dir('#libprogram_opts')])

LIBCLASP_SOURCES = find_files(claspEnv, 'libclasp/src')

libclaspEnv.StaticLibrary('libclasp', LIBCLASP_SOURCES)

# }}}
# {{{ Opts: Library

liboptsEnv = env.Clone()
liboptsEnv.Append(CPPPATH = [Dir('#libprogram_opts'), Dir('#libprogram_opts/src')])

LIBOPTS_SOURCES = find_files(liboptsEnv, 'libprogram_opts/src')

liboptsEnv.StaticLibrary('libprogram_opts', LIBOPTS_SOURCES)

# }}}
# {{{ Gringo: Library + Program

gringoEnv = env.Clone()
gringoEnv.Append(CPPPATH = [Dir('#libgringo'), 'libgringo/src'])

LIBGRINGO_SOURCES = find_files(gringoEnv, 'libgringo/src')

gringoEnv.StaticLibrary('libgringo', LIBGRINGO_SOURCES)

GRINGO_SOURCES    = find_files(gringoEnv, 'app/gringo')
gringoEnv.Prepend(LIBS=[ 'libgringo' ])
gringoProgram = gringoEnv.Program('gringo', GRINGO_SOURCES)
gringoEnv.Alias('gringo', gringoProgram)

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

# }}}
# {{{ Clingo: Library + Program

clingoEnv = claspEnv.Clone()
clingoEnv.Prepend(LIBS=[ 'libgringo', 'libclasp', 'libprogram_opts' ])
clingoEnv.Append(CPPPATH = [Dir('#libgringo'), 'libgringo/src'])
clingoEnv.Append(CPPPATH = [Dir('#libclasp'), 'libclasp/src'])
clingoEnv.Append(CPPPATH = [Dir('#libprogram_opts'), 'libprogram_opts/src'])

CLINGO_SOURCES = find_files(clingoEnv, 'app/clingo/src')
clingoProgram  = clingoEnv.Program('clingo', CLINGO_SOURCES)
clingoEnv.Alias('clingo', clingoProgram)

# }}}
# {{{ Gringo: Tests

if env['WITH_CPPUNIT']:
    testGringoEnv           = testEnv.Clone()
    testGringoEnv.Append(CPPPATH  = [Dir('#libgringo'), 'libgringo/src', Dir('#libclasp'), Dir('#libclasp/src')])
    testGringoEnv.Prepend(LIBS=['libgringo', 'libclasp'])
    TEST_LIBGRINGO_SOURCES  = find_files(testGringoEnv, 'libgringo/tests')
    testGringoProgram       = testGringoEnv.Program('test_libgringo', TEST_LIBGRINGO_SOURCES)
    testGringoAlias         = testGringoEnv.Alias('test', [testGringoProgram], testGringoProgram[0].path + (" " + GetOption("test_case") if GetOption("test_case") else ""))
    AlwaysBuild(testGringoAlias)

# }}}
# {{{ Clingo: Tests

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

# }}}
# {{{ Clingo: Configure

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

# }}}
# {{{ Ctags

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

# }}}
