# -*- coding: utf-8 -*-
"""
CityVistion CGA Publisher - CityEngine Plugin
将当前选中的 CGA 文件一键发布到 CityVistion 语义库
适用：CityEngine 2023.0+ (Python 3)
"""

import json
import os
import sys
import urllib.request
import urllib.error

# ==================== 配置区域 ====================
# 首次使用请修改为你的服务器信息
API_BASE = "https://api.cityvistion.com"
ADMIN_TOKEN = ""  # 从 https://pyprt.cityvistion.com/cv-admin.html 获取，或留空运行时输入

# 默认元数据（可在运行时修改）
DEFAULT_METADATA = {
    "roof_type": "歇山顶",
    "roof_layer": 1,
    "architectural_style": "官式",
    "dynasty_style": "明清",
    "has_dougong": False,
    "has_ridge_beast": False,
    "keywords": "",
    "description": ""
}

ROOF_TYPES = [
    "硬山顶", "悬山顶", "歇山顶", "重檐歇山顶",
    "庑殿顶", "重檐庑殿顶", "攒尖顶", "卷棚顶",
    "盔顶", "盝顶", "十字脊顶", "穹窿顶", "圆券顶", "平顶"
]

ARCH_STYLES = ["官式", "民居", "园林", "宗教", "商业", "现代", "混合"]


def _log(msg):
    """CityEngine 控制台输出"""
    print("[CityVistion] " + str(msg))


def _show_input_dialog(title, fields):
    """
    简易输入对话框（CityEngine 无内置复杂 GUI，用控制台输入代替）
    在实际使用中可替换为 CityEngine 的 GUI 组件或外部 Tkinter
    """
    result = {}
    print("\n=== " + title + " ===")
    for key, label, default in fields:
        prompt = "%s [%s]: " % (label, default)
        if sys.version_info[0] >= 3:
            val = input(prompt)
        else:
            val = raw_input(prompt)
        result[key] = val.strip() if val.strip() else default
    return result


def _read_file_content(filepath):
    """读取 CGA 文件内容"""
    encodings = ["utf-8", "gbk", "latin-1"]
    for enc in encodings:
        try:
            with open(filepath, "r", encoding=enc) as f:
                return f.read()
        except Exception:
            continue
    raise RuntimeError("无法读取文件编码: " + filepath)


def _upload_cga(filepath, metadata, admin_token):
    """调用平台 API 上传 CGA"""
    if not os.path.exists(filepath):
        raise RuntimeError("文件不存在: " + filepath)

    content = _read_file_content(filepath)
    filename = os.path.basename(filepath)
    title = os.path.splitext(filename)[0]

    # 自动提取 CGA 基础信息
    extracted = _extract_cga_info(content)

    # 合并元数据
    payload = {
        "title": metadata.get("title") or title,
        "cga_code": content,
        "asset_type": "rule",
        "source": "cityengine_plugin",
        "keywords": [k.strip() for k in metadata.get("keywords", "").split(",") if k.strip()],
        "nl2cga_tags": _generate_nl2cga_tags(metadata, extracted),
        "description": metadata.get("description") or extracted.get("description", ""),
        "params": {
            "roof_type": metadata.get("roof_type", ""),
            "roof_layer": int(metadata.get("roof_layer", 1)),
            "architectural_style": metadata.get("architectural_style", ""),
            "dynasty_style": metadata.get("dynasty_style", ""),
            "has_dougong": metadata.get("has_dougong", False),
            "has_ridge_beast": metadata.get("has_ridge_beast", False),
            "extracted_functions": extracted.get("functions", []),
            "extracted_attrs": extracted.get("attrs", []),
            "extracted_rules": extracted.get("rules", [])
        }
    }

    url = API_BASE.rstrip("/") + "/api/v1/admin/cga-assets"
    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + admin_token
    }

    req = urllib.request.Request(
        url,
        data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
        headers=headers,
        method="POST"
    )

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            data = json.loads(resp.read().decode("utf-8"))
            return True, data
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", errors="replace")
        return False, "HTTP %s: %s" % (e.code, body)
    except Exception as e:
        return False, str(e)


def _extract_cga_info(content):
    """从 CGA 代码提取基础信息"""
    import re
    info = {
        "attrs": [],
        "rules": [],
        "functions": [],
        "description": ""
    }

    # 提取 attr
    for m in re.finditer(r"^\s*attr\s+(\w+)\s*=", content, re.MULTILINE):
        info["attrs"].append(m.group(1))

    # 提取 rule 定义
    for m in re.finditer(r"^\s*(\w+)\s*-->", content, re.MULTILINE):
        info["rules"].append(m.group(1))

    # 提取函数调用
    funcs = set()
    for m in re.finditer(r"\b(extrude|comp|split|roofHip|roofGable|roofPyramid|roofShed|offset|setback|texture|projectUV|setupProjection|color|primitiveQuad|primitiveCube|primitiveCylinder|primitiveSphere|primitiveDisk|innerRectangle|alignScopeToAxes|geometry.isOriented)\s*\(", content):
        funcs.add(m.group(1))
    info["functions"] = sorted(list(funcs))

    # 提取注释描述
    desc_match = re.search(r"@description\s+(.+)", content)
    if desc_match:
        info["description"] = desc_match.group(1).strip()

    return info


def _generate_nl2cga_tags(metadata, extracted):
    """生成 NL2CGA 检索标签"""
    tags = ["cityengine_plugin", "user_upload"]

    roof_type = metadata.get("roof_type", "")
    if roof_type:
        tags.append("chinese_roof")
        if "重檐" in roof_type:
            tags.append("double_eave")
        if "歇山" in roof_type:
            tags.append("hip_gable")
        if "庑殿" in roof_type:
            tags.append("hip_roof")
        if "攒尖" in roof_type:
            tags.append("pyramid_roof")

    style = metadata.get("architectural_style", "")
    if style == "官式":
        tags.append("official_style")
    elif style == "民居":
        tags.append("folk_house")

    if metadata.get("has_dougong"):
        tags.append("dougong")
    if metadata.get("has_ridge_beast"):
        tags.append("ridge_beast")

    # 从提取函数补充
    funcs = extracted.get("functions", [])
    if "roofHip" in funcs:
        tags.append("roofHip")
    if "extrude" in funcs:
        tags.append("extrude")

    return tags


def publish_selected_file():
    """
    主入口：发布当前在 CityEngine 中选中的 CGA 文件
    使用方法：在 CityEngine 的 Scripting 窗口运行本脚本
    """
    try:
        import ce
    except ImportError:
        _log("错误：本脚本必须在 CityEngine 中运行")
        return

    # 获取当前选中的文件
    selection = ce.getSelection()
    if not selection:
        _log("请先在 CityEngine 中选中一个 CGA 文件")
        return

    # 尝试从选中对象获取文件路径
    filepath = None
    for obj in selection:
        if ce.isFile(obj):
            filepath = ce.getAttribute(obj, "ce:attr:file")
            if filepath and filepath.lower().endswith(".cga"):
                break

    if not filepath:
        # 备选：使用当前打开的编辑器文件
        _log("未找到选中的 .cga 文件，请手动输入路径")
        filepath = input("CGA 文件完整路径: ").strip()
        if not filepath.lower().endswith(".cga"):
            _log("文件扩展名必须是 .cga")
            return

    _log("准备发布: " + filepath)

    # 获取管理员 Token
    admin_token = ADMIN_TOKEN
    if not admin_token:
        admin_token = input("请输入管理员 Token: ").strip()
    if not admin_token:
        _log("Token 不能为空")
        return

    # 显示元数据输入
    default_title = os.path.splitext(os.path.basename(filepath))[0]
    fields = [
        ("title", "标题", default_title),
        ("roof_type", "屋顶类型 (%s)" % "/".join(ROOF_TYPES), DEFAULT_METADATA["roof_type"]),
        ("roof_layer", "屋顶层数 (1/2/3)", str(DEFAULT_METADATA["roof_layer"])),
        ("architectural_style", "建筑风格 (%s)" % "/".join(ARCH_STYLES), DEFAULT_METADATA["architectural_style"]),
        ("dynasty_style", "朝代风格", DEFAULT_METADATA["dynasty_style"]),
        ("has_dougong", "是否有斗拱 (y/n)", "y" if DEFAULT_METADATA["has_dougong"] else "n"),
        ("has_ridge_beast", "是否有脊兽 (y/n)", "y" if DEFAULT_METADATA["has_ridge_beast"] else "n"),
        ("keywords", "关键词 (逗号分隔)", DEFAULT_METADATA["keywords"]),
        ("description", "描述", DEFAULT_METADATA["description"])
    ]
    metadata = _show_input_dialog("CGA 元数据", fields)

    # 转换布尔值
    metadata["has_dougong"] = metadata.get("has_dougong", "").lower() in ("y", "yes", "true", "1")
    metadata["has_ridge_beast"] = metadata.get("has_ridge_beast", "").lower() in ("y", "yes", "true", "1")
    try:
        metadata["roof_layer"] = int(metadata.get("roof_layer", "1"))
    except Exception:
        metadata["roof_layer"] = 1

    # 上传
    _log("正在上传...")
    ok, result = _upload_cga(filepath, metadata, admin_token)

    if ok:
        _log("✅ 发布成功！资产 ID: %s" % result.get("id", "unknown"))
        _log("标题: %s" % result.get("title", ""))
        _log("关键词: %s" % ", ".join(result.get("keywords", [])))
        _log("NL2CGA 标签: %s" % ", ".join(result.get("nl2cga_tags", [])))
    else:
        _log("❌ 发布失败: %s" % result)


def batch_publish_directory():
    """
    批量发布目录下所有 .cga 文件
    """
    try:
        import ce
    except ImportError:
        _log("错误：本脚本必须在 CityEngine 中运行")
        return

    dirpath = input("请输入 CGA 目录路径: ").strip()
    if not os.path.isdir(dirpath):
        _log("目录不存在: " + dirpath)
        return

    admin_token = ADMIN_TOKEN
    if not admin_token:
        admin_token = input("请输入管理员 Token: ").strip()

    files = [f for f in os.listdir(dirpath) if f.lower().endswith(".cga")]
    if not files:
        _log("目录中没有 .cga 文件")
        return

    _log("发现 %d 个 CGA 文件，开始批量发布..." % len(files))

    success = 0
    failed = 0
    for filename in files:
        filepath = os.path.join(dirpath, filename)
        _log("发布: " + filename)

        # 使用默认元数据，可根据文件名自动推断
        metadata = DEFAULT_METADATA.copy()
        metadata["title"] = os.path.splitext(filename)[0]

        ok, result = _upload_cga(filepath, metadata, admin_token)
        if ok:
            _log("  ✅ 成功 (ID: %s)" % result.get("id", "?"))
            success += 1
        else:
            _log("  ❌ 失败: %s" % result)
            failed += 1

    _log("批量发布完成: 成功 %d, 失败 %d" % (success, failed))


# ==================== 菜单注册 ====================
def register_plugin():
    """在 CityEngine 中注册菜单"""
    try:
        import ce
        # 创建菜单项（不同 CityEngine 版本 API 可能不同）
        _log("CityVistion Publisher 已加载")
        _log("使用方法: 在 CityEngine Scripting 窗口运行 publish_selected_file() 或 batch_publish_directory()")
    except Exception as e:
        _log("插件注册失败: %s" % e)


if __name__ == "__main__":
    register_plugin()
