/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2007-2009  Michael Bell <michael.bell@opensync.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */
 
#include "sml_manager_internals.h"

#include "sml_error_internals.h"
#include "sml_support.h"

#include "sml_transport.h"
#include "sml_session.h"

//#include "sml_command_internals.h"
//#include "sml_elements_internals.h"

typedef struct managerSession {
	SmlLink *link;
	SmlSession *session;
	guint finalLock;
	SmlTransport *transport;
	GList *objects;
} managerSession;

managerSession* _smlManagerGetManagerSession (SmlManager *manager, SmlSession *session);
SmlObject*      smlManagerObjectFindInternal (SmlManager *manager, SmlSession *session, SmlCommandType type, SmlLocation *target, SmlLocation *source, const gchar* contentType);

static managerSession*
_manager_session_find (SmlManager *manager,
                       SmlSession *session)
{
	smlAssert(session);
	smlAssert(manager);
	
	GList *s = NULL;
	for (s = manager->sessions; s; s = s->next) {
		managerSession *sess = s->data;
		if (sess->session == session)
			return sess;
	}
	return NULL;
}

static void
_manager_session_free (managerSession *sess)
{
	smlAssert(sess);
	
	smlSessionUnref(sess->session);
	
	if (sess->link)
		smlLinkDeref(sess->link);
	
	while (sess->objects) {
		SmlObject *object = sess->objects->data;
		smlManagerObjectFree(object);
		sess->objects = g_list_delete_link(sess->objects, sess->objects);
	}
	
	smlSafeFree((gpointer *)&sess);
}

static gboolean
_manager_prepare_internal (GSource *source, gint *timeout_)
{
	smlTrace(TRACE_INTERNAL, "%s(%p, %p)", __func__, source, timeout_);
	*timeout_ = 1;
	return FALSE;
}

static gboolean
_manager_check_internal (GSource *source)
{
	SmlManager *manager = *((SmlManager **)(source + 1));
	GList *s = NULL;
	for (s = manager->sessions; s; s = s->next) {
		managerSession *session = s->data;
		if (smlSessionCheck(session->session))
			return TRUE;
	}
	return FALSE;
}

static gboolean
_manager_dispatch_internal (GSource *source,
                            GSourceFunc callback,
                            gpointer user_data)
{
	smlTrace(TRACE_INTERNAL, "%s(%p, %p, %p)", __func__, source, callback, user_data);
	int max = 100;
	
	SmlManager *manager = user_data;
	GList *s = NULL;
	for (s = manager->sessions; s; s = s->next) {
		managerSession *session = s->data;
		while (smlSessionCheck(session->session) && max) {
			/* A dispatch function should never block on a
			 * locked resource. This is like a busy wait
			 * because the thread is not available for this
			 * time which can block the whole system if two
			 * dispatchers use the same thread.
			 */
			if (smlSessionTryLock(session->session))
			{
				smlSessionDispatch(session->session);
				smlSessionUnlock(session->session);
			}
			max--;
		}
	}
	
	return TRUE;
}

static void
_smlManagerSendEvent (SmlManager *manager,
                      SmlManagerEventType type,
                      SmlSession *session,
                      SmlCommand *command,
                      SmlCommand *parent,
                      const GError *error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p, %p, %p)", __func__, manager, type, session, command, parent, error);

	GError *lerr = NULL;
	SmlManagerEvent *event = smlTryMalloc0(sizeof(SmlManagerEvent), &lerr);
	if (!event) {
		g_error("%s", lerr->message);
		smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, lerr->message);
		g_error_free(lerr);
		return;
	}

	event->type = type;
		
	if (session) {
		event->session = session;
		smlSessionRef(session);
	} else {
		event->session = NULL;
	}
	
	if (command) {
		event->command = command;
		smlCommandRef(command);
	} else {
		event->command = NULL;
	}
	
	if (parent) {
		event->parent = parent;
		smlCommandRef(parent);
	} else {
		event->parent = NULL;
	}
	
	if (error)
		event->error = g_error_copy(error);
	
	smlQueueSend(manager->userEventQueue, event);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void
_smlManagerEventFree (SmlManagerEvent *event)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, event);
	smlAssert(event);
	
	if (event->session)
		smlSessionUnref(event->session);
	
	if (event->command)
		smlCommandUnref(event->command);
	
	if (event->parent)
		smlCommandUnref(event->parent);
	
	if (event->error)
		g_error_free(event->error);
	
	smlSafeFree((gpointer *)&event);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

/* If we return FALSE in this function, the current request will get
 * aborted */
static gboolean
_smlManagerDataHandler (SmlTransport *tsp,
                        SmlLink *link_,
                        SmlTransportEventType type,
                        SmlTransportData *data,
                        const GError *error,
                        void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %i, %p, %p, %p)", __func__, tsp, link_, type, data, error, userdata);
	smlAssert(userdata);
	SmlManager *manager = userdata;
	SmlParser *parser = NULL;
	SmlHeader *header = NULL;
	SmlCred *cred = NULL;
	SmlSession *session = NULL;
	GError *locerror = NULL;

	switch (type) {
		case SML_TRANSPORT_EVENT_CONNECT_DONE:
			/* Pass the connect through */
			_smlManagerSendEvent(manager, SML_MANAGER_CONNECT_DONE, NULL, NULL, NULL, NULL);
			break;
		case SML_TRANSPORT_EVENT_DISCONNECT_DONE:
			/* Pass the disconnect through */
			_smlManagerSendEvent(manager, SML_MANAGER_DISCONNECT_DONE, NULL, NULL, NULL, NULL);
			break;
		case SML_TRANSPORT_EVENT_DATA:
	
			parser = smlParserNew(smlTransportDataGetType(data), 0, &locerror);
			if (!parser)
				goto error;
			/* add manager to parser for OMA DS 1.2 SAN handling */
			smlParserSetManager(parser, manager);
			
			/* start to parse the data */
			if (!smlParserStart(parser, smlTransportDataGetData(data), smlTransportDataGetSize(data), &locerror))
				goto error;

			/* Get the header of the message*/
			header = NULL;
			cred = NULL;
			if (!smlParserGetHeader(parser, &header, &cred, &locerror))
				goto error;
			
			/* Find the session if available otherwise register it as new */
			session = smlManagerSessionFind(manager, smlHeaderGetSessionID(header));
			if (!session) {
				/* If we use the target and source from the header of the received message
				 * then we MUST interchange source and target */
				SmlSessionType sessionType;
				switch(smlTransportGetType(tsp))
				{
					case SML_TRANSPORT_OBEX_CLIENT:
						/* this can happen if the remote
						 * client creates a new session ID.
						 */
						sessionType = SML_SESSION_TYPE_SERVER;
						break;
					case SML_TRANSPORT_OBEX_SERVER:
						sessionType = SML_SESSION_TYPE_CLIENT;
						break;
					case SML_TRANSPORT_HTTP_SERVER:
						sessionType = SML_SESSION_TYPE_SERVER;
						break;
					default:
						g_set_error(&locerror, SML_ERROR,
							SML_ERROR_NOT_IMPLEMENTED,
							"New session was unexpectedly received for transport %d.",
							smlTransportGetType(tsp));
						goto error;
						break;
				}
				if (!(session = smlSessionNew(
							sessionType,
							smlTransportDataGetType(data),
							smlHeaderGetProtocolVersion(header),
							smlHeaderGetProtocolType(header),
							smlHeaderGetSource(header),
							smlHeaderGetTarget(header),
							smlHeaderGetSessionID(header),
							1, &locerror)))
					goto error;
				if (link_)
				{
					char *responseURI = smlLinkGetResponseURI(link_, session, &locerror);
					if (!responseURI && locerror)
						goto error;
					if (responseURI)
					{
						if (!smlSessionSetResponseURI(session, responseURI, &locerror))
						{
							smlSafeCFree(&responseURI);
							goto error;
						}
						smlSafeCFree(&responseURI);
					}
				}
				if (!smlManagerSessionAdd(manager, session, link_, &locerror))
					goto error;
				smlSessionUnref(session);
			} else {
				/* SECURITY: let's check ResponseURI if supported
				 * SECURITY: this is important to avoid replay attacks
				 */
				if (link_) {
					char *responseUri = smlLinkGetResponseURI(link_, session, &locerror);
					if (!responseUri && locerror) {
						goto error;
					}
					if (responseUri)
						smlSafeCFree(&responseUri);
				}
			}

			/* This function is called by the tranport layer which
			 * runs in an own thread. Therefore we must lock the
			 * according session because otherwise we can get into
			 * trouble with parallel access to a SmlSession which is
			 * not safe against parallel use by multiple threads.
	 		 */
			smlSessionLock(session);

			/* Find the manager session so that we can update the link. We
			 * have to do this since each time a transport with links receives
			 * a new data packets, it might create a new link (connectionless
			 * protocols). So we have to update the link to be able to answer
			 * the data */
			GList *s;
			for (s = manager->sessions; s; s = s->next) {
				managerSession *sess = s->data;
				if (sess->session == session) {
					if (sess->link)
						smlLinkDeref(sess->link);
					sess->link = link_;
					if (link_)
						smlLinkRef(sess->link);
					break;
				}
			}
			
			/* Now check if the header is valid etc */
			if (!smlSessionReceiveHeader(session, header, &locerror))
				goto error;
			
			/* Then check if we are allowed to authenticate */
			if (!smlManagerDispatchHeader(manager, session, header, cred, &locerror))
				goto error;
			
			smlHeaderFree(header);
			header = NULL;
			if (cred) {
				smlCredUnref(cred);
				cred = NULL;
			}
			
			/* Now let the session handle the commands etc */
			if (!smlSessionReceiveBody(session, parser, &locerror))
				goto error;
			
			/* Free the parser */
			smlParserFree(parser);
			parser = NULL;

			/* make the session accessible for others */
			smlSessionUnlock(session);

			break;
		case SML_TRANSPORT_EVENT_ERROR:
			/* Pass the error through */
			_smlManagerSendEvent(manager, SML_MANAGER_TRANSPORT_ERROR, NULL, NULL, NULL, error);
			smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, error->message);
			return FALSE;
			break;
		case SML_TRANSPORT_EVENT_UNKNOWN:
			g_set_error(&locerror, SML_ERROR, SML_ERROR_GENERIC,
			            "%s received an unknown transport event.", __func__);
			_smlManagerSendEvent(manager, SML_MANAGER_TRANSPORT_ERROR, NULL, NULL, NULL, locerror);
			smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, locerror->message);
			g_error_free(locerror);
			return FALSE;
			break;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	if (header)
		smlHeaderFree(header);
	if (cred)
		smlCredUnref(cred);
	if (parser)
		smlParserFree(parser);
	if (session)
		smlSessionUnlock(session);
	if (session == NULL && link_ && type == SML_TRANSPORT_EVENT_DATA) {
		/* If there is no session and a link then this means
		 * that there is a failing server connection.
		 * It is a good idea to close the connection because
		 * the session has no chance to signal a clean
		 * disconnect to the link.
		 */
		GError *herror = NULL;
		if (!smlTransportDisconnect(manager->transport, link_, &herror)) {
			smlTrace(TRACE_ERROR, "%s: Disconnect on error failed. %s", 
				herror->message);
			g_error_free(herror);
		}
	}
	_smlManagerSendEvent(manager, SML_MANAGER_SESSION_ERROR, session, NULL, NULL, locerror);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, locerror->message);
	g_error_free(locerror);
	return FALSE;
}

SmlManager*
smlManagerNew (SmlTransport *tsp,
               GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, error);
	smlAssert(tsp);
	CHECK_ERROR_REF
	
	SmlManager *manager = smlTryMalloc0(sizeof(SmlManager), error);
	if (!manager)
		goto error;
		
	smlTransportSetEventCallback(tsp, _smlManagerDataHandler, manager);
	manager->transport = tsp;
	
	manager->running_mutex = g_mutex_new();
	manager->running = g_cond_new();
	
	manager->userEventQueue = smlQueueNew(error);
	if (!manager->userEventQueue)
		goto error;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return manager;

error:
	if (manager)
		smlSafeFree((gpointer *)&manager);	
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

void
smlManagerFree (SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, manager);
	smlAssert(manager);

	/* stop automatic session dispatch environment if necessary */
	if (manager->context)
		smlManagerStop(manager);

	/* Deactivate the event callback of the transport layer
	 * because the manager is no longer available
	 */

	if (manager->transport)
		smlTransportSetEventCallback(manager->transport, NULL, NULL);

	while (manager->sessions) {
		managerSession *sess = manager->sessions->data;

		_manager_session_free(sess);
	
		manager->sessions = g_list_delete_link(manager->sessions, manager->sessions);
	}
		
	while (manager->objects) {
		SmlObject *object = manager->objects->data;
		
		smlManagerObjectFree(object);
		manager->objects = g_list_remove(manager->objects, object);
	}
	
	if (manager->userEventQueue) {
		SmlManagerEvent *event = NULL;
		while ((event = smlQueueTryPop(manager->userEventQueue)))
			_smlManagerEventFree(event);
		
		smlQueueFree(manager->userEventQueue);
		manager->userEventQueue = NULL;
	}

	g_cond_free(manager->running);
	g_mutex_free(manager->running_mutex);
	
	smlSafeFree((gpointer *)&manager);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void
smlManagerSetEventCallback (SmlManager *manager,
                            SmlManagerEventCb callback,
                            void *userdata)
{
	smlAssert(manager);
	smlAssert(callback);
	
	manager->eventCallback = callback;
	manager->eventCallbackUserdata = userdata;
}

gboolean
smlManagerStart (SmlManager *manager,
                 GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, manager, error);
	smlAssert(manager);
	CHECK_ERROR_REF
    
	manager->context = g_main_context_new();
	if (!manager->context) {
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
			"Cannot create new main context.");
		goto error;
	}

	manager->functions = smlTryMalloc0(sizeof(GSourceFuncs), error);
	if (!manager->functions)
		goto error;

	manager->functions->prepare = _manager_prepare_internal;
	manager->functions->check = _manager_check_internal;
	manager->functions->dispatch = _manager_dispatch_internal;
	manager->functions->finalize = NULL;
	
	manager->thread = smlThreadNew(manager->context, error);
	if (!manager->thread)
		goto error;
	smlThreadStart(manager->thread);
	
	manager->source = g_source_new(manager->functions, sizeof(GSource) + sizeof(SmlManager *));
	SmlManager **managerptr = (SmlManager **)(manager->source + 1);
	*managerptr = manager;
	g_source_set_callback(manager->source, NULL, manager, NULL);
	g_source_attach(manager->source, manager->context);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	if (manager->context) {
		g_main_context_unref(manager->context);
		manager->context = NULL;
	}
	if (manager->functions)
		smlSafeFree((gpointer *)&(manager->functions));
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

/** @brief Stops the active manager part.
 * 
 * The active part of the manager is the automatic dispatching
 * of the SmlSession objects. The session dispatching consists
 * of the status and command dispatching. If the commands and
 * status are no longer dispatched then the session is no longer
 * in an active state.
 *
 * If this happens the manager should shoulds down the external
 * communication. In fact the manager disconnects all active
 * transport connections.
 *
 * You must still call smlManagerDispatch until you got all
 * required SML_MANAGER_DISCONNECT_DONE events. After this you
 * can safely call smlManagerFree without getting warnings or
 * errors from the transport layer because of forgotten
 * connections.
 *
 * Errors especially from the transport layer are ignored
 * because it is not possible to react on these errors.
 * Therefore it is correct to ignore the errors. Actually it is
 * a problem that one or more failed disconnects result in a
 * deadlock if somebody waits correctly for all disconnect
 * events. Additionally it is impossible to signal a user the
 * number of failed disconnect via a clean way.
 *
 * If a disconnect fails then g_warning is called with an
 * appropriate message. If there is a (G)UI which reconfigures
 * the glib to display warnings and errors to the user then
 * this is the only chance to communicate such errors.
 *
 * @param manager The manager
 * 
 */
void
smlManagerStop (SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, manager);
	smlAssert(manager);
	smlAssert(manager->thread);
	
	/* cleanup automatic session dispatch environment 
	 * the order is highly important
	 */

	/* stop thread */
	smlThreadStop(manager->thread);
	smlThreadFree(manager->thread);
	manager->thread = NULL;
	/* detach source */
	g_source_unref(manager->source);
	manager->source = NULL;
	/* free context */
	g_main_context_unref(manager->context);
	manager->context = NULL;
	/* free functions */
	smlSafeFree((gpointer *)&(manager->functions));
	manager->functions = NULL;

	/* close all open connections */
	
	GList *sessionItem = manager->sessions;
	while (sessionItem) {
		managerSession *sess = sessionItem->data;

		/* disconnect if necessary
		 * 
		 * only do this if:
		 *   1. the transport layer is still connected
		 *   and
		 *   2.1. this is a client connection or the server itself
		 *   or
		 *   2.2. this is an active server connection
		 */
		GError *error = NULL;
		if (smlTransportIsConnected(manager->transport) &&
		    (!sess->link || smlLinkIsConnected(sess->link)) &&
		    !smlTransportDisconnect(manager->transport, sess->link, &error)) {
			g_warning("Errors from the transport layer " \
				"cannot be handled while freeing the manager. %s",
				error->message);
			g_error_free(error);
		}

		sessionItem = g_list_next(sessionItem);
	}

	smlTrace(TRACE_EXIT, "%s", __func__);
}


void
smlManagerRun (SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, manager);
	smlAssert(manager);
	
	g_mutex_lock(manager->running_mutex);
	g_cond_wait(manager->running, manager->running_mutex);
	g_mutex_unlock(manager->running_mutex);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void
smlManagerQuit (SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, manager);
	smlAssert(manager);
	
	g_mutex_lock(manager->running_mutex);
	g_cond_signal(manager->running);
	g_mutex_unlock(manager->running_mutex);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlSession*
smlManagerSessionFind (SmlManager *manager,
                       gsize sessionID)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %uld)", __func__, manager, sessionID);
	GList *s = NULL;
	for (s = manager->sessions; s; s = s->next) {
		managerSession *session = s->data;
		if (smlSessionGetSessionID(session->session) == sessionID) {
			smlTrace(TRACE_EXIT, "%s: FOUND %p", __func__, session);
			return session->session;
		}
	}
	
	smlTrace(TRACE_EXIT, "%s: NOT FOUND", __func__);
	return NULL;
}

void
smlManagerDispatch (SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, manager);
	smlAssert(manager);
	SmlManagerEvent *event = NULL;
	while ((event = smlQueueTryPop(manager->userEventQueue))) {
		smlTrace(TRACE_INTERNAL, "%s: type ::= %d", __func__, event->type);
		/* Check that this is not a locked final event. */
		if (event->session != NULL &&
		    event->type == SML_MANAGER_SESSION_FINAL)
		{
			managerSession *sess = _smlManagerGetManagerSession(manager, event->session);
			if (sess && sess->finalLock)
			{
				smlTrace(TRACE_INTERNAL, "%s - locked final (%i)",
					__func__, sess->finalLock);
				smlQueuePushHead(manager->userEventQueue, event);
				/* It is necessary to stop the dispatching here.
				 * The events MUST BE dispatched in the same
				 * order like they were received. If the while
				 * loop does not break here then the loop tries
				 * to dispatch the locked event again and again.
				 */
				break;
			}
		}
		/* Execute the event callback. */
		smlAssert(manager->eventCallback);
		manager->eventCallback(manager, event->type, event->session, event->error, manager->eventCallbackUserdata);
		_smlManagerEventFree(event);
	}
	smlTrace(TRACE_EXIT, "%s", __func__);
}

gboolean
smlManagerCheck (SmlManager *manager)
{
	return smlQueueCheck(manager->userEventQueue);
}

static void
_event_callback (SmlSession *session,
                 SmlSessionEventType type,
                 SmlCommand *command,
                 SmlCommand *parent,
                 SmlStatus *reply,
                 const GError *error,
                 void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p, %p, %p, %p)", __func__, session, type, command, parent, reply, error, userdata);
	SmlManager *manager = userdata;
	GError *locerror = NULL;
	
	switch (type) {
		case SML_SESSION_EVENT_ESTABLISHED:
			/* Pass the established through */
			_smlManagerSendEvent(manager, SML_MANAGER_SESSION_ESTABLISHED, session, NULL, NULL, NULL);
			break;
		case SML_SESSION_EVENT_HEADER_REPLY:
			break;
		case SML_SESSION_EVENT_RESPONSE_URI:
			if (!smlTransportSetResponseURI(
				manager->transport,
				sml_location_get_uri(smlSessionGetTarget(session)),
				&locerror))
			{
				_smlManagerSendEvent(manager, SML_MANAGER_SESSION_ERROR, session, NULL, NULL, locerror);
				goto error;
			}
			break;
		case SML_SESSION_EVENT_COMMAND_START:
		case SML_SESSION_EVENT_COMMAND_END:
			if (!smlManagerDispatchCommand(manager, session, command, &locerror)) {
				_smlManagerSendEvent(manager, SML_MANAGER_SESSION_WARNING, session, NULL, NULL, locerror);
				goto error;
			}
			break;
		case SML_SESSION_EVENT_CHILD_COMMAND:
			if (!smlManagerDispatchChildCommand(manager, session, parent, command, &locerror)) {
				_smlManagerSendEvent(manager, SML_MANAGER_SESSION_WARNING, session, NULL, NULL, locerror);
				goto error;
			}
			break;
		case SML_SESSION_EVENT_FINAL:
			/* Pass the final through */
			_smlManagerSendEvent(manager, SML_MANAGER_SESSION_FINAL, session, NULL, NULL, NULL);
			break;
		case SML_SESSION_EVENT_END:
			/* Pass the end through */
			_smlManagerSendEvent(manager, SML_MANAGER_SESSION_END, session, NULL, NULL, NULL);
			break;
		case SML_SESSION_EVENT_FLUSH:
			/* Pass the flush through */
			_smlManagerSendEvent(manager, SML_MANAGER_SESSION_FLUSH, session, NULL, NULL, NULL);
			break;
		case SML_SESSION_EVENT_ERROR:
			/* Pass the error through */
			_smlManagerSendEvent(manager, SML_MANAGER_SESSION_ERROR, session, NULL, NULL, error);
			break;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, locerror->message);
	g_error_free(locerror);
}

static void
_data_send_callback (SmlSession *session,
                     SmlTransportData *data,
                     void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, data, userdata);
	managerSession *sess = userdata;

	/* The transport layer is able to signal the error via an event. */
	GError *error = NULL;
	if (!smlTransportSend(sess->transport, sess->link, data, &error))
	{
		smlTrace(TRACE_ERROR, "%s - %s", __func__, error->message);
		g_error_free(error);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

gboolean
smlManagerSessionAdd (SmlManager *manager,
                      SmlSession *session,
                      SmlLink *link_,
                      GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, manager, session, link_, error);
	CHECK_ERROR_REF
	
	smlSessionRef(session);

	if (smlSessionGetSessionID(session)) {
		//Check if sessionID exists
		smlTrace(TRACE_INTERNAL, "Checking if session ID %d already exists", smlSessionGetSessionID(session));
		
		if (smlManagerSessionFind(manager, smlSessionGetSessionID(session))) {
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Session ID already exists");
			goto error;
		}
	} else {
		smlSessionSetSessionID(session, smlManagerGetNewSessionID(manager));
	}
	
	managerSession *sess = smlTryMalloc0(sizeof(managerSession), error);
	if (!sess)
		goto error;
	
	sess->session = session;
	sess->finalLock = 0;
	
	if (link_) {
		sess->link = link_;
		smlLinkRef(link_);
	}
	
	sess->transport = manager->transport;
	
	manager->sessions = g_list_append(manager->sessions, sess);

	/* prepare large object support */
	if (0 < manager->localMaxMsgSize &&
	    (
	     manager->localMaxMsgSize < smlSessionGetLocalMaxMsgSize(session) ||
	     smlSessionGetLocalMaxMsgSize(session) <= 0
	    ))
		smlSessionSetLocalMaxMsgSize(session, manager->localMaxMsgSize);
	if (0 < manager->localMaxObjSize &&
	    (
	     manager->localMaxObjSize < smlSessionGetLocalMaxObjSize(session) ||
	     smlSessionGetLocalMaxObjSize(session) <= 0
	    ))
		smlSessionSetLocalMaxObjSize(session, manager->localMaxObjSize);
	if (smlSessionGetLocalMaxObjSize(session) &&
	    smlSessionGetLocalMaxMsgSize(session))
		smlSessionUseLargeObjects(session, TRUE);
	else
		smlSessionUseLargeObjects(session, FALSE);

	/* configure maximum number of changes in one message
	 *
	 * This is necessary if the user of the library implement
	 * own caching strategies which can block (e.g. OpenSync).
	 */
	if (manager->localMaxMsgChanges)
		smlSessionSetLocalMaxMsgChanges(session, manager->localMaxMsgChanges);

	smlSessionSetEventCallback(session, _event_callback, manager);
	smlSessionSetDataCallback(session, _data_send_callback, sess);
	
	_smlManagerSendEvent(manager, SML_MANAGER_SESSION_NEW, session, NULL, NULL, NULL);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	smlSessionUnref(session);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

managerSession*
_smlManagerGetManagerSession (SmlManager *manager,
                              SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, manager, session);
	
	GList *s;
	for (s = manager->sessions; s; s = s->next) {
		managerSession *sess = s->data;
		if (sess->session == session) {
			smlTrace(TRACE_EXIT, "%s - %p", __func__, sess);
			return sess;
		}
	}
	
	smlTrace(TRACE_EXIT, "%s: Not Found", __func__);
	return NULL;
}

void
smlManagerSessionRemove (SmlManager *manager,
                         SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, manager, session);

	managerSession *sess = _smlManagerGetManagerSession(manager, session);
	if (sess) {
		manager->sessions = g_list_remove(manager->sessions, sess);
		_manager_session_free(sess);
		smlTrace(TRACE_EXIT, "%s", __func__);
	} else {
		smlTrace(TRACE_EXIT, "%s: Not Found", __func__);
	}
}

void
smlManagerSessionFinalLockRef (SmlManager *manager,
                               SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, manager, session);
	
	managerSession *sess = _smlManagerGetManagerSession(manager, session);
	if (sess) {
		sess->finalLock++;
		smlTrace(TRACE_EXIT, "%s", __func__);
	} else {
		smlTrace(TRACE_EXIT, "%s: Not Found", __func__);
	}
}

void
smlManagerSessionFinalLockUnref (SmlManager *manager,
                                 SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, manager, session);
	
	managerSession *sess = _smlManagerGetManagerSession(manager, session);
	if (sess) {
		sess->finalLock--;
		smlTrace(TRACE_EXIT, "%s - %i", __func__, sess->finalLock);
	} else {
		smlTrace(TRACE_EXIT, "%s: Not Found", __func__);
	}
}

/** @brief Register an object with a session
 * 
 * A object waits at a certain location for incoming commands. So you can for example
 * register a object that listens for Get commands on the /test location.
 * 
 * Note that you cannot deregister an object from a already running session
 * 
 * @param session The session on which to listen
 * @param type The command type to listen to or SML_COMMAND_TYPE_UNKNOWN to listen for all
 * @param session If given, only listens for the commands on the given session. NULL for all
 * @param location The location where the object listens (This must match the Target of the command). NULL to listen for all targets
 * @param source The source of the command we want (This must match the Source of the command). NULL to listen for all sources
 * @param callback The callback that will receive the incoming commands.
 * @param childCallback The call that will receive the commands from the child commands. Set to NULL if the expected command has no childs
 * @param userdata The userdata for the callbacks
 * @param error A error struct
 * @return TRUE if successful, FALSE otherwise
 * 
 */
gboolean
smlManagerObjectRegister (SmlManager *manager,
                          SmlCommandType type,
                          SmlSession *session,
                          SmlLocation *location,
                          SmlLocation *source,
                          const gchar *contentType,
                          SmlCommandCb callback,
                          SmlCommandCb childCallback,
                          void *userdata, GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p, %p, %s, %p, %p, %p, %p)", __func__, manager, type, session, location, source, VA_STRING(contentType), callback, childCallback, userdata, error);
	smlAssert(manager);
	smlAssert(callback);
	CHECK_ERROR_REF

	/* first we have to check if this an update for an already registered object */
	/* this is important for actions which require more than one DsSession */
	
        SmlObject *object = smlManagerObjectFindInternal(
				manager, session, type,
				location, source, contentType);
	if (object != NULL && session != NULL)
	{
		/* check that it is not a global manager object */
		SmlObject *hObject = smlManagerObjectFindInternal(
                                	manager, NULL, type,
                                	location, source, contentType);
		if (hObject != NULL && hObject == object)
		{
			/* the found object is a global manager object */
			/* so we have to register a new object */
			object = NULL;
		}
	}
	if (object)
	{
		/* there is an object and so we reconfigure it */
		smlTrace(TRACE_INTERNAL, "%s: prepare a reusable object", __func__);

		if (object->location)
			g_object_unref(object->location);
		if (object->source)
			g_object_unref(object->source);
		if (object->contentType)
			smlSafeCFree(&(object->contentType));
	}
	else
	{
		/* let's create and register a new object */
		smlTrace(TRACE_INTERNAL, "%s: create and register a new object", __func__);

		object = smlTryMalloc0(sizeof(SmlObject), error);
		if (!object)
			goto error;

		if (session) {
			managerSession *sess = _manager_session_find(manager, session);
			if (!sess) {
				g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Session not found");
				goto error;
			}
		
			sess->objects = g_list_append(sess->objects, object);
		} else {
			manager->objects = g_list_append(manager->objects, object);
		}
	}

	smlTrace(TRACE_INTERNAL, "%s: configure registered object", __func__);

	object->type = type;
	
	if (location) {
		object->location = location;
		g_object_ref(location);
	}
	
	if (source) {
		object->source = source;
		g_object_ref(source);
	}
	
	if (contentType) {
		object->contentType = g_strdup(contentType);
	}
	
	object->commandCallback = callback;
	object->childCallback = childCallback;
	object->commandCallbackUserdata = userdata;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	if (object)
		smlManagerObjectFree(object);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

void
smlManagerObjectDeregister (SmlManager *manager,
                            SmlCommandType type,
                            SmlLocation *location,
                            SmlLocation *source)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p)", __func__, manager, type, location, source);
	smlAssert(manager);
	
	GList *o2 = g_list_copy(manager->objects);
	GList *o = NULL;
	for (o = o2; o; o = o->next) {
		SmlObject *object = o->data;
		
		
		if (object->type != type)
			continue;
		
		if (!sml_location_is_equal(object->location, location))
			continue;
		
		if (!sml_location_is_equal(object->source, source))
			continue;
		
		smlManagerObjectFree(object);
		manager->objects = g_list_remove(manager->objects, object);
	}
	g_list_free(o2);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void
smlManagerObjectFree (SmlObject *object)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, object);
	smlAssert(object);
	
	if (object->location)
		g_object_unref(object->location);
		
	if (object->source)
		g_object_unref(object->source);
	
	if (object->contentType)
		smlSafeCFree(&(object->contentType));
	
	smlSafeFree((gpointer *)&object);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlObject*
smlManagerObjectFind (SmlManager *manager,
                      SmlSession *session,
                      SmlCommand *cmd)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, manager, session, cmd);
	smlAssert(manager);
	smlAssert(cmd);

	SmlObject *object = smlManagerObjectFindInternal(
				manager,
				session,
				smlCommandGetType(cmd),
				smlCommandGetTarget(cmd),
				smlCommandGetSource(cmd),
				smlCommandGetContentType(cmd));
	smlTrace(TRACE_EXIT, "%s(%p)", __func__, object);
	return object;
}

SmlObject*
smlManagerObjectFindInternal(SmlManager *manager,
                             SmlSession *session,
                             SmlCommandType type,
                             SmlLocation *target,
                             SmlLocation *source,
                             const gchar* contentType)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %d, %p, %p)", __func__, manager, session, type, target, source);

	smlAssert(manager);
	SmlObject *object = NULL;
	GList *o = NULL;
	
	/* We first search for the object in the session specific objects */
	if (session) {
		managerSession *sess = _manager_session_find(manager, session);
		if (sess) {
			for (o = sess->objects; o; o = o->next) {
				object = o->data;

				if (object->type != SML_COMMAND_TYPE_UNKNOWN && type != object->type)
					continue;
				
				if (object->location && !sml_location_is_equal(object->location, target))
					continue;
				
				if (object->source && !sml_location_is_equal(object->source, source))
					continue;
				
				smlTrace(TRACE_EXIT, "%s: FOUND (session): %p", __func__, object);
				return object;
			}
		}
	}
	
	for (o = manager->objects; o; o = o->next) {
		object = o->data;
		
		if (object->type != SML_COMMAND_TYPE_UNKNOWN && type != object->type)
			continue;
		
		if (type == SML_COMMAND_TYPE_ALERT && contentType) {
			if (object->contentType) {
				/* This item is a san 11 alert */
				if (!strcmp(contentType, object->contentType)) {
					smlTrace(TRACE_EXIT, "%s: FOUND SAN TARGET: %p", __func__, object);
					return object;
				}
			}
			
			continue;
		}

		if (object->location && !sml_location_is_equal(object->location, target))
			continue;
		
		if (object->source && !sml_location_is_equal(object->source, source))
			continue;
		
		/* Check if the target is a san target */
		if (object->contentType)
			continue;
		
		smlTrace(TRACE_EXIT, "%s: FOUND: %p", __func__, object);
		return object;
	}
	
	smlTrace(TRACE_EXIT, "%s: NOT FOUND", __func__);
	return NULL;
}

SmlTransport*
smlManagerGetTransport (SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, manager);
	smlAssert(manager);
	
	SmlTransport *tsp = manager->transport;
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, tsp);
	return tsp;
}

gboolean
smlManagerDispatchHeader (SmlManager *manager,
                          SmlSession *session,
                          SmlHeader *header,
                          SmlCred *cred,
                          GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p)", __func__, manager, session, header, cred, error);
	smlAssert(manager);
	smlAssert(session);
	smlAssert(header);
	CHECK_ERROR_REF
	
	if (manager->headerCallback &&
	    smlSessionGetType(session) == SML_SESSION_TYPE_SERVER) {
		/* Authentication is only specified for OMA DS server. */
		manager->headerCallback(session, header, cred, manager->headerCallbackUserdata);
	} else {
		smlTrace(TRACE_INTERNAL, "%s: Header not handled!", __func__);
		smlTrace(TRACE_INTERNAL, "%s: type %d", __func__, smlSessionGetType(session));
		smlTrace(TRACE_INTERNAL, "%s: established %d", __func__, smlSessionGetEstablished(session));
		if (smlSessionGetType(session) == SML_SESSION_TYPE_SERVER &&
		    !smlSessionGetEstablished(session)) {
			/* An OMA DS server must handle the header for security
			 * reasons - especially authentication.
			 *
			 * Question: can we detect such a bug in an earlier phase?
			 */
			g_warning("%s: This is an OMA DS server. " \
				"The header callback is missing. " \
				"All requests will be accepted without authentication.",
				__func__);
			smlSessionSetEstablished(session, TRUE);
			smlSessionDispatchEvent(
                        	session, SML_SESSION_EVENT_ESTABLISHED,
                        	NULL, NULL, NULL, NULL);
		}
		SmlStatus *status = smlStatusNew(
					SML_NO_ERROR, 0,
					smlHeaderGetMessageID(header),
					smlHeaderGetSource(header),
					smlHeaderGetTarget(header),
					SML_COMMAND_TYPE_HEADER, error);
		if (!status)
			goto error;
			
		if (!smlSessionSendReply(session, status, error)) {
			smlStatusUnref(status);
			goto error;
		}
		
		smlStatusUnref(status);
	}

	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

gboolean
smlManagerDispatchChildCommand (SmlManager *manager,
                                SmlSession *session,
                                SmlCommand *parent,
                                SmlCommand *cmd,
                                GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p)", __func__, manager, session, parent, cmd, error);
	smlAssert(manager);
	smlAssert(session);
	smlAssert(parent);
	CHECK_ERROR_REF
	
	//Check if a handler for this object at this path has been installed.
	SmlObject *object = smlManagerObjectFind(manager, session, parent);
	if (object) {
		//Check if a handler for this object at this path has been installed.
		if (!object->childCallback) {
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "No handler for the child was installed");
			goto error;
		}
		
		object->childCallback(session, cmd, object->commandCallbackUserdata);
	} else {
		SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_NOT_FOUND, error);
		if (!reply)
			goto error;
		
		if (!smlSessionSendReply(session, reply, error)) {
			smlStatusUnref(reply);
			goto error;
		}
		
		smlStatusUnref(reply);
		
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Unable to find child command handler");
		
		goto error;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

gboolean
smlManagerDispatchCommand (SmlManager *manager,
                           SmlSession *session,
                           SmlCommand *cmd,
                           GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, manager, session, cmd, error);
	smlAssert(manager);
	smlAssert(session);
	smlAssert(cmd);
	CHECK_ERROR_REF
	
	//Check if a handler for this object at this path has been installed.
	SmlObject *object = smlManagerObjectFind(manager, session, cmd);
	if (object) {
		//Check if a handler for this object at this path has been installed.
		if (!object->commandCallback) {
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "No handler for the child was installed");
			goto error;
		}
		
		object->commandCallback(session, cmd, object->commandCallbackUserdata);
	} else {
		const char *type = smlCommandTypeToString(smlCommandGetType(cmd), error);
		/* If the type is unknown then this can be part of the error. */
		if (*error) {
			g_error_free(*error);
			*error = NULL;
		}
		const char *srcuri = NULL;
		if (smlCommandGetSource(cmd))
			srcuri = sml_location_get_uri(smlCommandGetSource(cmd));
		const char *dsturi = NULL;
		if (smlCommandGetTarget(cmd))
			dsturi = sml_location_get_uri(smlCommandGetTarget(cmd));
		g_set_error(error, SML_ERROR, SML_ERROR_NOT_FOUND, "Unable to find command handler (%s: %s -> %s)", (type) ? type : "UNKNOWN", VA_STRING(srcuri), VA_STRING(dsturi));

		GError *locerror = NULL;
		SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_NOT_FOUND, &locerror);
		if (!reply) {
			GError *herr = *error;
			*error = NULL;
			g_set_error(error, SML_ERROR, SML_ERROR_NOT_FOUND, "%s %s",
				herr->message,
				locerror->message);
			g_error_free(herr);
			g_error_free(locerror);
			goto error;
		}
		
		if (!smlSessionSendReply(session, reply, &locerror)) {
			GError *herr = *error;
			*error = NULL;
			smlStatusUnref(reply);
			g_set_error(error, SML_ERROR, SML_ERROR_NOT_FOUND, "%s %s",
				herr->message,
				locerror->message);
			g_error_free(herr);
			g_error_free(locerror);
			goto error;
		}
		
		smlStatusUnref(reply);
		
		goto error;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

void
smlManagerRegisterHeaderHandler (SmlManager *manager,
                                 SmlHeaderCb callback,
                                 SmlStatusReplyCb statuscb,
                                 void *userdata)
{
	smlAssert(manager);

	manager->headerCallback = callback;
	manager->headerStatusCallback = statuscb;
	manager->headerCallbackUserdata = userdata;
}

void
smlManagerSetLocalMaxMsgSize (SmlManager *manager,
                              gsize size)
{
	smlAssert(manager);
	manager->localMaxMsgSize = size;
}

void
smlManagerSetLocalMaxObjSize (SmlManager *manager,
                              gsize size)
{
	smlAssert(manager);
	manager->localMaxObjSize = size;
}

void
smlManagerSetLocalMaxMsgChanges (SmlManager *manager,
                                 gsize size)
{
	smlAssert(manager);
	manager->localMaxMsgChanges = size;
}

gsize
smlManagerGetNewSessionID (SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, manager);
	smlAssertMsg(manager, "The manager is required to avoid duplicated session IDs.");

	gsize sessionID = 0;
	while (sessionID == 0)
	{
		/* WARNING: only 4 digit session IDs are theoretically allowed !!! */
    		/* create random session ID - glib only supports 32 bit random numbers */
		sessionID = (gsize) g_random_int_range (0, 9999);
		smlTrace(TRACE_INTERNAL, "%s: new potential session ID is %ud.", __func__, sessionID);
		if (smlManagerSessionFind(manager, sessionID))
			sessionID = 0;
	}
	smlTrace(TRACE_EXIT, "%s - %ud", __func__, sessionID);
	return sessionID;
}

SmlLink*
smlManagerSessionGetLink(SmlManager *manager,
                         SmlSession *session,
                         GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, manager, session, error);
	CHECK_ERROR_REF

	managerSession *_msession = _manager_session_find(manager, session);
	if (!_msession)
	{
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
			"The session %ulld is not registered at the manager.",
			smlSessionGetSessionID(session));
		goto error;
	}

	if (_msession->link)
		smlLinkRef(_msession->link);

	smlTrace(TRACE_EXIT, "%s - %p", __func__, _msession->link);
	return _msession->link;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
	return NULL;
}

