/* swig.i */
 %module imobiledevice
 %feature("autodoc", "1");
 %{
 /* Includes the header in the wrapper code */
 #include <libimobiledevice/libimobiledevice.h>
 #include <libimobiledevice/lockdown.h>
 #include <libimobiledevice/mobilesync.h>
 #include <libimobiledevice/notification_proxy.h>
 #include <plist/plist.h>
 #include <plist/plist++.h>
 #include "../src/debug.h"

typedef struct {
    idevice_t dev;
} idevice;

typedef struct {
    idevice* dev;
    lockdownd_client_t client;
} Lockdownd;

typedef struct {
    idevice* dev;
    mobilesync_client_t client;
} MobileSync;

typedef struct {
    idevice* dev;
    np_client_t client;
} NotificationProxy;

//now declare funtions to handle creation and deletion of objects
static void my_delete_idevice(idevice* dev) {
	if (dev) {
		idevice_free(dev->dev);
		free(dev);
	}
}

static Lockdownd* my_new_Lockdownd(idevice* device) {
    if (!device) return NULL;
    Lockdownd* client = (Lockdownd*) malloc(sizeof(Lockdownd));
    client->dev = device;
    client->client = NULL;
    if (LOCKDOWN_E_SUCCESS == lockdownd_client_new_with_handshake(device->dev , &(client->client), NULL)) {
        return client;
    }
    else {
        free(client);
        return NULL;
    }
}

static void my_delete_Lockdownd(Lockdownd* lckd) {
    if (lckd) {
        lockdownd_client_free(lckd->client);
        free(lckd);
    }
}

static MobileSync* my_new_MobileSync(Lockdownd* lckd) {
	if (!lckd || !lckd->dev) return NULL;
	MobileSync* client = NULL;
	uint16_t port = 0;
	if (LOCKDOWN_E_SUCCESS == lockdownd_start_service(lckd->client, "com.apple.mobilesync", &port)) {
		client = (MobileSync*) malloc(sizeof(MobileSync));
		client->dev = lckd->dev;
		client->client = NULL;
		mobilesync_client_new(lckd->dev->dev, port, &(client->client));
	}
	return client;
}

static NotificationProxy* my_new_NotificationProxy(Lockdownd* lckd) {
    if (!lckd || !lckd->dev) return NULL;
    NotificationProxy* client = NULL;
    uint16_t port = 0;
	if (LOCKDOWN_E_SUCCESS == lockdownd_start_service(lckd->client, "com.apple.mobile.notification_proxy", &port)) {
        client = (NotificationProxy*) malloc(sizeof(NotificationProxy));
        client->dev = lckd->dev;
        client->client = NULL;
		np_client_new(lckd->dev->dev, port, &(client->client));
    }
    return client;
}

static PList::Node* new_node_from_plist(plist_t node)
{
	PList::Node* ret = NULL;
	plist_type subtype = plist_get_node_type(node);
	switch(subtype)
	{
	    case PLIST_DICT:
		ret = new PList::Dictionary(node);
		break;
	    case PLIST_ARRAY:
		ret = new PList::Array(node);
		break;
	    case PLIST_BOOLEAN:
		ret = new PList::Boolean(node);
		break;
	    case PLIST_UINT:
		ret = new PList::Integer(node);
		break;
	    case PLIST_REAL:
		ret = new PList::Real(node);
		break;
	    case PLIST_STRING:
		ret = new PList::String(node);
		break;
	    case PLIST_DATE:
		ret = new PList::Date(node);
		break;
	    case PLIST_DATA:
		ret = new PList::Data(node);
		break;
	    default:
		break;
	}
	return ret;
}

#ifdef SWIGPYTHON
static void NotificationProxyPythonCallback(const char *notification, void* user_data) {
    PyObject *func, *arglist;

    func = (PyObject *) user_data;
    arglist = Py_BuildValue("(s)",notification);

    PyEval_CallObject(func, arglist);

    Py_DECREF(arglist);
}
#endif
 %}

/* Parse the header file to generate wrappers */
%include "stdint.i"
%include "cstring.i"
%include "plist/swig/plist.i"

/* This needs to be here since if it's after
 * the structs, SWIG won't pick it up for %extend
 */
#ifdef SWIGPYTHON
%typemap(in) (PyObject *pyfunc) {
    if (!PyCallable_Check($input)) {
        PyErr_SetString(PyExc_TypeError, "Need a callable object!");
        return NULL;
    }
    $1 = $input;
}
%typemap(in) (const char **string_list) {
    /* Check if it's a list */
    if (PyList_Check($input)) {
        int size = PyList_Size($input);
        int i = 0;
        $1 = (char **) malloc((size+1)*sizeof(char *));
        for (i = 0; i < size; i++) {
            PyObject *o = PyList_GetItem($input,i);
            if (PyString_Check(o)) {
                $1[i] = PyString_AsString(PyList_GetItem($input,i));
            } else {
                PyErr_SetString(PyExc_TypeError,"List must contain strings");
                free($1);
                return NULL;
            }
        }
        $1[i] = 0;
    } else if (PyTuple_Check($input)) {
        int size = PyTuple_Size($input);
        int i = 0;
        $1 = (char **) malloc((size+1)*sizeof(char *));
        for (i = 0; i < size; i++) {
            PyObject *o = PyTuple_GetItem($input,i);
            if (PyString_Check(o)) {
                $1[i] = PyString_AsString(PyTuple_GetItem($input,i));
            } else {
                PyErr_SetString(PyExc_TypeError,"List must contain strings");
                free($1);
                return NULL;
            }
        }
        $1[i] = 0;
    } else {
        PyErr_SetString(PyExc_TypeError, "not a list or tuple");
        return NULL;
    }
}
%typemap(freearg) (const char **string_list) {
    free((char *) $1);
}
#endif

 typedef struct {
	idevice_t dev;
 } idevice;

typedef struct {
    idevice* dev;
    lockdownd_client_t client;
} Lockdownd;

typedef struct {
    idevice* dev;
    mobilesync_client_t client;
} MobileSync;

typedef struct {
    idevice* dev;
    np_client_t client;
} NotificationProxy;


%extend idevice {             // Attach these functions to struct idevice
	idevice() {
		idevice* device = (idevice*) malloc(sizeof(idevice));
		device->dev = NULL;
		return device;
	}

	~idevice() {
		my_delete_idevice($self);
	}

	void set_debug_level(int level) {
		idevice_set_debug_level(level);
	}

	int init_device_by_uuid(char* uuid) {
		if (IDEVICE_E_SUCCESS == idevice_new(&($self->dev), uuid))
			return 1;
		return 0;
	}

	int init_device() {
		if (IDEVICE_E_SUCCESS == idevice_new(&($self->dev), NULL))
			return 1;
		return 0;
	}

	%newobject get_uuid;
	char* get_uuid(){
		char* uuid = NULL;
		idevice_get_uuid($self->dev, &uuid);
		return uuid;
	}

	Lockdownd* get_lockdown_client() {
		return my_new_Lockdownd($self);
	}
};

%extend Lockdownd {             // Attach these functions to struct Lockdownd
	Lockdownd(idevice* device) {
		return my_new_Lockdownd(device);
	}

	~Lockdownd() {
		my_delete_Lockdownd($self);
	}

	void send(PList::Node* node) {
		lockdownd_send($self->client, node->GetPlist());
	}

	PList::Node* receive() {
		plist_t node = NULL;
		lockdownd_receive($self->client, &node);
		return new_node_from_plist(node);
	}

	MobileSync* get_mobilesync_client() {
		return my_new_MobileSync($self);
	}

    NotificationProxy* get_notification_proxy_client() {
        return my_new_NotificationProxy($self);
    }
};

%extend MobileSync {             // Attach these functions to struct MobileSync
	MobileSync(Lockdownd* lckd) {
		return my_new_MobileSync(lckd);
	}

	~MobileSync() {
		mobilesync_client_free($self->client);
		free($self);
	}

	void send(PList::Node* node) {
		mobilesync_send($self->client, node->GetPlist());
	}

	PList::Node* receive() {
		plist_t node = NULL;
		mobilesync_receive($self->client, &node);
		return new_node_from_plist(node);
	}
};

#define NP_SYNC_WILL_START           "com.apple.itunes-mobdev.syncWillStart"
#define NP_SYNC_DID_START            "com.apple.itunes-mobdev.syncDidStart"
#define NP_SYNC_DID_FINISH           "com.apple.itunes-mobdev.syncDidFinish"
#define NP_SYNC_LOCK_REQUEST         "com.apple.itunes-mobdev.syncLockRequest"

%extend NotificationProxy {
    NotificationProxy(Lockdownd* lckd) {
        return my_new_NotificationProxy(lckd);
    }

    ~NotificationProxy() {
        np_client_free($self->client);
        free($self);
    }

    int16_t post_notification(const char* notification) {
        return np_post_notification($self->client, notification);
    }

    int16_t observe_notification(const char* notification) {
        return np_observe_notification($self->client, notification);
    }

    int16_t observe_notifications(const char** string_list) {
        return np_observe_notifications($self->client, string_list);
    }
};

#ifdef SWIGPYTHON
%extend NotificationProxy {
    int16_t set_callback(PyObject *pyfunc) {
        int16_t res;
        res = np_set_notify_callback($self->client, NotificationProxyPythonCallback, (void *) pyfunc);
        Py_INCREF(pyfunc);
        return res;
    }
};
#endif