#################################
#                               #
#       C++ API Module          # 
#                               #
#################################
#系统目录下 gcu_api.py
import importlib.resources
import http.client
import subprocess
import os
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, "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'))
    
# 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
get_pipes_coord_ = gcu.get_pipes_coord
get_pipes_coord_.restype = None
get_pipes_coord_.argtypes = [ctypes.POINTER(ctypes.c_float)]

def get_pipes_coord():
    """Retrieve coordinates of pipes."""
    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
#######################################
# Create Grid Scene Function
gcu.create_grid_scene.restype = None
gcu.create_grid_scene.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
]

def create_grid_scene(x1: float, y1: float, z1: float, x2: float, y2: float, z2: float):
    """
    创建网格场景
    
    参数:
        x1, y1, z1: 场景起始坐标
        x2, y2, z2: 场景结束坐标
        
    示例:
        create_grid_scene(0, 0, 0, 50, 50, 50)  # 创建50x50x50的网格场景
    """
    try:
        gcu.create_grid_scene(
            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  
gcu.create_grid_writable.restype = None
gcu.create_grid_writable.argtypes = []

def create_grid_writable():
    """
    创建可写网格
    
    说明:
        基于当前网格场景创建可编辑的网格副本
    """
    try:
        gcu.create_grid_writable()
        print("可写网格创建成功")
    except Exception as e:
        print(f"创建可写网格失败: {e}")
        
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_value(x, y, z):
    """Get grid value at specified coordinates."""
    return gcu.get_grid_valW(ctypes.c_float(x), ctypes.c_float(y), ctypes.c_float(z))

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

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

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 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 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 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) 
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
gcu.save_grid_writable.restype = None
gcu.save_grid_writable.argtypes = [ctypes.c_char_p]

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

# 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 coordinates using octree structure.
    
    Args:
        x (float): X coordinate
        y (float): Y coordinate  
        z (float): Z coordinate
        
    Returns:
        short: Grid ID value at the 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)


#####################################
# gcu_api VIZ
##################################### 
def VisPHG(script):
   phg.vis(script)
   
#screen shot   
def ImagePHG(script, filename='shot.png'):
   phg.image(script, filename)