#################################
#                               #
#       C++ API Module          # 
#                               #
#################################
#系统目录下 gcu_api.py
import importlib.resources
import http.client
import subprocess
import os
from pathlib import Path
import psutil
import ctypes
import numpy as np
import phg
from coordinate_system import vec3, quat, coord3

#################################
# C++ DLL Related Code
#################################

library_module = "phg"

try:
    # 获取phg.vis模块的安装目录
    with importlib.resources.path(library_module, "") as pkg_path:
        # 拼接GCU.dll的完整路径
        gcu_dll_path = os.path.join(pkg_path, r"vis\GCU.dll")
        current_dir = os.path.join(pkg_path, "vis")
        # 加载DLL
        gcu = ctypes.CDLL(str(gcu_dll_path))
        print(f"成功加载DLL：{gcu_dll_path}")
except (ImportError, FileNotFoundError) as e:
    print(f"获取路径失败：{e}")

#################################
# Common
#################################

# Define echo function
echo_func = gcu.echo
echo_func.restype = None
echo_func.argtypes = [ctypes.c_int]

def echo(becho):
    """Echo a given integer value to the C++ function."""
    echo_func(ctypes.c_int(becho))

# GT_get_int Function
GT_get_int = gcu.GT_get_int
GT_get_int.restype = ctypes.c_int
GT_get_int.argtypes = [ctypes.c_char_p, ctypes.c_int]

def getint(key, defaultvalue):
    """Get integer value associated with a key."""
    return GT_get_int(key.encode(), defaultvalue)

# GT_get_str Function
GT_get_str = gcu.GT_get_string
GT_get_str.restype = ctypes.c_char_p
GT_get_str.argtypes = [ctypes.c_char_p, ctypes.c_char_p]

def getstr(key, defaultvalue=""):
    """Get GT_set_string value associated with a key.
    
    Args:
        key: The key to look up (string or bytes)
        defaultvalue: Default value if key not found (string or bytes)
    
    Returns:
        str: The string value associated with the key, or default if not found
    """
    # Convert inputs to bytes if they aren't already
    key_bytes = key.encode('utf-8') if isinstance(key, str) else bytes(key)
    default_bytes = defaultvalue.encode('utf-8') if isinstance(defaultvalue, str) else bytes(defaultvalue)
    
    # Call the C function
    result = GT_get_str(key_bytes, default_bytes)
    
    # Convert result back to Python string
    return result.decode('utf-8') if result else ""

# GT_set_string Function
gcu.GT_set_string.argtypes = (ctypes.c_char_p, ctypes.c_char_p)

# 定义函数返回类型（如果有的话）
gcu.GT_set_string.restype = None

# 创建 Python 封装函数
def setstr(key: str, value: str):
    gcu.GT_set_string(key.encode('utf-8'), value.encode('utf-8'))


def _load_vis_config_map():
    cfg = Path(current_dir) / 'config.ini'
    data = {}
    if not cfg.exists():
        return data
    cur_sec = None
    for raw_line in cfg.read_text(encoding='utf-8', errors='ignore').splitlines():
        line = raw_line.strip()
        if not line or line.startswith(';') or line.startswith('#'):
            continue
        if line.startswith('[') and line.endswith(']'):
            cur_sec = line[1:-1].strip()
            continue
        if cur_sec != 'Paths' or '=' not in line:
            continue
        key, value = line.split('=', 1)
        data[key.strip()] = value.strip()
    return data


def EnsureSceneReaderConfig():
    """Sync WLKX/RVM reader paths from phg\vis\config.ini into GCU runtime stringmap."""
    cfg = _load_vis_config_map()
    if not cfg:
        return {}

    if cfg.get('wlkxReader'):
        setstr('wlkx_reader', cfg['wlkxReader'])
    if cfg.get('rvmReader'):
        setstr('rvm_reader', cfg['rvmReader'])
    if cfg.get('meshRoot'):
        setstr('mesh_path', cfg['meshRoot'])
    return cfg


EnsureSceneReaderConfig()
    
# Get Mark Coordinates
GT_get_mark = gcu.GT_get_mark  # Assuming this is defined elsewhere
def get_markC(id):
    """Retrieve mark coordinates by ID."""
    m = (ctypes.c_float * 15)()  # Create an array for mark data
    result = GT_get_mark(id, m)
    if result == 1:
        c = coord3()  # Create coord3 instance
        c.ux = vec3(m[0], m[1], m[2])
        c.uy = vec3(m[3], m[4], m[5])
        c.uz = vec3(m[6], m[7], m[8])
        c.s = vec3(m[9], m[10], m[11])
        c.o = vec3(m[12], m[13], m[14])
        return c
    return None  # If the mark is not found
    
#####################################
# PHG
#####################################      
# DoPHG Function
do_phg_func = gcu.doPHG
do_phg_func.restype = ctypes.c_void_p
do_phg_func.argtypes = [ctypes.c_char_p]

def DoPHG(script):
    # 先打印 script 内容，检查是否符合预期
    try:
        do_phg_func(script.encode())
    except OSError as e:
        print(f"OSError occurred: {e}")

# Get PHG Node Coordinates
get_phg_nodeCL = gcu.get_phg_nodeCL
get_phg_nodeCL.restype = ctypes.c_bool
get_phg_nodeCL.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_float)]

def phg_nodeCL(node_name):
    """Get coordinates of a PHG node by name."""
    c = coord3()  # Create coord3 instance
    m = (ctypes.c_float * 15)()  # Create an array for coordinates
    node_name_encoded = node_name.encode('utf-8')  # Encode string to bytes
    success = get_phg_nodeCL(node_name_encoded, m)  # Call C++ function

    if not success:
        raise ValueError(f"Node '{node_name}' not found")

    c.ux = vec3(m[0], m[1], m[2])
    c.uy = vec3(m[3], m[4], m[5])
    c.uz = vec3(m[6], m[7], m[8])
    c.s = vec3(m[9], m[10], m[11])
    c.o = vec3(m[12], m[13], m[14])
    
    return c  # Return coord3 instance
    
get_phg_nodeCW = gcu.get_phg_nodeCW
get_phg_nodeCW.restype = ctypes.c_bool
get_phg_nodeCW.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_float)]

def phg_nodeCW(node_name):
    """Get coordinates of a PHG node by name."""
    c = coord3()  # Create coord3 instance
    m = (ctypes.c_float * 15)()  # Create an array for coordinates
    node_name_encoded = node_name.encode('utf-8')  # Encode string to bytes
    success = get_phg_nodeCW(node_name_encoded, m)  # Call C++ function

    if not success:
        raise ValueError(f"Node '{node_name}' not found")

    c.ux = vec3(m[0], m[1], m[2])
    c.uy = vec3(m[3], m[4], m[5])
    c.uz = vec3(m[6], m[7], m[8])
    c.s = vec3(m[9], m[10], m[11])
    c.o = vec3(m[12], m[13], m[14])
    
    return c  # Return coord3 instance
    
#####################################
# Pipe
#####################################     
pipe_directions = ['F', 'U', 'B', 'D']  # 前进、上升、后退、下降

# 4. 改进的路径距离度量
def path_distance(path1, path2):
    """综合考虑转向点、三维形状和方向分布的复合距离"""
    # 转向点差异
    def get_turns(path):
        return {i for i in range(1, len(path)) if path[i] != path[i-1]}
    turn_dist = len(get_turns(path1).symmetric_difference(get_turns(path2))) * 0.5
    
    # 三维形状差异
    def get_shape(path):
        pos = np.zeros(3)
        shape = {tuple(pos)}
        for d in path:
            pos += {'F': [1,0,0], 'B': [-1,0,0], 
                   'U': [0,1,0], 'D': [0,-1,0]}[d]
            shape.add(tuple(pos))
        return shape
    shape_dist = len(get_shape(path1).symmetric_difference(get_shape(path2))) * 0.8
    
    # 方向分布差异
    hist_dist = sum(abs(path1.count(d) - path2.count(d)) for d in pipe_directions) * 0.3
    
    return turn_dist + shape_dist + hist_dist
    
# Get Pipes Coordinates
try:
    get_pipes_coord_ = gcu.get_pipes_coord
    get_pipes_coord_.restype = None
    get_pipes_coord_.argtypes = [ctypes.POINTER(ctypes.c_float)]
    _get_pipes_coord_available = True
except AttributeError:
    # Function not available in this GCU DLL version
    _get_pipes_coord_available = False

def get_pipes_coord():
    """Retrieve coordinates of pipes."""
    if not _get_pipes_coord_available:
        # Return default coord3 with scale=1.0 (1 pipe character = 0.5m)
        c = coord3()
        c.s = vec3(0.5, 0.5, 0.5)  # Default: 1 character = 0.5 meters
        return c
    c = coord3()  # Create coord3 instance
    m = (ctypes.c_float * 15)()  # Create an array for coordinates
    get_pipes_coord_(m)  # Call the C++ function
    c.ux = vec3(m[0], m[1], m[2])
    c.uy = vec3(m[3], m[4], m[5])
    c.uz = vec3(m[6], m[7], m[8])
    c.s = vec3(m[9], m[10], m[11])
    c.o = vec3(m[12], m[13], m[14])
    return c 
    
#######################################
# Grid
#######################################
# Set Grid Profile Function
try:
    gcu.set_grid_profile.restype = None
    gcu.set_grid_profile.argtypes = [ctypes.c_float, ctypes.c_int]
    _set_grid_profile_available = True
except AttributeError:
    _set_grid_profile_available = False
    print("Warning: set_grid_profile function not found in GCU.dll, using fallback")

def set_grid_profile(unit_size: float, max_depth: int):
    """
    设置网格场景的配置参数
    
    参数说明:
        - unit_size: 网格单元的基本尺寸（单位：米）
        - max_depth: 八叉树的最大深度，影响网格的分辨率
        
    处理流程:
        1. 设置网格单元尺寸，同时更新世界坐标变换的缩放比例
        2. 设置八叉树的最大深度，控制网格细分级别
        3. 进行参数有效性检查并打印调试信息
        
    关键操作:
        - C_toworld.c.s = ONE3 * unit_size: 将世界坐标变换的缩放设置为单位尺寸
        - cur_grid.max_depth = max_depth: 设置八叉树最大深度
        
    注意事项:
        - unit_size必须大于0，否则会触发断言错误
        - 缩放变换使用相同的xyz比例（各向同性）
        - 该设置会影响后续网格构建的精度和性能
        
    示例:
        # 设置网格单元尺寸为0.5米，八叉树最大深度为8
        set_grid_profile(0.5, 8)
        
        # 设置高精度网格（小单元尺寸，大深度）
        set_grid_profile(0.1, 10)
        
        # 设置低精度网格（大单元尺寸，小深度）
        set_grid_profile(1.0, 6)
    """
    if not _set_grid_profile_available:
        print(f"[Fallback] 网格配置参数: 单元尺寸={unit_size}米, 最大深度={max_depth}")
        print("  (set_grid_profile 函数不可用，已记录参数但未实际设置)")
        return

    try:
        # 调用C++函数设置网格配置
        gcu.set_grid_profile(
            ctypes.c_float(unit_size),
            ctypes.c_int(max_depth)
        )
        print(f"网格配置设置成功: 单元尺寸={unit_size}米, 最大深度={max_depth}")
    except Exception as e:
        print(f"设置网格配置失败: {e}")

# 为方便使用，添加别名
set_grid_config = set_grid_profile
configure_grid = set_grid_profile

# Create Grid Scene Function - Integer version
try:
    gcu.create_grid_scene.restype = None
    gcu.create_grid_scene.argtypes = [
        ctypes.c_int,  # width
        ctypes.c_int,  # height
        ctypes.c_int   # length
    ]
    _create_grid_scene_available = True
except AttributeError:
    _create_grid_scene_available = False

def create_grid_scene(width: int, height: int, length: int):
    """
    创建网格场景（整数版本）
    
    参数:
        width: 场景宽度（x轴方向）
        height: 场景高度（y轴方向）  
        length: 场景长度（z轴方向）
        
    示例:
        create_grid_scene(50, 50, 50)  # 创建50x50x50的网格场景，从(0,0,0)开始
    """
    try:
        gcu.create_grid_scene(
            ctypes.c_int(width), 
            ctypes.c_int(height), 
            ctypes.c_int(length)
        )
        print(f"网格场景创建成功: 尺寸 {width}x{height}x{length}")
    except Exception as e:
        print(f"创建网格场景失败: {e}")

# Create Grid Scene Function
try:
    gcu.create_grid_sceneW.restype = None
    gcu.create_grid_sceneW.argtypes = [
        ctypes.c_float,  # x1
        ctypes.c_float,  # y1
        ctypes.c_float,  # z1
        ctypes.c_float,  # x2
        ctypes.c_float,  # y2
        ctypes.c_float   # z2
    ]
    _create_grid_sceneW_available = True
except AttributeError:
    _create_grid_sceneW_available = False

def create_grid_sceneW(x1: float, y1: float, z1: float, x2: float, y2: float, z2: float):
    """
    创建网格场景

    参数:
        x1, y1, z1: 场景起始坐标
        x2, y2, z2: 场景结束坐标

    示例:
        create_grid_sceneW(0, 0, 0, 50, 50, 50)  # 在(0,0,0)-(50,50,50)坐标点之间创建网格场景，具体格子尺寸要看上下文设置，默认0.1m/grid
    """
    if not _create_grid_sceneW_available:
        print(f"[Fallback] 网格场景参数: ({x1},{y1},{z1}) 到 ({x2},{y2},{z2})")
        print("  (create_grid_sceneW 函数不可用)")
        return

    try:
        gcu.create_grid_sceneW(
            ctypes.c_float(x1), ctypes.c_float(y1), ctypes.c_float(z1),
            ctypes.c_float(x2), ctypes.c_float(y2), ctypes.c_float(z2)
        )
        print(f"网格场景创建成功: ({x1},{y1},{z1}) 到 ({x2},{y2},{z2})")
    except Exception as e:
        print(f"创建网格场景失败: {e}")

# Create Writable Grid Function
try:
    gcu.create_grid_writable.restype = None
    gcu.create_grid_writable.argtypes = []
    _create_grid_writable_available = True
except AttributeError:
    _create_grid_writable_available = False

def create_grid_writable():
    """
    创建可写网格

    说明:
        基于当前网格场景创建可编辑的网格副本
    """
    if not _create_grid_writable_available:
        print("[Fallback] 可写网格（create_grid_writable 函数不可用）")
        return

    try:
        gcu.create_grid_writable()
        print("可写网格创建成功")
    except Exception as e:
        print(f"创建可写网格失败: {e}")
        
gcu.get_grid_val.restype = ctypes.c_int
gcu.get_grid_val.argtypes = [ctypes.c_float, ctypes.c_float, ctypes.c_float]

def get_grid_value(x, y, z):
    """Get grid value at specified grid space coordinates."""
    return gcu.get_grid_val(ctypes.c_float(x), ctypes.c_float(y), ctypes.c_float(z))
    
gcu.get_grid_valW.restype = ctypes.c_int
gcu.get_grid_valW.argtypes = [ctypes.c_float, ctypes.c_float, ctypes.c_float]

def get_grid_valueW(x, y, z):
    """Get grid value at specified world space coordinates."""
    return gcu.get_grid_valW(ctypes.c_float(x), ctypes.c_float(y), ctypes.c_float(z))

gcu.cell.restype = ctypes.c_int
gcu.cell.argtypes = [ctypes.c_float, ctypes.c_float, ctypes.c_float]

def cell(x, y, z):
    """Get grid value at specified grid space coordinates."""
    return gcu.cell(ctypes.c_float(x), ctypes.c_float(y), ctypes.c_float(z))
    
getcell = cell
getgrid = cell

gcu.set_grid_valW.restype = None
gcu.set_grid_valW.argtypes = [ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_int]

def set_grid_valueW(x, y, z, val):
    """Set grid value at specified grid space coordinates."""
    gcu.set_grid_valW(ctypes.c_float(x), ctypes.c_float(y), ctypes.c_float(z), ctypes.c_int(val))
    
gcu.set_grid_val.restype = None
gcu.set_grid_val.argtypes = [ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_int]

def set_grid_value(x, y, z, val):
    """Set grid value at specified grid space coordinates."""
    gcu.set_grid_val(ctypes.c_float(x), ctypes.c_float(y), ctypes.c_float(z), ctypes.c_int(val))

setcell = set_grid_value
setgrid = set_grid_value

def set_grid_valueXZY(x, z, y, val):
    """Set grid value at specified grid space coordinates."""
    gcu.set_grid_val(ctypes.c_float(x), ctypes.c_float(y), ctypes.c_float(z), ctypes.c_int(val))

setcellXZY = set_grid_valueXZY

# Field Value Function
gcu.get_fieldV_at.restype = ctypes.c_float
gcu.get_fieldV_at.argtypes = [ctypes.c_float, ctypes.c_float, ctypes.c_float]

def get_field_value(x, y, z):
    """Get field value at specified grid space coordinates."""
    return gcu.get_fieldV_at(ctypes.c_float(x), ctypes.c_float(y), ctypes.c_float(z))

# Get Grid Coordinate Function
get_grid_coord_ = gcu.get_grid_coord
get_grid_coord_.restype = None
get_grid_coord_.argtypes = [ctypes.POINTER(ctypes.c_float)]

def get_grid_coord():
    """Retrieve grid coordinates."""
    c = coord3()  # Create coord3 instance
    m = (ctypes.c_float * 15)()  # Create an array for coordinates
    get_grid_coord_(m)  # Call the C++ function
    c.ux = vec3(m[0], m[1], m[2])
    c.uy = vec3(m[3], m[4], m[5])
    c.uz = vec3(m[6], m[7], m[8])
    c.s = vec3(m[9], m[10], m[11])
    c.o = vec3(m[12], m[13], m[14])
    return c

# AABB (Axis-Aligned Bounding Box) grid coordinate
gcu.get_grid_aabb.restype = None  # No return value
gcu.get_grid_aabb.argtypes = [
    ctypes.POINTER(ctypes.c_int),  # x1
    ctypes.POINTER(ctypes.c_int),  # y1
    ctypes.POINTER(ctypes.c_int),  # z1
    ctypes.POINTER(ctypes.c_int),  # x2
    ctypes.POINTER(ctypes.c_int),  # y2
    ctypes.POINTER(ctypes.c_int)   # z2
]

def get_grid_aabb():
    """Retrieve the AABB of the grid."""
    x1, y1, z1 = ctypes.c_int(), ctypes.c_int(), ctypes.c_int()
    x2, y2, z2 = ctypes.c_int(), ctypes.c_int(), ctypes.c_int()
    
    # Call the C++ function
    gcu.get_grid_aabb(ctypes.byref(x1), ctypes.byref(y1), ctypes.byref(z1), 
                      ctypes.byref(x2), ctypes.byref(y2), ctypes.byref(z2))
    
    return (x1.value, y1.value, z1.value, x2.value, y2.value, z2.value)

gridaabb = get_grid_aabb

# Collision Detection
def check_collision(p):
    """Check for collision at point p."""
    offsets = np.array([
        [0, 0, 0], 
        [-0.17, 0, 0], [0.17, 0, 0], 
        [0, -0.17, 0], [0, 0.17, 0], 
        [0, 0, -0.17], [0, 0, 0.17], 
        [-0.17, -0.17, -0.17], [0.17, 0.17, 0.17], 
        [0.17, 0, -0.17]
    ])
    
    positions = np.array([p.x, p.y, p.z])
    return any(get_grid_value(*(positions + offset)) != 0 for offset in offsets)

# Load Grid Function
gcu.load_grid.restype = None
gcu.load_grid.argtypes = [ctypes.c_char_p]

def grid_scene_read(filename):
    """Load grid data from file."""
    gcu.load_grid(filename.encode('utf-8'))
    
# Grid Import Function
gcu.import_grid_file.restype = None
gcu.import_grid_file.argtypes = [ctypes.c_char_p]

def import_grid(filename):
    """
    导入grid文件到当前场景grid中
    
    参数:
        filename: 要导入的grid文件名
    """
    try:
        gcu.import_grid_file(filename.encode('utf-8'))
    except Exception as e:
        print(f"导入grid失败: {e}")


# Save Grid Function
gcu.save_grid.restype = None
gcu.save_grid.argtypes = [ctypes.c_char_p]

def save_scene_grid(filename):
    """Save grid data to file."""
    gcu.save_grid(filename.encode('utf-8'))

# Load Writable Grid Function
gcu.load_grid_writable.restype = None
gcu.load_grid_writable.argtypes = [ctypes.c_char_p]

def load_grid_writable(filename):
    """Load grid data into writable grid."""
    gcu.load_grid_writable(filename.encode('utf-8'))

# Save Writable Grid Function
try:
    gcu.save_grid_writable.restype = None
    gcu.save_grid_writable.argtypes = [ctypes.c_char_p]
    _save_grid_writable_available = True
except AttributeError:
    _save_grid_writable_available = False

def save_grid_writable(filename):
    """Save writable grid data to file."""
    if not _save_grid_writable_available:
        print(f"[Fallback] 保存可写网格到: {filename} (save_grid_writable 函数不可用)")
        return

    gcu.save_grid_writable(filename.encode('utf-8'))
    print(f"可写网格已保存到: {filename}")

# object_grid_from_sm
gcu.object_grid_from_sm.restype = ctypes.c_bool
gcu.object_grid_from_sm.argtypes = [ctypes.c_char_p, ctypes.c_char_p]

def object_grid_from_sm(smfile: str, name: str) -> bool:
    try:
        # 将字符串编码为UTF-8字节串
        smfile_encoded = smfile.encode('utf-8')
        name_encoded = name.encode('utf-8')
        
        # 调用C++函数
        success = gcu.object_grid_from_sm(smfile_encoded, name_encoded)
        
        return success
    except Exception as e:
        print(f"Error in object_grid_from_sm: {e}")
        return False

# object_grid_from_obj
gcu.object_grid_from_obj.restype = ctypes.c_bool
gcu.object_grid_from_obj.argtypes = [ctypes.c_char_p, ctypes.c_char_p]

def object_grid_from_obj(smfile: str, name: str) -> bool:
    try:
        # 将字符串编码为UTF-8字节串
        smfile_encoded = smfile.encode('utf-8')
        name_encoded = name.encode('utf-8')
        
        # 调用C++函数
        success = gcu.object_grid_from_obj(smfile_encoded, name_encoded)
        
        return success
    except Exception as e:
        print(f"Error in object_grid_from_obj: {e}")
        return False

# Object Grid AABB 接口
gcu.object_grid_aabb.restype = ctypes.c_bool
gcu.object_grid_aabb.argtypes = [
    ctypes.c_char_p,    # name
    ctypes.POINTER(ctypes.c_int),  # x1
    ctypes.POINTER(ctypes.c_int),  # y1
    ctypes.POINTER(ctypes.c_int),  # z1
    ctypes.POINTER(ctypes.c_int),  # x2
    ctypes.POINTER(ctypes.c_int),  # y2
    ctypes.POINTER(ctypes.c_int)   # z2
]

def get_object_aabb(obj_name: str):
    """
    获取指定对象的轴对齐包围盒(AABB)
    
    参数:
        obj_name: 对象名称
        
    返回:
        tuple: (x1, y1, z1, x2, y2, z2) 或 None(如果对象不存在)
    """
    x1, y1, z1 = ctypes.c_int(), ctypes.c_int(), ctypes.c_int()
    x2, y2, z2 = ctypes.c_int(), ctypes.c_int(), ctypes.c_int()
    
    success = gcu.object_grid_aabb(
        obj_name.encode('utf-8'),
        ctypes.byref(x1),
        ctypes.byref(y1),
        ctypes.byref(z1),
        ctypes.byref(x2),
        ctypes.byref(y2),
        ctypes.byref(z2)
    )
    
    if success:
        return (x1.value, y1.value, z1.value, 
                x2.value, y2.value, z2.value)
    return None

# 位置碰撞检测函数
gcu.object_grid_pos_collision.restype = ctypes.c_bool
gcu.object_grid_pos_collision.argtypes = [
    ctypes.c_char_p,  # obj_name
    ctypes.c_float,   # x
    ctypes.c_float,   # y
    ctypes.c_float    # z
]

def object_grid_pos_collision(obj_name: str, x: float, y: float, z: float) -> bool:
    """
    测试指定位置是否与对象发生碰撞（修正函数名引用）
    
    参数:
        obj_name: 对象名称
        x, y, z: 测试位置坐标
    
    返回:
        True: 发生碰撞
        False: 未发生碰撞
    """
    try:
        obj_name_encoded = obj_name.encode('utf-8')
        return gcu.object_grid_pos_collision(obj_name_encoded, x, y, z)  # 调用正确的函数名
    except Exception as e:
        print(f"碰撞检测错误: {e}")
        return False
        
# Grid PHG Node with ID Function (支持扩散版本)
gcu.grid_phgnode_with_id.restype = None
gcu.grid_phgnode_with_id.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char]

def grid_phgnode_with_id(gridid: int, node_name: str, diffusion_distance: int = 0, diffusion_value: int = 1):
    """
    Create a grid from a PHG node with specified grid ID, with optional diffusion
    
    Args:
        gridid: The ID to assign to the grid cells
        node_name: Name of the PHG node to gridize
        diffusion_distance: Distance for diffusion (0 = no diffusion)
        diffusion_value: Value to use for diffused cells
    
    Example:
        # Basic usage (no diffusion)
        grid_phgnode_with_id(1, "my_node")
        
        # With diffusion
        grid_phgnode_with_id(1, "my_node", 2, 64)  # Diffusion distance 2, value 64
        grid_phgnode_with_id(1, "my_node", 1)      # Diffusion distance 1, default value 1
    """
    try:
        # Convert parameters to C types
        c_gridid = ctypes.c_int(gridid)
        c_node_name = node_name.encode('utf-8')
        c_diffusion_distance = ctypes.c_int(diffusion_distance)
        c_diffusion_value = ctypes.c_char(diffusion_value)
        
        # Call the C++ function
        gcu.grid_phgnode_with_id(c_gridid, c_node_name, c_diffusion_distance, c_diffusion_value)
    except Exception as e:
        print(f"Error in grid_phgnode_with_id: {e}")
        
# Test Mesh Grid Collision Function
gcu.test_phgnode_collision.restype = ctypes.c_bool
gcu.test_phgnode_collision.argtypes = [ctypes.c_char_p, ctypes.c_int]

def test_phgnode_collision(node_name: str, gridid: int) -> bool:
    """
    Test if a mesh would collide with existing grid cells when gridized
    
    Args:
        node_name: Name of the PHG node to test
        
    Returns:
        bool: True if collision detected, False otherwise
        
    Example:
        if test_phgnode_collision("my_object"):
            print("Collision detected!")
    """
    try:
        c_node_name = node_name.encode('utf-8')
        return gcu.test_phgnode_collision(c_node_name, gridid)
    except Exception as e:
        print(f"Error in test_phgnode_collision: {e}")
        return False
        
def phg_to_grid(phg_script: str, node_name: str = "root", grid_id: int = 1) -> bool:
    """
    将PHG脚本转换为Grid
    
    参数:
        phg_script: PHG脚本字符串
        node_name: PHG节点名称
        grid_id: 网格ID
        
    返回:
        success: 是否成功
        
    示例:
        # 简单用法
        phg_to_grid("{md:sphere 1;}")
        
        # 指定节点名称和网格ID
        phg_to_grid("{my_sphere{md:sphere 1; p:5,5,5}}", "my_sphere", 2)
    """
    try:
        # 第一步：执行PHG脚本
        DoPHG(phg_script)
        
        # 第二步：设置绘制
        DoPHG(f"setup_draw('{node_name}');")
        
        # 第三步：转换为Grid
        grid_phgnode_with_id(grid_id, node_name)
        
        print(f"成功将PHG转换为Grid (节点: {node_name}, 网格ID: {grid_id})")
        return True
        
    except Exception as e:
        print(f"PHG到Grid转换失败: {e}")
        return False
        
# Grid Dump Function
gcu.grid_scene_dump.restype = None
gcu.grid_scene_dump.argtypes = []

def grid_dump():
    """Dump grid information to console."""
    gcu.grid_scene_dump()

#######################################    
# Grid ID Map
#######################################

# Get Grid ID Map Function
gcu.get_grid_idmap.restype = ctypes.c_short
gcu.get_grid_idmap.argtypes = [ctypes.c_float, ctypes.c_float, ctypes.c_float]

def get_grid_idmap(x, y, z):
    """
    Get grid ID value at specified grid space coordinates using octree structure.
    
    Args:
        x (float): X coordinate
        y (float): Y coordinate  
        z (float): Z coordinate
        
    Returns:
        short: Grid ID value at the grid space specified position
    """
    return gcu.get_grid_idmap(ctypes.c_float(x), ctypes.c_float(y), ctypes.c_float(z))    
    
#####################################
# mesh
##################################### 
# Get Mesh AABB Bounding Box
get_mesh_aabb = gcu.get_mesh_aabb
get_mesh_aabb.restype = ctypes.c_bool
get_mesh_aabb.argtypes = [
    ctypes.c_char_p, 
    ctypes.POINTER(ctypes.c_float),  # min_x
    ctypes.POINTER(ctypes.c_float),  # min_y
    ctypes.POINTER(ctypes.c_float),  # min_z
    ctypes.POINTER(ctypes.c_float),  # max_x
    ctypes.POINTER(ctypes.c_float),  # max_y
    ctypes.POINTER(ctypes.c_float)   # max_z
]

def mesh_aabb(mesh_name):
    """Get AABB bounding box of a mesh by name.
    
    Args:
        mesh_name (str): Name of the mesh
        
    Returns:
        tuple: (min_x, min_y, min_z, max_x, max_y, max_z) if successful
        
    Raises:
        ValueError: If mesh not found or empty
    """
    # Create variables to store the results
    min_x = ctypes.c_float()
    min_y = ctypes.c_float()
    min_z = ctypes.c_float()
    max_x = ctypes.c_float()
    max_y = ctypes.c_float()
    max_z = ctypes.c_float()
    
    # Encode string to bytes
    mesh_name_encoded = mesh_name.encode('utf-8')
    
    # Call C++ function
    success = get_mesh_aabb(
        mesh_name_encoded, 
        ctypes.byref(min_x),
        ctypes.byref(min_y),
        ctypes.byref(min_z),
        ctypes.byref(max_x),
        ctypes.byref(max_y),
        ctypes.byref(max_z)
    )
    
    if not success:
        raise ValueError(f"Mesh '{mesh_name}' not found or empty")
    
    # Return the AABB coordinates as a tuple
    return (min_x.value, min_y.value, min_z.value, 
            max_x.value, max_y.value, max_z.value)

#####################################
# OBJ Export Functions
##################################### 

# Save OBJ Function
gcu.saveobj.restype = ctypes.c_bool
gcu.saveobj.argtypes = [ctypes.c_char_p]

def saveobj(filename):
    """
    保存当前场景为OBJ文件
    
    参数:
        filename: 输出文件名
        
    返回:
        bool: 是否成功保存
        
    示例:
        saveobj("current_scene.obj")
    """
    try:
        filename_encoded = filename.encode('utf-8')
        success = gcu.saveobj(filename_encoded)
        if success:
            print(f"成功保存OBJ文件: {filename}")
        else:
            print(f"保存OBJ文件失败: {filename}")
        return success
    except Exception as e:
        print(f"保存OBJ文件时出错: {e}")
        return False
        
#####################################
# gcu_api VIZ
##################################### 
def VisPHG(script):
   phg.vis(script)
   
#screen shot   
def ImagePHG(script, filename='shot.png'):
   phg.image(script, filename)

#####################################
# Python-first boolean helpers
#####################################

def _norm_script_path(path: str) -> str:
    """Normalize Windows path for PHG script literals."""
    return str(path).replace('\\', '/').replace("'", "\\'")


def DoPHGFile(filepath: str, encoding: str = 'utf-8'):
    """Load a PHG script file and execute it via DoPHG."""
    with open(filepath, 'r', encoding=encoding, errors='ignore') as f:
        script = f.read()
    DoPHG(script)


def SaveSceneOBJ(filepath: str) -> bool:
    """Save current scene mesh to OBJ. Returns True on success."""
    return saveobj(filepath)


def BoolScriptToOBJ(script_file: str, out_obj: str, encoding: str = 'utf-8') -> bool:
    """Run a bool PHG script from file, then export current scene to OBJ."""
    DoPHGFile(script_file, encoding=encoding)
    return SaveSceneOBJ(out_obj)


def SaveCurrentPHG(filepath: str) -> None:
    """Export current entities to PHG using core API path."""
    path = _norm_script_path(filepath)
    DoPHG(f"api('save_entities_as_phg','{path}');")



def ReadSCEEntityCount(filepath: str) -> int:
    """Read SCE entity count from file header. Returns -1 if invalid."""
    try:
        with open(filepath, 'rb') as f:
            raw = f.read(4)
        if len(raw) != 4:
            return -1
        return int.from_bytes(raw, byteorder='little', signed=False)
    except Exception:
        return -1

def SaveCurrentSCE(filepath: str) -> int:
    """Export current PHG entities to SCE using core API path."""
    path = _norm_script_path(filepath)
    DoPHG(f"api('save_phgents_as_sce','{path}');")
    return ReadSCEEntityCount(filepath)


def _scene_paths(input_path: str, out_dir: str = None, scene_name: str = "scene.sce",
                 mdb_name: str = "mesh_database.mdb", phg_name: str = None):
    """Resolve output paths for scene conversion helpers."""
    src = Path(input_path)
    if out_dir:
        out_root = Path(out_dir)
    else:
        out_root = src.parent / "_sce"
    if phg_name is None:
        phg_name = src.stem + ".phg"
    return {
        "input_path": str(src),
        "out_dir": str(out_root),
        "sce_path": str(out_root / scene_name),
        "mdb_path": str(out_root / mdb_name),
        "phg_path": str(out_root / phg_name),
        "scene_name": scene_name,
        "mdb_name": mdb_name,
        "phg_name": phg_name,
    }


def LoadSceneFile(input_path: str, mesh_dir: str = None, scene_name: str = "scene.sce",
                  mdb_name: str = "mesh_database.mdb"):
    """Load a .wlkx/.rvm/.sce/.phg file through the native scene pipeline."""
    src = Path(input_path)
    ext = src.suffix.lower()
    script_path = _norm_script_path(str(src))

    if ext == ".phg":
        DoPHGFile(str(src))
        return {"input_path": str(src), "loaded_path": str(src), "type": "phg"}

    if ext in (".wlkx", ".rvm"):
        paths = _scene_paths(str(src), out_dir=mesh_dir, scene_name=scene_name, mdb_name=mdb_name)
        out_dir_script = _norm_script_path(paths["out_dir"])
        DoPHG(
            f"api('scenetest','{script_path}','{out_dir_script}','{scene_name}','{mdb_name}');"
        )
        return {
            "input_path": str(src),
            "loaded_path": paths["sce_path"],
            "type": ext.lstrip('.'),
            **paths,
        }

    if ext == ".sce":
        if mesh_dir:
            mesh_dir_script = _norm_script_path(mesh_dir)
            DoPHG(f"api('scenetest','{script_path}','{mesh_dir_script}');")
        else:
            DoPHG(f"api('scenetest','{script_path}');")
        return {"input_path": str(src), "loaded_path": str(src), "type": "sce", "mesh_dir": mesh_dir}

    raise ValueError(f"Unsupported scene format: {src.suffix}")


def ConvertScene(input_path: str, out_dir: str = None, target: str = "both",
                 scene_name: str = "scene.sce", mdb_name: str = "mesh_database.mdb",
                 phg_name: str = None, load: bool = True):
    """Convert .wlkx/.rvm/.sce into .sce and/or .phg via native scenetest pipeline."""
    src = Path(input_path)
    ext = src.suffix.lower()
    target = (target or "both").lower()
    if target not in {"sce", "phg", "both"}:
        raise ValueError("target must be one of: sce, phg, both")

    paths = _scene_paths(str(src), out_dir=out_dir, scene_name=scene_name, mdb_name=mdb_name, phg_name=phg_name)
    Path(paths["out_dir"]).mkdir(parents=True, exist_ok=True)

    if ext in (".wlkx", ".rvm", ".sce", ".phg"):
        load_info = LoadSceneFile(str(src), mesh_dir=paths["out_dir"] if ext == ".sce" else out_dir,
                                  scene_name=scene_name, mdb_name=mdb_name)
    else:
        raise ValueError(f"Unsupported scene format: {src.suffix}")

    result = {**paths, **load_info, "target": target}

    if target in {"sce", "both"} and ext == ".sce":
        result["sce_path"] = str(src)

    if target in {"phg", "both"}:
        SaveCurrentPHG(paths["phg_path"])

    if target == "sce" and ext in (".wlkx", ".rvm"):
        result.pop("phg_path", None)

    if target == "phg":
        result.pop("mdb_path", None)
        if ext == ".sce":
            result["sce_path"] = str(src)

    if not load:
        # Current native API converts by loading into runtime first; keep artifacts and note behavior.
        result["loaded_for_conversion"] = True

    return result


def ConvertWlkx(input_path: str, out_dir: str = None, target: str = "both",
                scene_name: str = "scene.sce", mdb_name: str = "mesh_database.mdb",
                phg_name: str = None, load: bool = True):
    """Convert a .wlkx file to .sce/.phg using the configured WLKX reader."""
    return ConvertScene(input_path, out_dir=out_dir, target=target, scene_name=scene_name,
                        mdb_name=mdb_name, phg_name=phg_name, load=load)


def ConvertRvm(input_path: str, out_dir: str = None, target: str = "both",
               scene_name: str = "scene.sce", mdb_name: str = "mesh_database.mdb",
               phg_name: str = None, load: bool = True):
    """Convert a .rvm file to .sce/.phg using the configured RVM reader."""
    return ConvertScene(input_path, out_dir=out_dir, target=target, scene_name=scene_name,
                        mdb_name=mdb_name, phg_name=phg_name, load=load)


def ConvertSceneToPhg(input_path: str, out_phg: str = None, out_dir: str = None,
                      scene_name: str = "scene.sce", mdb_name: str = "mesh_database.mdb"):
    """Convert .wlkx/.rvm/.sce directly to a PHG file and return output metadata."""
    phg_name = Path(out_phg).name if out_phg else None
    use_out_dir = out_dir or (str(Path(out_phg).parent) if out_phg else None)
    result = ConvertScene(input_path, out_dir=use_out_dir, target="phg", scene_name=scene_name,
                          mdb_name=mdb_name, phg_name=phg_name, load=True)
    if out_phg:
        result["phg_path"] = str(Path(out_phg))
        SaveCurrentPHG(result["phg_path"])
    return result
