from __future__ import print_function
import filecmp
import glob
import itertools
import os
import sys
import sysconfig
import tempfile
import unittest


project_dir = os.path.abspath(os.path.join(__file__, '..', '..', '..'))
test_dir = os.getenv("BROTLI_TESTS_PATH")
BRO_ARGS = [os.getenv("BROTLI_WRAPPER")]

# Fallbacks
if test_dir is None:
  test_dir = os.path.join(project_dir, 'tests')
if BRO_ARGS[0] is None:
  python_exe = sys.executable or 'python'
  bro_path = os.path.join(project_dir, 'python', 'bro.py')
  BRO_ARGS = [python_exe, bro_path]

# Get the platform/version-specific build folder.
# By default, the distutils build base is in the same location as setup.py.
platform_lib_name = 'lib.{platform}-{version[0]}.{version[1]}'.format(
    platform=sysconfig.get_platform(), version=sys.version_info)
build_dir = os.path.join(project_dir, 'bin', platform_lib_name)

# Prepend the build folder to sys.path and the PYTHONPATH environment variable.
if build_dir not in sys.path:
    sys.path.insert(0, build_dir)
TEST_ENV = os.environ.copy()
if 'PYTHONPATH' not in TEST_ENV:
    TEST_ENV['PYTHONPATH'] = build_dir
else:
    TEST_ENV['PYTHONPATH'] = build_dir + os.pathsep + TEST_ENV['PYTHONPATH']

TESTDATA_DIR = os.path.join(test_dir, 'testdata')

TESTDATA_FILES = [
    'empty',  # Empty file
    '10x10y',  # Small text
    'alice29.txt',  # Large text
    'random_org_10k.bin',  # Small data
    'mapsdatazrh',  # Large data
    'ukkonooa',  # Poem
    'cp1251-utf16le',  # Codepage 1251 table saved in UTF16-LE encoding
    'cp852-utf8',  # Codepage 852 table saved in UTF8 encoding
]

# Some files might be missing in a lightweight sources pack.
TESTDATA_PATH_CANDIDATES = [
    os.path.join(TESTDATA_DIR, f) for f in TESTDATA_FILES
]

TESTDATA_PATHS = [
    path for path in TESTDATA_PATH_CANDIDATES if os.path.isfile(path)
]

TESTDATA_PATHS_FOR_DECOMPRESSION = glob.glob(
    os.path.join(TESTDATA_DIR, '*.compressed'))

TEMP_DIR = tempfile.mkdtemp()


def get_temp_compressed_name(filename):
    return os.path.join(TEMP_DIR, os.path.basename(filename + '.bro'))


def get_temp_uncompressed_name(filename):
    return os.path.join(TEMP_DIR, os.path.basename(filename + '.unbro'))


def bind_method_args(method, *args, **kwargs):
    return lambda self: method(self, *args, **kwargs)


def generate_test_methods(test_case_class,
                          for_decompression=False,
                          variants=None):
    # Add test methods for each test data file.  This makes identifying problems
    # with specific compression scenarios easier.
    if for_decompression:
        paths = TESTDATA_PATHS_FOR_DECOMPRESSION
    else:
        paths = TESTDATA_PATHS
    opts = []
    if variants:
        opts_list = []
        for k, v in variants.items():
            opts_list.append([r for r in itertools.product([k], v)])
        for o in itertools.product(*opts_list):
            opts_name = '_'.join([str(i) for i in itertools.chain(*o)])
            opts_dict = dict(o)
            opts.append([opts_name, opts_dict])
    else:
        opts.append(['', {}])
    for method in [m for m in dir(test_case_class) if m.startswith('_test')]:
        for testdata in paths:
            for (opts_name, opts_dict) in opts:
                f = os.path.splitext(os.path.basename(testdata))[0]
                name = 'test_{method}_{options}_{file}'.format(
                    method=method, options=opts_name, file=f)
                func = bind_method_args(
                    getattr(test_case_class, method), testdata, **opts_dict)
                setattr(test_case_class, name, func)


class TestCase(unittest.TestCase):

    def tearDown(self):
        for f in TESTDATA_PATHS:
            try:
                os.unlink(get_temp_compressed_name(f))
            except OSError:
                pass
            try:
                os.unlink(get_temp_uncompressed_name(f))
            except OSError:
                pass

    def assertFilesMatch(self, first, second):
        self.assertTrue(
            filecmp.cmp(first, second, shallow=False),
            'File {} differs from {}'.format(first, second))
