#!/usr/bin/env python3

import os
import shutil
import subprocess
import sys
import zipfile
import argparse

from typing import List, Optional

try:
    from colorama import init, Fore, Style
    init()

except ImportError:
    # Just give an empty string for everything, no colored logging.
    class ColorDummy(object):
        def __getattr__(self, name):
            return ""

    Fore = ColorDummy()
    Style = ColorDummy()


p = os.path.join

SHARED_IGNORED_RESOURCES = {
    "ss13model.7z",
    "ResourcePack.zip",
    "buildResourcePack.py",
    "CONTENT_GOES_HERE",
    ".gitignore",
    ".directory",
    ".DS_Store"
}

CLIENT_IGNORED_RESOURCES = {
    "Maps",
    "emotes.xml",
    "Groups"
}

CLIENT_CONTENT_ASSEMBLIES = [
    "Content.Client",
    "Content.Shared"
]

def main() -> None:
    parser = argparse.ArgumentParser(
        description="Packages the SS14 client.")

    parser.add_argument("--skip-build",
                        action="store_true",
                        help=argparse.SUPPRESS)

    args = parser.parse_args()
    skip_build = args.skip_build

    if os.path.exists("release"):
        pass
    #    print(Fore.BLUE + Style.DIM +
    #          "Cleaning old release packages (release/)..." + Style.RESET_ALL)
    #    shutil.rmtree("release")
    else:
        os.mkdir("release")

    if not skip_build:
        wipe_bin()

    build(skip_build)


def wipe_bin():
    print(Fore.BLUE + Style.DIM +
          "Clearing old build artifacts (if any)..." + Style.RESET_ALL)

    if os.path.exists("bin"):
        shutil.rmtree("bin")


def build(skip_build: bool) -> None:
    # Run a full build.
    print(Fore.GREEN + "Building project..." + Style.RESET_ALL)

    if not skip_build:
        subprocess.run([
            "dotnet",
            "build",
            p("Content.Client", "Content.Client.csproj"),
            "-c", "Release",
            "--nologo",
            "/v:m",
            "/t:Rebuild",
            "/p:FullRelease=True"
        ], check=True)

    print(Fore.GREEN + "Packaging client..." + Style.RESET_ALL)

    client_zip = zipfile.ZipFile(
        p("release", "SS14.Client.zip"), "w",
        compression=zipfile.ZIP_DEFLATED)

    copy_resources(client_zip)
    copy_content_assemblies("Assemblies", client_zip)
    # Cool we're done.
    client_zip.close()


def copy_resources(zipf):
    ignore_set = SHARED_IGNORED_RESOURCES.union(CLIENT_IGNORED_RESOURCES)

    do_resource_copy("Resources", zipf, ignore_set)


def do_resource_copy(source, zipf, ignore_set):
    for filename in os.listdir(source):
        if filename in ignore_set:
            continue

        path = p(source, filename)
        target_path = filename
        if os.path.isdir(path):
            copy_dir_into_zip(path, target_path, zipf)

        else:
            zipf.write(path, target_path)


def zip_entry_exists(zipf, name):
    try:
        # Trick ZipInfo into sanitizing the name for us so this awful module stops spewing warnings.
        zinfo = zipfile.ZipInfo.from_file("Resources", name)
        zipf.getinfo(zinfo.filename)
    except KeyError:
        return False
    return True


def copy_dir_into_zip(directory, basepath, zipf):
    if basepath and not zip_entry_exists(zipf, basepath):
        zipf.write(directory, basepath)

    for root, _, files in os.walk(directory):
        relpath = os.path.relpath(root, directory)
        if relpath != "." and not zip_entry_exists(zipf, p(basepath, relpath)):
            zipf.write(root, p(basepath, relpath))

        for filename in files:
            zippath = p(basepath, relpath, filename)
            filepath = p(root, filename)

            message = "{dim}{diskroot}{sep}{zipfile}{dim} -> {ziproot}{sep}{zipfile}".format(
                sep=os.sep + Style.NORMAL,
                dim=Style.DIM,
                diskroot=directory,
                ziproot=zipf.filename,
                zipfile=os.path.normpath(zippath))

            print(Fore.CYAN + message + Style.RESET_ALL)
            zipf.write(filepath, zippath)


def copy_content_assemblies(target, zipf):
    files = []

    source_dir = p("bin", "Content.Client")
    base_assemblies = CLIENT_CONTENT_ASSEMBLIES

    # Include content assemblies.
    for asm in base_assemblies:
        files.append(asm + ".dll")
        # If PDB available, include it aswell.
        pdb_path = asm + ".pdb"
        if os.path.exists(p(source_dir, pdb_path)):
            files.append(pdb_path)

    # Write assemblies dir if necessary.
    if not zip_entry_exists(zipf, target):
        zipf.write(".", target)

    for x in files:
        zipf.write(p(source_dir, x), f"{target}/{x}")


if __name__ == '__main__':
    main()
