# Copyright 2013-2019 High Fidelity, Inc.
# Copyright 2020-2022 Vircadia contributors.
# Copyright 2020-2022 Overte e.V.
# SPDX-License-Identifier: Apache-2.0

import hifi_utils
import hifi_android
import hashlib
import os
import platform
import re
import shutil
import tempfile
import json
import xml.etree.ElementTree as ET
import functools

# The way Qt is handled is a bit complicated, so I'm documenting it here.
#
# 1. User runs cmake
# 2. cmake calls prebuild.py, which is referenced in /CMakeLists.txt
# 3. prebuild.py calls this code.
# 4. hifi_qt.py determines how to handle cmake: do we need to download a package, and which?
# 4.a - Using system Qt
#       No download, most special paths are turned off.
#       We build in the same way a normal Qt program would.
# 4.b - Using an user-provided Qt build in a custom directory.
#       We just need to set the cmakePath to the right dir (qt5-install/lib/cmake)
# 4.c - Using a premade package.
#       We check the OS and distro and set qtUrl to the URL to download.
#       After this, it works on the same pathway as 4.b.
# 5. We write /qt.cmake, which contains paths that are passed down to SetupQt.cmake
#    The template for this file is in CMAKE_TEMPLATE just below this comment
#    and it sets the QT_CMAKE_PREFIX_PATH variable used by SetupQt.cmake.
# 6. cmake includes /qt.cmake receiving our information
#    In the case of system Qt, this step is skipped.
# 7. cmake runs SetupQt.cmake which takes care of the cmake parts of the Qt configuration.
#    In the case of system Qt, SetupQt.cmake is a no-op. It runs but exits immediately.
#
# The format for a prebuilt qt is a package containing a top-level directory named
# 'qt5-install', which contains the result of a "make install" from a build of the Qt source.

print = functools.partial(print, flush=True)

# Encapsulates the vcpkg system
class QtDownloader:
    CMAKE_TEMPLATE = """
# this file auto-generated by hifi_qt.py
get_filename_component(QT_CMAKE_PREFIX_PATH "{}" ABSOLUTE CACHE)
get_filename_component(QT_CMAKE_PREFIX_PATH_UNCACHED "{}" ABSOLUTE)

# If the cached cmake toolchain path is different from the computed one, exit
if(NOT (QT_CMAKE_PREFIX_PATH_UNCACHED STREQUAL QT_CMAKE_PREFIX_PATH))
    message(FATAL_ERROR "QT_CMAKE_PREFIX_PATH has changed, please wipe the build directory and rerun cmake")
endif()
"""
    def __init__(self, args):
        self.args = args
        self.configFilePath = os.path.join(args.build_root, 'qt.cmake')
        self.version = os.getenv('OVERTE_USE_QT_VERSION', '5.15.2')
        self.assets_url = hifi_utils.readEnviromentVariableFromFile(args.build_root, 'EXTERNAL_BUILD_ASSETS')

        # OS dependent information
        system = platform.system()

        qt_found = False
        system_qt = False

        # Here we handle the 3 possible cases of dealing with Qt:
        if bool(os.getenv('OVERTE_USE_SYSTEM_QT', False)):
            # 1. Using the system provided Qt. This is only recommended for Qt 5.15.0 and above,
            # as it includes a required fix on Linux.
            #
            # This path only works on Linux as neither Windows nor OSX ship Qt.

            if system != "Linux":
                raise Exception("Using the system Qt is only supported on Linux")

            self.path = None
            self.cmakePath = None

            qt_found = True
            system_qt = True

            if not self.args.quiet:
                print("Using system Qt")

        elif os.getenv('OVERTE_QT_PATH', "") != "":
            # 2. Using an user-provided directory.
            # OVERTE_QT_PATH must point to a directory with a Qt install in it.

            self.path = os.getenv('OVERTE_QT_PATH')
            self.fullPath = self.path
            self.cmakePath = os.path.join(self.fullPath, 'lib', 'cmake')

            qt_found = True

            if not self.args.quiet:
                print("Using Qt from " + self.fullPath)

        else:
            # 3. Using a pre-built Qt.
            #
            # This works somewhat differently from above, notice how path and fullPath are
            # used differently in this case.
            #
            # In the case of an user-provided directory, we just use the user-supplied directory.
            #
            # For a pre-built qt, however, we have to unpack it. The archive is required to contain
            # a qt5-install directory in it.

            self.path = os.path.expanduser("~/overte-files/qt")
            self.fullPath = os.path.join(self.path, 'qt5-install')
            self.cmakePath = os.path.join(self.fullPath, 'lib', 'cmake')

            if (not os.path.isdir(self.path)):
                os.makedirs(self.path)

            qt_found = os.path.isdir(self.fullPath)
            print("Using a packaged Qt")


        if not system_qt:
            if qt_found:
                # Sanity check, ensure we have a good cmake directory
                qt5_dir = os.path.join(self.cmakePath, "Qt5")
                if not os.path.isdir(qt5_dir):
                    raise Exception("Failed to find Qt5 directory under " + self.cmakePath + ". There should be a " + qt5_dir)
                else:
                    print("Qt5 check passed, found " + qt5_dir)

            # I'm not sure why this is needed. It's used by hifi_singleton.
            # Perhaps it stops multiple build processes from interferring?
            lockDir, lockName = os.path.split(self.path)
            lockName += '.lock'
            if not os.path.isdir(lockDir):
                os.makedirs(lockDir)

            self.lockFile = os.path.join(lockDir, lockName)

        if qt_found:
            if not self.args.quiet:
                print("Found pre-built Qt5")
            return

        if 'Windows' == system:
            self.qtUrl = self.assets_url + '/dependencies/qt5/qt5-install-5.15.10-2023.10.02-windows-x86_64.tar.xz'
        elif 'Darwin' == system:
            self.qtUrl = self.assets_url + '/dependencies/vcpkg/qt5-install-5.15.2-macos.tar.gz'
        elif 'Linux' == system:
            import distro
            cpu_architecture = platform.machine()

            if 'x86_64' == cpu_architecture:
                # `major_version()` can return blank string on rolling release distros like arch
                # The `or 0` conditional assignment prevents the int parsing error from hiding the useful Qt package error
                u_major = int( distro.major_version() or '0' )
                if distro.id() == 'ubuntu' or distro.id() == 'linuxmint':
                    if (distro.id() == 'ubuntu' and u_major == 20) or distro.id() == 'linuxmint' and u_major == 20:
                        self.qtUrl = self.assets_url + '/dependencies/qt5/qt5-install-5.15.14-2024.06.17-kde_570f5b2105df1ea052bec0d6dbf8a00137274371-ubuntu-20.04-amd64.tar.xz'
                    elif (distro.id() == 'ubuntu' and u_major > 20) or (distro.id() == 'linuxmint' and u_major > 20):
                        self.__no_qt_package_error()
                    else:
                        self.__unsupported_error()
                else:
                    self.__no_qt_package_error()


            elif 'aarch64' == cpu_architecture:
                if distro.id() == 'ubuntu':
                    u_major = int( distro.major_version() )

                    if u_major == 20:
                        self.qtUrl = self.assets_url + '/dependencies/qt5/qt5-install-5.15.9-2023.05.21-kde_fb3ec282151b1ee281a24f0545a40ac6438537c2-ubuntu-20.04-aarch64.tar.xz'
                    elif u_major > 20:
                        self.__no_qt_package_error()
                    else:
                        self.__unsupported_error()

                elif distro.id() == 'debian':
                    u_major = int( distro.major_version() )

                    if u_major > 10:
                        self.__no_qt_package_error()
                    else:
                        self.__unsupported_error()

                else:
                    self.__no_qt_package_error()
            else:
                raise Exception('UNKNOWN CPU ARCHITECTURE!!!')

        else:
            print("System      : " + platform.system())
            print("Architecture: " + platform.architecture())
            print("Machine     : " + platform.machine())
            raise Exception('UNKNOWN OPERATING SYSTEM!!!')

    def showQtBuildInfo(self):
        print("")
        print("It's also possible to build Qt for your distribution, please see the documentation at:")
        print("https://github.com/overte-org/overte/tree/master/tools/qt-builder")
        print("")
        print("Alternatively, you can try building against the system Qt by setting the OVERTE_USE_SYSTEM_QT environment variable.")
        print("You'll need to install the development packages, and to have Qt 5.15.0 or later.")

    def writeConfig(self):
        print("Writing cmake config to {}".format(self.configFilePath))
        # Write out the configuration for use by CMake
        cmakeConfig = QtDownloader.CMAKE_TEMPLATE.format(self.cmakePath, self.cmakePath).replace('\\', '/')
        with open(self.configFilePath, 'w') as f:
            f.write(cmakeConfig)

    def installQt(self):
        if not os.path.isdir(self.fullPath):
            print ('Downloading Qt package')
            print('Extracting ' + self.qtUrl + ' to ' + self.path)
            hifi_utils.downloadAndExtract(self.qtUrl, self.path)
        else:
            print ('Qt has already been downloaded')


    def __unsupported_error(self):
        import distro
        cpu_architecture = platform.machine()

        print('')
        hifi_utils.color('red')
        print("Sorry, " + distro.name(pretty=True) + " on " + cpu_architecture + " is too old and won't be officially supported.")
        hifi_utils.color('white')
        print("Please upgrade to a more recent Linux distribution.")
        hifi_utils.color('clear')
        print('')
        raise hifi_utils.SilentFatalError(3)

    def __no_qt_package_error(self):
        import distro
        cpu_architecture = platform.machine()

        print('')
        hifi_utils.color('red')
        print("Sorry, we don't have a prebuilt Qt package for " + distro.name(pretty=True) + " on " + cpu_architecture + ".")
        hifi_utils.color('white')
        print('')
        print("If this is a recent distribution, dating from 2021 or so, you can try building")
        print("against the system Qt by running this command, and trying again:")
        print("    export OVERTE_USE_SYSTEM_QT=1")
        print("")
        hifi_utils.color('clear')
        print("If you'd like to try to build Qt from source either for building Overte, or")
        print("to contribute a prebuilt package for your distribution, please see the")
        print("documentation at: ", end='')
        hifi_utils.color('blue')
        print("https://github.com/overte-org/overte/tree/master/tools/qt-builder")
        hifi_utils.color('clear')
        print('')
        raise hifi_utils.SilentFatalError(2)