import os.path

Import('env', 'subenvs')

all_targets = []

#
# validate options
#
if [s for s in COMMAND_LINE_TARGETS if s == 'test' or s.startswith('test/')]:
    if not GetOption('enable_tests'):
        env.Die("can't use 'test*' target(s) without `--enable-tests' option")

if [s for s in COMMAND_LINE_TARGETS if s == 'bench' or s.startswith('bench/')]:
    if not GetOption('enable_benchmarks'):
        env.Die("can't use 'bench*' target(s) without `--enable-benchmarks' option")

#
# add common includes and defines
#
for senv in subenvs.all:
    senv.Append(CPPPATH=['#src/internal_modules'])

    for target_dir in senv.GlobRecursive('internal_modules', 'target_*'):
        if target_dir.name in senv['ROC_TARGETS']:
            senv.Append(CPPPATH=['#src/{}'.format(target_dir)])

    for t in env['ROC_TARGETS']:
        senv.Append(CPPDEFINES=['ROC_{}'.format(t.upper())])

#
# build internal modules
#
all_modules_libs = []
all_modules_objects = []

for module_name in env['ROC_MODULES']:
    module_dir = 'internal_modules/' + module_name

    module_env = subenvs.internal_modules.DeepClone()
    module_env.Append(CPPDEFINES=('ROC_MODULE', module_name))

    module_gen_env = subenvs.generated_code.DeepClone()
    module_gen_env.Append(CPPDEFINES=('ROC_MODULE', module_name))

    src_dirs = [module_dir]
    for target_dir in env.GlobDirs(module_dir + '/target_*'):
        if target_dir.name in env['ROC_TARGETS']:
            src_dirs.append('{}/{}/{}'.format(module_dir, target_dir.name, module_name))

    objects = []
    for src_dir in src_dirs:
        src_files = env.GlobFiles(src_dir + '/*.cpp') \
            + env.GlobFiles(src_dir + '/*.rl')

        for src_file in src_files:
            # ensure that every object file has a unique file name
            # this is needed by compose-libs.py
            tgt_file = os.path.splitext(os.path.relpath(
                src_file.srcnode().abspath,
                env.Dir(module_dir).srcnode().abspath))[0]
            tgt_file = tgt_file.replace(module_name, '')
            tgt_file = tgt_file.replace(os.sep+os.sep, os.sep)
            tgt_file = tgt_file.replace(os.sep, '_')
            tgt_file = module_name + '_' + tgt_file

            if src_file.path.endswith('.cpp'):
                objects += module_env.Object(target=tgt_file, source=src_file)
            if src_file.path.endswith('.rl'):
                objects += module_gen_env.Ragel(target=tgt_file, source=src_file)

    if not objects:
        continue

    # scons <modulename>
    lib = module_env.StaticLibrary('internal_modules/' + module_name, objects)
    env.Alias(module_name, [lib], env.Action(''))
    env.AlwaysBuild(module_name)

    all_modules_libs = [lib] + all_modules_libs
    all_modules_objects = objects + all_modules_objects

# scons internal_modules
env.Alias('internal_modules', all_modules_libs, env.Action(''))
env.AlwaysBuild('internal_modules')

all_targets += all_modules_libs

#
# build public api
#
if not GetOption('disable_shared') or GetOption('enable_static') or GetOption('enable_examples'):
    libs_env = subenvs.public_libs.DeepClone()
    libs_env.Append(CPPDEFINES=('ROC_MODULE', 'roc_api'))
    libs_env.Append(CPPPATH=['public_api/include'])

    public_api_objects = [libs_env.Object(
        target='public_api_' + os.path.splitext(os.path.basename(s.path))[0],
        source=s) for s in env.GlobFiles('public_api/src/*.cpp')]
    public_api_targets = []

    # shared library
    if not GetOption('disable_shared'):
        should_strip = not GetOption('enable_debug') and libs_env.SupportsStripSharedLibrary()

        libroc_shared = libs_env.SharedLibrary(
            'roc_unstripped' if should_strip else 'roc',
            public_api_objects,
            LIBS=all_modules_libs + libs_env['LIBS'],
            SHLIBSUFFIX=libs_env['SHLIBSUFFIX'])

        env.Depends(libroc_shared, all_modules_libs)
        env.Depends(libroc_shared, '#src/public_api/roc.version')

        if should_strip:
            libroc_shared = libs_env.StripSharedLibrary(
                libroc_shared[0].name.replace('_unstripped', ''),
                libroc_shared)

        install_target = env.Install(env['ROC_BINDIR'], libroc_shared)
        symlinks = env.SymlinkLibrary(install_target[0])

        public_api_targets += [install_target]
        public_api_targets += symlinks

        env.AddDistFile(env['ROC_SYSTEM_LIBDIR'], install_target)

        if env.NeedsFixupSharedLibrary():
            env.AddDistAction(env.FixupSharedLibrary(
                env.GetDistPath(env['ROC_SYSTEM_LIBDIR'], install_target[0].name)))

        for lnk in symlinks:
            env.AddDistFile(env['ROC_SYSTEM_LIBDIR'], lnk)

    # static library
    if GetOption('enable_static'):
        thirdparty_libs = libs_env.GetThirdPartyStaticLibs()

        if libs_env.SupportsRelocatableObject() and libs_env.SupportsLocalizedObject():
            libroc_modules = libs_env.RelocatableObject(
                'libroc_modules_unlocalized',
                public_api_objects + all_modules_objects)

            env.Depends(libroc_modules, all_modules_libs)

            libroc_modules = libs_env.LocalizedObject('libroc_modules', libroc_modules)
            libroc_static = libs_env.StaticLibrary(
                'roc_modules' if thirdparty_libs else 'roc',
                libroc_modules)
        else:
            libroc_static = libs_env.StaticLibrary(
                'roc_modules' if thirdparty_libs else 'roc',
                public_api_objects + all_modules_objects)

        if thirdparty_libs:
            libroc_static = libs_env.ComposeStaticLibraries(
                'roc', libroc_static + thirdparty_libs)

        install_target = env.Install(env['ROC_BINDIR'], libroc_static)
        public_api_targets += [install_target]

        env.AddDistFile(env['ROC_SYSTEM_LIBDIR'], install_target)

    # scons public_api
    if not GetOption('disable_shared') or GetOption('enable_static'):
        env.Alias('public_api', public_api_targets, env.Action(''))
        env.AlwaysBuild('public_api')

        env.AddDistFile(env['ROC_SYSTEM_INCDIR'], '#src/public_api/include/roc')

        pc_file = env.GeneratePkgConfig(
            build_dir='.',
            filename='roc.pc',
            prefix=GetOption('prefix'),
            libdir=env['ROC_SYSTEM_LIBDIR'],
            name='roc',
            desc='Real-time audio streaming over the network.',
            url='https://roc-streaming.org',
            version=env['ROC_VERSION'])

        env.AddDistFile(env['ROC_SYSTEM_PCDIR'], pc_file)

    all_targets += public_api_targets

#
# build examples
#
if GetOption('enable_examples'):
    examples_env = subenvs.examples.DeepClone()
    examples_env.Append(CPPPATH=['public_api/include'])
    examples_env.Prepend(LIBS=all_modules_libs)

    example_targets = []

    for example_source in env.GlobFiles('public_api/examples/*.c'):
        example_name = os.path.splitext(example_source.name)[0]

        if 'pulseaudio' in example_name and not 'target_pulseaudio' in env['ROC_TARGETS']:
            continue

        exe_name = 'roc-example-{}'.format(example_name.replace('_', '-'))
        exe = examples_env.Program(
            exe_name, [example_source] + public_api_objects)

        example_targets += [env.Install(env['ROC_BINDIR'], exe)]

    # scons examples
    env.Alias('examples', example_targets, env.Action(''))
    env.AlwaysBuild('examples')

    all_targets += example_targets

#
# build cli tools
#
if not GetOption('disable_tools') \
  or GetOption('enable_tests') or GetOption('enable_benchmarks'):
    sanitizer_objects = []
    if GetOption('sanitizers'):
        sanitizer_objects += subenvs.internal_modules.Object('sanitizer_options.cpp')

if not GetOption('disable_tools'):
    if env['ROC_COMMIT']:
        verion_str = '{} ({})'.format(env['ROC_VERSION'], env['ROC_COMMIT'])
    else:
        verion_str = env['ROC_VERSION']

    tool_targets = []

    for tool_dir in env.GlobDirs('tools/*'):
        tools_env = subenvs.tools.DeepClone()
        tools_env.Prepend(LIBS=all_modules_libs)
        tools_env.Append(CPPDEFINES=('ROC_MODULE', tool_dir.name))
        tools_env.Append(CPPPATH=['tools', '#src/tools/{}'.format(tool_dir.name)])

        sources = env.GlobFiles('{}/*.cpp'.format(tool_dir))

        objects = []
        for ggo in env.GlobFiles('{}/*.ggo'.format(tool_dir)):
            objects += subenvs.generated_code.GenGetOpt(ggo, verion_str)

        exe_name = tool_dir.name.replace('_', '-')

        target = env.Install(env['ROC_BINDIR'],
                    tools_env.Program(exe_name, sources+objects+sanitizer_objects))

        # scons <toolname>
        env.Alias(exe_name, [target], env.Action(''))
        env.AlwaysBuild(exe_name)

        env.AddDistFile(env['ROC_SYSTEM_BINDIR'], target)

        tool_targets += [target]

    # scons tools
    env.Alias('tools', tool_targets, env.Action(''))
    env.AlwaysBuild('tools')

    all_targets += tool_targets

#
# build tests
#
if GetOption('enable_tests') or GetOption('enable_benchmarks'):
    common_test_env = subenvs.tests.DeepClone()
    common_test_env.Append(CPPDEFINES=('ROC_MODULE', 'roc_test'))
    common_test_env.Prepend(LIBS=[all_modules_libs])

    if GetOption('enable_tests'):
        test_main_object = common_test_env.Object('tests/test_main.cpp')

    if GetOption('enable_benchmarks'):
        bench_main_object = common_test_env.Object('tests/bench_main.cpp')

    test_targets = []

    for test_name in env['ROC_MODULES'] + ['public_api']:
        test_dir = 'tests/' + test_name

        test_env = common_test_env.DeepClone()
        test_env.Append(CPPPATH=['#src/' + test_dir])

        if test_name == 'public_api':
            if GetOption('disable_shared') and not GetOption('enable_static'):
                continue
            test_env.Append(CPPPATH=['public_api/include'])

        for kind in ['test', 'bench']:
            if kind == 'test':
                if GetOption('enable_tests'):
                    main_object = test_main_object
                else:
                    continue
            else:
                if GetOption('enable_benchmarks'):
                    main_object = bench_main_object
                else:
                    continue

            sources = []

            sources += env.GlobFiles('{}/{}_*.cpp'.format(test_dir, kind))
            sources += env.GlobFiles('{}/test_helpers/*.cpp'.format(test_dir))

            for target_dir in env.GlobRecursive(test_dir, 'target_*'):
                if target_dir.name in env['ROC_TARGETS']:
                    test_env.Append(CPPPATH=['#src/{}'.format(target_dir)])
                    sources += env.GlobRecursive(target_dir, kind + '_*.cpp')

            if not sources:
                continue

            sources_and_objects = sources + main_object + sanitizer_objects
            if test_name == 'public_api':
                sources_and_objects += public_api_objects

            exe_name = 'roc-{}-{}'.format(kind, test_name.replace('roc_', '').replace('_', '-'))

            target = env.Install(env['ROC_BINDIR'],
                test_env.Program(exe_name, sources_and_objects))

            exe_file = '{}/{}'.format(env['ROC_BINDIR'], exe_name)
            if kind == 'test':
                # scons test/<modulename>
                # scons test
                env.AddTest(test_name, exe_file)
            else:
                # scons bench/<modulename>
                # scons bench
                env.AddBench(test_name, exe_file)

            test_targets += [target]

    all_targets += test_targets

#
# install compile_commands
#
if env.get('ROC_CLANGDB', None):
    compile_commands = env['ROC_CLANGDB']

    env.Artifact(compile_commands, all_targets)
    env.Install('#', compile_commands)
