# -*- coding: utf-8 -*-
"""
通用格式转换模块
Author  : NextPCG

提供通用文件格式（PNG/JPG/OBJ等）与内部数据类型之间的转换功能。
"""

import os
import numpy as np
from typing import List, Dict, Optional, Tuple
from PIL import Image

from .dson_geo import Texture, TextureNode, StaticMesh, StaticMeshNode, image2node, node2image
from .dson_field import FileField


# ============================================================================
# 图片格式转换 (Image/Texture)
# ============================================================================

def image_to_texture(image_path: str, name: str = None) -> Texture:
    """
    将通用图片文件（PNG、JPG、TGA、BMP）转换为 Texture 对象
    
    Args:
        image_path: 图片文件的绝对路径
        name: 纹理节点名称，默认使用文件名（不含扩展名）
        
    Returns:
        Texture: 转换后的 Texture 对象
        
    Raises:
        FileNotFoundError: 图片文件不存在
        ValueError: 不支持的图片格式
    """
    if not os.path.exists(image_path):
        raise FileNotFoundError(f"图片文件不存在: {image_path}")
    
    # 支持的图片格式
    supported_formats = {'.png', '.jpg', '.jpeg', '.tga', '.bmp', '.tiff'}
    ext = os.path.splitext(image_path)[1].lower()
    if ext not in supported_formats:
        raise ValueError(f"不支持的图片格式: {ext}，支持的格式: {supported_formats}")
    
    # 使用文件名作为默认名称
    if name is None:
        name = os.path.splitext(os.path.basename(image_path))[0]
    
    # 加载图片
    image = Image.open(image_path)
    
    # 确保图片至少有3个通道（RGB）
    if image.mode == 'L':  # 灰度图
        image = image.convert('RGB')
    elif image.mode == 'LA':  # 灰度图+Alpha
        image = image.convert('RGBA')
    elif image.mode == 'P':  # 调色板模式
        image = image.convert('RGBA')
    elif image.mode not in ('RGB', 'RGBA'):
        image = image.convert('RGB')
    
    # 使用现有的 Texture.create_from_images 方法
    texture = Texture.create_from_images([image])
    
    # 重命名纹理节点
    if texture.texture_nodes:
        old_name = list(texture.texture_nodes.keys())[0]
        texture.texture_nodes[name] = texture.texture_nodes.pop(old_name)
        texture.texture_nodes[name].name = name
    
    return texture


def texture_to_image(texture: Texture, output_path: str, node_name: str = None) -> None:
    """
    将 Texture 对象导出为通用图片格式（PNG）
    
    Args:
        texture: 要导出的 Texture 对象
        output_path: 输出文件路径（支持 .png、.jpg 等）
        node_name: 要导出的纹理节点名称，默认导出第一个
        
    Raises:
        ValueError: Texture 对象为空或指定的节点不存在
    """
    if not texture.texture_nodes:
        raise ValueError("Texture 对象没有纹理节点")
    
    # 选择要导出的节点
    if node_name is None:
        node_name = list(texture.texture_nodes.keys())[0]
    
    if node_name not in texture.texture_nodes:
        raise ValueError(f"纹理节点 '{node_name}' 不存在")
    
    texture_node = texture.texture_nodes[node_name]
    image = node2image(texture_node)
    
    # 确保输出目录存在
    output_dir = os.path.dirname(output_path)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    image.save(output_path)


def texture_to_images(texture: Texture, output_dir: str, format: str = 'png') -> Dict[str, str]:
    """
    将 Texture 对象的所有纹理节点导出为图片文件
    
    Args:
        texture: 要导出的 Texture 对象
        output_dir: 输出目录路径
        format: 输出格式（png、jpg等）
        
    Returns:
        Dict[str, str]: 节点名称到输出文件路径的映射
    """
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    result = {}
    images_dict = texture.to_images()
    for name, image in images_dict.items():
        output_path = os.path.join(output_dir, f"{name}.{format}")
        image.save(output_path)
        result[name] = output_path
    
    return result


# ============================================================================
# OBJ 模型格式转换 (StaticMesh)
# ============================================================================

def obj_to_staticmesh(obj_path: str, name: str = None) -> StaticMesh:
    """
    将 OBJ 文件转换为 StaticMesh 对象
    
    Args:
        obj_path: OBJ 文件的绝对路径
        name: 网格节点名称，默认使用文件名（不含扩展名）
        
    Returns:
        StaticMesh: 转换后的 StaticMesh 对象
        
    Raises:
        FileNotFoundError: OBJ 文件不存在
        ValueError: OBJ 文件格式错误
    """
    if not os.path.exists(obj_path):
        raise FileNotFoundError(f"OBJ 文件不存在: {obj_path}")
    
    # 使用文件名作为默认名称
    if name is None:
        name = os.path.splitext(os.path.basename(obj_path))[0]
    
    positions = []
    tex_coords = []
    normals = []
    faces = []
    
    with open(obj_path, 'r', encoding='utf-8') as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith('#'):
                continue
            
            parts = line.split()
            if not parts:
                continue
            
            if parts[0] == 'v':
                # 顶点位置: v x y z [w]
                if len(parts) >= 4:
                    positions.append([float(parts[1]), float(parts[2]), float(parts[3])])
            
            elif parts[0] == 'vt':
                # 纹理坐标: vt u v [w]
                if len(parts) >= 3:
                    tex_coords.append([float(parts[1]), float(parts[2])])
            
            elif parts[0] == 'vn':
                # 法线: vn x y z
                if len(parts) >= 4:
                    normals.append([float(parts[1]), float(parts[2]), float(parts[3])])
            
            elif parts[0] == 'f':
                # 面: f v1[/vt1][/vn1] v2[/vt2][/vn2] v3[/vt3][/vn3] ...
                face_vertices = []
                for vertex in parts[1:]:
                    # 解析顶点索引 (OBJ 索引从 1 开始)
                    indices = vertex.split('/')
                    v_idx = int(indices[0]) - 1  # 转换为 0-based 索引
                    face_vertices.append(v_idx)
                
                # 三角形化（如果是多边形）
                if len(face_vertices) >= 3:
                    for i in range(1, len(face_vertices) - 1):
                        faces.append([face_vertices[0], face_vertices[i], face_vertices[i + 1]])
    
    if not positions:
        raise ValueError(f"OBJ 文件没有有效的顶点数据: {obj_path}")
    
    # 创建 StaticMesh 对象
    static_mesh = StaticMesh()
    mesh_node = StaticMeshNode()
    mesh_node.name = name
    mesh_node.positions = positions
    mesh_node.faces = faces
    
    static_mesh.staticmesh_nodes[name] = mesh_node
    
    return static_mesh


def staticmesh_to_obj(staticmesh: StaticMesh, output_path: str, node_name: str = None) -> None:
    """
    将 StaticMesh 对象导出为 OBJ 格式
    
    Args:
        staticmesh: 要导出的 StaticMesh 对象
        output_path: 输出文件路径
        node_name: 要导出的网格节点名称，默认导出第一个
        
    Raises:
        ValueError: StaticMesh 对象为空或指定的节点不存在
    """
    if not staticmesh.staticmesh_nodes:
        raise ValueError("StaticMesh 对象没有网格节点")
    
    # 选择要导出的节点
    if node_name is None:
        node_name = list(staticmesh.staticmesh_nodes.keys())[0]
    
    if node_name not in staticmesh.staticmesh_nodes:
        raise ValueError(f"网格节点 '{node_name}' 不存在")
    
    mesh_node = staticmesh.staticmesh_nodes[node_name]
    
    # 确保输出目录存在
    output_dir = os.path.dirname(output_path)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(f"# OBJ file exported by NextPCG\n")
        f.write(f"# Object: {node_name}\n")
        f.write(f"o {node_name}\n")
        
        # 写入顶点位置
        for pos in mesh_node.positions:
            f.write(f"v {pos[0]} {pos[1]} {pos[2]}\n")
        
        # 写入面（OBJ 索引从 1 开始）
        for face in mesh_node.faces:
            if isinstance(face, (list, tuple)) and len(face) >= 3:
                # 转换为 1-based 索引
                indices = [str(idx + 1) for idx in face]
                f.write(f"f {' '.join(indices)}\n")
            elif isinstance(face, int):
                # 如果 faces 是平铺的索引列表，需要特殊处理
                pass  # 这种情况需要重新组织数据


def staticmesh_to_objs(staticmesh: StaticMesh, output_dir: str) -> Dict[str, str]:
    """
    将 StaticMesh 对象的所有网格节点导出为 OBJ 文件
    
    Args:
        staticmesh: 要导出的 StaticMesh 对象
        output_dir: 输出目录路径
        
    Returns:
        Dict[str, str]: 节点名称到输出文件路径的映射
    """
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    result = {}
    for name in staticmesh.staticmesh_nodes.keys():
        output_path = os.path.join(output_dir, f"{name}.obj")
        staticmesh_to_obj(staticmesh, output_path, name)
        result[name] = output_path
    
    return result


# ============================================================================
# 简单 OBJ 模型生成
# ============================================================================

def create_cube_obj(size: float = 1.0) -> Tuple[List[List[float]], List[List[int]]]:
    """
    创建一个简单立方体的顶点和面数据
    
    Args:
        size: 立方体边长的一半
        
    Returns:
        Tuple[positions, faces]: 顶点位置列表和面索引列表
    """
    s = size
    positions = [
        [-s, -s, -s], [s, -s, -s], [s, s, -s], [-s, s, -s],  # 前面
        [-s, -s, s], [s, -s, s], [s, s, s], [-s, s, s],       # 后面
    ]
    
    # 三角形面（每个四边形分为2个三角形）
    faces = [
        # 前面
        [0, 1, 2], [0, 2, 3],
        # 后面
        [5, 4, 7], [5, 7, 6],
        # 左面
        [4, 0, 3], [4, 3, 7],
        # 右面
        [1, 5, 6], [1, 6, 2],
        # 顶面
        [3, 2, 6], [3, 6, 7],
        # 底面
        [4, 5, 1], [4, 1, 0],
    ]
    
    return positions, faces


def create_cube_staticmesh(name: str = "cube", size: float = 1.0) -> StaticMesh:
    """
    创建一个简单立方体的 StaticMesh 对象
    
    Args:
        name: 网格名称
        size: 立方体边长的一半
        
    Returns:
        StaticMesh: 立方体的 StaticMesh 对象
    """
    positions, faces = create_cube_obj(size)
    
    static_mesh = StaticMesh()
    mesh_node = StaticMeshNode()
    mesh_node.name = name
    mesh_node.positions = positions
    mesh_node.faces = faces
    
    static_mesh.staticmesh_nodes[name] = mesh_node
    
    return static_mesh


def save_cube_obj(output_path: str, size: float = 1.0) -> None:
    """
    保存一个简单立方体 OBJ 文件
    
    Args:
        output_path: 输出文件路径
        size: 立方体边长的一半
    """
    positions, faces = create_cube_obj(size)
    
    # 确保输出目录存在
    output_dir = os.path.dirname(output_path)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write("# OBJ file - Simple Cube\n")
        f.write("o cube\n")
        
        for pos in positions:
            f.write(f"v {pos[0]} {pos[1]} {pos[2]}\n")
        
        for face in faces:
            # OBJ 索引从 1 开始
            f.write(f"f {face[0]+1} {face[1]+1} {face[2]+1}\n")


# ============================================================================
# 文件处理 (FileField)
# ============================================================================

def create_file_field(file_path: str, work_path: str = None) -> FileField:
    """
    创建 FileField 对象
    
    Args:
        file_path: 文件路径（相对于 work_path 或绝对路径）
        work_path: 工作目录路径，如果为 None 则使用 file_path 的目录
        
    Returns:
        FileField: 创建的 FileField 对象
        
    Raises:
        FileNotFoundError: 文件不存在
    """
    if work_path is None:
        # 如果是绝对路径，使用文件所在目录作为 work_path
        if os.path.isabs(file_path):
            work_path = os.path.dirname(file_path)
            file_name = os.path.basename(file_path)
        else:
            work_path = os.getcwd()
            file_name = file_path
    else:
        # 如果提供了 work_path，file_path 应该是相对路径或文件名
        if os.path.isabs(file_path):
            file_name = os.path.relpath(file_path, work_path)
        else:
            file_name = file_path
    
    # 验证文件存在
    full_path = os.path.join(work_path, file_name)
    if not os.path.exists(full_path):
        raise FileNotFoundError(f"文件不存在: {full_path}")
    
    return FileField(file_name, work_path)


def create_file_field_list(file_paths: List[str], work_path: str) -> List[FileField]:
    """
    创建 FileField 对象列表
    
    Args:
        file_paths: 文件路径列表（相对于 work_path）
        work_path: 工作目录路径
        
    Returns:
        List[FileField]: FileField 对象列表
    """
    return [create_file_field(fp, work_path) for fp in file_paths]


# ============================================================================
# 通用加载器（根据文件扩展名自动选择转换方法）
# ============================================================================

def load_texture_from_file(file_path: str, name: str = None) -> Texture:
    """
    从文件加载 Texture（自动识别格式）
    
    支持的格式: PNG, JPG, JPEG, TGA, BMP, TIFF
    """
    return image_to_texture(file_path, name)


def load_staticmesh_from_file(file_path: str, name: str = None) -> StaticMesh:
    """
    从文件加载 StaticMesh（自动识别格式）
    
    支持的格式: OBJ
    """
    ext = os.path.splitext(file_path)[1].lower()
    if ext == '.obj':
        return obj_to_staticmesh(file_path, name)
    else:
        raise ValueError(f"不支持的 3D 模型格式: {ext}，当前支持: .obj")


# ============================================================================
# 测试数据创建辅助函数
# ============================================================================

def create_test_text_file(output_path: str, content: str = "This is a test file.") -> None:
    """
    创建测试用文本文件
    
    Args:
        output_path: 输出文件路径
        content: 文件内容
    """
    output_dir = os.path.dirname(output_path)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(content)


def create_test_binary_file(output_path: str, size: int = 1024) -> None:
    """
    创建测试用二进制文件
    
    Args:
        output_path: 输出文件路径
        size: 文件大小（字节数）
    """
    output_dir = os.path.dirname(output_path)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    # 生成随机二进制数据
    data = bytes(range(256)) * (size // 256 + 1)
    data = data[:size]
    
    with open(output_path, 'wb') as f:
        f.write(data)


def create_test_image(output_path: str, width: int = 64, height: int = 64, 
                      color: Tuple[int, int, int] = (255, 128, 0)) -> None:
    """
    创建测试用图片文件
    
    Args:
        output_path: 输出文件路径
        width: 图片宽度
        height: 图片高度
        color: RGB 颜色值
    """
    output_dir = os.path.dirname(output_path)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    # 创建简单的彩色图片
    image = Image.new('RGB', (width, height), color)
    
    # 添加一些变化使图片不那么单调
    pixels = image.load()
    for i in range(width):
        for j in range(height):
            r = (color[0] + i * 2) % 256
            g = (color[1] + j * 2) % 256
            b = (color[2] + (i + j)) % 256
            pixels[i, j] = (r, g, b)
    
    image.save(output_path)
