import io
import glob
import ninja
import os
import platform
import re
import subprocess
import sys

from distutils.version import LooseVersion
from distutils.dir_util import copy_tree, remove_tree
from distutils.sysconfig import get_python_inc
from distutils import sysconfig
from typing import Any, Dict

from setuptools.command.build_ext import build_ext
from setuptools.extension import Extension


class CMakeExtension(Extension):
    name: str  # exists, even though IDE doesn't find it

    def __init__(self, name: str, sourcedir: str="") -> None:
        super().__init__(name, sources=[])
        self.sourcedir = os.path.abspath(sourcedir)
        self.sourcedir_rel = sourcedir


class ExtensionBuilder(build_ext):
    def run(self) -> None:
        self.validate_cmake()
        super().run()

    def build_extension(self, ext: Extension) -> None:
        if isinstance(ext, CMakeExtension):
            self.build_cmake_extension(ext)
        else:
            super().build_extension(ext)

    def validate_cmake(self) -> None:
        cmake_extensions = [x for x in self.extensions if isinstance(x, CMakeExtension)]
        if len(cmake_extensions) > 0:
            try:
                out = subprocess.check_output(["cmake", "--version"])
            except OSError:
                raise RuntimeError(
                    "CMake must be installed to build the following extensions: "
                    + ", ".join(e.name for e in cmake_extensions)
                )
            if platform.system() == "Windows":
                cmake_version = LooseVersion(re.search(r"version\s*([\d.]+)", out.decode()).group(1))  # type: ignore
                if cmake_version < "3.1.0":
                    raise RuntimeError("CMake >= 3.1.0 is required on Windows")

    def build_cmake_extension(self, ext: CMakeExtension) -> None:
        extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
        cmake_args = []

        ninja_executable_path = os.path.join(ninja.BIN_DIR, "ninja")
        cmake_args += ["-GNinja","-DCMAKE_MAKE_PROGRAM:FILEPATH=" + ninja_executable_path]

        cfg = "Debug" if self.debug else "Release"
        # cfg = 'Debug'
        build_args = ["--config", cfg]
        install_args = ["--config", cfg]

        cmake_args += ["-DCMAKE_BUILD_TYPE=" + cfg]
        if platform.system() == "Windows":
            if sys.maxsize > 2 ** 32:
                cmake_args += ["-A", "x64"]

        env = os.environ.copy()
        env["CXXFLAGS"] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get("CXXFLAGS", ""), self.distribution.get_version())

        build_dir = os.path.join(self.build_temp, ext.sourcedir_rel)
        install_dir = os.path.join(extdir, 'Coriolis')
        os.makedirs(build_dir,exist_ok=True)

        cmake_args += [f"-DCMAKE_MODULE_PATH={os.path.abspath('bootstrap/cmake_modules')};"
                       f"{os.path.join(install_dir, 'share/cmake/Modules')}"]
        cmake_args += [f"-DCMAKE_INSTALL_PREFIX={install_dir}"]
        cmake_args += [f"-DPython_CORIOLISLIB={install_dir}"]
        cmake_args += [f"-DPython_CORIOLISARCH={install_dir}"]
        cmake_args += [f"-DSYS_CONF_DIR={install_dir}"]
        cmake_args += [f"-DCORIOLIS_TOP={install_dir}"]
        cmake_args += [f"-DCORIOLIS_USER_TOP={install_dir}"]

        cmake_args += [f"-DPython_EXECUTABLE={sys.executable}"]

        cmake_args += ["-DPOETRY=1"]
        cmake_args += ["-DWITH_QT5=1"]
        cmake_args += ["-DCMAKE_BUILD_RPATH_USE_ORIGIN=1"]
        cmake_args += ["-DCMAKE_SKIP_BUILD_RPATH=FALSE"]
        cmake_args += ["-DCMAKE_BUILD_WITH_INSTALL_RPATH=FALSE"]
        cmake_args += ["-DCMAKE_INSTALL_RPATH='$ORIGIN/lib:$ORIGIN'"]
        cmake_args += ["-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=TRUE"]


        print(f"Using cmake args: {cmake_args}")
        subprocess.check_call(["cmake", "--trace-redirect=build.cmake.trace", "--trace-expand",  ext.sourcedir] + cmake_args, cwd=build_dir, env=env)
        subprocess.check_call(["cmake", "--build", "."] + build_args, cwd=build_dir)
        subprocess.check_call(["cmake", "--install", ".", "--prefix", install_dir] + install_args, cwd=build_dir)
        if os.path.exists(os.path.join(install_dir, "bin")):
            copy_tree(os.path.join(install_dir, "bin"), os.path.join(install_dir,"data/bin"))
            remove_tree(os.path.join(install_dir, "bin"))
            proc = subprocess.Popen(['file'] + glob.glob(os.path.join(install_dir,"data/bin/*")), stdout=subprocess.PIPE)
            for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):  
                if 'ELF' in line: #TODO support other OSs
                    f = line.split(':')[0]
                    print(f"fixing up {f}")
                    subprocess.check_call(["patchelf", "--set-rpath", '$ORIGIN/../../lib', f])

        # Remove docs
        if os.path.exists(os.path.join(install_dir, "share", "doc")):
            remove_tree(os.path.join(install_dir, "share", "doc"))

def build(setup_kwargs: Dict[str, Any]) -> None:
    cmake_modules = [
                     CMakeExtension("coloquinte", sourcedir="coloquinte"),
                     CMakeExtension("Hurricane", sourcedir="hurricane"),
                     CMakeExtension("crlcore", sourcedir="crlcore"),
                     CMakeExtension("flute", sourcedir="flute"),
                     CMakeExtension("etesian", sourcedir="etesian"),
                     CMakeExtension("anabatic", sourcedir="anabatic"),
                     CMakeExtension("katana", sourcedir="katana"),
                     CMakeExtension("equinox", sourcedir="equinox"),
                     CMakeExtension("solstice", sourcedir="solstice"),
                     CMakeExtension("oroshi", sourcedir="oroshi"),
                     CMakeExtension("bora", sourcedir="bora"),
                     CMakeExtension("karakaze", sourcedir="karakaze"),
                     #CMakeExtension("knik", sourcedir="knik"),
                     #CMakeExtension("unicorn", sourcedir="unicorn"),
                     CMakeExtension("tutorial", sourcedir="tutorial"),
                     CMakeExtension("cumulus", sourcedir="cumulus"),
                     CMakeExtension("stratus1", sourcedir="stratus1"),
                     CMakeExtension("documentation", sourcedir="documentation"),
                     CMakeExtension("unittests", sourcedir="unittests")
                     ]
 
    ext_modules = cmake_modules
    setup_kwargs.update(
        {
            "ext_modules": ext_modules,
            "cmdclass": dict(build_ext=ExtensionBuilder),
            "zip_safe": False,
        }
    )