#!/usr/bin/env python

import atexit
import glob
import os
import re
import shutil
import sys
import tempfile
import textwrap

from fabric.api import env, local, hide
from fabric.context_managers import lcd, settings, shell_env
from fabric.contrib.console import confirm
from fabric.contrib.files import exists
from fabric.decorators import runs_once
from fabric.utils import abort
from fabric import colors

sys.path.append('scripts')
from string_scripts.confirm_ready_for_release import confirm_ready_for_release as _confirm_ready_for_release


# --- Configuration ---------------------------------------------------------

env.verbose = False
env.libname = "libCardIO.a"
env.developer_dir = local("xcode-select -p", capture=True)

# --- Tasks -----------------------------------------------------------------


def verbose(be_verbose=True):
    """
    Makes all following tasks more verbose.
    """
    env.verbose = be_verbose


def developer_dir(dir):
    """
    Sets DEVELOPER_DIR environment variable to correct Xcode
    For example, `fab developer_dir:"/Applications/Xcode6.2.app"
    """
    if os.path.exists(dir):
        env.developer_dir = dir
    else:
        print(colors.red("{dir} is not a valid path".format(dir=dir), bold=True))
        sys.exit(1)


def _locate(fileset, root=os.curdir):
    # based on http://code.activestate.com/recipes/499305-locating-files-throughout-a-directory-tree/
    """
    Locate supplied files in supplied root directory.
    """
    for path, dirs, files in os.walk(os.path.abspath(root)):
        for filename in files:
            if filename in fileset:
                yield os.path.join(path, filename)


def _add_version_to_header_file(version_str, file):
    lines = []
    for line in file.readlines():
        lines.append(line)
        m = re.match("^(//\s+)CardIO.*\.h$", line)
        if m:
            lines.append("{0}Version {1}\n".format(m.groups()[0], version_str))
            lines.append("//\n")
    file.seek(0)
    file.truncate()
    for line in lines:
        file.write(line)


def _version_str(show_dirty=False):
    git_describe_cmd = "git describe --match='iOS_[0-9]*.[0-9]*' --tags --always --dirty"
    version_str = local(git_describe_cmd, capture=True).strip()[4:]
    if not show_dirty:
        version_str = version_str.replace('-dirty', '')
    return version_str


def _copy(source_files, dest_dir):
    for public_header_file in source_files:
        with open(public_header_file, "rb") as in_file:
            contents = in_file.read()
        unused, header_filename = os.path.split(public_header_file)
        header_filename = os.path.join(dest_dir, header_filename)
        with open(header_filename, "wb") as out_file:
            out_file.write(contents)
        with open(header_filename, "r+") as out_file:
            _add_version_to_header_file(_version_str(), out_file)


def build(outdir=None, device_sdk=None, simulator_sdk=None, **kwargs):
    """
    Build card.io SDK.
    """
    print(colors.white("Setup", bold=True))

    to_hide = [] if env.verbose else ["stdout", "stderr", "running"]

    xcode_preprocessor_flags = {}

    if not outdir:
        message = """
                     You must provide outdir=<sdk output parent dir>
                     Example usage:
                       `fab build:outdir=~` - normal build
                       `fab build:outdir=~,SCAN_EXPIRY=0` - to disable the experimental expiry-scan feature
                  """
        abort(textwrap.dedent(message).format(**locals()))

    if _confirm_ready_for_release("assets/strings"):
		sys.exit(1)

    outdir = os.path.abspath(os.path.expanduser(outdir))
    print colors.yellow("Will save release sdk to {outdir}".format(outdir=outdir))
    out_subdir = "card.io_ios_sdk_{0}".format(_version_str(show_dirty=True))

    xcode_preprocessor_flags.update(kwargs)
    formatted_xcode_preprocessor_flags = " ".join("{k}={v}".format(k=k, v=v) for k, v in xcode_preprocessor_flags.iteritems())
    extra_xcodebuild_settings = "GCC_PREPROCESSOR_DEFINITIONS='$(value) {formatted_xcode_preprocessor_flags}'".format(**locals())

    device_sdk = device_sdk or "iphoneos"
    simulator_sdk = simulator_sdk or "iphonesimulator"

    arch_to_sdk = (
                   ("i386", simulator_sdk),
                   ("x86_64", simulator_sdk)
                  )

    with settings(hide(*to_hide)):
        icc_root = local("git rev-parse --show-toplevel", capture=True)

    temp_dir = tempfile.mkdtemp() + os.sep
    atexit.register(shutil.rmtree, temp_dir, True)

    print(colors.white("Preparing dmz", bold=True))
    with settings(hide(*to_hide)):
        with lcd(os.path.join(icc_root, "dmz")):
            dmz_all_filename = os.path.join("dmz", "dmz_all.cpp")
            with open(dmz_all_filename) as f:
                old_dmz_all = f.read()
            local("fab concat")
            with open(dmz_all_filename) as f:
                new_dmz_all = f.read()
            if old_dmz_all != new_dmz_all:
                print(colors.red("WARNING: dmz_all.h was not up to date!", bold=True))
    
    print(colors.white("Building", bold=True))
    print(colors.white("Using temp dir {temp_dir}".format(**locals())))
    print(colors.white("Using extra Xcode flags: {formatted_xcode_preprocessor_flags}".format(**locals())))
    print(colors.white("Using developer directory: {}".format(env.developer_dir)))

    with lcd(icc_root):
        with shell_env(DEVELOPER_DIR=env.developer_dir):
            with settings(hide(*to_hide)):
                lipo_build_dirs = {}
                build_config = "Release"
                arch_build_dirs = {}

                # Build the Archive release
                print(colors.blue("({build_config}) Building Archive (arm* architectures specified in build config)".format(**locals())))
                base_xcodebuild_command = "xcrun xcodebuild -scheme \"CardIO Static Library\" -target CardIO-static -configuration {build_config} archive".format(**locals())
                build_dir = os.path.join(temp_dir, build_config, "Archive")
                arch_build_dirs["archive"] = build_dir
                os.makedirs(build_dir)
                parallelize = "" if env.verbose else "-parallelizeTargets"  # don't parallelize verbose builds, it's hard to read the output
                build_cmd = "{base_xcodebuild_command} {parallelize} CONFIGURATION_BUILD_DIR={build_dir}  {extra_xcodebuild_settings}".format(**locals())
                local(build_cmd)

                for arch, sdk in arch_to_sdk:
                    print(colors.blue("({build_config}) Building {arch}".format(**locals())))

                    base_xcodebuild_command = "xcrun xcodebuild OTHER_CFLAGS='-fembed-bitcode' -target CardIO-static -arch {arch} -sdk {sdk} -configuration {build_config}".format(**locals())

                    clean_cmd =  "{base_xcodebuild_command} clean".format(**locals())
                    local(clean_cmd)

                    build_dir = os.path.join(temp_dir, build_config, arch)
                    arch_build_dirs[arch] = build_dir
                    os.makedirs(build_dir)
                    parallelize = "" if env.verbose else "-parallelizeTargets"  # don't parallelize verbose builds, it's hard to read the output
                    build_cmd = "{base_xcodebuild_command} {parallelize} CONFIGURATION_BUILD_DIR={build_dir}  {extra_xcodebuild_settings}".format(**locals())
                    local(build_cmd)

                print(colors.blue("({build_config}) Lipoing".format(**locals())))
                lipo_dir = os.path.join(temp_dir, build_config, "universal")
                lipo_build_dirs[build_config] = lipo_dir
                os.makedirs(lipo_dir)
                arch_build_dirs["universal"] = lipo_dir
                # in Xcode 4.5 GM, xcrun selects the wrong lipo to use, so circumventing xcrun for now :(
                lipo_cmd = "`xcode-select -print-path`/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo " \
                           "           {archive}/{libname}" \
                           "           -arch i386 {i386}/{libname}" \
                           "           -arch x86_64 {x86_64}/{libname}" \
                           "           -create" \
                           "           -output {universal}/{libname}".format(libname=env.libname, **arch_build_dirs)
                local(lipo_cmd)

                print(colors.blue("({build_config}) Stripping debug symbols".format(**locals())))
                strip_cmd = "xcrun strip -S {universal}/{libname}".format(libname=env.libname, **arch_build_dirs)
                local(strip_cmd)

                out_subdir_suffix = "_".join("{k}-{v}".format(k=k, v=v) for k, v in kwargs.iteritems())
                if out_subdir_suffix:
                    out_subdir_suffix = "_" + out_subdir_suffix
                out_subdir += out_subdir_suffix
                sdk_dir = os.path.join(outdir, out_subdir)

                print(colors.white("Assembling release SDK in {sdk_dir}".format(sdk_dir=sdk_dir), bold=True))
                if os.path.isdir(sdk_dir):
                    shutil.rmtree(sdk_dir)
                cardio_dir = os.path.join(sdk_dir, "CardIO")
                os.makedirs(cardio_dir)

                header_files = glob.glob(os.path.join("CardIO_Public_API", "*.h"))
                _copy(header_files, cardio_dir)

                opencv_libraries = glob.glob(os.path.join("opencv_device/lib/", "*.a"))
                _copy(opencv_libraries, cardio_dir)

                libfile = os.path.join(lipo_build_dirs["Release"], env.libname)
                shutil.copy2(libfile, cardio_dir)

                release_dir = os.path.join(icc_root, "Release")
                shutil.copy2(os.path.join(release_dir, "release_notes.txt"), sdk_dir)
                shutil.copy2(os.path.join(release_dir, "CardIO.podspec"), sdk_dir)
                shutil.copy2(os.path.join(release_dir, "acknowledgments.md"), sdk_dir)
                shutil.copy2(os.path.join(release_dir, "LICENSE.md"), sdk_dir)
                shutil.copy2(os.path.join(release_dir, "README.md"), sdk_dir)
                shutil.copy2(os.path.join(release_dir, "CardIO/CardIO.m"), os.path.join(sdk_dir, "CardIO"))
                shutil.copytree(os.path.join(release_dir, "SampleApp"), os.path.join(sdk_dir, "SampleApp"), ignore=shutil.ignore_patterns(".DS_Store"))
                shutil.copytree(os.path.join(release_dir, "SampleApp-Swift"), os.path.join(sdk_dir, "SampleApp-Swift"), ignore=shutil.ignore_patterns(".DS_Store"))