#!/usr/bin/env python3
import copy
import json
import os
import pathlib
import shutil
import subprocess
import sys

import necxxt
import necxxt.dependencies
import necxxt.utils

__executable__ = os.path.basename(__file__)

__env__ = {
    "CXX": "docker run -ti -v {}:/necxxt --rm necxxt/clang".format(os.getcwd()),
    "CXXFLAGS": "-std=c++2a -fmodules-ts"
}


def help(args):
    print()
    print("Usage: {} <command>".format(__executable__))
    print()
    print("where <command> is one of:")
    print("    build, help, install")
    print()
    print("Specify configs in the ini-formatted file:")
    print("    {}".format(os.path.join(
        pathlib.Path.home(), ".{}rc".format(__executable__))))
    print("or on the command line via: {} < command > --key value".format(__executable__))
    print("Config info can be viewed via: {} help config".format(__executable__))
    print()
    print("{}@{} {}".format(__executable__, necxxt.__version__, __file__))


def runCompiler(cmds):
    proc = subprocess.Popen(cmds)
    proc.communicate()

    if proc.returncode != 0:
        exit(proc.returncode)


def checkIfModulesAreInstalled(modules):
    for m in modules:
        p = os.path.join("necxxt_modules", m)
        if not os.path.isdir(p):
            print("Module {} is not installed".format({m}))
            exit(1)


def precompileModules(order, build_modules):
    for dependency in order:
        p = os.path.join("necxxt_modules", dependency)
        for f in necxxt.utils.listFiles(p, filters=[".cxxm"], shorten=False):
            i = f
            d = os.path.dirname(f)
            f = os.path.basename(f)
            name, extension = os.path.splitext(f)
            o = os.path.join("build", d, name + ".pcm")
            pathlib.Path(os.path.dirname(o)).mkdir(parents=True)

            modules = []
            for _m in build_modules.get(dependency, []):
                for _f in necxxt.utils.listFiles(os.path.join("build", "necxxt_modules",
                                                              _m), filters=[".pcm"], shorten=False):
                    if _f not in modules:
                        modules.append(_f)

            cmds = __env__["CXX"].split(" ")
            cmds.extend(__env__["CXXFLAGS"].split(" "))
            cmds.append("--precompile")
            cmds.append(i)

            for m in modules:
                cmds.append("-fmodule-file={}".format(m))

            cmds.append("-o")
            cmds.append(o)

            print(" ".join(cmds))
            runCompiler(cmds)


def compileModules(order, build_modules):
    for f in necxxt.utils.listFiles(os.getcwd(), filters=[".pcm"]):
        i = f
        d = os.path.dirname(f)
        f = os.path.basename(f)
        name, extension = os.path.splitext(f)
        o = os.path.join(d, name + ".pcm.o")

        cmds = __env__["CXX"].split(" ")
        cmds.extend(__env__["CXXFLAGS"].split(" "))
        cmds.append("-c")
        cmds.append(i)
        cmds.append("-o")
        cmds.append(o)

        print(" ".join(cmds))
        runCompiler(cmds)


def compileSources(order, build_modules):
    modules = []
    for f in necxxt.utils.listFiles(os.getcwd(), filters=[".pcm"]):
        if f not in modules:
            modules.append(f)

    for f in necxxt.utils.listFiles(os.getcwd(), filters=[".cxx"]):
        i = f
        d = os.path.dirname(f)
        f = os.path.basename(f)
        name, extension = os.path.splitext(f)
        o = os.path.join("build", d, name + ".cxx.o")
        pathlib.Path(os.path.dirname(o)).mkdir(parents=True)

        cmds = __env__["CXX"].split(" ")
        cmds.extend(__env__["CXXFLAGS"].split(" "))
        cmds.append("-c")
        cmds.append(i)

        for m in modules:
            cmds.append("-fmodule-file={}".format(m))

        cmds.append("-o")
        cmds.append(o)

        print(" ".join(cmds))
        runCompiler(cmds)


def buildStatic(order, build_modules):
    cmds = __env__["CXX"].split(" ")
    cmds.extend(__env__["CXXFLAGS"].split(" "))

    module_paths = {}
    objects = []
    for o in order:
        for f in necxxt.utils.listFiles(os.getcwd(), filters=[".pcm.o"]):
            d = os.path.dirname(f)
            if o in d:
                if d not in module_paths:
                    module_paths[d] = []

                if f not in module_paths[d]:
                    module_paths[d].append(f)

    for f in necxxt.utils.listFiles(os.getcwd(), filters=[".cxx.o"]):
        if f not in objects:
            objects.append(f)

    for module_path in module_paths.keys():
        cmds.append("-fprebuilt-module-path={}".format(module_path))

        for o in module_paths.get(module_path, []):
            cmds.append(o)

    for o in objects:
        cmds.append(o)

    cmds.append("-o")

    module = necxxt.utils.readModule()
    cmds.append(os.path.join("build", module["name"]))
    cmds.append("-static")

    print(" ".join(cmds))
    runCompiler(cmds)


def build(args):
    if os.path.isdir("build"):
        shutil.rmtree("build")

    with open("necxxt-lock.json", "r") as f:
        lock = json.load(f)

    modules = {}
    for dependency in lock["dependencies"].keys():
        requires = lock["dependencies"][dependency].get("requires", {})
        modules[dependency] = list(requires.keys())

    build_modules = copy.deepcopy(modules)

    order = []
    i = 0
    while i < len(modules.keys()):
        keys = list(modules.keys())
        key = keys[i]
        dependencies = modules[key]

        if len(dependencies) == 0:
            order.append(key)
            del modules[key]
            for d in modules.keys():
                if key in modules[d]:
                    modules[d].remove(key)

        i += 1

        length = len(modules.keys())
        if length > 0 and i >= length:
            i = 0

    checkIfModulesAreInstalled(order)
    precompileModules(order, build_modules)
    compileModules(order, build_modules)
    compileSources(order, build_modules)
    buildStatic(order, build_modules)


def install(args):
    necxxt.dependencies.installDependencies()


def main(args):
    if len(args) == 0 or args[0] == "help":
        help(args)
    elif args[0] == "build":
        build(args)
    elif args[0] == "install":
        install(args)
    else:
        help(args)
        exit(1)


if __name__ == "__main__":
    args = sys.argv[1:]

    main(args)
