#!/usr/bin/env python3

from __future__ import print_function

import os
import re
import subprocess
import sys
import unittest

from .compat import TemporaryDirectory, makedirs
from .ndk_toolchain import create_targets


SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
VNDK_DEF_TOOL = os.path.join(SCRIPT_DIR, '..', 'vndk_definition_tool.py')

INPUT_DIR = os.path.join(SCRIPT_DIR, 'testdata', 'test_elfdump', 'input')
EXPECTED_DIR = os.path.join(SCRIPT_DIR, 'testdata', 'test_elfdump', 'expected')
test_dir_base = None


def run_elf_dump(path):
    cmd = [sys.executable, VNDK_DEF_TOOL, 'elfdump', path]
    return subprocess.check_output(cmd, universal_newlines=True)


class ELFDumpTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.targets = create_targets()

        if test_dir_base:
            cls.test_dir_base = test_dir_base
        else:
            cls.tmp_dir = TemporaryDirectory()
            cls.test_dir_base = cls.tmp_dir.name

        cls._build_fixtures(cls.target_name)


    @classmethod
    def tearDownClass(cls):
        if not test_dir_base:
            cls.tmp_dir.cleanup()


    @classmethod
    def _build_fixtures(cls, target_name):
        target = cls.targets[target_name]

        cls.expected_dir = os.path.join(EXPECTED_DIR, target_name)
        cls.test_dir = os.path.join(cls.test_dir_base, target_name)

        makedirs(cls.test_dir, exist_ok=True)

        # Compile main.o.
        src_file = os.path.join(INPUT_DIR, 'main.c')
        obj_file = os.path.join(cls.test_dir, 'main.o')
        target.compile(obj_file, src_file, [])

        # Link main.out.
        out_file = os.path.join(cls.test_dir, 'main.out')
        target.link(out_file, [obj_file], ['-ldl', '-lc', '-lstdc++'])

        # Compile test.o.
        src_file = os.path.join(INPUT_DIR, 'test.c')
        obj_file = os.path.join(cls.test_dir, 'test.o')
        target.compile(obj_file, src_file, [])

        # Link libtest.so.
        out_file = os.path.join(cls.test_dir, 'libtest.so')
        target.link(out_file, [obj_file], ['-shared', '-lc'])

        # Link libtest-rpath.so.
        out_file = os.path.join(cls.test_dir, 'libtest-rpath.so')
        target.link(out_file, [obj_file],
                    ['-shared', '-lc', '-Wl,-rpath,$ORIGIN/../lib',
                     '-Wl,--disable-new-dtags'])

        # Link libtest-rpath-multi.so.
        out_file = os.path.join(cls.test_dir, 'libtest-rpath-multi.so')
        target.link(out_file, [obj_file],
                    ['-shared', '-lc', '-Wl,-rpath,/system/lib:/vendor/lib',
                     '-Wl,--disable-new-dtags'])

        # Link libtest-runpath.so.
        out_file = os.path.join(cls.test_dir, 'libtest-runpath.so')
        target.link(out_file, [obj_file],
                    ['-shared', '-lc', '-Wl,-rpath,$ORIGIN/../lib',
                     '-Wl,--enable-new-dtags'])

        # Link libtest-runpath-multi.so.
        out_file = os.path.join(cls.test_dir, 'libtest-runpath-multi.so')
        target.link(out_file, [obj_file],
                    ['-shared', '-lc', '-Wl,-rpath,/system/lib:/vendor/lib',
                     '-Wl,--enable-new-dtags'])


    def _remove_size_lines(self, lines):
        """Remove file size information because they may vary."""
        prefixes = (
            'FILE_SIZE\t',
            'RO_SEG_FILE_SIZE\t',
            'RO_SEG_MEM_SIZE\t',
            'RW_SEG_FILE_SIZE\t',
            'RW_SEG_MEM_SIZE\t',
        )
        patt = re.compile('|'.join('(?:' + re.escape(x) +')' for x in prefixes))
        return [line for line in lines if not patt.match(line)]


    def _assert_equal_to_file(self, expected_file_name, actual):
        actual = actual.splitlines(True)
        expected_file_path = os.path.join(self.expected_dir, expected_file_name)
        with open(expected_file_path, 'r') as f:
            expected = f.readlines()
        self.assertEqual(self._remove_size_lines(expected),
                         self._remove_size_lines(actual))


    def _test_main_out(self):
        out_file = os.path.join(self.test_dir, 'main.out')
        self._assert_equal_to_file('main.out.txt', run_elf_dump(out_file))


    def _test_libtest(self, expected_file_name, lib_name):
        lib_file = os.path.join(self.test_dir, lib_name)
        self._assert_equal_to_file(expected_file_name, run_elf_dump(lib_file))


def create_target_test(target_name):
    def test_main(self):
        self._test_main_out()

    def test_libtest(self):
        self._test_libtest('libtest.so.txt', 'libtest.so')

    def test_libtest_rpath(self):
        self._test_libtest('libtest-rpath.so.txt', 'libtest-rpath.so')

    def test_libtest_rpath_multi(self):
        self._test_libtest('libtest-rpath-multi.so.txt',
                           'libtest-rpath-multi.so')

    def test_libtest_runpath(self):
        self._test_libtest('libtest-runpath.so.txt', 'libtest-runpath.so')

    def test_libtest_runpath_multi(self):
        self._test_libtest('libtest-runpath-multi.so.txt',
                           'libtest-runpath-multi.so')

    class_name = 'ELFDumpTest_' + target_name
    globals()[class_name] = type(
        class_name, (ELFDumpTest,),
        dict(test_main=test_main,
             test_libtest=test_libtest,
             test_libtest_rpath=test_libtest_rpath,
             test_libtest_rpath_multi=test_libtest_rpath_multi,
             test_libtest_runpath=test_libtest_runpath,
             test_libtest_runpath_multi=test_libtest_runpath_multi,
             target_name=target_name))


for target in ('arm', 'arm64', 'mips', 'mips64', 'x86', 'x86_64'):
    create_target_test(target)
