# A handful of classes and functions to support the generated data structures.
# This would be a good candidate for isolating in its own ffi-support lib.

class InternalError(Exception):
    pass

class _UniffiRustCallStatus(ctypes.Structure):
    """
    Error runtime.
    """
    _fields_ = [
        ("code", ctypes.c_int8),
        ("error_buf", _UniffiRustBuffer),
    ]

    # These match the values from the uniffi::rustcalls module
    CALL_SUCCESS = 0
    CALL_ERROR = 1
    CALL_UNEXPECTED_ERROR = 2

    @staticmethod
    def default():
        return _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer.default())

    def __str__(self):
        if self.code == _UniffiRustCallStatus.CALL_SUCCESS:
            return "_UniffiRustCallStatus(CALL_SUCCESS)"
        elif self.code == _UniffiRustCallStatus.CALL_ERROR:
            return "_UniffiRustCallStatus(CALL_ERROR)"
        elif self.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR:
            return "_UniffiRustCallStatus(CALL_UNEXPECTED_ERROR)"
        else:
            return "_UniffiRustCallStatus(<invalid code>)"

def _uniffi_rust_call(fn, *args):
    # Call a rust function
    return _uniffi_rust_call_with_error(None, fn, *args)

def _uniffi_rust_call_with_error(error_ffi_converter, fn, *args):
    # Call a rust function and handle any errors
    #
    # This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code.
    # error_ffi_converter must be set to the _UniffiConverter for the error class that corresponds to the result.
    call_status = _UniffiRustCallStatus.default()

    args_with_error = args + (ctypes.byref(call_status),)
    result = fn(*args_with_error)
    _uniffi_check_call_status(error_ffi_converter, call_status)
    return result

def _uniffi_check_call_status(error_ffi_converter, call_status):
    if call_status.code == _UniffiRustCallStatus.CALL_SUCCESS:
        pass
    elif call_status.code == _UniffiRustCallStatus.CALL_ERROR:
        if error_ffi_converter is None:
            call_status.error_buf.free()
            raise InternalError("_uniffi_rust_call_with_error: CALL_ERROR, but error_ffi_converter is None")
        else:
            raise error_ffi_converter.lift(call_status.error_buf)
    elif call_status.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR:
        # When the rust code sees a panic, it tries to construct a _UniffiRustBuffer
        # with the message.  But if that code panics, then it just sends back
        # an empty buffer.
        if call_status.error_buf.len > 0:
            msg = _UniffiConverterString.lift(call_status.error_buf)
        else:
            msg = "Unknown rust panic"
        raise InternalError(msg)
    else:
        raise InternalError("Invalid _UniffiRustCallStatus code: {}".format(
            call_status.code))

def _uniffi_trait_interface_call(call_status, make_call, write_return_value):
    try:
        return write_return_value(make_call())
    except Exception as e:
        call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR
        call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e))

def _uniffi_trait_interface_call_with_error(call_status, make_call, write_return_value, error_type, lower_error):
    try:
        try:
            return write_return_value(make_call())
        except error_type as e:
            call_status.code = _UniffiRustCallStatus.CALL_ERROR
            call_status.error_buf = lower_error(e)
    except Exception as e:
        call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR
        call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e))
