#!/usr/bin/env python

from __future__ import print_function

import argparse
import subprocess
import sys
import os
import multiprocessing
import pipes
import shutil
import platform
import distutils.spawn
import textwrap

from colorama import Fore, Style

project_root = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
os.chdir(project_root)

parser = argparse.ArgumentParser(description='Convenience script to build everything on a few select platforms.')
parser.add_argument('-t', '--target',
                    default=os.environ.get('SCRAPS_CROSS_TARGET'),
                    dest='cross_target',
                    choices=['iphoneos', 'appletvos', 'android'],
                    help='cross-compilation target')
parser.add_argument('--variant',
                    default='release',
                    choices=['release', 'debug'],
                    help='variant to build')
parser.add_argument('-c', '--clean',
                    action='store_true',
                    help='clean prior to building')
parser.add_argument('--install',
                    action='store_true',
                    help='install targets as well')
parser.add_argument('--enable-coverage',
                    action='store_true',
                    help='enables building code coverage')
parser.add_argument('--enable-analysis',
                    action='store_true',
                    help='enables static analysis via simon')
parser.add_argument('--linkage',
                    choices=['static', 'shared'],
                    help='library linkage')
parser.add_argument('--prefix',
                    default='dist',
                    help='installation prefix')
args = parser.parse_args()

if not args.linkage:
    args.linkage = 'shared' if args.cross_target == 'android' else 'static'


def print_diagnostic(msg):
    print(Fore.YELLOW + Style.BRIGHT + msg + Style.RESET_ALL)


def print_error(msg):
    print(Fore.RED + Style.BRIGHT + msg + Style.RESET_ALL, file=sys.stderr)


android_path = ''
if args.cross_target == 'android':
    if 'ANDROID_TOOLCHAIN' not in os.environ:
        print_error('ANDROID_TOOLCHAIN must be defined')
        sys.exit(1)
    android_path = '$ANDROID_TOOLCHAIN/arm-linux-androideabi/bin:$PATH'

if not distutils.spawn.find_executable('simon'):
    print_error(textwrap.dedent('''\
        unable to perform license checks without simon
        see https://github.com/bittorrent/simon'''))
    if args.enable_analysis:
        print_error(textwrap.dedent('''\
            unable to perform static analysis without simon
            see https://github.com/bittorrent/simon'''))
        sys.exit(1)

if args.enable_coverage and args.cross_target:
    print_error('coverage is only supported for host')
    sys.exit(1)

variables = {
    'android_path': android_path,
    'cpu_count': multiprocessing.cpu_count(),
    'target_os': {
        'iphoneos': 'iphone',
        'appletvos': 'appletv',
        'android': 'android',
    }.get(args.cross_target),
    'needy_target': {
        'iphoneos': 'ios',
        'appletvos': 'tvos',
        'android': 'android',
    }.get(args.cross_target)
}
variables.update(vars(args))


def run_command(command):
    if isinstance(command, list):
        run_command(shell_quote([c.format(**variables) for c in command]))
    else:
        command = command.format(**variables)
        print(Fore.CYAN + Style.BRIGHT + '+ {}'.format(command) + Style.RESET_ALL)
        subprocess.check_call(command, shell=True)


def run_commands(commands):
    for command in commands:
        run_command(command)


def shell_quote(command):
    return ' '.join([pipes.quote(c) for c in command])


def write_b2_project_config(args):
    b2_config = ['scripts/configure-b2']
    if args.cross_target:
        b2_config.append('--target={cross_target}')
    run_command('{} > project-config.jam'.format(shell_quote(b2_config)))


def build_deps(args):
    build_deps = ['./build-deps', '--bootstrap-b2', '--configure']
    if args.clean:
        build_deps.append('--clean')
    if args.cross_target:
        build_deps.append('--needy-target-args=--target={needy_target}')
    if not args.cross_target:
        build_deps.append('--enable-benchmark')
    if args.cross_target == 'android':
        build_deps.append('--enable-jshackle')
    run_command(build_deps)


def build(args):
    def targets():
        targets = ['scraps', 'tests/unit']
        if not args.cross_target:
            targets.extend([
                'tests/benchmarks',
                'examples/log',
            ])
        return shell_quote(targets)

    def common_args():
        common_args = ['-j{cpu_count}', 'link={linkage}', 'variant={variant}']
        if args.cross_target:
            common_args.append('target-os={target_os}')
        if args.clean:
            common_args.append('-a')
        if args.enable_coverage:
            common_args.extend([
                'cxxflags=-coverage',
                'linkflags={}'.format(coverage_lib(platform.system().lower()))
            ])
        return shell_quote([c.format(**variables) for c in common_args])

    def b2():
        if args.enable_analysis:
            b2 = 'simon c++-analysis --report-dir analysis ./b2 --build-dir=./bin/analysis'
        else:
            b2 = './b2'
        if args.cross_target == 'android':
            b2 = 'PATH={android_path} ' + b2
        return b2.format(**variables)

    run_command('{b2} {common_args} {targets}'.format(
        b2=b2(),
        targets=targets(),
        common_args=common_args()
    ))


def coverage_lib(uname):
    if uname == 'darwin':
        toolchain_root = '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain'
        return subprocess.check_output(
            'find {}/usr/lib -name "libclang_rt.profile_osx.a"'.format(toolchain_root), shell=True).decode().strip()
    else:
        # XXX: currently only supported for clang 3.8
        return subprocess.check_output(
            'find /usr/lib*/llvm-3.8/lib -name "libclang_rt.profile*.a"', shell=True).decode().strip()


def install(args):
    def targets():
        targets = ['install', 'tests/unit//install']
        if not args.cross_target:
            targets.append('tests/benchmarks//install')
        return shell_quote(targets)

    def common_args():
        common_args = ['-j{cpu_count}', 'link={linkage}', 'variant={variant}', '--prefix={prefix}']
        if args.cross_target:
            common_args.append('target-os={target_os}')
        if args.clean:
            common_args.append('-a')
        return shell_quote([c.format(**variables) for c in common_args])

    def b2():
        if args.cross_target == 'android':
            return 'PATH={android_path} ./b2'
        return './b2'

    run_commands([
        'rm -rf {prefix}',
        'rm -rf tests/benchmark/{prefix}',
        'rm -rf tests/unit/{prefix}',
        '{b2} {common_args} {targets}'.format(b2=b2(), targets=targets(), common_args=common_args()),
        'rm -rf {prefix}/needy.status',
    ])


def verify():
    if distutils.spawn.find_executable('simon'):
        run_command('simon license check --license=apache')

try:
    write_b2_project_config(args)
    build_deps(args)
    build(args)
    verify()
    if args.install:
        install(args)
except subprocess.CalledProcessError:
    sys.exit(1)
    pass
