/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2008-2010  Nokia Corporation
 *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
 *
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

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

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus.h>

#include "log.h"
#include "telephony.h"
#include "error.h"

/* SSC D-Bus definitions */
#define SSC_DBUS_NAME  "com.nokia.phone.SSC"
#define SSC_DBUS_IFACE "com.nokia.phone.SSC"
#define SSC_DBUS_PATH  "/com/nokia/phone/SSC"

/* libcsnet D-Bus definitions */
#define CSD_CSNET_BUS_NAME	"com.nokia.csd.CSNet"
#define CSD_CSNET_PATH		"/com/nokia/csd/csnet"
#define CSD_CSNET_IFACE		"com.nokia.csd.CSNet"
#define CSD_CSNET_REGISTRATION	"com.nokia.csd.CSNet.NetworkRegistration"
#define CSD_CSNET_OPERATOR	"com.nokia.csd.CSNet.NetworkOperator"
#define CSD_CSNET_SIGNAL	"com.nokia.csd.CSNet.SignalStrength"

enum net_registration_status {
	NETWORK_REG_STATUS_HOME,
	NETWORK_REG_STATUS_ROAMING,
	NETWORK_REG_STATUS_OFFLINE,
	NETWORK_REG_STATUS_SEARCHING,
	NETWORK_REG_STATUS_NO_SIM,
	NETWORK_REG_STATUS_POWEROFF,
	NETWORK_REG_STATUS_POWERSAFE,
	NETWORK_REG_STATUS_NO_COVERAGE,
	NETWORK_REG_STATUS_REJECTED,
	NETWORK_REG_STATUS_UNKOWN
};

/* CSD CALL plugin D-Bus definitions */
#define CSD_CALL_BUS_NAME	"com.nokia.csd.Call"
#define CSD_CALL_INTERFACE	"com.nokia.csd.Call"
#define CSD_CALL_INSTANCE	"com.nokia.csd.Call.Instance"
#define CSD_CALL_CONFERENCE	"com.nokia.csd.Call.Conference"
#define CSD_CALL_PATH		"/com/nokia/csd/call"
#define CSD_CALL_CONFERENCE_PATH "/com/nokia/csd/call/conference"

/* Call status values as exported by the CSD CALL plugin */
#define CSD_CALL_STATUS_IDLE			0
#define CSD_CALL_STATUS_CREATE			1
#define CSD_CALL_STATUS_COMING			2
#define CSD_CALL_STATUS_PROCEEDING		3
#define CSD_CALL_STATUS_MO_ALERTING		4
#define CSD_CALL_STATUS_MT_ALERTING		5
#define CSD_CALL_STATUS_WAITING			6
#define CSD_CALL_STATUS_ANSWERED		7
#define CSD_CALL_STATUS_ACTIVE			8
#define CSD_CALL_STATUS_MO_RELEASE		9
#define CSD_CALL_STATUS_MT_RELEASE		10
#define CSD_CALL_STATUS_HOLD_INITIATED		11
#define CSD_CALL_STATUS_HOLD			12
#define CSD_CALL_STATUS_RETRIEVE_INITIATED	13
#define CSD_CALL_STATUS_RECONNECT_PENDING	14
#define CSD_CALL_STATUS_TERMINATED		15
#define CSD_CALL_STATUS_SWAP_INITIATED		16

#define CALL_FLAG_NONE				0
#define CALL_FLAG_PRESENTATION_ALLOWED		0x01
#define CALL_FLAG_PRESENTATION_RESTRICTED	0x02

/* SIM Phonebook D-Bus definitions */
#define CSD_SIMPB_BUS_NAME			"com.nokia.csd.SIM"
#define CSD_SIMPB_INTERFACE			"com.nokia.csd.SIM.Phonebook"
#define CSD_SIMPB_PATH				"/com/nokia/csd/sim/phonebook"

#define CSD_SIMPB_TYPE_ADN			"ADN"
#define CSD_SIMPB_TYPE_FDN			"FDN"
#define CSD_SIMPB_TYPE_SDN			"SDN"
#define CSD_SIMPB_TYPE_VMBX			"VMBX"
#define CSD_SIMPB_TYPE_MBDN			"MBDN"
#define CSD_SIMPB_TYPE_EN			"EN"
#define CSD_SIMPB_TYPE_MSISDN			"MSISDN"

struct csd_call {
	char *object_path;
	int status;
	gboolean originating;
	gboolean emergency;
	gboolean on_hold;
	gboolean conference;
	char *number;
	gboolean setup;
};

static struct {
	char *operator_name;
	uint8_t status;
	int32_t signal_bars;
} net = {
	.operator_name = NULL,
	.status = NETWORK_REG_STATUS_UNKOWN,
	/* Init as 0 meaning inactive mode. In modem power off state
	 * can be be -1, but we treat all values as 0s regardless
	 * inactive or power off. */
	.signal_bars = 0,
};

struct pending_req {
	DBusPendingCall *call;
	void *user_data;
};

static int get_property(const char *iface, const char *prop);

static DBusConnection *connection = NULL;

static GSList *calls = NULL;
static GSList *watches = NULL;
static GSList *pending = NULL;

/* Reference count for determining the call indicator status */
static GSList *active_calls = NULL;

static char *msisdn = NULL;	/* Subscriber number */
static char *vmbx = NULL;	/* Voice mailbox number */

/* HAL battery namespace key values */
static int battchg_cur = -1;	/* "battery.charge_level.current" */
static int battchg_last = -1;	/* "battery.charge_level.last_full" */
static int battchg_design = -1;	/* "battery.charge_level.design" */

static gboolean get_calls_active = FALSE;

static gboolean events_enabled = FALSE;

/* Supported set of call hold operations */
static const char *chld_str = "0,1,1x,2,2x,3,4";

/* Timer for tracking call creation requests */
static guint create_request_timer = 0;

static struct indicator maemo_indicators[] =
{
	{ "battchg",	"0-5",	5,	TRUE },
	/* signal strength in terms of bars */
	{ "signal",	"0-5",	0,	TRUE },
	{ "service",	"0,1",	0,	TRUE },
	{ "call",	"0,1",	0,	TRUE },
	{ "callsetup",	"0-3",	0,	TRUE },
	{ "callheld",	"0-2",	0,	FALSE },
	{ "roam",	"0,1",	0,	TRUE },
	{ NULL }
};

static char *call_status_str[] = {
	"IDLE",
	"CREATE",
	"COMING",
	"PROCEEDING",
	"MO_ALERTING",
	"MT_ALERTING",
	"WAITING",
	"ANSWERED",
	"ACTIVE",
	"MO_RELEASE",
	"MT_RELEASE",
	"HOLD_INITIATED",
	"HOLD",
	"RETRIEVE_INITIATED",
	"RECONNECT_PENDING",
	"TERMINATED",
	"SWAP_INITIATED",
	"???"
};

static struct csd_call *find_call(const char *path)
{
	GSList *l;

	for (l = calls; l != NULL; l = l->next) {
		struct csd_call *call = l->data;

		if (g_str_equal(call->object_path, path))
			return call;
	}

	return NULL;
}

static struct csd_call *find_non_held_call(void)
{
	GSList *l;

	for (l = calls; l != NULL; l = l->next) {
		struct csd_call *call = l->data;

		if (call->status == CSD_CALL_STATUS_IDLE)
			continue;

		if (call->status != CSD_CALL_STATUS_HOLD)
			return call;
	}

	return NULL;
}

static struct csd_call *find_non_idle_call(void)
{
	GSList *l;

	for (l = calls; l != NULL; l = l->next) {
		struct csd_call *call = l->data;

		if (call->status != CSD_CALL_STATUS_IDLE)
			return call;
	}

	return NULL;
}

static struct csd_call *find_call_with_status(int status)
{
	GSList *l;

	for (l = calls; l != NULL; l = l->next) {
		struct csd_call *call = l->data;

		if (call->status == status)
			return call;
	}

	return NULL;
}

static int release_conference(void)
{
	DBusMessage *msg;

	DBG("telephony-maemo6: releasing conference call");

	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
						CSD_CALL_CONFERENCE_PATH,
						CSD_CALL_INSTANCE,
						"Release");
	if (!msg) {
		error("Unable to allocate new D-Bus message");
		return -ENOMEM;
	}

	g_dbus_send_message(connection, msg);

	return 0;
}

static int release_call(struct csd_call *call)
{
	DBusMessage *msg;

	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
						call->object_path,
						CSD_CALL_INSTANCE,
						"Release");
	if (!msg) {
		error("Unable to allocate new D-Bus message");
		return -ENOMEM;
	}

	g_dbus_send_message(connection, msg);

	return 0;
}

static int answer_call(struct csd_call *call)
{
	DBusMessage *msg;

	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
						call->object_path,
						CSD_CALL_INSTANCE,
						"Answer");
	if (!msg) {
		error("Unable to allocate new D-Bus message");
		return -ENOMEM;
	}

	g_dbus_send_message(connection, msg);

	return 0;
}

static int split_call(struct csd_call *call)
{
	DBusMessage *msg;

	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
						call->object_path,
						CSD_CALL_INSTANCE,
						"Split");
	if (!msg) {
		error("Unable to allocate new D-Bus message");
		return -ENOMEM;
	}

	g_dbus_send_message(connection, msg);

	return 0;
}

static int unhold_call(struct csd_call *call)
{
	DBusMessage *msg;

	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
						CSD_CALL_INTERFACE,
						"Unhold");
	if (!msg) {
		error("Unable to allocate new D-Bus message");
		return -ENOMEM;
	}

	g_dbus_send_message(connection, msg);

	return 0;
}

static int hold_call(struct csd_call *call)
{
	DBusMessage *msg;

	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
						CSD_CALL_INTERFACE,
						"Hold");
	if (!msg) {
		error("Unable to allocate new D-Bus message");
		return -ENOMEM;
	}

	g_dbus_send_message(connection, msg);

	return 0;
}

static int swap_calls(void)
{
	DBusMessage *msg;

	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
						CSD_CALL_INTERFACE,
						"Swap");
	if (!msg) {
		error("Unable to allocate new D-Bus message");
		return -ENOMEM;
	}

	g_dbus_send_message(connection, msg);

	return 0;
}

static int create_conference(void)
{
	DBusMessage *msg;

	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
						CSD_CALL_INTERFACE,
						"Conference");
	if (!msg) {
		error("Unable to allocate new D-Bus message");
		return -ENOMEM;
	}

	g_dbus_send_message(connection, msg);

	return 0;
}

static int call_transfer(void)
{
	DBusMessage *msg;

	msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
						CSD_CALL_INTERFACE,
						"Transfer");
	if (!msg) {
		error("Unable to allocate new D-Bus message");
		return -ENOMEM;
	}

	g_dbus_send_message(connection, msg);

	return 0;
}

static int number_type(const char *number)
{
	if (number == NULL)
		return NUMBER_TYPE_TELEPHONY;

	if (number[0] == '+' || strncmp(number, "00", 2) == 0)
		return NUMBER_TYPE_INTERNATIONAL;

	return NUMBER_TYPE_TELEPHONY;
}

void telephony_device_connected(void *telephony_device)
{
	struct csd_call *coming;

	DBG("telephony-maemo6: device %p connected", telephony_device);

	coming = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING);
	if (coming) {
		if (find_call_with_status(CSD_CALL_STATUS_ACTIVE))
			telephony_call_waiting_ind(coming->number,
						number_type(coming->number));
		else
			telephony_incoming_call_ind(coming->number,
						number_type(coming->number));
	}
}

static void pending_req_finalize(struct pending_req *req)
{
	if (!dbus_pending_call_get_completed(req->call))
		dbus_pending_call_cancel(req->call);

	dbus_pending_call_unref(req->call);
	g_free(req);
}

static void remove_pending_by_data(gpointer data, gpointer user_data)
{
	struct pending_req *req = data;

	if (req->user_data == user_data) {
		pending = g_slist_remove(pending, req);
		pending_req_finalize(req);
	}
}

void telephony_device_disconnected(void *telephony_device)
{
	DBG("telephony-maemo6: device %p disconnected", telephony_device);
	events_enabled = FALSE;

	g_slist_foreach(pending, remove_pending_by_data, telephony_device);
}

void telephony_event_reporting_req(void *telephony_device, int ind)
{
	events_enabled = ind == 1 ? TRUE : FALSE;

	telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE);
}

void telephony_response_and_hold_req(void *telephony_device, int rh)
{
	telephony_response_and_hold_rsp(telephony_device,
						CME_ERROR_NOT_SUPPORTED);
}

void telephony_terminate_call_req(void *telephony_device)
{
	struct csd_call *call;
	struct csd_call *alerting;
	int err;

	call = find_call_with_status(CSD_CALL_STATUS_ACTIVE);
	if (!call)
		call = find_non_idle_call();

	if (!call) {
		error("No active call");
		telephony_terminate_call_rsp(telephony_device,
						CME_ERROR_NOT_ALLOWED);
		return;
	}

	alerting = find_call_with_status(CSD_CALL_STATUS_MO_ALERTING);
	if (call->on_hold && alerting)
		err = release_call(alerting);
	else if (call->conference)
		err = release_conference();
	else
		err = release_call(call);

	if (err < 0)
		telephony_terminate_call_rsp(telephony_device,
						CME_ERROR_AG_FAILURE);
	else
		telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE);
}

void telephony_answer_call_req(void *telephony_device)
{
	struct csd_call *call;

	call = find_call_with_status(CSD_CALL_STATUS_COMING);
	if (!call)
		call = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING);

	if (!call)
		call = find_call_with_status(CSD_CALL_STATUS_PROCEEDING);

	if (!call)
		call = find_call_with_status(CSD_CALL_STATUS_WAITING);

	if (!call) {
		telephony_answer_call_rsp(telephony_device,
						CME_ERROR_NOT_ALLOWED);
		return;
	}

	if (answer_call(call) < 0)
		telephony_answer_call_rsp(telephony_device,
						CME_ERROR_AG_FAILURE);
	else
		telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
}

static int send_method_call(const char *dest, const char *path,
				const char *interface, const char *method,
				DBusPendingCallNotifyFunction cb,
				void *user_data, int type, ...)
{
	DBusMessage *msg;
	DBusPendingCall *call;
	va_list args;
	struct pending_req *req;

	msg = dbus_message_new_method_call(dest, path, interface, method);
	if (!msg) {
		error("Unable to allocate new D-Bus %s message", method);
		return -ENOMEM;
	}

	va_start(args, type);

	if (!dbus_message_append_args_valist(msg, type, args)) {
		dbus_message_unref(msg);
		va_end(args);
		return -EIO;
	}

	va_end(args);

	if (!cb) {
		g_dbus_send_message(connection, msg);
		return 0;
	}

	if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) {
		error("Sending %s failed", method);
		dbus_message_unref(msg);
		return -EIO;
	}

	dbus_pending_call_set_notify(call, cb, user_data, NULL);

	req = g_new0(struct pending_req, 1);
	req->call = call;
	req->user_data = user_data;

	pending = g_slist_prepend(pending, req);
	dbus_message_unref(msg);

	return 0;
}

static struct pending_req *find_request(const DBusPendingCall *call)
{
	GSList *l;

	for (l = pending; l; l = l->next) {
		struct pending_req *req = l->data;

		if (req->call == call)
			return req;
	}

	return NULL;
}

static void remove_pending(DBusPendingCall *call)
{
	struct pending_req *req = find_request(call);

	pending = g_slist_remove(pending, req);
	pending_req_finalize(req);
}

static void create_call_reply(DBusPendingCall *call, void *user_data)
{
	DBusError err;
	DBusMessage *reply;
	void *telephony_device = user_data;

	reply = dbus_pending_call_steal_reply(call);

	dbus_error_init(&err);
	if (dbus_set_error_from_message(&err, reply)) {
		error("csd replied with an error: %s, %s",
				err.name, err.message);
		if (g_strcmp0(err.name,
				"com.nokia.csd.Call.Error.CSInactive") == 0)
			telephony_dial_number_rsp(telephony_device,
						CME_ERROR_NO_NETWORK_SERVICE);
		else
			telephony_dial_number_rsp(telephony_device,
							CME_ERROR_AG_FAILURE);
		dbus_error_free(&err);
	} else
		telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);

	dbus_message_unref(reply);
	remove_pending(call);
}

void telephony_last_dialed_number_req(void *telephony_device)
{
	int ret;

	DBG("telephony-maemo6: last dialed number request");

	ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
				CSD_CALL_INTERFACE, "CreateFromLast",
				create_call_reply, telephony_device,
				DBUS_TYPE_INVALID);
	if (ret < 0)
		telephony_dial_number_rsp(telephony_device,
						CME_ERROR_AG_FAILURE);
}

static const char *memory_dial_lookup(int location)
{
	if (location == 1)
		return vmbx;
	else
		return NULL;
}

void telephony_dial_number_req(void *telephony_device, const char *number)
{
	int ret;

	DBG("telephony-maemo6: dial request to %s", number);

	if (strncmp(number, "*31#", 4) == 0)
		number += 4;
	else if (strncmp(number, "#31#", 4) == 0)
		number += 4;
	else if (number[0] == '>') {
		const char *location = &number[1];

		number = memory_dial_lookup(strtol(&number[1], NULL, 0));
		if (!number) {
			error("No number at memory location %s", location);
			telephony_dial_number_rsp(telephony_device,
						CME_ERROR_INVALID_INDEX);
			return;
		}
	}

	ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
				CSD_CALL_INTERFACE, "Create",
				create_call_reply, telephony_device,
				DBUS_TYPE_STRING, &number,
				DBUS_TYPE_INVALID);
	if (ret < 0)
		telephony_dial_number_rsp(telephony_device,
						CME_ERROR_AG_FAILURE);
}

void telephony_transmit_dtmf_req(void *telephony_device, char tone)
{
	int ret;
	char buf[2] = { tone, '\0' }, *buf_ptr = buf;

	DBG("telephony-maemo6: transmit dtmf: %s", buf);

	ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
				CSD_CALL_INTERFACE, "SendDTMF",
				NULL, NULL,
				DBUS_TYPE_STRING, &buf_ptr,
				DBUS_TYPE_INVALID);
	if (ret < 0) {
		telephony_transmit_dtmf_rsp(telephony_device,
						CME_ERROR_AG_FAILURE);
		return;
	}

	telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE);
}

void telephony_subscriber_number_req(void *telephony_device)
{
	DBG("telephony-maemo6: subscriber number request");
	if (msisdn)
		telephony_subscriber_number_ind(msisdn,
						number_type(msisdn),
						SUBSCRIBER_SERVICE_VOICE);
	telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE);
}

static int csd_status_to_hfp(struct csd_call *call)
{
	switch (call->status) {
	case CSD_CALL_STATUS_IDLE:
	case CSD_CALL_STATUS_MO_RELEASE:
	case CSD_CALL_STATUS_MT_RELEASE:
	case CSD_CALL_STATUS_TERMINATED:
		return -1;
	case CSD_CALL_STATUS_CREATE:
		return CALL_STATUS_DIALING;
	case CSD_CALL_STATUS_WAITING:
		return CALL_STATUS_WAITING;
	case CSD_CALL_STATUS_PROCEEDING:
		/* PROCEEDING can happen in outgoing/incoming */
		if (call->originating)
			return CALL_STATUS_DIALING;
		else
			return CALL_STATUS_INCOMING;
	case CSD_CALL_STATUS_COMING:
		return CALL_STATUS_INCOMING;
	case CSD_CALL_STATUS_MO_ALERTING:
		return CALL_STATUS_ALERTING;
	case CSD_CALL_STATUS_MT_ALERTING:
		return CALL_STATUS_INCOMING;
	case CSD_CALL_STATUS_ANSWERED:
	case CSD_CALL_STATUS_ACTIVE:
	case CSD_CALL_STATUS_RECONNECT_PENDING:
	case CSD_CALL_STATUS_SWAP_INITIATED:
	case CSD_CALL_STATUS_HOLD_INITIATED:
		return CALL_STATUS_ACTIVE;
	case CSD_CALL_STATUS_RETRIEVE_INITIATED:
	case CSD_CALL_STATUS_HOLD:
		return CALL_STATUS_HELD;
	default:
		return -1;
	}
}

void telephony_list_current_calls_req(void *telephony_device)
{
	GSList *l;
	int i;

	DBG("telephony-maemo6: list current calls request");

	for (l = calls, i = 1; l != NULL; l = l->next, i++) {
		struct csd_call *call = l->data;
		int status, direction, multiparty;

		status = csd_status_to_hfp(call);
		if (status < 0)
			continue;

		direction = call->originating ?
				CALL_DIR_OUTGOING : CALL_DIR_INCOMING;

		multiparty = call->conference ?
				CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO;

		telephony_list_current_call_ind(i, direction, status,
						CALL_MODE_VOICE, multiparty,
						call->number,
						number_type(call->number));
	}

	telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
}

void telephony_operator_selection_req(void *telephony_device)
{
	telephony_operator_selection_ind(OPERATOR_MODE_AUTO,
				net.operator_name ? net.operator_name : "");
	telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE);
}

static void foreach_call_with_status(int status,
					int (*func)(struct csd_call *call))
{
	GSList *l;

	for (l = calls; l != NULL; l = l->next) {
		struct csd_call *call = l->data;

		if (call->status == status)
			func(call);
	}
}

void telephony_call_hold_req(void *telephony_device, const char *cmd)
{
	const char *idx;
	struct csd_call *call;
	int err = 0;

	DBG("telephony-maemo6: got call hold request %s", cmd);

	if (strlen(cmd) > 1)
		idx = &cmd[1];
	else
		idx = NULL;

	if (idx)
		call = g_slist_nth_data(calls, strtol(idx, NULL, 0) - 1);
	else
		call = NULL;

	switch (cmd[0]) {
	case '0':
		if (find_call_with_status(CSD_CALL_STATUS_WAITING))
			foreach_call_with_status(CSD_CALL_STATUS_WAITING,
								release_call);
		else
			foreach_call_with_status(CSD_CALL_STATUS_HOLD,
								release_call);
		break;
	case '1':
		if (idx) {
			if (call)
				err = release_call(call);
			break;
		}
		foreach_call_with_status(CSD_CALL_STATUS_ACTIVE, release_call);
		call = find_call_with_status(CSD_CALL_STATUS_WAITING);
		if (call)
			err = answer_call(call);
		break;
	case '2':
		if (idx) {
			if (call)
				err = split_call(call);
		} else {
			struct csd_call *held, *wait;

			call = find_call_with_status(CSD_CALL_STATUS_ACTIVE);
			held = find_call_with_status(CSD_CALL_STATUS_HOLD);
			wait = find_call_with_status(CSD_CALL_STATUS_WAITING);

			if (wait)
				err = answer_call(wait);
			else if (call && held)
				err = swap_calls();
			else {
				if (call)
					err = hold_call(call);
				if (held)
					err = unhold_call(held);
			}
		}
		break;
	case '3':
		if (find_call_with_status(CSD_CALL_STATUS_HOLD) ||
				find_call_with_status(CSD_CALL_STATUS_WAITING))
			err = create_conference();
		break;
	case '4':
		err = call_transfer();
		break;
	default:
		DBG("Unknown call hold request");
		break;
	}

	if (err)
		telephony_call_hold_rsp(telephony_device,
					CME_ERROR_AG_FAILURE);
	else
		telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE);
}

void telephony_nr_and_ec_req(void *telephony_device, gboolean enable)
{
	DBG("telephony-maemo6: got %s NR and EC request",
			enable ? "enable" : "disable");
	telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE);
}

void telephony_key_press_req(void *telephony_device, const char *keys)
{
	struct csd_call *active, *waiting;
	int err;

	DBG("telephony-maemo6: got key press request for %s", keys);

	waiting = find_call_with_status(CSD_CALL_STATUS_COMING);
	if (!waiting)
		waiting = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING);
	if (!waiting)
		waiting = find_call_with_status(CSD_CALL_STATUS_PROCEEDING);

	active = find_call_with_status(CSD_CALL_STATUS_ACTIVE);

	if (waiting)
		err = answer_call(waiting);
	else if (active)
		err = release_call(active);
	else
		err = 0;

	if (err < 0)
		telephony_key_press_rsp(telephony_device,
							CME_ERROR_AG_FAILURE);
	else
		telephony_key_press_rsp(telephony_device, CME_ERROR_NONE);
}

void telephony_voice_dial_req(void *telephony_device, gboolean enable)
{
	DBG("telephony-maemo6: got %s voice dial request",
			enable ? "enable" : "disable");

	telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED);
}

static void handle_incoming_call(DBusMessage *msg)
{
	const char *number, *call_path;
	struct csd_call *call;

	if (!dbus_message_get_args(msg, NULL,
					DBUS_TYPE_OBJECT_PATH, &call_path,
					DBUS_TYPE_STRING, &number,
					DBUS_TYPE_INVALID)) {
		error("Unexpected parameters in Call.Coming() signal");
		return;
	}

	call = find_call(call_path);
	if (!call) {
		error("Didn't find any matching call object for %s",
				call_path);
		return;
	}

	DBG("Incoming call to %s from number %s", call_path, number);

	g_free(call->number);
	call->number = g_strdup(number);

	if (find_call_with_status(CSD_CALL_STATUS_ACTIVE) ||
			find_call_with_status(CSD_CALL_STATUS_HOLD))
		telephony_call_waiting_ind(call->number,
						number_type(call->number));
	else
		telephony_incoming_call_ind(call->number,
						number_type(call->number));

	telephony_update_indicator(maemo_indicators, "callsetup",
					EV_CALLSETUP_INCOMING);
}

static void handle_outgoing_call(DBusMessage *msg)
{
	const char *number, *call_path;
	struct csd_call *call;

	if (!dbus_message_get_args(msg, NULL,
					DBUS_TYPE_OBJECT_PATH, &call_path,
					DBUS_TYPE_STRING, &number,
					DBUS_TYPE_INVALID)) {
		error("Unexpected parameters in Call.Created() signal");
		return;
	}

	call = find_call(call_path);
	if (!call) {
		error("Didn't find any matching call object for %s",
				call_path);
		return;
	}

	DBG("Outgoing call from %s to number %s", call_path, number);

	g_free(call->number);
	call->number = g_strdup(number);

	if (create_request_timer) {
		g_source_remove(create_request_timer);
		create_request_timer = 0;
	}
}

static gboolean create_timeout(gpointer user_data)
{
	telephony_update_indicator(maemo_indicators, "callsetup",
					EV_CALLSETUP_INACTIVE);
	create_request_timer = 0;
	return FALSE;
}

static void handle_create_requested(DBusMessage *msg)
{
	DBG("Call.CreateRequested()");

	if (create_request_timer)
		g_source_remove(create_request_timer);

	create_request_timer = g_timeout_add_seconds(5, create_timeout, NULL);

	telephony_update_indicator(maemo_indicators, "callsetup",
					EV_CALLSETUP_OUTGOING);
}

static void call_set_status(struct csd_call *call, dbus_uint32_t status)
{
	dbus_uint32_t prev_status;
	int callheld = telephony_get_indicator(maemo_indicators, "callheld");

	prev_status = call->status;
	DBG("Call %s changed from %s to %s", call->object_path,
		call_status_str[prev_status], call_status_str[status]);

	if (prev_status == status) {
		DBG("Ignoring CSD Call state change to existing state");
		return;
	}

	call->status = (int) status;

	switch (status) {
	case CSD_CALL_STATUS_IDLE:
		if (call->setup) {
			telephony_update_indicator(maemo_indicators,
							"callsetup",
							EV_CALLSETUP_INACTIVE);
			if (!call->originating)
				telephony_calling_stopped_ind();
		}

		g_free(call->number);
		call->number = NULL;
		call->originating = FALSE;
		call->emergency = FALSE;
		call->on_hold = FALSE;
		call->conference = FALSE;
		call->setup = FALSE;
		break;
	case CSD_CALL_STATUS_CREATE:
		call->originating = TRUE;
		call->setup = TRUE;
		break;
	case CSD_CALL_STATUS_COMING:
		call->originating = FALSE;
		call->setup = TRUE;
		break;
	case CSD_CALL_STATUS_PROCEEDING:
		break;
	case CSD_CALL_STATUS_MO_ALERTING:
		telephony_update_indicator(maemo_indicators, "callsetup",
						EV_CALLSETUP_ALERTING);
		break;
	case CSD_CALL_STATUS_MT_ALERTING:
		/* Some headsets expect incoming call notification before they
		 * can send ATA command. When call changed status from waiting
		 * to alerting we need to send missing notification. Otherwise
		 * headsets like Nokia BH-108 or BackBeat 903 are unable to
		 * answer incoming call that was previously waiting. */
		if (prev_status == CSD_CALL_STATUS_WAITING)
			telephony_incoming_call_ind(call->number,
						number_type(call->number));
		break;
	case CSD_CALL_STATUS_WAITING:
		break;
	case CSD_CALL_STATUS_ANSWERED:
		break;
	case CSD_CALL_STATUS_ACTIVE:
		if (call->on_hold) {
			call->on_hold = FALSE;
			if (find_call_with_status(CSD_CALL_STATUS_HOLD))
				telephony_update_indicator(maemo_indicators,
							"callheld",
							EV_CALLHELD_MULTIPLE);
			else
				telephony_update_indicator(maemo_indicators,
							"callheld",
							EV_CALLHELD_NONE);
		} else {
			if (!g_slist_find(active_calls, call))
				active_calls = g_slist_prepend(active_calls, call);
			if (g_slist_length(active_calls) == 1)
				telephony_update_indicator(maemo_indicators,
								"call",
								EV_CALL_ACTIVE);
			/* Upgrade callheld status if necessary */
			if (callheld == EV_CALLHELD_ON_HOLD)
				telephony_update_indicator(maemo_indicators,
							"callheld",
							EV_CALLHELD_MULTIPLE);
			telephony_update_indicator(maemo_indicators,
							"callsetup",
							EV_CALLSETUP_INACTIVE);
			if (!call->originating)
				telephony_calling_stopped_ind();
			call->setup = FALSE;
		}
		break;
	case CSD_CALL_STATUS_MO_RELEASE:
	case CSD_CALL_STATUS_MT_RELEASE:
		active_calls = g_slist_remove(active_calls, call);
		if (g_slist_length(active_calls) == 0)
			telephony_update_indicator(maemo_indicators, "call",
							EV_CALL_INACTIVE);
		break;
	case CSD_CALL_STATUS_HOLD_INITIATED:
		break;
	case CSD_CALL_STATUS_HOLD:
		call->on_hold = TRUE;
		if (find_non_held_call())
			telephony_update_indicator(maemo_indicators,
							"callheld",
							EV_CALLHELD_MULTIPLE);
		else
			telephony_update_indicator(maemo_indicators,
							"callheld",
							EV_CALLHELD_ON_HOLD);
		break;
	case CSD_CALL_STATUS_RETRIEVE_INITIATED:
		break;
	case CSD_CALL_STATUS_RECONNECT_PENDING:
		break;
	case CSD_CALL_STATUS_TERMINATED:
		if (call->on_hold &&
				!find_call_with_status(CSD_CALL_STATUS_HOLD))
			telephony_update_indicator(maemo_indicators,
							"callheld",
							EV_CALLHELD_NONE);
		else if (callheld == EV_CALLHELD_MULTIPLE &&
				find_call_with_status(CSD_CALL_STATUS_HOLD))
			telephony_update_indicator(maemo_indicators,
							"callheld",
							EV_CALLHELD_ON_HOLD);
		break;
	case CSD_CALL_STATUS_SWAP_INITIATED:
		break;
	default:
		error("Unknown call status %u", status);
		break;
	}
}

static void handle_call_status(DBusMessage *msg, const char *call_path)
{
	struct csd_call *call;
	dbus_uint32_t status, cause_type, cause;

	if (!dbus_message_get_args(msg, NULL,
					DBUS_TYPE_UINT32, &status,
					DBUS_TYPE_UINT32, &cause_type,
					DBUS_TYPE_UINT32, &cause,
					DBUS_TYPE_INVALID)) {
		error("Unexpected paramters in Instance.CallStatus() signal");
		return;
	}

	call = find_call(call_path);
	if (!call) {
		error("Didn't find any matching call object for %s",
				call_path);
		return;
	}

	if (status > 16) {
		error("Invalid call status %u", status);
		return;
	}

	call_set_status(call, status);
}

static void handle_conference(DBusMessage *msg, gboolean joined)
{
	const char *path;
	struct csd_call *call;

	if (!dbus_message_get_args(msg, NULL,
					DBUS_TYPE_OBJECT_PATH, &path,
					DBUS_TYPE_INVALID)) {
		error("Unexpected parameters in Conference.%s",
					dbus_message_get_member(msg));
		return;
	}

	call = find_call(path);
	if (!call) {
		error("Conference signal for unknown call %s", path);
		return;
	}

	DBG("Call %s %s the conference", path, joined ? "joined" : "left");

	call->conference = joined;
}

static uint8_t str2status(const char *state)
{
	if (g_strcmp0(state, "Home") == 0)
		return NETWORK_REG_STATUS_HOME;
	else if (g_strcmp0(state, "Roaming") == 0)
		return NETWORK_REG_STATUS_ROAMING;
	else if (g_strcmp0(state, "Offline") == 0)
		return NETWORK_REG_STATUS_OFFLINE;
	else if (g_strcmp0(state, "Searching") == 0)
		return NETWORK_REG_STATUS_SEARCHING;
	else if (g_strcmp0(state, "NoSim") == 0)
		return NETWORK_REG_STATUS_NO_SIM;
	else if (g_strcmp0(state, "Poweroff") == 0)
		return NETWORK_REG_STATUS_POWEROFF;
	else if (g_strcmp0(state, "Powersafe") == 0)
		return NETWORK_REG_STATUS_POWERSAFE;
	else if (g_strcmp0(state, "NoCoverage") == 0)
		return NETWORK_REG_STATUS_NO_COVERAGE;
	else if (g_strcmp0(state, "Reject") == 0)
		return NETWORK_REG_STATUS_REJECTED;
	else
		return NETWORK_REG_STATUS_UNKOWN;
}

static void update_registration_status(const char *status)
{
	uint8_t new_status;

	new_status = str2status(status);

	if (net.status == new_status)
		return;

	switch (new_status) {
	case NETWORK_REG_STATUS_HOME:
		telephony_update_indicator(maemo_indicators, "roam",
							EV_ROAM_INACTIVE);
		if (net.status > NETWORK_REG_STATUS_ROAMING)
			telephony_update_indicator(maemo_indicators,
							"service",
							EV_SERVICE_PRESENT);
		break;
	case NETWORK_REG_STATUS_ROAMING:
		telephony_update_indicator(maemo_indicators, "roam",
							EV_ROAM_ACTIVE);
		if (net.status > NETWORK_REG_STATUS_ROAMING)
			telephony_update_indicator(maemo_indicators,
							"service",
							EV_SERVICE_PRESENT);
		break;
	case NETWORK_REG_STATUS_OFFLINE:
	case NETWORK_REG_STATUS_SEARCHING:
	case NETWORK_REG_STATUS_NO_SIM:
	case NETWORK_REG_STATUS_POWEROFF:
	case NETWORK_REG_STATUS_POWERSAFE:
	case NETWORK_REG_STATUS_NO_COVERAGE:
	case NETWORK_REG_STATUS_REJECTED:
	case NETWORK_REG_STATUS_UNKOWN:
		if (net.status < NETWORK_REG_STATUS_OFFLINE)
			telephony_update_indicator(maemo_indicators,
							"service",
							EV_SERVICE_NONE);
		break;
	}

	net.status = new_status;

	DBG("telephony-maemo6: registration status changed: %s", status);
}

static void handle_registration_changed(DBusMessage *msg)
{
	const char *status;

	if (!dbus_message_get_args(msg, NULL,
					DBUS_TYPE_STRING, &status,
					DBUS_TYPE_INVALID)) {
		error("Unexpected parameters in RegistrationChanged");
		return;
	}

	update_registration_status(status);
}

static void update_signal_strength(int32_t signal_bars)
{
	if (signal_bars < 0) {
		DBG("signal strength smaller than expected: %d < 0",
								signal_bars);
		signal_bars = 0;
	} else if (signal_bars > 5) {
		DBG("signal strength greater than expected: %d > 5",
								signal_bars);
		signal_bars = 5;
	}

	if (net.signal_bars == signal_bars)
		return;

	telephony_update_indicator(maemo_indicators, "signal", signal_bars);

	net.signal_bars = signal_bars;
	DBG("telephony-maemo6: signal strength updated: %d/5", signal_bars);
}

static void handle_signal_bars_changed(DBusMessage *msg)
{
	int32_t signal_bars;

	if (!dbus_message_get_args(msg, NULL,
					DBUS_TYPE_INT32, &signal_bars,
					DBUS_TYPE_INVALID)) {
		error("Unexpected parameters in SignalBarsChanged");
		return;
	}

	update_signal_strength(signal_bars);
}

static gboolean iter_get_basic_args(DBusMessageIter *iter,
					int first_arg_type, ...)
{
	int type;
	va_list ap;

	va_start(ap, first_arg_type);

	for (type = first_arg_type; type != DBUS_TYPE_INVALID;
			type = va_arg(ap, int)) {
		void *value = va_arg(ap, void *);
		int real_type = dbus_message_iter_get_arg_type(iter);

		if (real_type != type) {
			error("iter_get_basic_args: expected %c but got %c",
					(char) type, (char) real_type);
			break;
		}

		dbus_message_iter_get_basic(iter, value);
		dbus_message_iter_next(iter);
	}

	va_end(ap);

	return type == DBUS_TYPE_INVALID ? TRUE : FALSE;
}

static void hal_battery_level_reply(DBusPendingCall *call, void *user_data)
{
	DBusError err;
	DBusMessage *reply;
	dbus_int32_t level;
	int *value = user_data;

	reply = dbus_pending_call_steal_reply(call);

	dbus_error_init(&err);
	if (dbus_set_error_from_message(&err, reply)) {
		error("hald replied with an error: %s, %s",
				err.name, err.message);
		dbus_error_free(&err);
		goto done;
	}

	if (!dbus_message_get_args(reply, NULL,
				DBUS_TYPE_INT32, &level,
				DBUS_TYPE_INVALID)) {
		error("Unexpected args in hald reply");
		goto done;
	}

	*value = (int) level;

	if (value == &battchg_last)
		DBG("telephony-maemo6: battery.charge_level.last_full is %d",
				*value);
	else if (value == &battchg_design)
		DBG("telephony-maemo6: battery.charge_level.design is %d",
				*value);
	else
		DBG("telephony-maemo6: battery.charge_level.current is %d",
				*value);

	if ((battchg_design > 0 || battchg_last > 0) && battchg_cur >= 0) {
		int new, max;

		if (battchg_last > 0)
			max = battchg_last;
		else
			max = battchg_design;

		new = battchg_cur * 5 / max;

		telephony_update_indicator(maemo_indicators, "battchg", new);
	}

done:
	dbus_message_unref(reply);
	remove_pending(call);
}

static void hal_get_integer(const char *path, const char *key, void *user_data)
{
	send_method_call("org.freedesktop.Hal", path,
				"org.freedesktop.Hal.Device",
				"GetPropertyInteger",
				hal_battery_level_reply, user_data,
				DBUS_TYPE_STRING, &key,
				DBUS_TYPE_INVALID);
}

static void handle_hal_property_modified(DBusMessage *msg)
{
	DBusMessageIter iter, array;
	dbus_int32_t num_changes;
	const char *path;

	path = dbus_message_get_path(msg);

	dbus_message_iter_init(msg, &iter);

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
		error("Unexpected signature in hal PropertyModified signal");
		return;
	}

	dbus_message_iter_get_basic(&iter, &num_changes);
	dbus_message_iter_next(&iter);

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
		error("Unexpected signature in hal PropertyModified signal");
		return;
	}

	dbus_message_iter_recurse(&iter, &array);

	while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) {
		DBusMessageIter prop;
		const char *name;
		dbus_bool_t added, removed;

		dbus_message_iter_recurse(&array, &prop);

		if (!iter_get_basic_args(&prop,
					DBUS_TYPE_STRING, &name,
					DBUS_TYPE_BOOLEAN, &added,
					DBUS_TYPE_BOOLEAN, &removed,
					DBUS_TYPE_INVALID)) {
			error("Invalid hal PropertyModified parameters");
			break;
		}

		if (g_str_equal(name, "battery.charge_level.last_full"))
			hal_get_integer(path, name, &battchg_last);
		else if (g_str_equal(name, "battery.charge_level.current"))
			hal_get_integer(path, name, &battchg_cur);
		else if (g_str_equal(name, "battery.charge_level.design"))
			hal_get_integer(path, name, &battchg_design);

		dbus_message_iter_next(&array);
	}
}

static void csd_call_free(struct csd_call *call)
{
	if (!call)
		return;

	g_free(call->object_path);
	g_free(call->number);

	g_free(call);
}

static void parse_call_list(DBusMessageIter *iter)
{
	do {
		DBusMessageIter call_iter;
		struct csd_call *call;
		const char *object_path, *number;
		dbus_uint32_t status;
		dbus_bool_t originating, terminating, emerg, on_hold, conf;

		if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT) {
			error("Unexpected signature in GetCallInfoAll reply");
			break;
		}

		dbus_message_iter_recurse(iter, &call_iter);

		if (!iter_get_basic_args(&call_iter,
					DBUS_TYPE_OBJECT_PATH, &object_path,
					DBUS_TYPE_UINT32, &status,
					DBUS_TYPE_BOOLEAN, &originating,
					DBUS_TYPE_BOOLEAN, &terminating,
					DBUS_TYPE_BOOLEAN, &emerg,
					DBUS_TYPE_BOOLEAN, &on_hold,
					DBUS_TYPE_BOOLEAN, &conf,
					DBUS_TYPE_STRING, &number,
					DBUS_TYPE_INVALID)) {
			error("Parsing call D-Bus parameters failed");
			break;
		}

		call = find_call(object_path);
		if (!call) {
			call = g_new0(struct csd_call, 1);
			call->object_path = g_strdup(object_path);
			calls = g_slist_append(calls, call);
			DBG("telephony-maemo6: new csd call instance at %s",
								object_path);
		}

		if (status == CSD_CALL_STATUS_IDLE)
			continue;

		/* CSD gives incorrect call_hold property sometimes */
		if ((call->status != CSD_CALL_STATUS_HOLD && on_hold) ||
				(call->status == CSD_CALL_STATUS_HOLD &&
								!on_hold)) {
			error("Conflicting call status and on_hold property!");
			on_hold = call->status == CSD_CALL_STATUS_HOLD;
		}

		call->originating = originating;
		call->on_hold = on_hold;
		call->conference = conf;
		g_free(call->number);
		call->number = g_strdup(number);

		/* Update indicators */
		call_set_status(call, status);

	} while (dbus_message_iter_next(iter));
}

static void update_operator_name(const char *name)
{
	if (name == NULL)
		return;

	g_free(net.operator_name);
	net.operator_name = g_strndup(name, 16);
	DBG("telephony-maemo6: operator name updated: %s", name);
}

static void get_property_reply(DBusPendingCall *call, void *user_data)
{
	char *prop = user_data;
	DBusError err;
	DBusMessage *reply;
	DBusMessageIter iter, sub;

	reply = dbus_pending_call_steal_reply(call);

	dbus_error_init(&err);
	if (dbus_set_error_from_message(&err, reply)) {
		error("csd replied with an error: %s, %s",
				err.name, err.message);
		dbus_error_free(&err);
		goto done;
	}

	dbus_message_iter_init(reply, &iter);

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
		error("Unexpected signature in Get return");
		goto done;
	}

	dbus_message_iter_recurse(&iter, &sub);

	if (g_strcmp0(prop, "RegistrationStatus") == 0) {
		const char *status;

		dbus_message_iter_get_basic(&sub, &status);
		update_registration_status(status);

		get_property(CSD_CSNET_OPERATOR, "OperatorName");
		get_property(CSD_CSNET_SIGNAL, "SignalBars");
	} else if (g_strcmp0(prop, "OperatorName") == 0) {
		const char *name;

		dbus_message_iter_get_basic(&sub, &name);
		update_operator_name(name);
	} else if (g_strcmp0(prop, "SignalBars") == 0) {
		int32_t signal_bars;

		dbus_message_iter_get_basic(&sub, &signal_bars);
		update_signal_strength(signal_bars);
	}

done:
	g_free(prop);
	dbus_message_unref(reply);
	remove_pending(call);
}

static int get_property(const char *iface, const char *prop)
{
	return send_method_call(CSD_CSNET_BUS_NAME, CSD_CSNET_PATH,
				DBUS_INTERFACE_PROPERTIES, "Get",
				get_property_reply, g_strdup(prop),
				DBUS_TYPE_STRING, &iface,
				DBUS_TYPE_STRING, &prop,
				DBUS_TYPE_INVALID);
}

static void handle_operator_name_changed(DBusMessage *msg)
{
	const char *name;

	if (!dbus_message_get_args(msg, NULL,
					DBUS_TYPE_STRING, &name,
					DBUS_TYPE_INVALID)) {
		error("Unexpected parameters in OperatorNameChanged");
		return;
	}

	update_operator_name(name);
}

static void call_info_reply(DBusPendingCall *call, void *user_data)
{
	DBusError err;
	DBusMessage *reply;
	DBusMessageIter iter, sub;;

	get_calls_active = FALSE;

	reply = dbus_pending_call_steal_reply(call);

	dbus_error_init(&err);
	if (dbus_set_error_from_message(&err, reply)) {
		error("csd replied with an error: %s, %s",
				err.name, err.message);
		dbus_error_free(&err);
		goto done;
	}

	dbus_message_iter_init(reply, &iter);

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
		error("Unexpected signature in GetCallInfoAll return");
		goto done;
	}

	dbus_message_iter_recurse(&iter, &sub);

	parse_call_list(&sub);

	get_property(CSD_CSNET_REGISTRATION, "RegistrationStatus");

done:
	dbus_message_unref(reply);
	remove_pending(call);
}


static void phonebook_read_reply(DBusPendingCall *call, void *user_data)
{
	DBusError derr;
	DBusMessage *reply;
	const char *name, *number, *secondname, *additionalnumber, *email;
	int index;
	char **number_type = user_data;

	reply = dbus_pending_call_steal_reply(call);

	dbus_error_init(&derr);
	if (dbus_set_error_from_message(&derr, reply)) {
		error("%s.ReadFirst replied with an error: %s, %s",
				CSD_SIMPB_INTERFACE, derr.name, derr.message);
		dbus_error_free(&derr);
		if (number_type == &vmbx)
			vmbx = g_strdup(getenv("VMBX_NUMBER"));
		goto done;
	}

	dbus_error_init(&derr);
	if (dbus_message_get_args(reply, NULL,
				DBUS_TYPE_INT32, &index,
				DBUS_TYPE_STRING, &name,
				DBUS_TYPE_STRING, &number,
				DBUS_TYPE_STRING, &secondname,
				DBUS_TYPE_STRING, &additionalnumber,
				DBUS_TYPE_STRING, &email,
				DBUS_TYPE_INVALID) == FALSE) {
		error("Unable to parse %s.ReadFirst arguments: %s, %s",
				CSD_SIMPB_INTERFACE, derr.name, derr.message);
		dbus_error_free(&derr);
		goto done;
	}

	if (number_type == &msisdn) {
		g_free(msisdn);
		msisdn = g_strdup(number);
		DBG("Got MSISDN %s (%s)", number, name);
	} else {
		g_free(vmbx);
		vmbx = g_strdup(number);
		DBG("Got voice mailbox number %s (%s)", number, name);
	}

done:
	dbus_message_unref(reply);
	remove_pending(call);
}

static void csd_init(void)
{
	const char *pb_type;
	int ret;

	ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
				CSD_CALL_INTERFACE, "GetCallInfoAll",
				call_info_reply, NULL, DBUS_TYPE_INVALID);
	if (ret < 0) {
		error("Unable to sent GetCallInfoAll method call");
		return;
	}

	get_calls_active = TRUE;

	pb_type = CSD_SIMPB_TYPE_MSISDN;

	ret = send_method_call(CSD_SIMPB_BUS_NAME, CSD_SIMPB_PATH,
				CSD_SIMPB_INTERFACE, "ReadFirst",
				phonebook_read_reply, &msisdn,
				DBUS_TYPE_STRING, &pb_type,
				DBUS_TYPE_INVALID);
	if (ret < 0) {
		error("Unable to send " CSD_SIMPB_INTERFACE ".read()");
		return;
	}

	/* Voicemail should be in MBDN index 0 */
	pb_type = CSD_SIMPB_TYPE_MBDN;

	ret = send_method_call(CSD_SIMPB_BUS_NAME, CSD_SIMPB_PATH,
				CSD_SIMPB_INTERFACE, "ReadFirst",
				phonebook_read_reply, &vmbx,
				DBUS_TYPE_STRING, &pb_type,
				DBUS_TYPE_INVALID);
	if (ret < 0) {
		error("Unable to send " CSD_SIMPB_INTERFACE ".read()");
		return;
	}
}

static void handle_modem_state(DBusMessage *msg)
{
	const char *state;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &state,
							DBUS_TYPE_INVALID)) {
		error("Unexpected modem state parameters");
		return;
	}

	DBG("SSC modem state: %s", state);

	if (calls != NULL || get_calls_active)
		return;

	if (g_str_equal(state, "cmt_ready") || g_str_equal(state, "online"))
		csd_init();
}

static void modem_state_reply(DBusPendingCall *call, void *user_data)
{
	DBusMessage *reply = dbus_pending_call_steal_reply(call);
	DBusError err;

	dbus_error_init(&err);
	if (dbus_set_error_from_message(&err, reply)) {
		error("get_modem_state: %s, %s", err.name, err.message);
		dbus_error_free(&err);
	} else
		handle_modem_state(reply);

	dbus_message_unref(reply);
	remove_pending(call);
}

static gboolean signal_filter(DBusConnection *conn, DBusMessage *msg,
								void *data)
{
	const char *path = dbus_message_get_path(msg);

	if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Coming"))
		handle_incoming_call(msg);
	else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Created"))
		handle_outgoing_call(msg);
	else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE,
							"CreateRequested"))
		handle_create_requested(msg);
	else if (dbus_message_is_signal(msg, CSD_CALL_INSTANCE, "CallStatus"))
		handle_call_status(msg, path);
	else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Joined"))
		handle_conference(msg, TRUE);
	else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Left"))
		handle_conference(msg, FALSE);
	else if (dbus_message_is_signal(msg, CSD_CSNET_REGISTRATION,
				"RegistrationChanged"))
		handle_registration_changed(msg);
	else if (dbus_message_is_signal(msg, CSD_CSNET_OPERATOR,
				"OperatorNameChanged"))
		handle_operator_name_changed(msg);
	else if (dbus_message_is_signal(msg, CSD_CSNET_SIGNAL,
				"SignalBarsChanged"))
		handle_signal_bars_changed(msg);
	else if (dbus_message_is_signal(msg, "org.freedesktop.Hal.Device",
					"PropertyModified"))
		handle_hal_property_modified(msg);
	else if (dbus_message_is_signal(msg, SSC_DBUS_IFACE,
						"modem_state_changed_ind"))
		handle_modem_state(msg);

	return TRUE;
}

static void add_watch(const char *sender, const char *path,
				const char *interface, const char *member)
{
	guint watch;

	watch = g_dbus_add_signal_watch(connection, sender, path, interface,
					member, signal_filter, NULL, NULL);

	watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch));
}

static void hal_find_device_reply(DBusPendingCall *call, void *user_data)
{
	DBusError err;
	DBusMessage *reply;
	DBusMessageIter iter, sub;
	const char *path;
	int type;

	reply = dbus_pending_call_steal_reply(call);

	dbus_error_init(&err);
	if (dbus_set_error_from_message(&err, reply)) {
		error("hald replied with an error: %s, %s",
				err.name, err.message);
		dbus_error_free(&err);
		goto done;
	}

	dbus_message_iter_init(reply, &iter);

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
		error("Unexpected signature in FindDeviceByCapability return");
		goto done;
	}

	dbus_message_iter_recurse(&iter, &sub);

	type = dbus_message_iter_get_arg_type(&sub);

	if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) {
		error("No hal device with battery capability found");
		goto done;
	}

	dbus_message_iter_get_basic(&sub, &path);

	DBG("telephony-maemo6: found battery device at %s", path);

	add_watch(NULL, path, "org.freedesktop.Hal.Device",
							"PropertyModified");

	hal_get_integer(path, "battery.charge_level.last_full", &battchg_last);
	hal_get_integer(path, "battery.charge_level.current", &battchg_cur);
	hal_get_integer(path, "battery.charge_level.design", &battchg_design);

done:
	dbus_message_unref(reply);
	remove_pending(call);
}

int telephony_init(void)
{
	const char *battery_cap = "battery";
	uint32_t features = AG_FEATURE_EC_ANDOR_NR |
				AG_FEATURE_INBAND_RINGTONE |
				AG_FEATURE_REJECT_A_CALL |
				AG_FEATURE_ENHANCED_CALL_STATUS |
				AG_FEATURE_ENHANCED_CALL_CONTROL |
				AG_FEATURE_EXTENDED_ERROR_RESULT_CODES |
				AG_FEATURE_THREE_WAY_CALLING;
	int i;

	DBG("");

	connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);

	add_watch(NULL, NULL, CSD_CALL_INTERFACE, NULL);
	add_watch(NULL, NULL, CSD_CALL_INSTANCE, NULL);
	add_watch(NULL, NULL, CSD_CALL_CONFERENCE, NULL);
	add_watch(NULL, NULL, CSD_CSNET_REGISTRATION, "RegistrationChanged");
	add_watch(NULL, NULL, CSD_CSNET_OPERATOR, "OperatorNameChanged");
	add_watch(NULL, NULL, CSD_CSNET_SIGNAL, "SignalBarsChanged");
	add_watch(NULL, NULL, SSC_DBUS_IFACE, "modem_state_changed_ind");

	if (send_method_call(SSC_DBUS_NAME, SSC_DBUS_PATH, SSC_DBUS_IFACE,
					"get_modem_state", modem_state_reply,
					NULL, DBUS_TYPE_INVALID) < 0)
		error("Unable to send " SSC_DBUS_IFACE ".get_modem_state()");

	/* Reset indicators */
	for (i = 0; maemo_indicators[i].desc != NULL; i++) {
		if (g_str_equal(maemo_indicators[i].desc, "battchg"))
			maemo_indicators[i].val = 5;
		else
			maemo_indicators[i].val = 0;
	}

	telephony_ready_ind(features, maemo_indicators, BTRH_NOT_SUPPORTED,
								chld_str);
	if (send_method_call("org.freedesktop.Hal",
				"/org/freedesktop/Hal/Manager",
				"org.freedesktop.Hal.Manager",
				"FindDeviceByCapability",
				hal_find_device_reply, NULL,
				DBUS_TYPE_STRING, &battery_cap,
				DBUS_TYPE_INVALID) < 0)
		error("Unable to send HAL method call");

	return 0;
}

static void remove_watch(gpointer data)
{
	g_dbus_remove_watch(connection, GPOINTER_TO_UINT(data));
}

void telephony_exit(void)
{
	DBG("");

	g_free(net.operator_name);
	net.operator_name = NULL;

	net.status = NETWORK_REG_STATUS_UNKOWN;
	net.signal_bars = 0;

	g_slist_free(active_calls);
	active_calls = NULL;

	g_slist_foreach(calls, (GFunc) csd_call_free, NULL);
	g_slist_free(calls);
	calls = NULL;

	g_slist_foreach(pending, (GFunc) pending_req_finalize, NULL);
	g_slist_free(pending);
	pending = NULL;

	g_slist_foreach(watches, (GFunc) remove_watch, NULL);
	g_slist_free(watches);
	watches = NULL;

	dbus_connection_unref(connection);
	connection = NULL;

	telephony_deinit();
}