#!/usr/bin/env python3

import argparse
import os
import re
import shutil
import plistlib

p = os.path.join

symlinkable_re = re.compile(r"(?:runtimes|.+\.(?:dll|pdb|json))$", re.IGNORECASE)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--webview", action="store_true")
    parser.add_argument("--name", required=True)
    parser.add_argument("--directory", required=True)
    parser.add_argument("--apphost", required=True)
    parser.add_argument("--identifier", required=True)
    parser.add_argument("--icon")

    args = parser.parse_args()
    dir: str = args.directory
    name: str = args.name

    # Create base app directory structure.
    os.makedirs(p(dir, f"{name}.app", "Contents", "MacOS"), exist_ok=True)
    os.makedirs(p(dir, f"{name}.app", "Contents", "Resources"), exist_ok=True)
    os.makedirs(p(dir, f"{name}.app", "Contents", "Frameworks"), exist_ok=True)

    # Copy apphost
    dest_apphost = p(dir, f"{name}.app", "Contents", "MacOS", name)
    shutil.copy(p(dir, args.apphost), dest_apphost)

    # Symlink most files in the bin dir.
    symlink_files(args.directory, p(dir, f"{name}.app", "Contents", "MacOS"), "")

    # Copy icon
    if args.icon:
        shutil.copy(args.icon, p(dir, f"{name}.app", "Contents", "Resources", "icon.icns"))

    # Write plist
    plist_dat = {
        "CFBundleName": name,
        "CFBundleDisplayName": name,
        "CFBundleIdentifier": args.identifier,
        "CFBundleIconFile": "icon",
        "CFBundleExecutable": name,
        "LSApplicationCategoryType": "public.app-category.games"
    }

    with open(p(dir, f"{name}.app", "Contents", "Info.plist"), "wb") as f:
        plistlib.dump(plist_dat, f)

    if args.webview:
        chromium_framework_path = p(dir, f"{name}.app", "Contents", "Frameworks", "Chromium Embedded Framework.framework")
        if not os.path.exists(chromium_framework_path):
            os.symlink("../../../Chromium Embedded Framework.framework", chromium_framework_path)

        create_webview_helper(dir, name, args.identifier, None, None)
        create_webview_helper(dir, name, args.identifier, "GPU", "gpu")
        create_webview_helper(dir, name, args.identifier, "Renderer", "renderer")
        create_webview_helper(dir, name, args.identifier, "Alerts", "alerts")

def create_webview_helper(dir: str, name: str, identifier: str, suffix: str | None, identifier_suffix: str | None):
    helper_name = f"{name} helper"
    if suffix is not None:
        helper_name += f" ({suffix})"

    sub_app_path = p(dir, f"{name}.app", "Contents", "Frameworks", f"{helper_name}.app")

    os.makedirs(p(sub_app_path, "Contents", "MacOS"), exist_ok=True)
    os.makedirs(p(sub_app_path, "Contents", "Resources"), exist_ok=True)

    # Copy apphost for Robust.Client.WebView
    shutil.copy(p(dir, "Robust.Client.WebView"), p(sub_app_path, "Contents", "MacOS", helper_name))

    # Symlink files
    symlink_files(dir, p(sub_app_path, "Contents", "MacOS"), "../../../")

    helper_identifier = f"{identifier}.cef.{identifier_suffix}"

    if identifier_suffix is not None:
        helper_identifier += "." + identifier_suffix

    plist_dat = {
        "CFBundleName": f"{name} helper",
        "CFBundleDisplayName": f"{name} helper",
        "CFBundleIdentifier": f"{identifier}.cef.{identifier_suffix}",
        "CFBundleExecutable": helper_name
    }

    with open(p(sub_app_path, "Contents", "Info.plist"), "wb") as f:
        plistlib.dump(plist_dat, f)

def symlink_files(src_dir: str, dest_dir: str, relative: str):
    for file in os.listdir(src_dir):
        if not symlinkable_re.match(file):
            continue

        src_path = p(src_dir, file)
        dest_symlink = p(dest_dir, file)
        if os.path.isdir(src_path):
            # Symlink directories
            if not os.path.islink(dest_symlink):
                os.symlink(f"../../../{relative}{file}", dest_symlink)
        else:
            # Hardlink files
            # (so that .NET doesn't report the real file path for assembly locations)
            try:
                os.remove(dest_symlink)
            except FileNotFoundError:
                pass # Fine

            os.link(src_path, dest_symlink)


main()
