from cffi import FFI
import os

ffi = FFI()

ffi.cdef("""
    typedef enum {
        Success = 0,
        ParseError = 2,
        RuntimeError = 3,
        FfiError = 4,
    } OpenExStatus;

    // 声明不透明结构体
    typedef struct OpenEX OpenEX;

    typedef enum {
        Int = 0,
        Bool = 1,
        Float = 2,
        String = 3,
        Ref = 4,
        Null = 5
    } ValueTag;

    typedef union {
        int64_t i;
        bool b;
        double f;
        const char* s;
    } ValueData;

    typedef struct {
        ValueTag tag;
        ValueData data;
    } CValue;

    // 声明函数
    OpenEX* openex_init(const char* lib_path);
    OpenExStatus openex_add_file(OpenEX* handle, const char* code, const char* name);
    OpenExStatus openex_compile(OpenEX* handle);
    OpenExStatus openex_free(OpenEX* handle);
    OpenExStatus openex_free_c_value(CValue* c_val);
    OpenExStatus openex_initialize_executor(OpenEX* handle);
    OpenExStatus openex_call_function(OpenEX* handle, const char* file, const char* func, CValue* args_ptr, size_t arg_count, CValue* out_result);
""")

lib_path = "./libopenex.so"
if os.name == 'nt':  # Windows
    lib_path = "./openex.dll"

lib = ffi.dlopen(lib_path)

class OpenEXInterpreter:
    """封装 OpenEX 解释器的 Python 类"""

    def __init__(self, lib_path: str):
        # 将字符串转换为 C 兼容的字节流
        c_path = lib_path.encode('utf-8') if lib_path else ffi.NULL

        # 调用 openex_init，直接返回 OpenEX*
        self._handle = lib.openex_init(c_path)

        # 检查是否返回了空指针
        if self._handle == ffi.NULL:
            raise RuntimeError("OpenEX 解释器初始化失败：返回了空指针")

        print(f"OpenEX 初始化成功，句柄地址: {self._handle}")

    def add_file(self, code: str, name: str = "main.ex"):
        """添加源代码到解释器"""
        c_code = code.encode('utf-8')
        c_name = name.encode('utf-8')

        status = lib.openex_add_file(self._handle, c_code, c_name)
        self._check_status(status, f"添加文件 '{name}' 失败")

    def compile(self):
        """编译已加载的所有源文件"""
        status = lib.openex_compile(self._handle)
        self._check_status(status, "编译代码失败")
        print("代码编译完成")

    def init_exec(self):
        status = lib.openex_initialize_executor(self._handle)
        self._check_status(status, "初始化执行环境失败")
        print("初始化执行环境成功")

    def call_function(self, file, name, py_args):
        count = len(py_args)
        c_args_array = ffi.new("CValue[]", count)

        c_file = file.encode('utf-8')
        c_name = name.encode('utf-8')

        for i, val in enumerate(py_args):
            if val is None:
                c_args_array[i].tag = 5 # Null
                c_args_array[i].data.i = 0
            elif isinstance(val, bool):
                c_args_array[i].tag = 1 # Bool
                c_args_array[i].data.b = val
            elif isinstance(val, int): # Number
                c_args_array[i].tag = lib.Int
                c_args_array[i].data.i = val
            elif isinstance(val, float): # Float
                c_args_array[i].tag = lib.Float
                c_args_array[i].data.f = val
            elif isinstance(val, str): # String
                c_args_array[i].tag = lib.String
                # 必须手动转换 bytes，cffi 会负责管理这个临时 char 数组的生命周期
                c_args_array[i].data.s = ffi.new("char[]", val.encode('utf-8'))

        out_ptr = ffi.new("CValue*")
        status = lib.openex_call_function(self._handle, c_file, c_name, c_args_array, count, out_ptr)
        self._check_status(status, "调用函数失败")

        result = out_ptr[0]
        final_py_val = None

        if result.tag == lib.Int:
            final_py_val = result.data.i
        elif result.tag == lib.Bool:
            final_py_val = result.data.b
        elif result.tag == lib.Float:
            final_py_val = result.data.f
        elif result.tag == lib.String or result.tag == lib.Ref:
            # 将 C 字符串转为 Python 字符串
            final_py_val = ffi.string(result.data.s).decode('utf-8')
        elif result.tag == lib.Null:
            final_py_val = None

        lib.openex_free_c_value(out_ptr)
        return final_py_val

    def _check_status(self, status, msg_prefix):
        """私有方法：检查返回状态码"""
        if status == lib.Success:
            return

        # 映射错误码
        error_map = {
            lib.ParseError: "解析错误 (ParseError)",
            lib.RuntimeError: "运行错误 (RuntimeError)",
            lib.FfiError: "FFI 交互错误 (FfiError)"
        }
        err_msg = error_map.get(status, f"未知错误 (Code: {status})")
        raise RuntimeError(f"{msg_prefix}: {err_msg}")

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def close(self):
        if hasattr(self, '_handle') and self._handle != ffi.NULL:
            lib.openex_free(self._handle)
            self._handle = ffi.NULL

if __name__ == "__main__":
    try:
        # 1. 创建解释器实例
        with OpenEXInterpreter("./lib") as interp:
            # 2. 添加代码 (code, name)
            script = """
            import system;
            function main(num, num2) {
                var a = num + num2;
                system.println("Hello OpenEX from Python! " + a);
                return 10;
            }
            """
            interp.add_file(script, "example.exf")

            script1 = """
            import system;
            import example;
            function main() {
                example.main(1, 3);
            }
            """
            interp.add_file(script1, "example1.exf")

            # 3. 执行编译
            interp.compile()

            interp.init_exec()

            a = interp.call_function("example1","main", [])
            print(f"print return {a}")
    except Exception as e:
        print(f"发生错误: {e}")