// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *
 *  OBEX Client
 *
 *  Copyright (C) 2011  Bartosz Szatkowski <bulislaw@linux.com> for Comarch
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
#include <stdlib.h>

#include <glib.h>

#include "bluetooth/sdp.h"

#include "gobex/gobex-apparam.h"
#include "gdbus/gdbus.h"

#include "obexd/src/log.h"
#include "obexd/src/obexd.h"
#include "obexd/src/map_ap.h"
#include "map-event.h"

#include "map.h"
#include "transfer.h"
#include "session.h"
#include "driver.h"

#define OBEX_MAS_UUID \
	"\xBB\x58\x2B\x40\x42\x0C\x11\xDB\xB0\xDE\x08\x00\x20\x0C\x9A\x66"
#define OBEX_MAS_UUID_LEN 16
#define SUPPORTED_FEATURES_TAG  0x29

#define NOTIFICATION_REGISTRATION_FEATURE 0x00000001
#define NOTIFICATION_FEATURE 0x00000002
#define BROWSING_FEATURE 0x00000004
#define UPLOADING_FEATURE 0x00000008
#define DELETE_FEATURE 0x00000010
#define INSTANCE_INFORMATION_FEATURE 0x00000020
#define EXTENDED_EVENT_REPORT_1_1 0x00000040
#define MESSAGES_LISTING_FORMAT_VERSION_1_1 0x00000200
#define MAPSUPPORTEDFEATURES_IN_CONNECT_REQUEST 0x00080000

#define MAP_INTERFACE "org.bluez.obex.MessageAccess1"
#define MAP_MSG_INTERFACE "org.bluez.obex.Message1"
#define ERROR_INTERFACE "org.bluez.obex.Error"
#define MAS_UUID "00001132-0000-1000-8000-00805f9b34fb"

#define DEFAULT_COUNT 1024
#define DEFAULT_OFFSET 0

#define CHARSET_NATIVE 0
#define CHARSET_UTF8 1

#define SDP_MESSAGE_TYPE_EMAIL		0x01
#define SDP_MESSAGE_TYPE_SMS_GSM	0x02
#define SDP_MESSAGE_TYPE_SMS_CDMA	0x04
#define SDP_MESSAGE_TYPE_MMS		0x08
#define SDP_MESSAGE_TYPE_IM		0x10

static const char * const filter_list[] = {
	"subject",
	"timestamp",
	"sender",
	"sender-address",
	"recipient",
	"recipient-address",
	"type",
	"size",
	"status",
	"text",
	"attachment",
	"priority",
	"read",
	"sent",
	"protected",
	"replyto",
	NULL
};

#define FILTER_BIT_MAX	15
#define FILTER_ALL	0x0000FFFF

#define FILTER_READ_STATUS_NONE		0x00
#define FILTER_READ_STATUS_ONLY_UNREAD	0x01
#define FILTER_READ_STATUS_ONLY_READ	0x02

#define FILTER_PRIORITY_NONE		0x00
#define FILTER_PRIORITY_ONLY_HIGH	0x01
#define FILTER_PRIORITY_ONLY_NONHIGH	0x02

#define STATUS_READ 0
#define STATUS_DELETE 1
#define FILLER_BYTE 0x30

struct map_data {
	struct obc_session *session;
	GHashTable *messages;
	int16_t mas_instance_id;
	uint8_t supported_message_types;
	uint32_t supported_features;
};

struct pending_request {
	struct map_data *map;
	DBusMessage *msg;
	char *folder;
};

#define MAP_MSG_FLAG_PRIORITY	0x01
#define MAP_MSG_FLAG_READ	0x02
#define MAP_MSG_FLAG_SENT	0x04
#define MAP_MSG_FLAG_PROTECTED	0x08
#define MAP_MSG_FLAG_TEXT	0x10

struct map_msg {
	struct map_data *data;
	char *path;
	uint64_t handle;
	char *subject;
	char *timestamp;
	char *sender;
	char *sender_address;
	char *replyto;
	char *recipient;
	char *recipient_address;
	char *type;
	uint64_t size;
	char *status;
	uint64_t attachment_size;
	uint8_t flags;
	char *folder;
	char *delivery_status;
	uint64_t conversation_id;
	char *conversation_name;
	char *direction;
	char *attachment_mime_types;
	GDBusPendingPropertySet pending;
};

struct map_parser {
	struct pending_request *request;
	DBusMessageIter *iter;
};

static DBusConnection *conn = NULL;

static struct pending_request *pending_request_new(struct map_data *map,
							DBusMessage *message)
{
	struct pending_request *p;

	p = g_new0(struct pending_request, 1);
	p->map = map;
	p->msg = dbus_message_ref(message);

	return p;
}

static void pending_request_free(struct pending_request *p)
{
	dbus_message_unref(p->msg);

	g_free(p->folder);
	g_free(p);
}

static void simple_cb(struct obc_session *session,
						struct obc_transfer *transfer,
						GError *err, void *user_data)
{
	struct pending_request *request = user_data;
	DBusMessage *reply;

	if (err != NULL)
		reply = g_dbus_create_error(request->msg,
						ERROR_INTERFACE ".Failed",
						"%s", err->message);
	else
		reply = dbus_message_new_method_return(request->msg);

	g_dbus_send_message(conn, reply);
	pending_request_free(request);
}

static DBusMessage *map_setpath(DBusConnection *connection,
					DBusMessage *message, void *user_data)
{
	struct map_data *map = user_data;
	const char *folder;
	struct pending_request *request;
	GError *err = NULL;

	if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &folder,
						DBUS_TYPE_INVALID) == FALSE)
		return g_dbus_create_error(message,
					ERROR_INTERFACE ".InvalidArguments",
					NULL);

	request = pending_request_new(map, message);

	obc_session_setpath(map->session, folder, simple_cb, request, &err);
	if (err != NULL) {
		DBusMessage *reply;
		reply =  g_dbus_create_error(message,
						ERROR_INTERFACE ".Failed",
						"%s", err->message);
		g_error_free(err);
		pending_request_free(request);
		return reply;
	}

	return NULL;
}

static void folder_element(GMarkupParseContext *ctxt, const char *element,
				const char **names, const char **values,
				gpointer user_data, GError **gerr)
{
	DBusMessageIter dict, *iter = user_data;
	const char *key;
	int i;

	if (strcasecmp("folder", element) != 0)
		return;

	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);

	for (i = 0, key = names[i]; key; key = names[++i]) {
		if (strcasecmp("name", key) == 0)
			g_dbus_dict_append_entry(&dict, "Name",
							DBUS_TYPE_STRING,
							&values[i]);
	}

	dbus_message_iter_close_container(iter, &dict);
}

static const GMarkupParser folder_parser = {
	folder_element,
	NULL,
	NULL,
	NULL,
	NULL
};

static void folder_listing_cb(struct obc_session *session,
						struct obc_transfer *transfer,
						GError *err, void *user_data)
{
	struct pending_request *request = user_data;
	GMarkupParseContext *ctxt;
	DBusMessage *reply;
	DBusMessageIter iter, array;
	char *contents;
	size_t size;
	int perr;

	if (err != NULL) {
		reply = g_dbus_create_error(request->msg,
						ERROR_INTERFACE ".Failed",
						"%s", err->message);
		goto done;
	}

	perr = obc_transfer_get_contents(transfer, &contents, &size);
	if (perr < 0) {
		reply = g_dbus_create_error(request->msg,
						ERROR_INTERFACE ".Failed",
						"Error reading contents: %s",
						strerror(-perr));
		goto done;
	}

	reply = dbus_message_new_method_return(request->msg);
	if (reply == NULL) {
		g_free(contents);
		goto clean;
	}

	dbus_message_iter_init_append(reply, &iter);
	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
			DBUS_TYPE_ARRAY_AS_STRING
			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array);
	ctxt = g_markup_parse_context_new(&folder_parser, 0, &array, NULL);
	g_markup_parse_context_parse(ctxt, contents, size, NULL);
	g_markup_parse_context_free(ctxt);
	dbus_message_iter_close_container(&iter, &array);
	g_free(contents);

done:
	g_dbus_send_message(conn, reply);
clean:
	pending_request_free(request);
}

static DBusMessage *get_folder_listing(struct map_data *map,
							DBusMessage *message,
							GObexApparam *apparam)
{
	struct pending_request *request;
	struct obc_transfer *transfer;
	GError *err = NULL;
	DBusMessage *reply;

	transfer = obc_transfer_get("x-obex/folder-listing", NULL, NULL, &err);
	if (transfer == NULL) {
		g_obex_apparam_free(apparam);
		goto fail;
	}

	obc_transfer_set_apparam(transfer, apparam);

	request = pending_request_new(map, message);

	if (!obc_session_queue(map->session, transfer, folder_listing_cb,
							request, &err)) {
		pending_request_free(request);
		goto fail;
	}

	return NULL;

fail:
	reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s",
								err->message);
	g_error_free(err);
	return reply;
}

static GObexApparam *parse_offset(GObexApparam *apparam, DBusMessageIter *iter)
{
	guint16 num;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16)
		return NULL;

	dbus_message_iter_get_basic(iter, &num);

	return g_obex_apparam_set_uint16(apparam, MAP_AP_STARTOFFSET, num);
}

static GObexApparam *parse_max_count(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	guint16 num;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16)
		return NULL;

	dbus_message_iter_get_basic(iter, &num);

	return g_obex_apparam_set_uint16(apparam, MAP_AP_MAXLISTCOUNT, num);
}

static GObexApparam *parse_folder_filters(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	DBusMessageIter array;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return NULL;

	dbus_message_iter_recurse(iter, &array);

	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
		const char *key;
		DBusMessageIter value, entry;

		dbus_message_iter_recurse(&array, &entry);
		dbus_message_iter_get_basic(&entry, &key);

		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &value);

		if (strcasecmp(key, "Offset") == 0) {
			if (parse_offset(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "MaxCount") == 0) {
			if (parse_max_count(apparam, &value) == NULL)
				return NULL;
		}

		dbus_message_iter_next(&array);
	}

	return apparam;
}

static DBusMessage *map_list_folders(DBusConnection *connection,
					DBusMessage *message, void *user_data)
{
	struct map_data *map = user_data;
	GObexApparam *apparam;
	DBusMessageIter args;

	dbus_message_iter_init(message, &args);

	apparam = g_obex_apparam_set_uint16(NULL, MAP_AP_MAXLISTCOUNT,
							DEFAULT_COUNT);
	apparam = g_obex_apparam_set_uint16(apparam, MAP_AP_STARTOFFSET,
							DEFAULT_OFFSET);

	if (parse_folder_filters(apparam, &args) == NULL) {
		g_obex_apparam_free(apparam);
		return g_dbus_create_error(message,
				ERROR_INTERFACE ".InvalidArguments", NULL);
	}

	return get_folder_listing(map, message, apparam);
}

static void map_msg_free(void *data)
{
	struct map_msg *msg = data;

	g_free(msg->path);
	g_free(msg->subject);
	g_free(msg->folder);
	g_free(msg->timestamp);
	g_free(msg->sender);
	g_free(msg->sender_address);
	g_free(msg->replyto);
	g_free(msg->recipient);
	g_free(msg->recipient_address);
	g_free(msg->type);
	g_free(msg->status);
	g_free(msg->delivery_status);
	g_free(msg->conversation_name);
	g_free(msg->direction);
	g_free(msg->attachment_mime_types);
	g_free(msg);
}

static DBusMessage *map_msg_get(DBusConnection *connection,
					DBusMessage *message, void *user_data)
{
	struct map_msg *msg = user_data;
	struct obc_transfer *transfer;
	const char *target_file;
	gboolean attachment;
	GError *err = NULL;
	DBusMessage *reply;
	GObexApparam *apparam;
	char handle[17];

	if (dbus_message_get_args(message, NULL,
				DBUS_TYPE_STRING, &target_file,
				DBUS_TYPE_BOOLEAN, &attachment,
				DBUS_TYPE_INVALID) == FALSE)
		return g_dbus_create_error(message,
				ERROR_INTERFACE ".InvalidArguments", NULL);

	snprintf(handle, sizeof(handle), "%" PRIx64, msg->handle);

	transfer = obc_transfer_get("x-bt/message", handle, target_file, &err);
	if (transfer == NULL)
		goto fail;

	apparam = g_obex_apparam_set_uint8(NULL, MAP_AP_ATTACHMENT,
								attachment);
	apparam = g_obex_apparam_set_uint8(apparam, MAP_AP_CHARSET,
								CHARSET_UTF8);

	obc_transfer_set_apparam(transfer, apparam);

	if (!obc_session_queue(msg->data->session, transfer, NULL, NULL, &err))
		goto fail;

	return obc_transfer_create_dbus_reply(transfer, message);

fail:
	reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s",
								err->message);
	g_error_free(err);
	return reply;
}

static void set_message_status_cb(struct obc_session *session,
						struct obc_transfer *transfer,
						GError *err, void *user_data)
{
	struct map_msg *msg = user_data;

	if (err != NULL) {
		g_dbus_pending_property_error(msg->pending,
						ERROR_INTERFACE ".Failed",
						"%s", err->message);
		goto done;
	}

	g_dbus_pending_property_success(msg->pending);

done:
	msg->pending = 0;
}

static gboolean get_folder(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->folder);

	return TRUE;
}

static gboolean subject_exists(const GDBusPropertyTable *property, void *data)
{
	struct map_msg *msg = data;

	return msg->subject != NULL;
}

static gboolean get_subject(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->subject);

	return TRUE;
}

static gboolean timestamp_exists(const GDBusPropertyTable *property, void *data)
{
	struct map_msg *msg = data;

	return msg->timestamp != NULL;
}

static gboolean get_timestamp(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->timestamp);

	return TRUE;
}

static gboolean sender_exists(const GDBusPropertyTable *property, void *data)
{
	struct map_msg *msg = data;

	return msg->sender != NULL;
}

static gboolean get_sender(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->sender);

	return TRUE;
}

static gboolean sender_address_exists(const GDBusPropertyTable *property,
								void *data)
{
	struct map_msg *msg = data;

	return msg->sender_address != NULL;
}

static gboolean get_sender_address(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
							&msg->sender_address);

	return TRUE;
}

static gboolean replyto_exists(const GDBusPropertyTable *property, void *data)
{
	struct map_msg *msg = data;

	return msg->replyto != NULL;
}

static gboolean get_replyto(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->replyto);

	return TRUE;
}

static gboolean recipient_exists(const GDBusPropertyTable *property, void *data)
{
	struct map_msg *msg = data;

	return msg->recipient != NULL;
}

static gboolean get_recipient(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->recipient);

	return TRUE;
}

static gboolean recipient_address_exists(const GDBusPropertyTable *property,
								void *data)
{
	struct map_msg *msg = data;

	return msg->recipient_address != NULL;
}

static gboolean get_recipient_address(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
						&msg->recipient_address);

	return TRUE;
}

static gboolean type_exists(const GDBusPropertyTable *property, void *data)
{
	struct map_msg *msg = data;

	return msg->type != NULL;
}

static gboolean get_type(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->type);

	return TRUE;
}

static gboolean get_size(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &msg->size);

	return TRUE;
}

static gboolean reception_status_exists(const GDBusPropertyTable *property,
								void *data)
{
	struct map_msg *msg = data;

	return msg->status != NULL;
}

static gboolean get_reception_status(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->status);

	return TRUE;
}

static gboolean get_attachment_size(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64,
							&msg->attachment_size);

	return TRUE;
}

static gboolean get_flag(const GDBusPropertyTable *property,
					DBusMessageIter *iter, uint8_t flag,
					void *data)
{
	struct map_msg *msg = data;
	dbus_bool_t value = (msg->flags & flag) != 0;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);

	return TRUE;
}

static gboolean get_priority(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	return get_flag(property, iter, MAP_MSG_FLAG_PRIORITY, data);
}

static gboolean get_read(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	return get_flag(property, iter, MAP_MSG_FLAG_READ, data);
}

static gboolean get_sent(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	return get_flag(property, iter, MAP_MSG_FLAG_SENT, data);
}

static gboolean get_protected(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	return get_flag(property, iter, MAP_MSG_FLAG_PROTECTED, data);
}

static gboolean get_text(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	return get_flag(property, iter, MAP_MSG_FLAG_TEXT, data);
}

static void set_status(const GDBusPropertyTable *property,
			DBusMessageIter *iter, GDBusPendingPropertySet id,
			uint8_t status, void *data)
{
	struct map_msg *msg = data;
	struct obc_transfer *transfer;
	gboolean value;
	GError *err = NULL;
	GObexApparam *apparam;
	char contents[1];
	char handle[17];

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) {
		g_dbus_pending_property_error(id,
					ERROR_INTERFACE ".InvalidArguments",
					"Invalid arguments in method call");
		return;
	}

	dbus_message_iter_get_basic(iter, &value);

	contents[0] = FILLER_BYTE;

	snprintf(handle, sizeof(handle), "%" PRIx64, msg->handle);

	transfer = obc_transfer_put("x-bt/messageStatus", handle, NULL,
					contents, sizeof(contents), &err);
	if (transfer == NULL)
		goto fail;

	apparam = g_obex_apparam_set_uint8(NULL, MAP_AP_STATUSINDICATOR,
								status);
	apparam = g_obex_apparam_set_uint8(apparam, MAP_AP_STATUSVALUE,
								value);
	obc_transfer_set_apparam(transfer, apparam);

	if (!obc_session_queue(msg->data->session, transfer,
				set_message_status_cb, msg, &err))
		goto fail;

	msg->pending = id;
	return;

fail:
	g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", "%s",
								err->message);
	g_error_free(err);
}

static void set_read(const GDBusPropertyTable *property,
			DBusMessageIter *iter, GDBusPendingPropertySet id,
			void *data)
{
	set_status(property, iter, id, STATUS_READ, data);
}

static void set_deleted(const GDBusPropertyTable *property,
			DBusMessageIter *iter, GDBusPendingPropertySet id,
			void *data)
{
	set_status(property, iter, id, STATUS_DELETE, data);
}

static gboolean delivery_status_exists(const GDBusPropertyTable *property,
								void *data)
{
	struct map_msg *msg = data;

	return msg->delivery_status != NULL;
}

static gboolean get_delivery_status(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
						&msg->delivery_status);

	return TRUE;
}

static gboolean get_conversation_id(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64,
						&msg->conversation_id);

	return TRUE;
}

static gboolean conversation_name_exists(const GDBusPropertyTable *property,
								void *data)
{
	struct map_msg *msg = data;

	return msg->conversation_name != NULL;
}

static gboolean get_conversation_name(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
						&msg->conversation_name);

	return TRUE;
}

static gboolean direction_exists(const GDBusPropertyTable *property,
								void *data)
{
	struct map_msg *msg = data;

	return msg->direction != NULL;
}

static gboolean get_direction(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
							&msg->direction);

	return TRUE;
}

static gboolean attachment_mime_exists(const GDBusPropertyTable *property,
								void *data)
{
	struct map_msg *msg = data;

	return msg->attachment_mime_types != NULL;
}

static gboolean get_attachment_mime_types(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct map_msg *msg = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
						&msg->attachment_mime_types);

	return TRUE;
}

static const GDBusMethodTable map_msg_methods[] = {
	{ GDBUS_METHOD("Get",
			GDBUS_ARGS({ "targetfile", "s" },
						{ "attachment", "b" }),
			GDBUS_ARGS({ "transfer", "o" },
						{ "properties", "a{sv}" }),
			map_msg_get) },
	{ }
};

static const GDBusPropertyTable map_msg_properties[] = {
	{ "Folder", "s", get_folder },
	{ "Subject", "s", get_subject, NULL, subject_exists },
	{ "Timestamp", "s", get_timestamp, NULL, timestamp_exists },
	{ "Sender", "s", get_sender, NULL, sender_exists },
	{ "SenderAddress", "s", get_sender_address, NULL,
						sender_address_exists },
	{ "ReplyTo", "s", get_replyto, NULL, replyto_exists },
	{ "Recipient", "s", get_recipient, NULL, recipient_exists },
	{ "RecipientAddress", "s", get_recipient_address, NULL,
						recipient_address_exists },
	{ "Type", "s", get_type, NULL, type_exists },
	{ "Size", "t", get_size },
	{ "Text", "b", get_text },
	{ "Status", "s", get_reception_status, NULL, reception_status_exists },
	{ "AttachmentSize", "t", get_attachment_size },
	{ "Priority", "b", get_priority },
	{ "Read", "b", get_read, set_read },
	{ "Sent", "b", get_sent },
	{ "Protected", "b", get_protected },
	{ "Deleted", "b", NULL, set_deleted },
	{ "DeliveryStatus", "s", get_delivery_status, NULL,
						delivery_status_exists },
	{ "ConversationId", "t", get_conversation_id },
	{ "ConversationName", "s", get_conversation_name, NULL,
						conversation_name_exists },
	{ "Direction", "s", get_direction, NULL, direction_exists },
	{ "AttachmentMimeTypes", "s", get_attachment_mime_types, NULL,
						attachment_mime_exists },
	{ }
};

static void parse_type(struct map_msg *msg, const char *value)
{
	const char *type = NULL;

	if (strcasecmp(value, "SMS_GSM") == 0)
		type = "sms-gsm";
	else if (strcasecmp(value, "SMS_CDMA") == 0)
		type = "sms-cdma";
	else if (strcasecmp(value, "EMAIL") == 0)
		type = "email";
	else if (strcasecmp(value, "MMS") == 0)
		type = "mms";

	if (g_strcmp0(msg->type, type) == 0)
		return;

	g_free(msg->type);
	msg->type = g_strdup(type);

	g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "Type");
}

static struct map_msg *map_msg_create(struct map_data *data, uint64_t handle,
					const char *folder, const char *type)
{
	struct map_msg *msg;

	msg = g_new0(struct map_msg, 1);
	msg->data = data;
	msg->handle = handle;
	msg->path = g_strdup_printf("%s/message%" PRIu64,
					obc_session_get_path(data->session),
					msg->handle);
	msg->folder = g_strdup(folder);

	if (!g_dbus_register_interface(conn, msg->path, MAP_MSG_INTERFACE,
						map_msg_methods, NULL,
						map_msg_properties,
						msg, map_msg_free)) {
		map_msg_free(msg);
		return NULL;
	}

	g_hash_table_insert(data->messages, &msg->handle, msg);

	if (type)
		parse_type(msg, type);

	return msg;
}

static void parse_subject(struct map_msg *msg, const char *value)
{
	if (g_strcmp0(msg->subject, value) == 0)
		return;

	g_free(msg->subject);
	msg->subject = g_strdup(value);

	g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "Subject");
}

static void parse_datetime(struct map_msg *msg, const char *value)
{
	if (g_strcmp0(msg->timestamp, value) == 0)
		return;

	g_free(msg->timestamp);
	msg->timestamp = g_strdup(value);

	g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "Timestamp");
}

static void parse_sender(struct map_msg *msg, const char *value)
{
	if (g_strcmp0(msg->sender, value) == 0)
		return;

	g_free(msg->sender);
	msg->sender = g_strdup(value);

	g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "Sender");
}

static void parse_sender_address(struct map_msg *msg, const char *value)
{
	if (g_strcmp0(msg->sender_address, value) == 0)
		return;

	g_free(msg->sender_address);
	msg->sender_address = g_strdup(value);

	g_dbus_emit_property_changed(conn, msg->path,
					MAP_MSG_INTERFACE, "SenderAddress");
}

static void parse_replyto(struct map_msg *msg, const char *value)
{
	if (g_strcmp0(msg->replyto, value) == 0)
		return;

	g_free(msg->replyto);
	msg->replyto = g_strdup(value);

	g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "ReplyTo");
}

static void parse_recipient(struct map_msg *msg, const char *value)
{
	if (g_strcmp0(msg->recipient, value) == 0)
		return;

	g_free(msg->recipient);
	msg->recipient = g_strdup(value);

	g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "Recipient");
}

static void parse_recipient_address(struct map_msg *msg, const char *value)
{
	if (g_strcmp0(msg->recipient_address, value) == 0)
		return;

	g_free(msg->recipient_address);
	msg->recipient_address = g_strdup(value);

	g_dbus_emit_property_changed(conn, msg->path,
					MAP_MSG_INTERFACE, "RecipientAddress");
}

static void parse_size(struct map_msg *msg, const char *value)
{
	uint64_t size = g_ascii_strtoll(value, NULL, 10);

	if (msg->size == size)
		return;

	msg->size = size;

	g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "Size");
}

static void parse_text(struct map_msg *msg, const char *value)
{
	gboolean flag = strcasecmp(value, "no") != 0;
	uint8_t oldflags = msg->flags;

	if (flag)
		msg->flags |= MAP_MSG_FLAG_TEXT;
	else
		msg->flags &= ~MAP_MSG_FLAG_TEXT;

	if (msg->flags != oldflags)
		g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "Text");
}

static void parse_status(struct map_msg *msg, const char *value)
{
	if (g_strcmp0(msg->status, value) == 0)
		return;

	g_free(msg->status);
	msg->status = g_strdup(value);

	g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "Status");
}

static void parse_attachment_size(struct map_msg *msg, const char *value)
{
	uint64_t attachment_size = g_ascii_strtoll(value, NULL, 10);

	if (msg->attachment_size == attachment_size)
		return;

	msg->attachment_size = attachment_size;

	g_dbus_emit_property_changed(conn, msg->path,
					MAP_MSG_INTERFACE, "AttachmentSize");
}

static void parse_priority(struct map_msg *msg, const char *value)
{
	gboolean flag = strcasecmp(value, "no") != 0;
	uint8_t oldflags = msg->flags;

	if (flag)
		msg->flags |= MAP_MSG_FLAG_PRIORITY;
	else
		msg->flags &= ~MAP_MSG_FLAG_PRIORITY;

	if (msg->flags != oldflags)
		g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "Priority");
}

static void parse_read(struct map_msg *msg, const char *value)
{
	gboolean flag = strcasecmp(value, "no") != 0;
	uint8_t oldflags = msg->flags;

	if (flag)
		msg->flags |= MAP_MSG_FLAG_READ;
	else
		msg->flags &= ~MAP_MSG_FLAG_READ;

	if (msg->flags != oldflags)
		g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "Read");
}

static void parse_sent(struct map_msg *msg, const char *value)
{
	gboolean flag = strcasecmp(value, "no") != 0;
	uint8_t oldflags = msg->flags;

	if (flag)
		msg->flags |= MAP_MSG_FLAG_SENT;
	else
		msg->flags &= ~MAP_MSG_FLAG_SENT;

	if (msg->flags != oldflags)
		g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "Sent");
}

static void parse_protected(struct map_msg *msg, const char *value)
{
	gboolean flag = strcasecmp(value, "no") != 0;
	uint8_t oldflags = msg->flags;

	if (flag)
		msg->flags |= MAP_MSG_FLAG_PROTECTED;
	else
		msg->flags &= ~MAP_MSG_FLAG_PROTECTED;

	if (msg->flags != oldflags)
		g_dbus_emit_property_changed(conn, msg->path,
						MAP_MSG_INTERFACE, "Protected");
}

static void parse_delivery_status(struct map_msg *msg, const char *value)
{
	if (g_strcmp0(msg->delivery_status, value) == 0)
		return;

	g_free(msg->delivery_status);
	msg->delivery_status = g_strdup(value);

	g_dbus_emit_property_changed(conn, msg->path,
					MAP_MSG_INTERFACE, "DeliveryStatus");
}

static void parse_conversation_id(struct map_msg *msg, const char *value)
{
	uint64_t conversation_id = strtoull(value, NULL, 16);

	if (msg->conversation_id == conversation_id)
		return;

	msg->conversation_id = conversation_id;

	g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE,
						"ConversationId");
}

static void parse_conversation_name(struct map_msg *msg, const char *value)
{
	if (g_strcmp0(msg->conversation_name, value) == 0)
		return;

	g_free(msg->conversation_name);
	msg->conversation_name = g_strdup(value);

	g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE,
						"ConversationName");
}

static void parse_direction(struct map_msg *msg, const char *value)
{
	if (g_strcmp0(msg->direction, value) == 0)
		return;

	g_free(msg->direction);
	msg->direction = g_strdup(value);

	g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE,
						"Direction");
}

static void parse_mime_types(struct map_msg *msg, const char *value)
{
	if (g_strcmp0(msg->attachment_mime_types, value) == 0)
		return;

	g_free(msg->attachment_mime_types);
	msg->attachment_mime_types = g_strdup(value);

	g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE,
						"AttachmentMimeTypes");
}

static const struct map_msg_parser {
	const char *name;
	void (*func) (struct map_msg *msg, const char *value);
} msg_parsers[] = {
		{ "subject", parse_subject },
		{ "datetime", parse_datetime },
		{ "sender_name", parse_sender },
		{ "sender_addressing", parse_sender_address },
		{ "replyto_addressing", parse_replyto },
		{ "recipient_name", parse_recipient },
		{ "recipient_addressing", parse_recipient_address },
		{ "type", parse_type },
		{ "size", parse_size },
		{ "text", parse_text },
		{ "reception_status", parse_status },
		{ "attachment_size", parse_attachment_size },
		{ "priority", parse_priority },
		{ "read", parse_read },
		{ "sent", parse_sent },
		{ "protected", parse_protected },
		{ "delivery_status", parse_delivery_status},
		{ "conversation_id", parse_conversation_id},
		{ "conversation_name", parse_conversation_name},
		{ "direction", parse_direction},
		{ "attachment_mime_types", parse_mime_types},
		{ }
};

static void msg_element(GMarkupParseContext *ctxt, const char *element,
				const char **names, const char **values,
				gpointer user_data, GError **gerr)
{
	struct map_parser *parser = user_data;
	struct map_data *data = parser->request->map;
	DBusMessageIter entry, *iter = parser->iter;
	struct map_msg *msg;
	const char *key;
	int i;
	uint64_t handle;

	if (strcasecmp("msg", element) != 0)
		return;

	for (i = 0, key = names[i]; key; key = names[++i]) {
		if (strcasecmp(key, "handle") == 0)
			break;
	}

	handle = strtoull(values[i], NULL, 16);

	msg = g_hash_table_lookup(data->messages, &handle);
	if (msg == NULL) {
		msg = map_msg_create(data, handle, parser->request->folder,
									NULL);
		if (msg == NULL)
			return;
	}

	dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL,
								&entry);

	dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH,
								&msg->path);

	for (i = 0, key = names[i]; key; key = names[++i]) {
		const struct map_msg_parser *parser;

		for (parser = msg_parsers; parser && parser->name; parser++) {
			if (strcasecmp(key, parser->name) == 0) {
				if (values[i])
					parser->func(msg, values[i]);
				break;
			}
		}
	}

	g_dbus_get_properties(conn, msg->path, MAP_MSG_INTERFACE, &entry);

	dbus_message_iter_close_container(iter, &entry);
}

static const GMarkupParser msg_parser = {
	msg_element,
	NULL,
	NULL,
	NULL,
	NULL
};

static void message_listing_cb(struct obc_session *session,
						struct obc_transfer *transfer,
						GError *err, void *user_data)
{
	struct pending_request *request = user_data;
	struct map_parser *parser;
	GMarkupParseContext *ctxt;
	DBusMessage *reply;
	DBusMessageIter iter, array;
	char *contents;
	size_t size;
	int perr;

	if (err != NULL) {
		reply = g_dbus_create_error(request->msg,
						ERROR_INTERFACE ".Failed",
						"%s", err->message);
		goto done;
	}

	perr = obc_transfer_get_contents(transfer, &contents, &size);
	if (perr < 0) {
		reply = g_dbus_create_error(request->msg,
						ERROR_INTERFACE ".Failed",
						"Error reading contents: %s",
						strerror(-perr));
		goto done;
	}

	reply = dbus_message_new_method_return(request->msg);
	if (reply == NULL) {
		g_free(contents);
		goto clean;
	}

	dbus_message_iter_init_append(reply, &iter);
	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
					DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
					DBUS_TYPE_OBJECT_PATH_AS_STRING
					DBUS_TYPE_ARRAY_AS_STRING
					DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
					DBUS_TYPE_STRING_AS_STRING
					DBUS_TYPE_VARIANT_AS_STRING
					DBUS_DICT_ENTRY_END_CHAR_AS_STRING
					DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
					&array);

	parser = g_new(struct map_parser, 1);
	parser->request = request;
	parser->iter = &array;

	ctxt = g_markup_parse_context_new(&msg_parser, 0, parser, NULL);
	g_markup_parse_context_parse(ctxt, contents, size, NULL);
	g_markup_parse_context_free(ctxt);
	dbus_message_iter_close_container(&iter, &array);
	g_free(contents);
	g_free(parser);

done:
	g_dbus_send_message(conn, reply);
clean:
	pending_request_free(request);
}

static char *get_absolute_folder(struct map_data *map, const char *subfolder)
{
	const char *root = obc_session_get_folder(map->session);

	if (!subfolder || strlen(subfolder) == 0)
		return g_strdup(root);
	else if (g_str_has_suffix(root, "/"))
		return g_strconcat(root, subfolder, NULL);
	else
		return g_strconcat(root, "/", subfolder, NULL);
}

static DBusMessage *get_message_listing(struct map_data *map,
							DBusMessage *message,
							const char *folder,
							GObexApparam *apparam)
{
	struct pending_request *request;
	struct obc_transfer *transfer;
	GError *err = NULL;
	DBusMessage *reply;

	transfer = obc_transfer_get("x-bt/MAP-msg-listing", folder, NULL, &err);
	if (transfer == NULL) {
		g_obex_apparam_free(apparam);
		goto fail;
	}

	obc_transfer_set_apparam(transfer, apparam);

	request = pending_request_new(map, message);
	request->folder = get_absolute_folder(map, folder);

	if (!obc_session_queue(map->session, transfer, message_listing_cb,
							request, &err)) {
		pending_request_free(request);
		goto fail;
	}

	return NULL;

fail:
	reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s",
								err->message);
	g_error_free(err);
	return reply;
}

static GObexApparam *parse_subject_length(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	guint8 num;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BYTE)
		return NULL;

	dbus_message_iter_get_basic(iter, &num);

	return g_obex_apparam_set_uint8(apparam, MAP_AP_SUBJECTLENGTH, num);
}

static uint64_t get_filter_mask(const char *filterstr)
{
	int i;

	if (!filterstr)
		return 0;

	if (!g_ascii_strcasecmp(filterstr, "ALL"))
		return FILTER_ALL;

	for (i = 0; filter_list[i] != NULL; i++)
		if (!g_ascii_strcasecmp(filterstr, filter_list[i]))
			return 1ULL << i;

	return 0;
}

static int set_field(guint32 *filter, const char *filterstr)
{
	guint64 mask;

	mask = get_filter_mask(filterstr);

	if (mask == 0)
		return -EINVAL;

	*filter |= mask;
	return 0;
}

static GObexApparam *parse_fields(GObexApparam *apparam, DBusMessageIter *iter)
{
	DBusMessageIter array;
	guint32 filter = 0;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return NULL;

	dbus_message_iter_recurse(iter, &array);

	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
		const char *string;

		dbus_message_iter_get_basic(&array, &string);

		if (set_field(&filter, string) < 0)
			return NULL;

		dbus_message_iter_next(&array);
	}

	return g_obex_apparam_set_uint32(apparam, MAP_AP_PARAMETERMASK,
								filter);
}

static GObexApparam *parse_filter_type(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	DBusMessageIter array;
	guint8 types = 0;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return NULL;

	dbus_message_iter_recurse(iter, &array);

	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
		const char *string;

		dbus_message_iter_get_basic(&array, &string);

		if (!g_ascii_strcasecmp(string, "sms"))
			types |= 0x03; /* sms-gsm and sms-cdma */
		else if (!g_ascii_strcasecmp(string, "email"))
			types |= 0x04; /* email */
		else if (!g_ascii_strcasecmp(string, "mms"))
			types |= 0x08; /* mms */
		else
			return NULL;

		dbus_message_iter_next(&array);
	}

	return g_obex_apparam_set_uint8(apparam, MAP_AP_FILTERMESSAGETYPE,
									types);
}

static GObexApparam *parse_period_begin(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	const char *string;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
		return NULL;

	dbus_message_iter_get_basic(iter, &string);

	return g_obex_apparam_set_string(apparam, MAP_AP_FILTERPERIODBEGIN,
								string);
}

static GObexApparam *parse_period_end(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	const char *string;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
		return NULL;

	dbus_message_iter_get_basic(iter, &string);

	return g_obex_apparam_set_string(apparam, MAP_AP_FILTERPERIODEND,
								string);
}

static GObexApparam *parse_filter_read(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	guint8 status = FILTER_READ_STATUS_NONE;
	dbus_bool_t dbus_status = FALSE;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN)
		return NULL;

	dbus_message_iter_get_basic(iter, &dbus_status);

	if (dbus_status)
		status = FILTER_READ_STATUS_ONLY_READ;
	else
		status = FILTER_READ_STATUS_ONLY_UNREAD;

	return g_obex_apparam_set_uint8(apparam, MAP_AP_FILTERREADSTATUS,
								status);
}

static GObexApparam *parse_filter_recipient(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	const char *string;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
		return NULL;

	dbus_message_iter_get_basic(iter, &string);

	return g_obex_apparam_set_string(apparam, MAP_AP_FILTERRECIPIENT,
								string);
}

static GObexApparam *parse_filter_sender(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	const char *string;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
		return NULL;

	dbus_message_iter_get_basic(iter, &string);

	return g_obex_apparam_set_string(apparam, MAP_AP_FILTERORIGINATOR,
								string);
}

static GObexApparam *parse_filter_priority(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	guint8 priority = FILTER_PRIORITY_NONE;
	dbus_bool_t dbus_priority = FALSE;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN)
		return NULL;

	dbus_message_iter_get_basic(iter, &dbus_priority);

	if (dbus_priority)
		priority = FILTER_PRIORITY_ONLY_HIGH;
	else
		priority = FILTER_PRIORITY_ONLY_NONHIGH;

	return g_obex_apparam_set_uint8(apparam, MAP_AP_FILTERPRIORITY,
								priority);
}

static GObexApparam *parse_message_filters(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	DBusMessageIter array;

	DBG("");

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return NULL;

	dbus_message_iter_recurse(iter, &array);

	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
		const char *key;
		DBusMessageIter value, entry;

		dbus_message_iter_recurse(&array, &entry);
		dbus_message_iter_get_basic(&entry, &key);

		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &value);

		if (strcasecmp(key, "Offset") == 0) {
			if (parse_offset(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "MaxCount") == 0) {
			if (parse_max_count(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "SubjectLength") == 0) {
			if (parse_subject_length(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "Fields") == 0) {
			if (parse_fields(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "Types") == 0) {
			if (parse_filter_type(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "PeriodBegin") == 0) {
			if (parse_period_begin(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "PeriodEnd") == 0) {
			if (parse_period_end(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "Read") == 0) {
			if (parse_filter_read(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "Recipient") == 0) {
			if (parse_filter_recipient(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "Sender") == 0) {
			if (parse_filter_sender(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "Priority") == 0) {
			if (parse_filter_priority(apparam, &value) == NULL)
				return NULL;
		}

		dbus_message_iter_next(&array);
	}

	return apparam;
}

static DBusMessage *map_list_messages(DBusConnection *connection,
					DBusMessage *message, void *user_data)
{
	struct map_data *map = user_data;
	const char *folder;
	GObexApparam *apparam;
	DBusMessageIter args;

	dbus_message_iter_init(message, &args);

	if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING)
		return g_dbus_create_error(message,
				ERROR_INTERFACE ".InvalidArguments", NULL);

	dbus_message_iter_get_basic(&args, &folder);

	apparam = g_obex_apparam_set_uint16(NULL, MAP_AP_MAXLISTCOUNT,
							DEFAULT_COUNT);
	apparam = g_obex_apparam_set_uint16(apparam, MAP_AP_STARTOFFSET,
							DEFAULT_OFFSET);

	dbus_message_iter_next(&args);

	if (parse_message_filters(apparam, &args) == NULL) {
		g_obex_apparam_free(apparam);
		return g_dbus_create_error(message,
				ERROR_INTERFACE ".InvalidArguments", NULL);
	}

	return get_message_listing(map, message, folder, apparam);
}

static char **get_filter_strs(uint64_t filter, int *size)
{
	char **list, **item;
	int i;

	list = g_malloc0(sizeof(char **) * (FILTER_BIT_MAX + 2));

	item = list;

	for (i = 0; filter_list[i] != NULL; i++)
		if (filter & (1ULL << i))
			*(item++) = g_strdup(filter_list[i]);

	*item = NULL;
	*size = item - list;
	return list;
}

static DBusMessage *map_list_filter_fields(DBusConnection *connection,
					DBusMessage *message, void *user_data)
{
	char **filters = NULL;
	int size;
	DBusMessage *reply;

	filters = get_filter_strs(FILTER_ALL, &size);
	reply = dbus_message_new_method_return(message);
	dbus_message_append_args(reply, DBUS_TYPE_ARRAY,
				DBUS_TYPE_STRING, &filters, size,
				DBUS_TYPE_INVALID);

	g_strfreev(filters);
	return reply;
}

static void update_inbox_cb(struct obc_session *session,
				struct obc_transfer *transfer,
				GError *err, void *user_data)
{
	struct pending_request *request = user_data;
	DBusMessage *reply;

	if (err != NULL) {
		reply = g_dbus_create_error(request->msg,
						ERROR_INTERFACE ".Failed",
						"%s", err->message);
		goto done;
	}

	reply = dbus_message_new_method_return(request->msg);

done:
	g_dbus_send_message(conn, reply);
	pending_request_free(request);
}

static DBusMessage *map_update_inbox(DBusConnection *connection,
					DBusMessage *message, void *user_data)
{
	struct map_data *map = user_data;
	DBusMessage *reply;
	char contents[1];
	struct obc_transfer *transfer;
	GError *err = NULL;
	struct pending_request *request;

	contents[0] = FILLER_BYTE;

	transfer = obc_transfer_put("x-bt/MAP-messageUpdate", NULL, NULL,
						contents, sizeof(contents),
						&err);
	if (transfer == NULL)
		goto fail;

	request = pending_request_new(map, message);

	if (!obc_session_queue(map->session, transfer, update_inbox_cb,
							request, &err)) {
		pending_request_free(request);
		goto fail;
	}

	return NULL;

fail:
	reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s",
								err->message);
	g_error_free(err);
	return reply;
}

static DBusMessage *push_message(struct map_data *map,
							DBusMessage *message,
							const char *filename,
							const char *folder,
							GObexApparam *apparam)
{
	struct obc_transfer *transfer;
	GError *err = NULL;
	DBusMessage *reply;

	transfer = obc_transfer_put("x-bt/message", folder, filename,
								NULL, 0, &err);
	if (transfer == NULL) {
		g_obex_apparam_free(apparam);
		goto fail;
	}

	obc_transfer_set_apparam(transfer, apparam);

	if (!obc_session_queue(map->session, transfer, NULL, NULL, &err))
		goto fail;

	return obc_transfer_create_dbus_reply(transfer, message);

fail:
	reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s",
								err->message);
	g_error_free(err);
	return reply;
}

static GObexApparam *parse_transparent(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	dbus_bool_t transparent;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN)
		return NULL;

	dbus_message_iter_get_basic(iter, &transparent);

	return g_obex_apparam_set_uint8(apparam, MAP_AP_TRANSPARENT,
						transparent ? TRUE : FALSE);
}

static GObexApparam *parse_retry(GObexApparam *apparam, DBusMessageIter *iter)
{
	dbus_bool_t retry;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN)
		return NULL;

	dbus_message_iter_get_basic(iter, &retry);

	return g_obex_apparam_set_uint8(apparam, MAP_AP_RETRY,
							retry ? TRUE : FALSE);
}

static GObexApparam *parse_charset(GObexApparam *apparam, DBusMessageIter *iter)
{
	guint8 charset = 0;
	const char *string;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
		return NULL;

	dbus_message_iter_get_basic(iter, &string);

	if (strcasecmp(string, "native") == 0)
		charset = CHARSET_NATIVE;
	else if (strcasecmp(string, "utf8") == 0)
		charset = CHARSET_UTF8;
	else
		return NULL;

	return g_obex_apparam_set_uint8(apparam, MAP_AP_CHARSET, charset);
}

static GObexApparam *parse_push_options(GObexApparam *apparam,
							DBusMessageIter *iter)
{
	DBusMessageIter array;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return NULL;

	dbus_message_iter_recurse(iter, &array);

	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
		const char *key;
		DBusMessageIter value, entry;

		dbus_message_iter_recurse(&array, &entry);
		dbus_message_iter_get_basic(&entry, &key);

		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &value);

		if (strcasecmp(key, "Transparent") == 0) {
			if (parse_transparent(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "Retry") == 0) {
			if (parse_retry(apparam, &value) == NULL)
				return NULL;
		} else if (strcasecmp(key, "Charset") == 0) {
			if (parse_charset(apparam, &value) == NULL)
				return NULL;
		}

		dbus_message_iter_next(&array);
	}

	return apparam;
}

static DBusMessage *map_push_message(DBusConnection *connection,
					DBusMessage *message, void *user_data)
{
	struct map_data *map = user_data;
	char *filename;
	char *folder;
	GObexApparam *apparam;
	DBusMessageIter args;

	dbus_message_iter_init(message, &args);

	if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING)
		return g_dbus_create_error(message,
				ERROR_INTERFACE ".InvalidArguments", NULL);

	dbus_message_iter_get_basic(&args, &filename);

	dbus_message_iter_next(&args);

	if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) {
		return g_dbus_create_error(message,
				ERROR_INTERFACE ".InvalidArguments", NULL);
	}

	dbus_message_iter_get_basic(&args, &folder);

	dbus_message_iter_next(&args);

	apparam = g_obex_apparam_set_uint8(NULL, MAP_AP_CHARSET, CHARSET_UTF8);

	if (parse_push_options(apparam, &args) == NULL) {
		g_obex_apparam_free(apparam);
		return g_dbus_create_error(message,
				ERROR_INTERFACE ".InvalidArguments", NULL);
	}

	return push_message(map, message, filename, folder, apparam);
}

static const GDBusMethodTable map_methods[] = {
	{ GDBUS_ASYNC_METHOD("SetFolder",
				GDBUS_ARGS({ "name", "s" }), NULL,
				map_setpath) },
	{ GDBUS_ASYNC_METHOD("ListFolders",
			GDBUS_ARGS({ "filters", "a{sv}" }),
			GDBUS_ARGS({ "content", "aa{sv}" }),
			map_list_folders) },
	{ GDBUS_ASYNC_METHOD("ListMessages",
			GDBUS_ARGS({ "folder", "s" }, { "filter", "a{sv}" }),
			GDBUS_ARGS({ "messages", "a{oa{sv}}" }),
			map_list_messages) },
	{ GDBUS_METHOD("ListFilterFields",
			NULL,
			GDBUS_ARGS({ "fields", "as" }),
			map_list_filter_fields) },
	{ GDBUS_ASYNC_METHOD("UpdateInbox",
			NULL,
			NULL,
			map_update_inbox) },
	{ GDBUS_ASYNC_METHOD("PushMessage",
			GDBUS_ARGS({ "file", "s" }, { "folder", "s" },
						{ "args", "a{sv}" }),
			GDBUS_ARGS({ "transfer", "o" },
						{ "properties", "a{sv}" }),
			map_push_message) },
	{ }
};

static gboolean get_supported_types(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *user_data)
{
	struct map_data *map = user_data;
	DBusMessageIter entry;
	const char *str;

	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
					DBUS_TYPE_STRING_AS_STRING, &entry);
	if (map->supported_message_types & SDP_MESSAGE_TYPE_EMAIL) {
		str = "EMAIL";
		dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str);
	}
	if (map->supported_message_types & SDP_MESSAGE_TYPE_SMS_GSM) {
		str = "SMS_GSM";
		dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str);
	}
	if (map->supported_message_types & SDP_MESSAGE_TYPE_SMS_CDMA) {
		str = "SMS_CDMA";
		dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str);
	}
	if (map->supported_message_types & SDP_MESSAGE_TYPE_MMS) {
		str = "MMS";
		dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str);
	}
	if (map->supported_message_types & SDP_MESSAGE_TYPE_IM) {
		str = "IM";
		dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str);
	}
	dbus_message_iter_close_container(iter, &entry);

	return TRUE;
}

static const GDBusPropertyTable map_properties[] = {
	{ "SupportedTypes", "as", get_supported_types },
	{ }
};

static void map_msg_remove(void *data)
{
	struct map_msg *msg = data;
	char *path;

	path = msg->path;
	msg->path = NULL;
	g_dbus_unregister_interface(conn, path, MAP_MSG_INTERFACE);
	g_free(path);
}

static void map_handle_new_message(struct map_data *map,
							struct map_event *event)
{
	struct map_msg *msg;

	msg = g_hash_table_lookup(map->messages, &event->handle);
	/* New message event can be used if a new message replaces an old one */
	if (msg)
		g_hash_table_remove(map->messages, &event->handle);

	map_msg_create(map, event->handle, event->folder, event->msg_type);
}

static void map_handle_status_changed(struct map_data *map,
							struct map_event *event,
							const char *status)
{
	struct map_msg *msg;

	msg = g_hash_table_lookup(map->messages, &event->handle);
	if (msg == NULL)
		return;

	if (g_strcmp0(msg->status, status) == 0)
		return;

	g_free(msg->status);
	msg->status = g_strdup(status);

	g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE,
								"Status");
}

static void map_handle_folder_changed(struct map_data *map,
							struct map_event *event,
							const char *folder)
{
	struct map_msg *msg;

	if (!folder)
		return;

	msg = g_hash_table_lookup(map->messages, &event->handle);
	if (!msg)
		return;

	if (g_strcmp0(msg->folder, folder) == 0)
		return;

	g_free(msg->folder);
	msg->folder = g_strdup(folder);

	g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE,
								"Folder");
}

static void map_handle_notification(struct map_event *event, void *user_data)
{
	struct map_data *map = user_data;

	DBG("Event report for %s:%d", obc_session_get_destination(map->session),
							map->mas_instance_id);
	DBG("type=%x handle=%" PRIx64 " folder=%s old_folder=%s msg_type=%s",
		event->type, event->handle, event->folder, event->old_folder,
		event->msg_type);

	switch (event->type) {
	case MAP_ET_NEW_MESSAGE:
		map_handle_new_message(map, event);
		map_handle_status_changed(map, event, "notification");
		break;
	case MAP_ET_DELIVERY_SUCCESS:
		map_handle_status_changed(map, event, "delivery-success");
		break;
	case MAP_ET_SENDING_SUCCESS:
		map_handle_status_changed(map, event, "sending-success");
		break;
	case MAP_ET_DELIVERY_FAILURE:
		map_handle_status_changed(map, event, "delivery-failure");
		break;
	case MAP_ET_SENDING_FAILURE:
		map_handle_status_changed(map, event, "sending-failure");
		break;
	case MAP_ET_MESSAGE_DELETED:
		map_handle_folder_changed(map, event, "/telecom/msg/deleted");
		break;
	case MAP_ET_MESSAGE_SHIFT:
		map_handle_folder_changed(map, event, event->folder);
		break;
	case MAP_ET_MEMORY_FULL:
	case MAP_ET_MEMORY_AVAILABLE:
	default:
		break;
	}
}

static bool set_notification_registration(struct map_data *map, bool status)
{
	struct obc_transfer *transfer;
	GError *err = NULL;
	GObexApparam *apparam;
	char contents[1];
	const char *address;

	address = obc_session_get_destination(map->session);
	if (!address || map->mas_instance_id < 0)
		return FALSE;

	if (status) {
		map_register_event_handler(map->session, map->mas_instance_id,
						&map_handle_notification, map);
	} else {
		map_unregister_event_handler(map->session,
							map->mas_instance_id);
	}

	contents[0] = FILLER_BYTE;

	transfer = obc_transfer_put("x-bt/MAP-NotificationRegistration", NULL,
					NULL, contents, sizeof(contents), &err);

	if (transfer == NULL)
		return false;

	apparam = g_obex_apparam_set_uint8(NULL, MAP_AP_NOTIFICATIONSTATUS,
									status);

	obc_transfer_set_apparam(transfer, apparam);

	if (obc_session_queue(map->session, transfer, NULL, map, &err))
		return true;

	return false;
}

static void map_free(void *data)
{
	struct map_data *map = data;

	set_notification_registration(map, false);

	obc_session_unref(map->session);
	g_hash_table_unref(map->messages);
	g_free(map);
}

static void parse_service_record(struct map_data *map)
{
	const void *data;

	/* MAS instance id */
	map->mas_instance_id = -1;
	data = obc_session_get_attribute(map->session,
						SDP_ATTR_MAS_INSTANCE_ID);
	if (data != NULL)
		map->mas_instance_id = *(uint8_t *)data;
	else
		DBG("Failed to read MAS instance id");

	/* Supported Message Types */
	data = obc_session_get_attribute(map->session,
					SDP_ATTR_SUPPORTED_MESSAGE_TYPES);
	if (data != NULL)
		map->supported_message_types = *(uint8_t *)data;
	else
		DBG("Failed to read supported message types");

	/* Supported Feature Bits */
	data = obc_session_get_attribute(map->session,
					SDP_ATTR_MAP_SUPPORTED_FEATURES);
	if(data != NULL)
		map->supported_features = *(uint32_t *) data;
	else
		map->supported_features = 0x0000001f;
}

static void *map_supported_features(struct obc_session *session)
{
	const void *data;
	uint32_t supported_features;

	/* Supported Feature Bits */
	data = obc_session_get_attribute(session,
					SDP_ATTR_MAP_SUPPORTED_FEATURES);
	if (!data)
		return NULL;

	supported_features = *(uint32_t *) data;
	if (!supported_features)
		return NULL;

	if (supported_features & MAPSUPPORTEDFEATURES_IN_CONNECT_REQUEST)
		return g_obex_apparam_set_uint32(NULL, SUPPORTED_FEATURES_TAG,
				NOTIFICATION_REGISTRATION_FEATURE |
				NOTIFICATION_FEATURE |
				BROWSING_FEATURE |
				UPLOADING_FEATURE |
				DELETE_FEATURE |
				INSTANCE_INFORMATION_FEATURE |
				EXTENDED_EVENT_REPORT_1_1 |
				MESSAGES_LISTING_FORMAT_VERSION_1_1);

	return NULL;
}

static int map_probe(struct obc_session *session)
{
	struct map_data *map;
	const char *path;

	path = obc_session_get_path(session);

	map = g_try_new0(struct map_data, 1);
	if (!map)
		return -ENOMEM;

	map->session = obc_session_ref(session);
	map->messages = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL,
								map_msg_remove);

	parse_service_record(map);

	DBG("%s, instance id %d", path, map->mas_instance_id);

	set_notification_registration(map, true);

	if (!g_dbus_register_interface(conn, path, MAP_INTERFACE, map_methods,
					NULL, map_properties, map, map_free)) {
		map_free(map);

		return -ENOMEM;
	}

	return 0;
}

static void map_remove(struct obc_session *session)
{
	const char *path = obc_session_get_path(session);

	DBG("%s", path);

	g_dbus_unregister_interface(conn, path, MAP_INTERFACE);
}

static struct obc_driver map = {
	.service = "MAP",
	.uuid = MAS_UUID,
	.target = OBEX_MAS_UUID,
	.target_len = OBEX_MAS_UUID_LEN,
	.supported_features = map_supported_features,
	.probe = map_probe,
	.remove = map_remove
};

int map_init(void)
{
	int err;

	DBG("");

	conn = obex_get_dbus_connection();
	if (!conn)
		return -EIO;

	err = obc_driver_register(&map);
	if (err < 0) {
		dbus_connection_unref(conn);
		conn = NULL;
		return err;
	}

	return 0;
}

void map_exit(void)
{
	DBG("");

	dbus_connection_unref(conn);
	conn = NULL;

	obc_driver_unregister(&map);
}
