import jpype
import os
from typing import Dict, Any, Optional

class Enjoy:
    """
    适配JFinal Enjoy 5.2.2模板引擎的Python调用类
    核心类：com.jfinal.template.Engine
    实现：mrzhou@miw.cn
    """
    _instance: Optional['Enjoy'] = None
    _jvm_started: bool = False
    _engine: Optional[Any] = None  # 保存JFinal Engine实例

    def __new__(cls, jar_path: str = None, template_base_path: str = "./"):
        """单例模式：全局唯一Engine实例"""
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            # 如果未提供jar_path，使用项目内置的enjoy-5.2.2.jar
            if jar_path is None:
                # 获取当前文件所在目录，然后拼接jar文件名
                current_dir = os.path.dirname(os.path.abspath(__file__))
                cls._instance.jar_path = os.path.join(current_dir, "enjoy-5.2.2.jar")
            else:
                cls._instance.jar_path = os.path.abspath(jar_path)
            cls._instance.template_base_path = os.path.abspath(template_base_path)
            cls._instance._init_jvm()
            cls._instance._init_engine()  # 初始化JFinal Engine
        return cls._instance

    def _init_jvm(self):
        """启动JVM并加载enjoy-5.2.2.jar"""
        if not self._jvm_started:
            try:
                # 使用JPype1的自动类路径管理，让JPype1自己处理其支持库
                jpype.startJVM(
                    "-ea",
                    f"-Djava.class.path={self.jar_path}",
                    "-Dfile.encoding=UTF-8",
                    convertStrings=False
                )
                self._jvm_started = True
                print(f"JVM启动成功 | 工作目录: {jpype.java.lang.System.getProperty('user.dir')}")
            except Exception as e:
                raise RuntimeError(f"JVM启动失败: {e}") from e

    def _init_engine(self):
        """初始化JFinal Engine实例（核心）"""
        try:
            # 加载JFinal Engine类
            Engine = jpype.JClass("com.jfinal.template.Engine")
            # 获取默认Engine实例（JFinal推荐单例使用）
            self._engine = Engine.use()
            # 配置模板基础路径（关键：指定模板文件所在目录）
            self._engine.setBaseTemplatePath(self.template_base_path)
            # 可选配置：设置编码、开启热加载等
            self._engine.setEncoding("UTF-8")
            self._engine.setDevMode(True)  # 开发模式，便于调试
            print(f"Engine初始化成功 | 模板基础路径: {self.template_base_path}")
        except jpype.JException as e:
            e.printStackTrace()
            raise RuntimeError(f"Engine初始化失败: {e.getMessage()}") from e

    def _convert_python_to_java(self, obj: Any) -> Any:
        """转换Python数据为Java Map（适配Enjoy模板数据格式）"""
        if isinstance(obj, dict):
            # 优先使用JFinal的Map（也可用java.util.HashMap）
            HashMap = jpype.JClass("java.util.HashMap")
            java_map = HashMap()
            for key, value in obj.items():
                # 递归转换嵌套数据
                if isinstance(value, (dict, list)):
                    java_map.put(key, self._convert_python_to_java(value))
                else:
                    java_map.put(key, value)
            return java_map
        elif isinstance(obj, list):
            return jpype.JArray(jpype.JObject)([self._convert_python_to_java(x) for x in obj])
        else:
            return obj

    def render(self, tempStr: str, data: Dict[str, Any]) -> str:
        """
        渲染Enjoy模板（核心方法）
        :param tempStr: 模板文件名（相对于baseTemplatePath的路径，如index.html）
        :param data: 模板渲染数据（Python字典）
        :return: 渲染后的字符串
        """
        if not self._engine:
            raise RuntimeError("Engine未初始化，请检查Jar包路径")

        try:
            # 1. 转换Python字典为Java Map
            java_data = self._convert_python_to_java(data)
            # 2. 获取Template实例（Engine.getTemplate）
            template = self._engine.getTemplate(tempStr)
            # 3. 渲染模板：使用Writer接收结果
            StringWriter = jpype.JClass("java.io.StringWriter")
            writer = StringWriter()
            template.render(java_data, writer)
            # 4. 转换为Python字符串
            result = writer.toString()
            writer.close()
            return result

        except jpype.JException as e:
            print("\n=== JFinal Enjoy异常详情 ===")
            e.printStackTrace()
            raise RuntimeError(f"模板渲染失败: {e.getMessage()}") from e
        except Exception as e:
            raise RuntimeError(f"调用失败: {e}") from e

    def addSharedFunction(self, layouts: [str]):
        """添加全局共享函数"""
        for layout in layouts:
            self._engine.addSharedFunction(layout)
    
    def invoke_engine_method(self, method_name: str, *args, **kwargs) -> Any:
        """
        通用方法：调用内部_engine的其他可调用方法
        
        :param method_name: 要调用的_engine方法名
        :param args: 位置参数
        :param kwargs: 关键字参数
        :return: 方法调用结果
        :raises RuntimeError: 如果Engine未初始化或方法调用失败
        """
        if not self._engine:
            raise RuntimeError("Engine未初始化，请检查Jar包路径")
        
        try:
            # 获取_engine的方法
            method = getattr(self._engine, method_name)
            # 转换Python参数为Java兼容类型
            converted_args = [self._convert_python_to_java(arg) for arg in args]
            converted_kwargs = {k: self._convert_python_to_java(v) for k, v in kwargs.items()}
            # 调用方法并返回结果
            return method(*converted_args, **converted_kwargs)
        except AttributeError:
            raise RuntimeError(f"Engine对象没有方法: {method_name}") from None
        except jpype.JException as e:
            print(f"\n=== JFinal Enjoy {method_name} 方法调用异常详情 ===")
            e.printStackTrace()
            raise RuntimeError(f"{method_name}方法调用失败: {e.getMessage()}") from e
        except Exception as e:
            raise RuntimeError(f"{method_name}方法调用失败: {e}") from e

    def close(self):
        """关闭JVM，释放资源"""
        if self._jvm_started:
            jpype.shutdownJVM()
            self._jvm_started = False
            self._engine = None
            self.__class__._instance = None
            print("JVM已关闭")

    def __del__(self):
        """析构函数：自动关闭"""
        self.close()

# ------------------------------
# 标准调用示例（适配enjoy-5.2.2.jar）
# ------------------------------
if __name__ == "__main__":
    # 1. 配置参数（仅需配置模板路径，jar文件自动使用内置版本）
    TEMPLATE_BASE_PATH = "./templates"  # 模板文件所在目录（如index.html放在这个目录下）

    # 2. 创建Enjoy实例（初始化Engine，自动使用内置的enjoy-5.2.2.jar）
    try:
        enjoy = Enjoy(
            template_base_path=TEMPLATE_BASE_PATH
        )

        enjoy.addSharedFunction(['layout.html'])
        # 3. 准备模板参数
        render_data = {
            "name": "JFinal Enjoy 5.2.2测试",
            "hobbies": ["Python调用Java", "模板渲染", "跨语言开发"],
            "user": {"age": 25, "gender": "男"}
        }

        # 4. 渲染模板（tempStr是模板文件名，相对于TEMPLATE_BASE_PATH）
        result = enjoy.render(
            tempStr="layout-test.html",  # 模板文件：./templates/index.html
            data=render_data       # 渲染数据（Python字典）
        )

        # 5. 输出结果
        print("\n=== 模板渲染结果 ===")
        print(result)

    except RuntimeError as e:
        print(f"\n执行失败: {e}")
    finally:
        # 6. 关闭资源
        if 'enjoy' in locals():
            enjoy.close()