import t, c
from stdint import *
import memhub
import string
import stdio
import stdlib
import viperlib
import lib.core.VLogger as VLogger
from json.__parser import parse as json_parse
from json import JsonValue, JSON_OBJECT, JSON_INT
import w32.fileio as fileio


# ============================================================
# Config - 工程配置
# ============================================================

# 全局 mbuddy 指针
_mbuddy: memhub.MemManager | t.CPtr

# 配置值（从 project.vpj 加载）
SourceDir: str
BuildDir: str
TempDir: str               # = {BuildDir}/temp（自动计算）
OutputDir: str             # = {BuildDir}/output（自动计算）
ProjectName: str
ProjectVersion: str
CompilerCmd: str
CompilerFlags: str         # 编译器参数（空格分隔，从 compiler.flags 数组拼接）
LinkerCmd: str
LinkerFlags: str           # 链接器参数（空格分隔，从 linker.flags 数组拼接）
LinkerOutput: str
TargetTriple: str
TargetDataLayout: str
Sha1SliceLevel: t.CInt       # SHA1 切片层数（目录分散等级）
StrictMode: t.CInt
IncludesDir: str           # includes 目录路径（从 includes 数组首元素）


def Load_project_config(path: str) -> int:
    """加载工程配置文件（project.vpj）

    Args:
        path: 配置文件路径

    Returns:
        0 表示成功，非 0 表示失败
    """
    global SourceDir, BuildDir, TempDir, OutputDir, ProjectName, ProjectVersion
    global CompilerCmd, CompilerFlags, LinkerCmd, LinkerFlags, LinkerOutput
    global TargetTriple, TargetDataLayout, Sha1SliceLevel, StrictMode
    global IncludesDir

    if path is None:
        return 1
    if _mbuddy is None:
        return 1

    # 打开文件
    f: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.R)
    if f is None:
        return 1
    if f.closed:
        return 1

    # 分配读取缓冲区
    CFG_BUF_SIZE: t.CSizeT = 8192
    buf: bytes = _mbuddy.alloc(CFG_BUF_SIZE)
    if buf is None:
        f.close()
        return 1

    bytes_read: t.CInt64T = f.read_all(buf, CFG_BUF_SIZE)
    f.close()

    if bytes_read <= 0:
        return 1

    # 解析 JSON
    root: JsonValue | t.CPtr = json_parse(_mbuddy, buf)
    if root is None:
        return 1
    if not root.is_object():
        return 1

    # 读取顶层字段（使用显式 __getitem__ 调用，兼容旧编译器二进制）
    sd_val: JsonValue | t.CPtr = root.__getitem__("source_dir")
    SourceDir = sd_val.as_string() if sd_val is not None else None
    # build_dir: 统一构建目录（默认 ./.tpv_build），temp 和 output 作为其子目录
    bd_val: JsonValue | t.CPtr = root.__getitem__("build_dir")
    if bd_val is not None and bd_val.is_string():
        BuildDir = bd_val.as_string()
    else:
        BuildDir = "./.tpv_build"
    name_val: JsonValue | t.CPtr = root.__getitem__("name")
    ProjectName = name_val.as_string() if name_val is not None else None
    ver_val: JsonValue | t.CPtr = root.__getitem__("version")
    ProjectVersion = ver_val.as_string() if ver_val is not None else None

    # 读取 compiler 子对象
    compiler: JsonValue | t.CPtr = root.__getitem__("compiler")
    if compiler is not None and compiler.is_object():
        cc_cmd_val: JsonValue | t.CPtr = compiler.__getitem__("cmd")
        CompilerCmd = cc_cmd_val.as_string() if cc_cmd_val is not None else None
        # 读取 compiler.flags 数组，拼接成空格分隔的字符串
        cc_flags_val: JsonValue | t.CPtr = compiler.__getitem__("flags")
        CompilerFlags = _join_flags_array(cc_flags_val)

    # 读取 linker 子对象
    linker: JsonValue | t.CPtr = root.__getitem__("linker")
    if linker is not None and linker.is_object():
        ld_cmd_val: JsonValue | t.CPtr = linker.__getitem__("cmd")
        LinkerCmd = ld_cmd_val.as_string() if ld_cmd_val is not None else None
        ld_out_val: JsonValue | t.CPtr = linker.__getitem__("output")
        LinkerOutput = ld_out_val.as_string() if ld_out_val is not None else None
        # 读取 linker.flags 数组，拼接成空格分隔的字符串
        ld_flags_val: JsonValue | t.CPtr = linker.__getitem__("flags")
        LinkerFlags = _join_flags_array(ld_flags_val)

    # 读取 target 子对象
    target: JsonValue | t.CPtr = root.__getitem__("target")
    if target is not None and target.is_object():
        tgt_triple_val: JsonValue | t.CPtr = target.__getitem__("triple")
        TargetTriple = tgt_triple_val.as_string() if tgt_triple_val is not None else None
        tgt_dl_val: JsonValue | t.CPtr = target.__getitem__("datalayout")
        TargetDataLayout = tgt_dl_val.as_string() if tgt_dl_val is not None else None

    # 读取 options 子对象
    # Sha1SliceLevel: SHA1 切片层数（默认 1，每层取 SHA1 前 2 字符作为子目录）
    #   level=0 → 不切片，文件直接放 base_dir
    #   level=1 → b7/{sha1}.ext
    #   level=2 → b7/90/{sha1}.ext
    options: JsonValue | t.CPtr = root.__getitem__("options")
    Sha1SliceLevel = 1
    StrictMode = 1
    if options is not None and options.is_object():
        sl: JsonValue | t.CPtr = options.__getitem__("sha1_slice_level")
        if sl is not None and sl.is_int():
            Sha1SliceLevel = sl.as_int()
        sm: JsonValue | t.CPtr = options.__getitem__("strict_mode")
        if sm is not None and sm.is_bool():
            StrictMode = 1 if sm.as_bool() else 0

    # 读取 includes 数组（取首个元素作为 includes 目录）
    includes_val: JsonValue | t.CPtr = root.__getitem__("includes")
    if includes_val is not None and includes_val.is_array():
        arr_len: t.CSizeT = includes_val.__len__()
        if arr_len > 0:
            first_inc: JsonValue | t.CPtr = includes_val.get_item(0)
            if first_inc is not None and first_inc.is_string():
                IncludesDir = first_inc.as_string()

    return 0


def _join_flags_array(flags_val: JsonValue | t.CPtr) -> str:
    """将 JSON 字符串数组拼接成空格分隔的字符串

    用于 linker.flags 和 compiler.flags 数组。
    用 while + 索引遍历，避免 list 迭代器 bug。

    Args:
        flags_val: JsonValue，应为字符串数组

    Returns:
        空格分隔的字符串，或 None 如果 flags_val 为 None 或非数组或空数组
    """
    if flags_val is None:
        return None
    if not flags_val.is_array():
        return None

    arr_len: t.CSizeT = flags_val.__len__()
    if arr_len == 0:
        return None

    # 第一遍：计算总长度（所有 flag 长度 + 空格分隔符）
    total_len: t.CSizeT = 0
    i: t.CSizeT = 0
    while i < arr_len:
        item: JsonValue | t.CPtr = flags_val.get_item(i)
        if item is not None and item.is_string():
            s: str = item.as_string()
            if s is not None:
                total_len += string.strlen(s) + 1  # +1 for space
        i += 1

    if total_len == 0:
        return None

    # 分配缓冲区（total_len 已含每个 flag 后的空格，最后一个是 '\0'）
    buf: str = _mbuddy.alloc(total_len + 1)
    if buf is None:
        return None

    # 第二遍：拼接（flag 之间用空格分隔）
    pos: t.CSizeT = 0
    i = 0
    while i < arr_len:
        item = flags_val.get_item(i)
        if item is not None and item.is_string():
            s = item.as_string()
            if s is not None:
                slen: t.CSizeT = string.strlen(s)
                if pos > 0:
                    buf[pos] = ' '
                    pos += 1
                string.strcpy(buf + pos, s)
                pos += slen
        i += 1
    buf[pos] = '\0'
    return buf


def _join_path(rel: str, project_dir: str) -> str:
    """将相对路径与 project_dir 拼接为绝对路径

    - ./App + Test → Test/App
    - ./temp + Test → Test/temp
    - ../includes + Test → Test/../includes（OS 解析为 includes）
    - 绝对路径保持不变
    - None 返回 None
    """
    if rel is None:
        return None
    # 绝对路径（以 / 或 \ 开头）保持不变
    if rel[0] == '/' or rel[0] == '\\':
        return rel

    rel_len: t.CSizeT = string.strlen(rel)
    dir_len: t.CSizeT = string.strlen(project_dir)

    # 处理 ../ 或 ..\\ 前缀：拼接 project_dir/../...
    if rel_len >= 3 and rel[0] == '.' and rel[1] == '.':
        if rel[2] == '/' or rel[2] == '\\':
            if dir_len == 0:
                return rel
            total_len: t.CSizeT = dir_len + 1 + rel_len + 1
            buf: str = _mbuddy.alloc(total_len)
            if buf is None:
                return rel
            viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel)
            return buf

    # 处理 ./ 或 .\\ 前缀：拼接 project_dir + rel[2:]
    rel_off: t.CSizeT = 0
    if rel_len >= 2 and rel[0] == '.':
        if rel[1] == '/' or rel[1] == '\\':
            rel_off = 2
        else:
            return rel  # 不是 ./ 开头，保持不变
    else:
        return rel  # 不是 . 开头，保持不变

    if dir_len == 0:
        # project_dir 为空（当前目录），去掉 ./ 前缀即可
        return rel + rel_off
    # 拼接: project_dir + "/" + rel[rel_off:]
    total_len = dir_len + 1 + (rel_len - rel_off) + 1
    buf = _mbuddy.alloc(total_len)
    if buf is None:
        return rel  # 分配失败，返回原路径
    viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel + rel_off)
    return buf


def resolve_paths(project_dir: str) -> int:
    """解析并规范化工程路径（src/build/includes）

    将 project.vpj 中的相对路径（以 ./ 开头）转换为基于 project_dir 的路径。
    BuildDir 解析后，TempDir = {BuildDir}/temp，OutputDir = {BuildDir}/output。

    Args:
        project_dir: 工程根目录（project.vpj 所在目录）

    Returns:
        0 表示成功，非 0 表示失败
    """
    global SourceDir, BuildDir, TempDir, OutputDir, IncludesDir
    if project_dir is None:
        return 1
    if _mbuddy is None:
        return 1

    SourceDir = _join_path(SourceDir, project_dir)
    BuildDir = _join_path(BuildDir, project_dir)

    # TempDir = {BuildDir}/temp
    if BuildDir is not None:
        bd_len: t.CSizeT = string.strlen(BuildDir)
        temp_buf: str = _mbuddy.alloc(bd_len + 8)
        if temp_buf is not None:
            viperlib.snprintf(temp_buf, bd_len + 8, "%s/temp", BuildDir)
            TempDir = temp_buf
        # OutputDir = {BuildDir}/output
        output_buf: str = _mbuddy.alloc(bd_len + 8)
        if output_buf is not None:
            viperlib.snprintf(output_buf, bd_len + 8, "%s/output", BuildDir)
            OutputDir = output_buf

    # includes 路径可能是 ../includes 形式，需要基于 project_dir 解析
    if IncludesDir is not None:
        IncludesDir = _join_path(IncludesDir, project_dir)
    return 0


def get_includes_binary_dir() -> str:
    """获取 includes.binary 目录路径（includes 目录 + .binary 后缀）

    includes 目录如 ../includes，则 includes.binary 为 ../includes.binary
    """
    if IncludesDir is None:
        return None
    inc_len: t.CSizeT = string.strlen(IncludesDir)
    buf: str = _mbuddy.alloc(inc_len + 8)
    if buf is None:
        return None
    viperlib.snprintf(buf, inc_len + 8, "%s.binary", IncludesDir)
    return buf


def print_config():
    """打印当前配置"""
    buf: t.CChar | t.CPtr = VLogger.fmt_buf()
    if buf is None:
        return
    if ProjectName is not None:
        viperlib.snprintf(buf, 1024, "project: %s v%s", ProjectName,
                         ProjectVersion if ProjectVersion is not None else "?")
        VLogger.info(buf, "config")
    if SourceDir is not None:
        viperlib.snprintf(buf, 1024, "source_dir: %s", SourceDir)
        VLogger.info(buf, "config")
    if BuildDir is not None:
        viperlib.snprintf(buf, 1024, "build_dir: %s", BuildDir)
        VLogger.info(buf, "config")
    if TempDir is not None:
        viperlib.snprintf(buf, 1024, "temp_dir: %s", TempDir)
        VLogger.info(buf, "config")
    if OutputDir is not None:
        viperlib.snprintf(buf, 1024, "output_dir: %s", OutputDir)
        VLogger.info(buf, "config")
    if CompilerCmd is not None:
        viperlib.snprintf(buf, 1024, "compiler: %s", CompilerCmd)
        VLogger.info(buf, "config")
    if LinkerCmd is not None:
        viperlib.snprintf(buf, 1024, "linker: %s -> %s", LinkerCmd,
                         LinkerOutput if LinkerOutput is not None else "?")
        VLogger.info(buf, "config")
    if IncludesDir is not None:
        viperlib.snprintf(buf, 1024, "includes: %s", IncludesDir)
        VLogger.info(buf, "config")
    viperlib.snprintf(buf, 1024, "sha1_slice_level: %d", Sha1SliceLevel)
    VLogger.info(buf, "config")


# ============================================================
# slice_subdir - 根据 SHA1 和切片层数返回子目录路径
#
# 每层取 SHA1 的 2 个字符作为子目录名:
#   level=0 → None（不切片）
#   level=1 → "b7"
#   level=2 → "b7/90"
#   level=3 → "b7/90/23"
#
# Args:
#   sha1: SHA1 字符串（至少 2*level 字符）
#   level: 切片层数
#
# Returns:
#   子目录路径字符串（mbuddy 分配，无需释放），level<=0 时返回 None
# ============================================================
def slice_subdir(sha1: str, level: int) -> str:
    """根据 SHA1 和切片层数返回子目录路径"""
    if level <= 0 or sha1 is None:
        return None
    if _mbuddy is None:
        return None
    # 每层 2 字符 + 分隔符，最后无分隔符 + null
    # buf 大小: level * 2 + (level - 1) + 1 = level * 3
    buf_len: t.CSizeT = t.CSizeT(level * 3)
    buf: str = _mbuddy.alloc(buf_len)
    if buf is None:
        return None
    pos: int = 0
    i: int = 0
    while i < level:
        if i > 0:
            buf[pos] = '/'
            pos += 1
        idx: int = i * 2
        buf[pos] = sha1[idx]
        buf[pos + 1] = sha1[idx + 1]
        pos += 2
        i += 1
    buf[pos] = '\0'
    return buf