#!/usr/bin/env python

from __future__ import print_function

import collections
import os
import re
import subprocess
import sys


def detect_ndk_dir():
    ndk_dir = os.getenv('NDK')
    if not ndk_dir:
        error_msg = '''error: NDK toolchain is required for this test case.
error:
error: Steps:
error:   1. Download NDK from https://developer.android.com/ndk/downloads/
error:   2. Unzip the archive (android-ndk-r15c-linux-x86_64.zip)
error:   3. Set environment variable NDK to the extracted directory
error:      (export NDK=android-ndk-r15c)
error:'''
        print(error_msg, file=sys.stderr)
        raise ValueError('NDK toolchain not specified')

    if not os.path.exists(ndk_dir):
        raise ValueError('NDK toolchain not found')

    return ndk_dir


def detect_api_level(ndk_dir):
    try:
        apis = []
        pattern = re.compile('android-(\\d+)')
        for name in os.listdir(os.path.join(ndk_dir, 'platforms')):
            match = pattern.match(name)
            if match:
                apis.append(int(match.group(1)))
        if not apis:
            raise ValueError('failed to find latest api')
        return 'android-{}'.format(max(apis))
    except IOError:
        raise ValueError('failed to find latest api')


def detect_host():
    if sys.platform.startswith('linux'):
        return 'linux-x86_64'
    if sys.platform.startswith('darwin'):
        return 'darwin-x86_64'
    raise NotImplementedError('unknown host platform')


def get_gcc_dir(ndk_dir, arch, host):
    return os.path.join(ndk_dir, 'toolchains', arch, 'prebuilt', host)


def get_clang_dir(ndk_dir, host):
    return os.path.join(ndk_dir, 'toolchains', 'llvm', 'prebuilt', host)


def get_platform_dir(ndk_dir, api, subdirs):
    return os.path.join(ndk_dir, 'platforms', api, *subdirs)


class Target(object):
    def __init__(self, name, triple, cflags, ldflags, gcc_toolchain_dir,
                 clang_dir, ndk_include, ndk_lib):
        self.name = name
        self.target_triple = triple
        self.target_cflags = cflags
        self.target_ldflags = ldflags

        self.gcc_toolchain_dir = gcc_toolchain_dir
        self.clang_dir = clang_dir
        self.ndk_include = ndk_include
        self.ndk_lib = ndk_lib


    def check_paths(self):
        def check_path(path):
            if os.path.exists(path):
                return True
            print('error: File not found:', path, file=sys.stderr)
            return False

        ld_exeutable = os.path.join(
            self.gcc_toolchain_dir, 'bin', self.target_triple + '-ld')

        success = check_path(self.gcc_toolchain_dir)
        success &= check_path(ld_exeutable)
        success &= check_path(self.clang_dir)
        success &= check_path(self.ndk_include)
        success &= check_path(self.ndk_lib)
        return success


    def compile(self, obj_file, src_file, cflags):
        clang = os.path.join(self.clang_dir, 'bin', 'clang')

        cmd = [clang, '-o', obj_file, '-c', src_file]
        cmd.extend(['-fPIE', '-fPIC'])
        cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir])
        cmd.extend(['-target', self.target_triple])
        cmd.extend(['-isystem', self.ndk_include])
        cmd.extend(cflags)
        cmd.extend(self.target_cflags)
        subprocess.check_call(cmd)


    def link(self, out_file, obj_files, ldflags):
        if '-shared' in ldflags:
            crtbegin = os.path.join(self.ndk_lib, 'crtbegin_so.o')
            crtend = os.path.join(self.ndk_lib, 'crtend_so.o')
        else:
            crtbegin = os.path.join(self.ndk_lib, 'crtbegin_static.o')
            crtend = os.path.join(self.ndk_lib, 'crtend_android.o')

        clang = os.path.join(self.clang_dir, 'bin', 'clang')

        cmd = [clang, '-o', out_file]
        cmd.extend(['-fPIE', '-fPIC', '-Wl,--no-undefined', '-nostdlib'])
        cmd.append('-L' + self.ndk_lib)
        cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir])
        cmd.extend(['-target', self.target_triple])
        cmd.append(crtbegin)
        cmd.extend(obj_files)
        cmd.append(crtend)
        cmd.extend(ldflags)
        cmd.extend(self.target_ldflags)
        if '-shared' not in ldflags:
            cmd.append('-Wl,-pie')
        subprocess.check_call(cmd)


def create_targets(ndk_dir=None, api=None, host=None):
    if ndk_dir is None:
        ndk_dir = detect_ndk_dir()
    if api is None:
        api = detect_api_level(ndk_dir)
    if host is None:
        host = detect_host()

    targets = collections.OrderedDict()

    targets['arm'] = Target(
        'arm', 'arm-linux-androideabi', [], [],
        get_gcc_dir(ndk_dir, 'arm-linux-androideabi-4.9', host),
        get_clang_dir(ndk_dir, host),
        get_platform_dir(ndk_dir, api, ['arch-arm', 'usr', 'include']),
        get_platform_dir(ndk_dir, api, ['arch-arm', 'usr', 'lib']))

    targets['arm64'] = Target(
        'arm64', 'aarch64-linux-android', [], [],
        get_gcc_dir(ndk_dir, 'aarch64-linux-android-4.9', host),
        get_clang_dir(ndk_dir, host),
        get_platform_dir(ndk_dir, api, ['arch-arm64', 'usr', 'include']),
        get_platform_dir(ndk_dir, api, ['arch-arm64', 'usr', 'lib']))

    targets['x86'] = Target(
        'x86', 'i686-linux-android', ['-m32'], ['-m32'],
        get_gcc_dir(ndk_dir, 'x86-4.9', host),
        get_clang_dir(ndk_dir, host),
        get_platform_dir(ndk_dir, api, ['arch-x86', 'usr', 'include']),
        get_platform_dir(ndk_dir, api, ['arch-x86', 'usr', 'lib']))

    targets['x86_64'] = Target(
        'x86_64', 'x86_64-linux-android', ['-m64'], ['-m64'],
        get_gcc_dir(ndk_dir, 'x86_64-4.9', host),
        get_clang_dir(ndk_dir, host),
        get_platform_dir(ndk_dir, api, ['arch-x86_64', 'usr', 'include']),
        get_platform_dir(ndk_dir, api, ['arch-x86_64', 'usr', 'lib64']))

    targets['mips'] = Target(
        'mips', 'mipsel-linux-android', [], [],
        get_gcc_dir(ndk_dir, 'mipsel-linux-android-4.9', host),
        get_clang_dir(ndk_dir, host),
        get_platform_dir(ndk_dir, api, ['arch-mips', 'usr', 'include']),
        get_platform_dir(ndk_dir, api, ['arch-mips', 'usr', 'lib']))

    targets['mips64'] = Target(
        'mips64', 'mips64el-linux-android',
        ['-march=mips64el', '-mcpu=mips64r6'],
        ['-march=mips64el', '-mcpu=mips64r6'],
        get_gcc_dir(ndk_dir, 'mips64el-linux-android-4.9', host),
        get_clang_dir(ndk_dir, host),
        get_platform_dir(ndk_dir, api, ['arch-mips64', 'usr', 'include']),
        get_platform_dir(ndk_dir, api, ['arch-mips64', 'usr', 'lib64']))

    return targets
