/*
 * Copyright (c) 2007 Josef Cejka
 * Copyright (c) 2011 Jiri Svoboda
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <str.h>
#include <ipc/services.h>
#include <ns.h>
#include <ipc/loc.h>
#include <loc.h>
#include <fibril_synch.h>
#include <async.h>
#include <errno.h>
#include <malloc.h>
#include <bool.h>

static FIBRIL_MUTEX_INITIALIZE(loc_supp_block_mutex);
static FIBRIL_MUTEX_INITIALIZE(loc_cons_block_mutex);

static FIBRIL_MUTEX_INITIALIZE(loc_supplier_mutex);
static FIBRIL_MUTEX_INITIALIZE(loc_consumer_mutex);

static FIBRIL_MUTEX_INITIALIZE(loc_callback_mutex);
static bool loc_callback_created = false;

static async_sess_t *loc_supp_block_sess = NULL;
static async_sess_t *loc_cons_block_sess = NULL;

static async_sess_t *loc_supplier_sess = NULL;
static async_sess_t *loc_consumer_sess = NULL;

static loc_cat_change_cb_t cat_change_cb = NULL;

static void loc_cb_conn(ipc_callid_t iid, ipc_call_t *icall, void *arg)
{
	loc_cat_change_cb_t cb_fun;
	
	while (true) {
		ipc_call_t call;
		ipc_callid_t callid = async_get_call(&call);
		
		if (!IPC_GET_IMETHOD(call)) {
			/* TODO: Handle hangup */
			return;
		}
		
		int retval;
		
		switch (IPC_GET_IMETHOD(call)) {
		case LOC_EVENT_CAT_CHANGE:
			fibril_mutex_lock(&loc_callback_mutex);
			cb_fun = cat_change_cb;
			if (cb_fun != NULL) {
				(*cb_fun)();
			}
			fibril_mutex_unlock(&loc_callback_mutex);
			retval = 0;
			break;
		default:
			retval = ENOTSUP;
		}
		
		async_answer_0(callid, retval);
	}
}


static void clone_session(fibril_mutex_t *mtx, async_sess_t *src,
    async_sess_t **dst)
{
	fibril_mutex_lock(mtx);
	
	if ((*dst == NULL) && (src != NULL))
		*dst = src;
	
	fibril_mutex_unlock(mtx);
}

static int loc_callback_create(void)
{
	async_exch_t *exch;
	sysarg_t retval;
	int rc = EOK;

	fibril_mutex_lock(&loc_callback_mutex);
	
	if (!loc_callback_created) {
		exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);
		
		ipc_call_t answer;
		aid_t req = async_send_0(exch, LOC_CALLBACK_CREATE, &answer);
		async_connect_to_me(exch, 0, 0, 0, loc_cb_conn, NULL);
		loc_exchange_end(exch);
		
		async_wait_for(req, &retval);
		if (rc != EOK)
			goto done;
		
		if (retval != EOK) {
			rc = retval;
			goto done;
		}
		
		loc_callback_created = true;
	}
	
	rc = EOK;
done:
	fibril_mutex_unlock(&loc_callback_mutex);
	return rc;
}

/** Start an async exchange on the loc session (blocking).
 *
 * @param iface Location service interface to choose
 *
 * @return New exchange.
 *
 */
async_exch_t *loc_exchange_begin_blocking(loc_interface_t iface)
{
	switch (iface) {
	case LOC_PORT_SUPPLIER:
		fibril_mutex_lock(&loc_supp_block_mutex);
		
		while (loc_supp_block_sess == NULL) {
			clone_session(&loc_supplier_mutex, loc_supplier_sess,
			    &loc_supp_block_sess);
			
			if (loc_supp_block_sess == NULL)
				loc_supp_block_sess =
				    service_connect_blocking(EXCHANGE_SERIALIZE,
				    SERVICE_LOC, LOC_PORT_SUPPLIER, 0);
		}
		
		fibril_mutex_unlock(&loc_supp_block_mutex);
		
		clone_session(&loc_supplier_mutex, loc_supp_block_sess,
		    &loc_supplier_sess);
		
		return async_exchange_begin(loc_supp_block_sess);
	case LOC_PORT_CONSUMER:
		fibril_mutex_lock(&loc_cons_block_mutex);
		
		while (loc_cons_block_sess == NULL) {
			clone_session(&loc_consumer_mutex, loc_consumer_sess,
			    &loc_cons_block_sess);
			
			if (loc_cons_block_sess == NULL)
				loc_cons_block_sess =
				    service_connect_blocking(EXCHANGE_SERIALIZE,
				    SERVICE_LOC, LOC_PORT_CONSUMER, 0);
		}
		
		fibril_mutex_unlock(&loc_cons_block_mutex);
		
		clone_session(&loc_consumer_mutex, loc_cons_block_sess,
		    &loc_consumer_sess);
		
		return async_exchange_begin(loc_cons_block_sess);
	default:
		return NULL;
	}
}

/** Start an async exchange on the loc session.
 *
 * @param iface Location service interface to choose
 *
 * @return New exchange.
 *
 */
async_exch_t *loc_exchange_begin(loc_interface_t iface)
{
	switch (iface) {
	case LOC_PORT_SUPPLIER:
		fibril_mutex_lock(&loc_supplier_mutex);
		
		if (loc_supplier_sess == NULL)
			loc_supplier_sess =
			    service_connect(EXCHANGE_SERIALIZE, SERVICE_LOC,
			    LOC_PORT_SUPPLIER, 0);
		
		fibril_mutex_unlock(&loc_supplier_mutex);
		
		if (loc_supplier_sess == NULL)
			return NULL;
		
		return async_exchange_begin(loc_supplier_sess);
	case LOC_PORT_CONSUMER:
		fibril_mutex_lock(&loc_consumer_mutex);
		
		if (loc_consumer_sess == NULL)
			loc_consumer_sess =
			    service_connect(EXCHANGE_SERIALIZE, SERVICE_LOC,
			    LOC_PORT_CONSUMER, 0);
		
		fibril_mutex_unlock(&loc_consumer_mutex);
		
		if (loc_consumer_sess == NULL)
			return NULL;
		
		return async_exchange_begin(loc_consumer_sess);
	default:
		return NULL;
	}
}

/** Finish an async exchange on the loc session.
 *
 * @param exch Exchange to be finished.
 *
 */
void loc_exchange_end(async_exch_t *exch)
{
	async_exchange_end(exch);
}

/** Register new driver with loc. */
int loc_server_register(const char *name, async_client_conn_t conn)
{
	async_exch_t *exch = loc_exchange_begin_blocking(LOC_PORT_SUPPLIER);
	
	ipc_call_t answer;
	aid_t req = async_send_2(exch, LOC_SERVER_REGISTER, 0, 0, &answer);
	sysarg_t retval = async_data_write_start(exch, name, str_size(name));
	
	loc_exchange_end(exch);
	
	if (retval != EOK) {
		async_wait_for(req, NULL);
		return retval;
	}
	
	async_set_client_connection(conn);
	
	exch = loc_exchange_begin(LOC_PORT_SUPPLIER);
	async_connect_to_me(exch, 0, 0, 0, NULL, NULL);
	loc_exchange_end(exch);
	
	async_wait_for(req, &retval);
	return retval;
}

/** Register new service.
 *
 * The @p interface is used when forwarding connection to the server.
 * If not 0, the first argument is the interface and the second argument
 * is the service ID.
 *
 * When the interface is zero (default), the first argument is directly
 * the handle (to ensure backward compatibility).
 *
 * @param      fqsn      Fully qualified service name
 * @param[out] sid       Service ID of new service
 * @param      interface Interface when forwarding
 *
 */
int loc_service_register_with_iface(const char *fqsn,
    service_id_t *sid, sysarg_t interface)
{
	async_exch_t *exch = loc_exchange_begin_blocking(LOC_PORT_SUPPLIER);
	
	ipc_call_t answer;
	aid_t req = async_send_2(exch, LOC_SERVICE_REGISTER, interface, 0,
	    &answer);
	sysarg_t retval = async_data_write_start(exch, fqsn, str_size(fqsn));
	
	loc_exchange_end(exch);
	
	if (retval != EOK) {
		async_wait_for(req, NULL);
		return retval;
	}
	
	async_wait_for(req, &retval);
	
	if (retval != EOK) {
		if (sid != NULL)
			*sid = -1;
		
		return retval;
	}
	
	if (sid != NULL)
		*sid = (service_id_t) IPC_GET_ARG1(answer);
	
	return retval;
}

/** Register new service.
 *
 * @param fqsn Fully qualified service name
 * @param sid  Output: ID of new service
 *
 */
int loc_service_register(const char *fqdn, service_id_t *sid)
{
	return loc_service_register_with_iface(fqdn, sid, 0);
}

/** Unregister service.
 *
 * @param sid	Service ID
 */
int loc_service_unregister(service_id_t sid)
{
	async_exch_t *exch;
	sysarg_t retval;
	
	exch = loc_exchange_begin_blocking(LOC_PORT_SUPPLIER);
	retval = async_req_1_0(exch, LOC_SERVICE_UNREGISTER, sid);
	loc_exchange_end(exch);
	
	return (int)retval;
}

int loc_service_get_id(const char *fqdn, service_id_t *handle,
    unsigned int flags)
{
	async_exch_t *exch;
	
	if (flags & IPC_FLAG_BLOCKING)
		exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);
	else {
		exch = loc_exchange_begin(LOC_PORT_CONSUMER);
		if (exch == NULL)
			return errno;
	}
	
	ipc_call_t answer;
	aid_t req = async_send_2(exch, LOC_SERVICE_GET_ID, flags, 0,
	    &answer);
	sysarg_t retval = async_data_write_start(exch, fqdn, str_size(fqdn));
	
	loc_exchange_end(exch);
	
	if (retval != EOK) {
		async_wait_for(req, NULL);
		return retval;
	}
	
	async_wait_for(req, &retval);
	
	if (retval != EOK) {
		if (handle != NULL)
			*handle = (service_id_t) -1;
		
		return retval;
	}
	
	if (handle != NULL)
		*handle = (service_id_t) IPC_GET_ARG1(answer);
	
	return retval;
}

/** Get object name.
 *
 * Provided ID of an object, return its name.
 *
 * @param method	IPC method
 * @param id		Object ID
 * @param name		Place to store pointer to new string. Caller should
 *			free it using free().
 * @return		EOK on success or negative error code
 */
static int loc_get_name_internal(sysarg_t method, sysarg_t id, char **name)
{
	async_exch_t *exch;
	char name_buf[LOC_NAME_MAXLEN + 1];
	ipc_call_t dreply;
	size_t act_size;
	sysarg_t dretval;
	
	*name = NULL;
	exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);
	
	ipc_call_t answer;
	aid_t req = async_send_1(exch, method, id, &answer);
	aid_t dreq = async_data_read(exch, name_buf, LOC_NAME_MAXLEN,
	    &dreply);
	async_wait_for(dreq, &dretval);
	
	loc_exchange_end(exch);
	
	if (dretval != EOK) {
		async_wait_for(req, NULL);
		return dretval;
	}
	
	sysarg_t retval;
	async_wait_for(req, &retval);
	
	if (retval != EOK)
		return retval;
	
	act_size = IPC_GET_ARG2(dreply);
	assert(act_size <= LOC_NAME_MAXLEN);
	name_buf[act_size] = '\0';

	*name = str_dup(name_buf);
	if (*name == NULL)
		return ENOMEM;
	
	return EOK;
}

/** Get category name.
 *
 * Provided ID of a service, return its name.
 *
 * @param cat_id	Category ID
 * @param name		Place to store pointer to new string. Caller should
 *			free it using free().
 * @return		EOK on success or negative error code
 */
int loc_category_get_name(category_id_t cat_id, char **name)
{
	return loc_get_name_internal(LOC_CATEGORY_GET_NAME, cat_id, name);
}

/** Get service name.
 *
 * Provided ID of a service, return its name.
 *
 * @param svc_id	Service ID
 * @param name		Place to store pointer to new string. Caller should
 *			free it using free().
 * @return		EOK on success or negative error code
 */
int loc_service_get_name(service_id_t svc_id, char **name)
{
	return loc_get_name_internal(LOC_SERVICE_GET_NAME, svc_id, name);
}

int loc_namespace_get_id(const char *name, service_id_t *handle,
    unsigned int flags)
{
	async_exch_t *exch;
	
	if (flags & IPC_FLAG_BLOCKING)
		exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);
	else {
		exch = loc_exchange_begin(LOC_PORT_CONSUMER);
		if (exch == NULL)
			return errno;
	}
	
	ipc_call_t answer;
	aid_t req = async_send_2(exch, LOC_NAMESPACE_GET_ID, flags, 0,
	    &answer);
	sysarg_t retval = async_data_write_start(exch, name, str_size(name));
	
	loc_exchange_end(exch);
	
	if (retval != EOK) {
		async_wait_for(req, NULL);
		return retval;
	}
	
	async_wait_for(req, &retval);
	
	if (retval != EOK) {
		if (handle != NULL)
			*handle = (service_id_t) -1;
		
		return retval;
	}
	
	if (handle != NULL)
		*handle = (service_id_t) IPC_GET_ARG1(answer);
	
	return retval;
}

/** Get category ID.
 *
 * Provided name of a category, return its ID.
 *
 * @param name		Category name
 * @param cat_id	Place to store ID
 * @param flags		IPC_FLAG_BLOCKING to wait for location service to start
 * @return		EOK on success or negative error code
 */
int loc_category_get_id(const char *name, category_id_t *cat_id,
    unsigned int flags)
{
	async_exch_t *exch;
	
	if (flags & IPC_FLAG_BLOCKING)
		exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);
	else {
		exch = loc_exchange_begin(LOC_PORT_CONSUMER);
		if (exch == NULL)
			return errno;
	}
	
	ipc_call_t answer;
	aid_t req = async_send_0(exch, LOC_CATEGORY_GET_ID,
	    &answer);
	sysarg_t retval = async_data_write_start(exch, name, str_size(name));
	
	loc_exchange_end(exch);
	
	if (retval != EOK) {
		async_wait_for(req, NULL);
		return retval;
	}
	
	async_wait_for(req, &retval);
	
	if (retval != EOK) {
		if (cat_id != NULL)
			*cat_id = (category_id_t) -1;
		
		return retval;
	}
	
	if (cat_id != NULL)
		*cat_id = (category_id_t) IPC_GET_ARG1(answer);
	
	return retval;
}


loc_object_type_t loc_id_probe(service_id_t handle)
{
	async_exch_t *exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);
	
	sysarg_t type;
	int retval = async_req_1_1(exch, LOC_ID_PROBE, handle, &type);
	
	loc_exchange_end(exch);
	
	if (retval != EOK)
		return LOC_OBJECT_NONE;
	
	return (loc_object_type_t) type;
}

async_sess_t *loc_service_connect(exch_mgmt_t mgmt, service_id_t handle,
    unsigned int flags)
{
	async_sess_t *sess;
	
	if (flags & IPC_FLAG_BLOCKING)
		sess = service_connect_blocking(mgmt, SERVICE_LOC,
		    LOC_CONNECT_TO_SERVICE, handle);
	else
		sess = service_connect(mgmt, SERVICE_LOC,
		    LOC_CONNECT_TO_SERVICE, handle);
	
	return sess;
}

int loc_null_create(void)
{
	async_exch_t *exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);
	
	sysarg_t null_id;
	int retval = async_req_0_1(exch, LOC_NULL_CREATE, &null_id);
	
	loc_exchange_end(exch);
	
	if (retval != EOK)
		return -1;
	
	return (int) null_id;
}

void loc_null_destroy(int null_id)
{
	async_exch_t *exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);
	async_req_1_0(exch, LOC_NULL_DESTROY, (sysarg_t) null_id);
	loc_exchange_end(exch);
}

static size_t loc_count_namespaces_internal(async_exch_t *exch)
{
	sysarg_t count;
	int retval = async_req_0_1(exch, LOC_GET_NAMESPACE_COUNT, &count);
	if (retval != EOK)
		return 0;
	
	return count;
}

/** Add service to category.
 *
 * @param svc_id	Service ID
 * @param cat_id	Category ID
 * @return		EOK on success or negative error code
 */
int loc_service_add_to_cat(service_id_t svc_id, service_id_t cat_id)
{
	async_exch_t *exch;
	sysarg_t retval;
	
	exch = loc_exchange_begin_blocking(LOC_PORT_SUPPLIER);
	retval = async_req_2_0(exch, LOC_SERVICE_ADD_TO_CAT, svc_id, cat_id);
	loc_exchange_end(exch);
	
	return retval;
}

static size_t loc_count_services_internal(async_exch_t *exch,
    service_id_t ns_handle)
{
	sysarg_t count;
	int retval = async_req_1_1(exch, LOC_GET_SERVICE_COUNT, ns_handle,
	    &count);
	if (retval != EOK)
		return 0;
	
	return count;
}

size_t loc_count_namespaces(void)
{
	async_exch_t *exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);
	size_t size = loc_count_namespaces_internal(exch);
	loc_exchange_end(exch);
	
	return size;
}

size_t loc_count_services(service_id_t ns_handle)
{
	async_exch_t *exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);
	size_t size = loc_count_services_internal(exch, ns_handle);
	loc_exchange_end(exch);
	
	return size;
}

size_t loc_get_namespaces(loc_sdesc_t **data)
{
	/* Loop until read is succesful */
	while (true) {
		async_exch_t *exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);
		size_t count = loc_count_namespaces_internal(exch);
		loc_exchange_end(exch);
		
		if (count == 0)
			return 0;
		
		loc_sdesc_t *devs = (loc_sdesc_t *) calloc(count, sizeof(loc_sdesc_t));
		if (devs == NULL)
			return 0;
		
		exch = loc_exchange_begin(LOC_PORT_CONSUMER);
		
		ipc_call_t answer;
		aid_t req = async_send_0(exch, LOC_GET_NAMESPACES, &answer);
		int rc = async_data_read_start(exch, devs, count * sizeof(loc_sdesc_t));
		
		loc_exchange_end(exch);
		
		if (rc == EOVERFLOW) {
			/*
			 * Number of namespaces has changed since
			 * the last call of LOC_GET_NAMESPACE_COUNT
			 */
			free(devs);
			continue;
		}
		
		if (rc != EOK) {
			async_wait_for(req, NULL);
			free(devs);
			return 0;
		}
		
		sysarg_t retval;
		async_wait_for(req, &retval);
		
		if (retval != EOK)
			return 0;
		
		*data = devs;
		return count;
	}
}

size_t loc_get_services(service_id_t ns_handle, loc_sdesc_t **data)
{
	/* Loop until read is succesful */
	while (true) {
		async_exch_t *exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);
		size_t count = loc_count_services_internal(exch, ns_handle);
		loc_exchange_end(exch);
		
		if (count == 0)
			return 0;
		
		loc_sdesc_t *devs = (loc_sdesc_t *) calloc(count, sizeof(loc_sdesc_t));
		if (devs == NULL)
			return 0;
		
		exch = loc_exchange_begin(LOC_PORT_CONSUMER);
		
		ipc_call_t answer;
		aid_t req = async_send_1(exch, LOC_GET_SERVICES, ns_handle, &answer);
		int rc = async_data_read_start(exch, devs, count * sizeof(loc_sdesc_t));
		
		loc_exchange_end(exch);
		
		if (rc == EOVERFLOW) {
			/*
			 * Number of services has changed since
			 * the last call of LOC_GET_SERVICE_COUNT
			 */
			free(devs);
			continue;
		}
		
		if (rc != EOK) {
			async_wait_for(req, NULL);
			free(devs);
			return 0;
		}
		
		sysarg_t retval;
		async_wait_for(req, &retval);
		
		if (retval != EOK)
			return 0;
		
		*data = devs;
		return count;
	}
}

static int loc_category_get_ids_once(sysarg_t method, sysarg_t arg1,
    sysarg_t *id_buf, size_t buf_size, size_t *act_size)
{
	async_exch_t *exch = loc_exchange_begin_blocking(LOC_PORT_CONSUMER);

	ipc_call_t answer;
	aid_t req = async_send_1(exch, method, arg1, &answer);
	int rc = async_data_read_start(exch, id_buf, buf_size);
	
	loc_exchange_end(exch);
	
	if (rc != EOK) {
		async_wait_for(req, NULL);
		return rc;
	}
	
	sysarg_t retval;
	async_wait_for(req, &retval);
	
	if (retval != EOK) {
		return retval;
	}
	
	*act_size = IPC_GET_ARG1(answer);
	return EOK;
}

/** Get list of IDs.
 *
 * Returns an allocated array of service IDs.
 *
 * @param method	IPC method
 * @param arg1		IPC argument 1
 * @param data		Place to store pointer to array of IDs
 * @param count		Place to store number of IDs
 * @return 		EOK on success or negative error code
 */
static int loc_get_ids_internal(sysarg_t method, sysarg_t arg1,
    sysarg_t **data, size_t *count)
{
	service_id_t *ids;
	size_t act_size;
	size_t alloc_size;
	int rc;

	*data = NULL;
	act_size = 0;	/* silence warning */

	rc = loc_category_get_ids_once(method, arg1, NULL, 0,
	    &act_size);
	if (rc != EOK)
		return rc;

	alloc_size = act_size;
	ids = malloc(alloc_size);
	if (ids == NULL)
		return ENOMEM;

	while (true) {
		rc = loc_category_get_ids_once(method, arg1, ids, alloc_size,
		    &act_size);
		if (rc != EOK)
			return rc;

		if (act_size <= alloc_size)
			break;

		alloc_size *= 2;
		free(ids);

		ids = malloc(alloc_size);
		if (ids == NULL)
			return ENOMEM;
	}

	*count = act_size / sizeof(category_id_t);
	*data = ids;
	return EOK;
}

/** Get list of services in category.
 *
 * Returns an allocated array of service IDs.
 *
 * @param cat_id	Category ID
 * @param data		Place to store pointer to array of IDs
 * @param count		Place to store number of IDs
 * @return 		EOK on success or negative error code
 */
int loc_category_get_svcs(category_id_t cat_id, service_id_t **data,
    size_t *count)
{
	return loc_get_ids_internal(LOC_CATEGORY_GET_SVCS, cat_id,
	    data, count);
}

/** Get list of categories.
 *
 * Returns an allocated array of category IDs.
 *
 * @param data		Place to store pointer to array of IDs
 * @param count		Place to store number of IDs
 * @return 		EOK on success or negative error code
 */
int loc_get_categories(category_id_t **data, size_t *count)
{
	return loc_get_ids_internal(LOC_GET_CATEGORIES, 0,
	    data, count);
}

int loc_register_cat_change_cb(loc_cat_change_cb_t cb_fun)
{
	if (loc_callback_create() != EOK)
		return EIO;

	cat_change_cb = cb_fun;
	return EOK;
}
