#!/usr/bin/env python3
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

from __future__ import annotations
import argparse
from itertools import islice
import os
from pathlib import Path
from typing import NamedTuple

from common import (is_linux_platform, is_mac_platform, is_windows_platform,
                    download_and_extract, check_print_call)


class BuildParams(NamedTuple):
    src_path: Path
    build_path: Path
    target_path: Path
    make_command: str
    universal: bool = False
    platform: str | None = None


def qt_static_configure_options() -> list[str]:
    return ['-release', '-opensource', '-confirm-license', '-accessibility',
            '-no-gui',
            '-no-openssl',
            '-no-feature-sql',
            '-qt-zlib',
            '-nomake', 'examples',
            '-nomake', 'tests',
            '-static'] + qt_static_platform_configure_options()


def qt_static_platform_configure_options() -> list[str]:
    if is_windows_platform():
        return ['-static-runtime', '-no-icu']
    if is_linux_platform():
        return ['-no-icu', '-no-glib', '-qt-zlib', '-qt-pcre', '-qt-doubleconversion']
    return []


def get_qt_src_path(qt_build_base: Path) -> Path:
    return qt_build_base / 'src'


def get_qt_build_path(qt_build_base: Path) -> Path:
    return qt_build_base / 'build'


def get_qt_install_path(qt_build_base: Path) -> Path:
    return qt_build_base / 'install'


def configure_qt(params: BuildParams, src: Path, build: Path, install: Path) -> None:
    build.mkdir(parents=True, exist_ok=True)
    configure = src / "configure"
    cmd = [str(configure), "-prefix", str(install)] + qt_static_configure_options()
    if params.platform:
        cmd.extend(['-platform', params.platform])
    if params.universal:
        cmd.extend(['--', '-DCMAKE_OSX_ARCHITECTURES=x86_64;arm64'])
    check_print_call(cmd, cwd=build)


def build_qt(params: BuildParams, build: Path) -> None:
    check_print_call([params.make_command], cwd=build)


def install_qt(params: BuildParams, build: Path) -> None:
    check_print_call([params.make_command, 'install'], cwd=build)


def build_sdktool_impl(params: BuildParams, qt_install_path: Path) -> None:
    params.build_path.mkdir(parents=True, exist_ok=True)
    cmake_args = [
        'cmake', '-DCMAKE_PREFIX_PATH=' + str(qt_install_path), '-DCMAKE_BUILD_TYPE=Release'
    ]
    # force MSVC on Windows, because it looks for GCC in the PATH first,
    # even if MSVC is first mentioned in the PATH...
    # TODO would be nicer if we only did this if cl.exe is indeed first in the PATH
    if is_windows_platform():
        cmake_args += ['-DCMAKE_C_COMPILER=cl', '-DCMAKE_CXX_COMPILER=cl']
        cmake_args += ['-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded']

    if params.universal:
        cmake_args += ['-DCMAKE_OSX_ARCHITECTURES=x86_64;arm64']

    cmd = cmake_args + ['-G', 'Ninja', str(params.src_path)]
    check_print_call(cmd, cwd=params.build_path)
    check_print_call(['cmake', '--build', '.'], cwd=params.build_path)
    cmd = ['cmake', '--install', '.', '--prefix', str(params.target_path)]
    check_print_call(cmd, cwd=params.build_path)


def sign_sdktool(params: BuildParams,
                 environment: dict[str, str]) -> None:
    signing_identity = environment.get('SIGNING_IDENTITY')
    if not is_mac_platform() or not signing_identity:
        return
    check_print_call(['codesign', '-o', 'runtime', '--force', '-s', signing_identity,
             '-v', 'sdktool'],
            cwd=params.target_path,
            env=environment)


def get_single_subdir(path: Path):
    entries = list(islice(path.iterdir(), 2))
    if len(entries) == 1:
        return path / entries[0]
    return path


def build_sdktool(
    qt_src_url: str,
    qt_build_base: Path,
    sdktool_src_path: Path,
    sdktool_build_path: Path,
    sdktool_target_path: Path,
    make_command: str,
    universal: bool = False,
    platform: str | None = None,
    environment: dict[str, str] | None = None
) -> None:
    if not environment:
        environment = os.environ.copy()
    params = BuildParams(
        src_path=sdktool_src_path,
        build_path=sdktool_build_path,
        target_path=sdktool_target_path,
        make_command=make_command,
        platform=platform,
        universal=universal
    )
    qt_src = get_qt_src_path(qt_build_base)
    qt_build = get_qt_build_path(qt_build_base)
    qt_install = get_qt_install_path(qt_build_base)
    download_and_extract([qt_src_url], qt_src, qt_build_base)
    qt_src = get_single_subdir(qt_src)
    configure_qt(params, qt_src, qt_build, qt_install)
    build_qt(params, qt_build)
    install_qt(params, qt_build)
    build_sdktool_impl(params, qt_install)
    sign_sdktool(params, environment)


def zip_sdktool(
    sdktool_target_path: Path, out_7zip: Path
) -> None:
    glob = "*.exe" if is_windows_platform() else "*"
    check_print_call(
        cmd=["7z", "a", str(out_7zip), glob],
        cwd=sdktool_target_path
    )


def get_arguments() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description='Build sdktool')
    parser.add_argument('--qt-url', help='URL to Qt sources', required=True)
    parser.add_argument(
        '--qt-build', help='Path that is used for building Qt', required=True, type=Path
    )
    parser.add_argument('--src', help='Path to sdktool sources', required=True, type=Path)
    parser.add_argument(
        '--build', help='Path that is used for building sdktool', required=True, type=Path
    )
    parser.add_argument(
        '--install', help='Path that is used for installing sdktool', required=True, type=Path
    )
    parser.add_argument('--make-command', help='Make command to use for Qt', required=True)
    parser.add_argument('--platform', help='Platform argument for configuring Qt',
                        required=False)
    parser.add_argument('--universal', help='Build universal binaries on macOS',
                        action='store_true', default=False, required=False)
    return parser.parse_args()


def main() -> None:
    args = get_arguments()
    build_sdktool(
        qt_src_url=args.qt_url,
        qt_build_base=args.qt_build,
        sdktool_src_path=args.src,
        sdktool_build_path=args.build,
        sdktool_target_path=args.install,
        make_command=args.make_command,
        platform=args.platform
    )


if __name__ == '__main__':
    main()
