cdef class BaseError(Exception):
    def __cinit__(self, int16_t errcode):
        self._c_errcode = errcode

    def __nonzero__(self):
        return self._c_errcode != 0

    property message:
        def __get__(self):
            if self._c_errcode in self._lookup_table:
                return self._lookup_table[self._c_errcode]
            else:
                return "Unknown error ({0})".format(self._c_errcode)

    property code:
        def __get__(self):
            return self._c_errcode

    def __str__(self):
        return '%s (%s)' % (self.message, self.code)

    def __repr__(self):
        return self.__str__()

cdef class Base:
    cdef inline int handle_error(self, int16_t ret) except -1:
        if ret == 0:
            return 0
        cdef BaseError err = self._error(ret)
        raise err

    cdef BaseError _error(self, int16_t ret): pass

cdef extern from "libimobiledevice/libimobiledevice.h":
    ctypedef enum idevice_error_t:
        IDEVICE_E_SUCCESS = 0
        IDEVICE_E_INVALID_ARG = -1
        IDEVICE_E_UNKNOWN_ERROR = -2
        IDEVICE_E_NO_DEVICE = -3
        IDEVICE_E_NOT_ENOUGH_DATA = -4
        IDEVICE_E_SSL_ERROR = -6
        IDEVICE_E_TIMEOUT = -7
    cdef enum idevice_options:
        IDEVICE_LOOKUP_USBMUX = 1 << 1
        IDEVICE_LOOKUP_NETWORK = 1 << 2
        IDEVICE_LOOKUP_PREFER_NETWORK = 1 << 3
    ctypedef void (*idevice_event_cb_t) (const_idevice_event_t event, void *user_data)
    cdef extern idevice_error_t idevice_event_subscribe(idevice_event_cb_t callback, void *user_data)
    cdef extern idevice_error_t idevice_event_unsubscribe()
    idevice_error_t idevice_get_device_list(char ***devices, int *count)
    idevice_error_t idevice_device_list_free(char **devices)
    void idevice_set_debug_level(int level)
    idevice_error_t idevice_new(idevice_t *device, char *udid)
    idevice_error_t idevice_new_with_options(idevice_t *device, const char *udid, idevice_options options);
    idevice_error_t idevice_free(idevice_t device)
    idevice_error_t idevice_get_udid(idevice_t device, char** udid)
    idevice_error_t idevice_get_handle(idevice_t device, uint32_t *handle)
    idevice_error_t idevice_connect(idevice_t device, uint16_t port, idevice_connection_t *connection)
    idevice_error_t idevice_disconnect(idevice_connection_t connection)
    idevice_error_t idevice_connection_send(idevice_connection_t connection, char *data, uint32_t len, uint32_t *sent_bytes)
    idevice_error_t idevice_connection_receive_timeout(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout)
    idevice_error_t idevice_connection_receive(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes)

cdef class iDeviceError(BaseError):
    def __init__(self, *args, **kwargs):
        self._lookup_table = {
            IDEVICE_E_SUCCESS: 'Success',
            IDEVICE_E_INVALID_ARG: 'Invalid argument',
            IDEVICE_E_UNKNOWN_ERROR: 'Unknown error',
            IDEVICE_E_NO_DEVICE: 'No device',
            IDEVICE_E_NOT_ENOUGH_DATA: 'Not enough data',
            IDEVICE_E_SSL_ERROR: 'SSL Error',
            IDEVICE_E_TIMEOUT: 'Connection timeout'
        }
        BaseError.__init__(self, *args, **kwargs)

def set_debug_level(int level):
    idevice_set_debug_level(level)

cdef class iDeviceEvent:
    def __init__(self, *args, **kwargs):
        raise TypeError("iDeviceEvent cannot be instantiated")

    def __str__(self):
        return 'iDeviceEvent: %s (%s)' % (self.event == IDEVICE_DEVICE_ADD and 'Add' or 'Remove', self.udid)

    property event:
        def __get__(self):
            return self._c_event.event
    property udid:
        def __get__(self):
            return self._c_event.udid
    property conn_type:
        def __get__(self):
            return self._c_event.conn_type

cdef void idevice_event_cb(const_idevice_event_t c_event, void *user_data) noexcept:
    cdef iDeviceEvent event = iDeviceEvent.__new__(iDeviceEvent)
    event._c_event = c_event
    (<object>user_data)(event)

def event_subscribe(object callback):
    cdef iDeviceError err = iDeviceError(idevice_event_subscribe(idevice_event_cb, <void*>callback))
    if err: raise err

def event_unsubscribe():
    cdef iDeviceError err = iDeviceError(idevice_event_unsubscribe())
    if err: raise err

def get_device_list():
    cdef:
        char** devices = NULL
        int count
        list result
        bytes device
        iDeviceError err = iDeviceError(idevice_get_device_list(&devices, &count))

    if err:
        if devices != NULL:
            idevice_device_list_free(devices)
        raise err

    result = []
    for i from 0 <= i < count:
        device = devices[i]
        result.append(device)

    err = iDeviceError(idevice_device_list_free(devices))
    if err: raise err
    return result

cdef class iDeviceConnection(Base):
    def __init__(self, *args, **kwargs):
        raise TypeError("iDeviceConnection cannot be instantiated.  Please use iDevice.connect()")

    cpdef bytes receive_timeout(self, uint32_t max_len, unsigned int timeout):
        cdef:
            uint32_t bytes_received
            char* c_data = <char *>malloc(max_len)
            bytes result

        try:
            self.handle_error(idevice_connection_receive_timeout(self._c_connection, c_data, max_len, &bytes_received, timeout))
            result = c_data[:bytes_received]
            return result
        except BaseError, e:
            raise
        finally:
            free(c_data)

    cpdef bytes receive(self, max_len):
        cdef:
            uint32_t bytes_received
            char* c_data = <char *>malloc(max_len)
            bytes result

        try:
            self.handle_error(idevice_connection_receive(self._c_connection, c_data, max_len, &bytes_received))
            result = c_data[:bytes_received]
            return result
        except BaseError, e:
            raise
        finally:
            free(c_data)

    cpdef disconnect(self):
        cdef idevice_error_t err
        err = idevice_disconnect(self._c_connection)
        self.handle_error(err)

    cdef BaseError _error(self, int16_t ret):
        return iDeviceError(ret)

from libc.stdlib cimport *

cdef class iDevice(Base):
    def __cinit__(self, object udid=None, *args, **kwargs):
        cdef char* c_udid = NULL
        if isinstance(udid, (str, bytes)):
            c_udid = <bytes>udid
        elif udid is not None:
            raise TypeError("iDevice's constructor takes a string or None as the udid argument")
        self.handle_error(idevice_new(&self._c_dev, c_udid))

    def __dealloc__(self):
        if self._c_dev is not NULL:
            self.handle_error(idevice_free(self._c_dev))

    cdef BaseError _error(self, int16_t ret):
        return iDeviceError(ret)

    cpdef iDeviceConnection connect(self, uint16_t port):
        cdef:
            idevice_error_t err
            idevice_connection_t c_conn = NULL
            iDeviceConnection conn
        err = idevice_connect(self._c_dev, port, &c_conn)
        try:
            self.handle_error(err)

            conn = iDeviceConnection.__new__(iDeviceConnection)
            conn._c_connection = c_conn

            return conn
        except Exception, e:
            if c_conn != NULL:
                idevice_disconnect(c_conn)

    property udid:
        def __get__(self):
            cdef:
                char* udid
                idevice_error_t err
            err = idevice_get_udid(self._c_dev, &udid)
            try:
                self.handle_error(err)
                return udid
            except Exception, e:
                if udid != NULL:
                    free(udid)
    property handle:
        def __get__(self):
            cdef uint32_t handle
            self.handle_error(idevice_get_handle(self._c_dev, &handle))
            return handle

cdef extern from *:
    ctypedef char* const_char_ptr "const char*"

cdef class BaseService(Base):
    __service_name__ = None

cdef class PropertyListService(BaseService):
    cpdef send(self, plist.Node node):
        self.handle_error(self._send(node._c_node))

    cpdef object receive(self):
        cdef:
            plist.plist_t c_node = NULL
            int16_t err
        err = self._receive(&c_node)
        try:
            self.handle_error(err)

            return plist.plist_t_to_node(c_node)
        except BaseError, e:
            if c_node != NULL:
                plist.plist_free(c_node)
            raise

    cpdef object receive_with_timeout(self, int timeout_ms):
        cdef:
            plist.plist_t c_node = NULL
            int16_t err
        err = self._receive_with_timeout(&c_node, timeout_ms)
        try:
            self.handle_error(err)

            return plist.plist_t_to_node(c_node)
        except BaseError, e:
            if c_node != NULL:
                plist.plist_free(c_node)
            raise

    cdef int16_t _send(self, plist.plist_t node):
        raise NotImplementedError("send is not implemented")

    cdef int16_t _receive(self, plist.plist_t* c_node):
        raise NotImplementedError("receive is not implemented")

    cdef int16_t _receive_with_timeout(self, plist.plist_t* c_node, int timeout_ms):
        raise NotImplementedError("receive_with_timeout is not implemented")

cdef class DeviceLinkService(PropertyListService):
    pass

include "lockdown.pxi"
include "mobilesync.pxi"
include "notification_proxy.pxi"
include "sbservices.pxi"
include "mobilebackup.pxi"
include "mobilebackup2.pxi"
include "afc.pxi"
include "file_relay.pxi"
include "screenshotr.pxi"
include "installation_proxy.pxi"
include "mobile_image_mounter.pxi"
include "webinspector.pxi"
include "heartbeat.pxi"
include "diagnostics_relay.pxi"
include "misagent.pxi"
include "house_arrest.pxi"
include "restore.pxi"
include "debugserver.pxi"
