#!/usr/bin/python
# -*- coding: utf-8 -*-

import logging
import os
import platform
import stat
import subprocess
import sys
import tempfile
import uuid
from collections import OrderedDict
from contextlib import contextmanager
from packaging import version

import colorama
from conans.client.tools.scm import Git
from tabulate import tabulate
from conans import __version__ as conan_version


FAIL_FAST = os.getenv("FAIL_FAST", "0").lower() in ["1", "y", "yes", "true"]
LOGGING_LEVEL = int(os.getenv("CONAN_LOGGING_LEVEL", logging.INFO))
logging.basicConfig(format='%(asctime)s [%(levelname)s]: %(message)s', level=LOGGING_LEVEL)


def is_appveyor():
    return os.getenv("APPVEYOR", False)

def appveyor_image():
    return os.getenv("APPVEYOR_BUILD_WORKER_IMAGE","")
    

@contextmanager
def chdir(dir_path):
    current = os.getcwd()
    os.chdir(dir_path)
    logging.debug("cd {}".format(dir_path))
    try:
        yield
    finally:
        logging.debug("cd {}".format(current))
        os.chdir(current)


def writeln_console(message):
    sys.stderr.flush()
    sys.stdout.write(message)
    sys.stdout.write('\n')
    sys.stdout.flush()


def get_examples_to_skip(current_version):
    skip = []
    # Given the Conan version, some examples are skipped
    required_conan = {
        version.parse("1.24.0"): [
            './libraries/folly/basic',  # Requires fix related to import cppstd_flag in boost
            './libraries/poco/md5',  # Uses get_safe() with 3 arguments
            './features/deployment',  # Fails because of poco requirement
            ],
        version.parse("1.22.0"): [
            './libraries/dear-imgui/basic',  # Requires fix related to CMake link order/targets
            ],
        }
    for v, examples in required_conan.items():
        if current_version < v:
            skip.extend(examples)

    # Some binaries are not available # TODO: All the examples should have binaries available
    if is_appveyor():  # Folly is not availble!! and appveyor_image() == "Visual Studio 2019":
        skip.extend(['./libraries/folly/basic', ])
        skip.extend(['./features/makefiles', ])
        # waf does not support Visual Studio 2019 for 2.0.19
        if appveyor_image() == "Visual Studio 2019":
            skip.extend(['./features/integrate_build_system', ])

    return [os.path.normpath(it) for it in skip]


def get_build_list():
    skip_examples = get_examples_to_skip(current_version=version.parse(conan_version))

    builds = []
    script = "build.bat" if platform.system() == "Windows" else "build.sh"
    skip_folders = [os.path.normpath(it) for it in ['./.ci', './.git', './.tox']]
    for root, dirs, files in os.walk('.'):
        root = os.path.normpath(root)
        if root in skip_folders:
            dirs[:] = []
            continue
        if root in skip_examples:
            dirs[:] = []
            sys.stdout.write("Skip {!r} example\n".format(root))
            continue

        # Look for 'build' script, prefer 'build.py' over all of them
        build = [it for it in files if "build.py" in it]
        if not build:
            build = [it for it in files if os.path.basename(it) == script]
        
        if build:
            builds.append(os.path.join(root, build[0]))
            dirs[:] = []
            continue
    return builds


def chmod_x(script):
    logging.debug("chmod +x {}".format(script))
    st = os.stat(script)
    os.chmod(script, st.st_mode | stat.S_IEXEC)


def get_conan_env(script):
    temp_folder = os.path.join(tempfile.gettempdir(), str(uuid.uuid4())[:4])
    os.environ["CONAN_USER_HOME"] = temp_folder
    logging.debug("CONAN_USER_HOME: {}".format(temp_folder))
    return os.environ


def configure_profile(env):
    subprocess.check_output("conan profile new default --detect",
                            stderr=subprocess.STDOUT,
                            shell=True,
                            env=env)
    if platform.system() == "Linux":
        subprocess.check_output("conan profile update settings.compiler.libcxx=libstdc++11 default",
                                stderr=subprocess.STDOUT,
                                shell=True,
                                env=env)


def print_build(script):
    dir_name = os.path.dirname(script)
    dir_name = dir_name[2:] if dir_name.startswith('.') else dir_name
    writeln_console("================================================================")
    writeln_console("* " + colorama.Style.BRIGHT + "{}".format(dir_name.upper()))
    writeln_console("================================================================")


@contextmanager
def ensure_cache_preserved():
    cache_directory = os.environ["CONAN_USER_HOME"]

    git = Git(folder=cache_directory)
    with open(os.path.join(cache_directory, '.gitignore'), 'w') as gitignore:
        gitignore.write(".conan/data/")
    git.run("init .")
    git.run("add .")

    try:
        yield
    finally:
        r = git.run("diff")
        if r:
            writeln_console(">>> " + colorama.Fore.RED + "This is example modifies the cache!")
            writeln_console(r)
            raise Exception("Example modifies cache!")


@contextmanager
def ensure_python_environment_preserved():
    freeze = subprocess.check_output("{} -m pip freeze".format(sys.executable), stderr=subprocess.STDOUT, shell=True).decode()
    try:
        yield
    finally:
        freeze_after = subprocess.check_output("{} -m pip freeze".format(sys.executable), stderr=subprocess.STDOUT, shell=True).decode()
        if freeze != freeze_after:
            writeln_console(">>> " + colorama.Fore.RED + "This example modifies the Python dependencies!")
            removed = set(freeze.splitlines()) - set(freeze_after.splitlines())
            added = set(freeze_after.splitlines()) - set(freeze.splitlines())
            for it in removed:
                writeln_console("- " + it)
            for it in added:
                writeln_console("+ " + it)
            raise Exception("Example modifies Python environment!")


def run_scripts(scripts):
    results = OrderedDict.fromkeys(scripts, '')
    for script in scripts:
        chmod_x(script)
        abspath = os.path.abspath(script)
        env = get_conan_env(script)
        configure_profile(env)
        with chdir(os.path.dirname(script)):
            print_build(script)
            build_script = [sys.executable, abspath] if abspath.endswith(".py") else abspath
            
            # Need to initialize the cache with default files if they are not already there
            try:
                subprocess.call(['conan', 'install', 'foobar/foobar@conan/stable'], env=env,
                                stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            except:
                pass
 
            with ensure_python_environment_preserved():
                with ensure_cache_preserved():
                    result = subprocess.call(build_script, env=env)
                
            results[script] = result
            if result != 0 and FAIL_FAST:
                break
    return results


def print_results(results):
    columns = []
    for build, result in results.items():
        build_name = os.path.dirname(build).upper()
        build_name = build_name[2:] if build_name.startswith(".") else build_name
        columns.append([build_name, get_result_message(result)])
    writeln_console("\n")
    writeln_console(tabulate(columns, headers=["CONAN EXAMPLE", "RESULT"], tablefmt="grid"))


def get_result_message(result):
    if result == 0:
        return colorama.Fore.GREEN + "SUCCESS" + colorama.Style.RESET_ALL
    return colorama.Fore.RED + "FAILURE" + colorama.Style.RESET_ALL


def validate_results(results):
    for value in results.values():
        if value != 0:
            sys.exit(value)


if __name__ == "__main__":
    colorama.init(autoreset=True)
    scripts = get_build_list()
    results = run_scripts(scripts)
    print_results(results)
    validate_results(results)