from .writer import Writer
from .types import C_INT_TYPES, C_FLOAT_TYPES, C_BOOL_TYPES, C_STRING_TYPES, VMATH_TYPES

class Converter:
    def __init__(self, T: str):
        self.T = T
    def c2py(self, w: Writer, out: str, expr: str):
        raise NotImplementedError
    def py2c(self, w: Writer, out: str, expr: str):
        raise NotImplementedError
    @property
    def py_T(self) -> str:
        raise NotImplementedError
    
    def is_const(self):
        return self.T.startswith('const ') or '[' in self.T
    
class _SimpleConverter(Converter):
    def c2py(self, w: Writer, out: str, expr: str):
        w.write(f'py_new{self.py_T}({out}, {expr});')
    def py2c(self, w: Writer, out: str, expr: str):
        w.write(f'if(!py_check{self.py_T}({expr})) return false;')
        w.write(f'{out} = py_to{self.py_T}({expr});')
    
class IntConverter(_SimpleConverter):
    @property
    def py_T(self) -> str:
        return 'int'
    
class BoolConverter(_SimpleConverter):
    @property
    def py_T(self) -> str:
        return 'bool'
    
class StringConverter(_SimpleConverter):
    @property
    def py_T(self) -> str:
        return 'str'
    
class FloatConverter(Converter):
    def c2py(self, w: Writer, out: str, expr: str):
        w.write(f'py_newfloat({out}, {expr});')
    def py2c(self, w: Writer, out: str, expr: str):
        if self.T == 'float':
            w.write(f'if(!py_castfloat32({expr}, &{out})) return false;')
        else:
            w.write(f'if(!py_castfloat({expr}, &{out})) return false;')

    @property
    def py_T(self) -> str:
        return 'float'

class PointerConverter(Converter):
    def c2py(self, w: Writer, out: str, expr: str):
        w.write(f'py_newint({out}, (py_i64){expr});')
    def py2c(self, w: Writer, out: str, expr: str):
        w.write(f'if(!py_checkint({expr})) return false;')
        w.write(f'{out} = ({self.T})py_toint({expr});')

    @property
    def py_T(self) -> str:
        return 'intptr'

class StructConverter(Converter):
    def __init__(self, T: str, type_index: str | None):
        super().__init__(T)
        if type_index is None:
            type_index = f'tp_user_{T}'
        self.type_index = type_index
    def c2py(self, w: Writer, out: str, expr: str):
        w.write('do {')
        w.indent()
        w.write(f'{self.T}* ud = py_newobject({out}, {self.type_index}, 0, sizeof({self.T}));')
        w.write(f'*ud = {expr};')
        w.dedent()
        w.write('} while(0);')
    def py2c(self, w: Writer, out: str, expr: str):
        w.write('do {')
        w.indent()
        w.write(f'if(!py_checktype({expr}, {self.type_index})) return false;')
        w.write(f'{out} = *({self.T}*)py_touserdata({expr});')
        w.dedent()
        w.write('} while(0);')

    @property
    def py_T(self) -> str:
        return self.T
    
class EnumConverter(Converter):
    def __init__(self, T: str):
        super().__init__(T)
    def c2py(self, w: Writer, out: str, expr: str):
        w.write(f'py_newint({out}, (py_i64){expr});')
    def py2c(self, w: Writer, out: str, expr: str):
        w.write(f'if(!py_checkint({expr})) return false;')
        w.write(f'{out} = ({self.T})py_toint({expr});')

    @property
    def py_T(self) -> str:
        return 'int'
    
class VoidConverter(Converter):
    def c2py(self, w: Writer, out: str, expr: str):
        w.write(f'py_newnone({out});')
    def py2c(self, w: Writer, out: str, expr: str):
        # raise NotImplementedError
        w.write(f'? // VoidConverter.py2c is not implemented')
    
    @property
    def py_T(self) -> str:
        return 'None'
    
class BuiltinVMathConverter(Converter):
    def __init__(self, T: str, py_builtin_T: str):
        super().__init__(T)
        self.py_builtin_T = py_builtin_T

    def c2py(self, w: Writer, out: str, expr: str):
        w.write(f'py_new{self.py_builtin_T}({out}, *(c11_{self.py_T}*)(&{expr}));')

    def py2c(self, w: Writer, out: str, expr: str):
        w.write('do {')
        w.indent()
        w.write(f'if(!py_checktype({expr}, tp_{self.py_T})) return false;')
        w.write(f'c11_{self.py_T} tmp = py_to{self.py_builtin_T}({expr});')
        # w.write(f'memcpy(&{out}, &tmp, sizeof(c11_{self.py_T}));')
        w.write(f'{out} = *({self.T}*)(&tmp);')
        w.dedent()
        w.write('} while(0);')
    
    @property
    def py_T(self) -> str:
        return self.py_builtin_T


_CONVERTERS: dict[str, Converter] = {}

for t in C_INT_TYPES:
    _CONVERTERS[t] = IntConverter(t)
for t in C_FLOAT_TYPES:
    _CONVERTERS[t] = FloatConverter(t)
for t in C_BOOL_TYPES:
    _CONVERTERS[t] = BoolConverter(t)
for t in C_STRING_TYPES:
    _CONVERTERS[t] = StringConverter(t)
for t in VMATH_TYPES:
    _CONVERTERS[t] = BuiltinVMathConverter(f'c11_{t}', t)

_CONVERTERS['void'] = VoidConverter('void')
_CONVERTERS['c11_array2d'] = StructConverter('c11_array2d', 'tp_array2d')

def is_vmath_type(T: str) -> bool:
    cvt = _CONVERTERS.get(T)
    if cvt is None:
        return False
    return isinstance(cvt, BuiltinVMathConverter)

_has_vmath_converter = False
def set_vmath_converter(T: str, py_T: str):
    global _has_vmath_converter
    assert py_T in VMATH_TYPES
    _CONVERTERS[T] = BuiltinVMathConverter(T, py_T)
    _has_vmath_converter = True

def has_vmath_converter() -> bool:
    return _has_vmath_converter

def set_enum_converter(T: str):
    _CONVERTERS[T] = EnumConverter(T)

def get_converter(T: str) -> Converter:
    if T in _CONVERTERS:
        return _CONVERTERS[T]
    if T.endswith('*'):
        return PointerConverter(T)
    if '[' in T:
        return PointerConverter(T)
    cvt = _CONVERTERS.get(T)
    if cvt is None:
        return StructConverter(T, None)
    return cvt
