import os
import subprocess
from pathlib import Path
import sys
import shutil
import time

# ========== CONFIG ==========
SRC_DIRS = ["src/src", "src/vendor"]


INCLUDE_DIRS = [
    "src/include",
    "src/vendor",
    "src/vendor/imgui",
    "C:/msys64/mingw64/include"
]



LIB_DIRS = ["C:/msys64/mingw64/lib"]
BUILD_DIR = Path("src/build")
TARGET = BUILD_DIR / "app.exe"
LOG_FILE = Path("build.log")
LIBS = ["glfw3", "glew32", "opengl32", "gdi32", "yaml-cpp", "comdlg32", "ssl", "crypto"]


CXX = "g++"
CXXFLAGS = ["-std=c++20", "-Wall"] + [f"-I{inc}" for inc in INCLUDE_DIRS]
LDFLAGS = [f"-L{lib}" for lib in LIB_DIRS] + [f"-l{lib}" for lib in LIBS]


















# ========== COLOR UTILS ==========
class Colors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKCYAN = '\033[96m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    BOLD = '\033[1m'
    RESET = '\033[0m'
    GRAY = '\033[90m'

def color(text, style): return f"{style}{text}{Colors.RESET}"
def banner(title): print(color(f"\n╔═ {title} ═══════════════════════════════╗", Colors.BOLD + Colors.OKBLUE))
def info(msg): print(color(f"✅ {msg}", Colors.OKGREEN))
def warn(msg): print(color(f"⚠️  {msg}", Colors.WARNING))
def error(msg): print(color(f"❌ {msg}", Colors.FAIL))

def log(msg: str):
    with LOG_FILE.open("a", encoding="utf-8") as f:
        f.write(msg + "\n")

# ========== BUILD SYSTEM ==========
def find_cpp_files():
    cpp_files = []
    for folder in SRC_DIRS:
        for path in Path(folder).rglob("*.cpp"):
            cpp_files.append(path)
    return cpp_files

def obj_path(source): return BUILD_DIR / source.relative_to("src").with_suffix(".o")
def dep_path(obj): return obj.with_suffix(".d")

def parse_dep_file(dep_file):
    if not dep_file.exists():
        return []
    deps = []
    with dep_file.open() as f:
        for line in f:
            line = line.strip().replace("\\", "")
            if ":" in line:
                line = line.split(":", 1)[1]
            deps.extend(line.strip().split())
    return deps

def compile_source(source, obj):
    obj.parent.mkdir(parents=True, exist_ok=True)
    cmd = [CXX, *CXXFLAGS, "-MMD", "-MP", "-c", str(source), "-o", str(obj)]
    try:
        print(f"{color('🔨 Compiling:', Colors.OKCYAN)} {source}")
        result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        log(f"[COMPILE] {' '.join(cmd)}{result.stdout.decode()}{result.stderr.decode()}")
    except subprocess.CalledProcessError as e:
        error(f"Failed to compile {source}")
        print("🔧 Command:", " ".join(cmd))
        print(e.stderr.decode())
        log(f"[ERROR] {' '.join(cmd)}\n{e.stderr.decode()}")
        sys.exit(1)

def link_objects(obj_files):
    cmd = [CXX, *map(str, obj_files), "-o", str(TARGET), *LDFLAGS]
    try:
        print(f"{color('📦 Linking:', Colors.OKBLUE)} {TARGET}")
        result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        log(f"[LINK] {' '.join(cmd)}\n{result.stdout.decode()}{result.stderr.decode()}")
    except subprocess.CalledProcessError as e:
        error("Linking failed.")
        print("🔧 Command:", " ".join(cmd))
        print(e.stderr.decode())
        log(f"[ERROR] {' '.join(cmd)}\n{e.stderr.decode()}")
        sys.exit(1)



def build():
    build_start = time.time()
    banner("🚀 Building Project")

    cpp_files = find_cpp_files()
    obj_files = []

    for source in cpp_files:
        obj = obj_path(source)
        dep = dep_path(obj)

        obj_mtime = obj.stat().st_mtime if obj.exists() else 0
        needs_build = not obj.exists()

        # If source is newer than object
        if not needs_build and source.stat().st_mtime > obj_mtime:
            needs_build = True

        # If any dependencies are newer than object
        if not needs_build and dep.exists():
            for dep_file in parse_dep_file(dep):
                try:
                    if Path(dep_file).exists() and Path(dep_file).stat().st_mtime > obj_mtime:
                        needs_build = True
                        break
                except Exception:
                    needs_build = True
                    break

        if needs_build:
            compile_source(source, obj)
        else:
            print(f"{color('👌 Up-to-date:', Colors.GRAY)} {source}")
        obj_files.append(obj)

    link_objects(obj_files)
    banner("✅ Build Complete")
    build_end = time.time()
    log(f"[TIME] Build duration: {build_end - build_start:.2f}s")
    print(color(f"⏱ Build time: {build_end - build_start:.2f}s", Colors.OKCYAN))





def run():
    build()
    if TARGET.exists():
        banner("🚀 Running")
        try:
            result = subprocess.run(str(TARGET), check=True)
            log("[RUN] Executed app.exe successfully.")
        except subprocess.CalledProcessError as e:
            error("Program exited with error.")
            log(f"[ERROR] Runtime crash\n{e}")
            sys.exit(e.returncode)
    else:
        error("Executable not found.")
        log("[ERROR] Executable not found.")

def clean():
    banner("🧹 Cleaning")
    if BUILD_DIR.exists():
        shutil.rmtree(BUILD_DIR)
        info("Build directory removed.")
        log("[CLEAN] Build directory removed.")
    else:
        warn("Nothing to clean.")
        log("[CLEAN] No build directory found.")
    if LOG_FILE.exists():
        LOG_FILE.unlink()
        info("Log file cleared.")

# ========== ENTRY ==========
if __name__ == "__main__":
    total_start = time.time()

    # Clear old log
    LOG_FILE.write_text("", encoding="utf-8")

    try:
        if "clean" in sys.argv:
            clean()
        elif "run" in sys.argv:
            run()
        else:
            build()
    except KeyboardInterrupt:
        error("Interrupted by user.")
        log("[ERROR] Build interrupted by user.")
        sys.exit(1)

    total_end = time.time()
    print(color(f"\n⏱ Total time: {total_end - total_start:.2f}s", Colors.BOLD + Colors.OKGREEN))
    log(f"[TIME] Total runtime: {total_end - total_start:.2f}s")