/*
 * oFono - Open Source Telephony
 * Copyright (C) 2008-2011  Intel Corporation
 *
 * SPDX-License-Identifier: GPL-2.0-only
 */

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

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include <glib.h>
#include <gdbus.h>

#include "ofono.h"

#include "common.h"
#include "simutil.h"

#define CALL_FORWARDING_FLAG_CACHED	0x1
#define CALL_FORWARDING_FLAG_CPHS_CFF	0x2

/* According to 27.007 Spec */
#define DEFAULT_NO_REPLY_TIMEOUT 20

#define is_cfu_enabled(_cf)				\
({							\
	cf_find_unconditional(_cf) ? TRUE : FALSE;	\
})

enum call_forwarding_type {
	CALL_FORWARDING_TYPE_UNCONDITIONAL =	0,
	CALL_FORWARDING_TYPE_BUSY =		1,
	CALL_FORWARDING_TYPE_NO_REPLY =		2,
	CALL_FORWARDING_TYPE_NOT_REACHABLE =	3,
	CALL_FORWARDING_TYPE_ALL =		4,
	CALL_FORWARDING_TYPE_ALL_CONDITIONAL =	5
};

struct ofono_call_forwarding {
	GSList *cf_conditions[4];
	int flags;
	DBusMessage *pending;
	int query_next;
	int query_end;
	struct cf_ss_request *ss_req;
	struct ofono_sim *sim;
	struct ofono_sim_context *sim_context;
	unsigned char cfis_record_id;
	struct ofono_ussd *ussd;
	unsigned int ussd_watch;
	const struct ofono_call_forwarding_driver *driver;
	void *driver_data;
	struct ofono_atom *atom;
};

struct cf_ss_request {
	int ss_type;
	int cf_type;
	int cls;
	GSList *cf_list[4];
};

static void get_query_next_cf_cond(struct ofono_call_forwarding *cf);
static void set_query_next_cf_cond(struct ofono_call_forwarding *cf);
static void ss_set_query_next_cf_cond(struct ofono_call_forwarding *cf);

static gint cf_cond_compare(gconstpointer a, gconstpointer b)
{
	const struct ofono_call_forwarding_condition *ca = a;
	const struct ofono_call_forwarding_condition *cb = b;

	return ca->cls - cb->cls;
}

static struct ofono_call_forwarding_condition *cf_cond_find(GSList *l, int cls)
{
	struct ofono_call_forwarding_condition *c;

	for (; l; l = l->next) {
		c = l->data;

		if (c->cls == cls)
			return c;
	}

	return NULL;
}

static int cf_cond_find_timeout(GSList *l, int cls)
{
	struct ofono_call_forwarding_condition *cond = cf_cond_find(l, cls);

	return cond ? cond->time : DEFAULT_NO_REPLY_TIMEOUT;
}

static void cf_cond_list_print(GSList *l)
{
	struct ofono_call_forwarding_condition *cond;

	for (; l ; l = l->next) {
		cond = l->data;

		DBG("CF Condition status: %d, class: %d, number: %s,"
			" number_type: %d, time: %d",
			cond->status, cond->cls, cond->phone_number.number,
			cond->phone_number.type, cond->time);
	}
}

static GSList *cf_cond_list_create(int total,
			const struct ofono_call_forwarding_condition *list)
{
	GSList *l = NULL;
	int i;
	int j;
	struct ofono_call_forwarding_condition *cond;

	/*
	 * Specification is not really clear how the results are reported,
	 * so assume both multiple list items & compound values of class
	 * are possible
	 */
	for (i = 0; i < total; i++) {
		for (j = 1; j <= BEARER_CLASS_PAD; j = j << 1) {
			if (!(list[i].cls & j))
				continue;

			if (list[i].status == 0)
				continue;

			cond = g_try_new0(
				struct ofono_call_forwarding_condition, 1);
			if (cond == NULL)
				continue;

			memcpy(cond, &list[i],
				sizeof(struct ofono_call_forwarding_condition));
			cond->cls = j;

			l = g_slist_insert_sorted(l, cond, cf_cond_compare);
		}
	}

	return l;
}

static inline void cf_clear_all(struct ofono_call_forwarding *cf)
{
	int i;

	for (i = 0; i < 4; i++) {
		g_slist_free_full(cf->cf_conditions[i], g_free);
		cf->cf_conditions[i] = NULL;
	}
}

static const char *cf_type_lut[] = {
	"Unconditional",
	"Busy",
	"NoReply",
	"NotReachable",
	"All",
	"AllConditional"
};

static void sim_cfis_update_cb(int ok, void *data)
{
	if (!ok)
		ofono_info("Failed to update EFcfis");
}

static void sim_cphs_cff_update_cb(int ok, void *data)
{
	if (!ok)
		ofono_info("Failed to update EFcphs-cff");
}

static inline struct ofono_call_forwarding_condition *cf_find_unconditional(
					struct ofono_call_forwarding *cf)
{
	return cf_cond_find(
		cf->cf_conditions[CALL_FORWARDING_TYPE_UNCONDITIONAL],
		BEARER_CLASS_VOICE);
}

static void sim_set_cf_indicator(struct ofono_call_forwarding *cf)
{
	struct ofono_call_forwarding_condition *cfu_voice =
						cf_find_unconditional(cf);

	if (cf->cfis_record_id) {
		unsigned char data[16];
		int number_len;

		memset(data, 0xff, sizeof(data));

		/* Profile Identifier */
		data[0] = 0x01;

		if (cfu_voice) {
			number_len = strlen(cfu_voice->phone_number.number);

			/* CFU indicator Status - Voice */
			data[1] = 0x01;
			number_len = (number_len + 1) / 2;
			data[2] = number_len + 1;
			data[3] = cfu_voice->phone_number.type;

			sim_encode_bcd_number(cfu_voice->phone_number.number,
						data + 4);
		} else {
			data[1] = 0x00;
			data[2] = 1;
			data[3] = 128;
		}

		ofono_sim_write(cf->sim_context, SIM_EFCFIS_FILEID,
					sim_cfis_update_cb,
					OFONO_SIM_FILE_STRUCTURE_FIXED,
					cf->cfis_record_id, data,
					sizeof(data), cf);
		return;
	}

	if (cf->flags & CALL_FORWARDING_FLAG_CPHS_CFF) {
		unsigned char cff_voice = cfu_voice ? 0x0A : 0x05;

		ofono_sim_write(cf->sim_context, SIM_EF_CPHS_CFF_FILEID,
					sim_cphs_cff_update_cb,
					OFONO_SIM_FILE_STRUCTURE_TRANSPARENT,
					0, &cff_voice, sizeof(cff_voice), cf);
	}
}

static void set_new_cond_list(struct ofono_call_forwarding *cf,
				int type, GSList *list)
{
	GSList *old = cf->cf_conditions[type];
	DBusConnection *conn = ofono_dbus_get_connection();
	const char *path = __ofono_atom_get_path(cf->atom);
	GSList *l;
	GSList *o;
	struct ofono_call_forwarding_condition *lc;
	struct ofono_call_forwarding_condition *oc;
	const char *number;
	dbus_uint16_t timeout;
	char attr[64];
	char tattr[72];
	gboolean update_sim = FALSE;
	gboolean old_cfu;
	gboolean new_cfu;

	if ((cf->flags & CALL_FORWARDING_FLAG_CPHS_CFF) ||
			cf->cfis_record_id > 0)
		old_cfu = is_cfu_enabled(cf);
	else
		old_cfu = FALSE;

	for (l = list; l; l = l->next) {
		lc = l->data;

		/*
		 * New condition lists might have attributes we don't care about
		 * triggered by e.g. ss control magic strings just skip them
		 * here.  For now we only support Voice, although Fax & all Data
		 * basic services are applicable as well.
		 */
		if (lc->cls > BEARER_CLASS_VOICE)
			continue;

		timeout = lc->time;
		number = phone_number_to_string(&lc->phone_number);

		snprintf(attr, sizeof(attr), "%s%s",
			bearer_class_to_string(lc->cls), cf_type_lut[type]);

		if (type == CALL_FORWARDING_TYPE_NO_REPLY)
			snprintf(tattr, sizeof(tattr), "%sTimeout", attr);

		oc = cf_cond_find(old, lc->cls);
		if (oc) { /* On the old list, must be active */
			if (oc->phone_number.type != lc->phone_number.type ||
				strcmp(oc->phone_number.number,
					lc->phone_number.number)) {
				ofono_dbus_signal_property_changed(conn, path,
						OFONO_CALL_FORWARDING_INTERFACE,
						attr, DBUS_TYPE_STRING,
						&number);

				if (type == CALL_FORWARDING_TYPE_UNCONDITIONAL)
					update_sim = TRUE;
			}

			if (type == CALL_FORWARDING_TYPE_NO_REPLY &&
					oc->time != lc->time)
				ofono_dbus_signal_property_changed(conn, path,
						OFONO_CALL_FORWARDING_INTERFACE,
						tattr, DBUS_TYPE_UINT16,
						&timeout);

			/* Remove from the old list */
			old = g_slist_remove(old, oc);
			g_free(oc);
		} else {
			number = phone_number_to_string(&lc->phone_number);

			ofono_dbus_signal_property_changed(conn, path,
						OFONO_CALL_FORWARDING_INTERFACE,
						attr, DBUS_TYPE_STRING,
						&number);

			if (type == CALL_FORWARDING_TYPE_UNCONDITIONAL)
				update_sim = TRUE;

			if (type == CALL_FORWARDING_TYPE_NO_REPLY &&
					lc->time != DEFAULT_NO_REPLY_TIMEOUT)
				ofono_dbus_signal_property_changed(conn, path,
						OFONO_CALL_FORWARDING_INTERFACE,
						tattr, DBUS_TYPE_UINT16,
						&timeout);
		}
	}

	timeout = DEFAULT_NO_REPLY_TIMEOUT;
	number = "";

	for (o = old; o; o = o->next) {
		oc = o->data;

		/*
		 * For now we only support Voice, although Fax & all Data
		 * basic services are applicable as well.
		 */
		if (oc->cls > BEARER_CLASS_VOICE)
			continue;

		snprintf(attr, sizeof(attr), "%s%s",
			bearer_class_to_string(oc->cls), cf_type_lut[type]);

		if (type == CALL_FORWARDING_TYPE_NO_REPLY)
			snprintf(tattr, sizeof(tattr), "%sTimeout", attr);

		ofono_dbus_signal_property_changed(conn, path,
					OFONO_CALL_FORWARDING_INTERFACE, attr,
					DBUS_TYPE_STRING, &number);

		if (type == CALL_FORWARDING_TYPE_UNCONDITIONAL)
			update_sim = TRUE;

		if (type == CALL_FORWARDING_TYPE_NO_REPLY &&
				oc->time != DEFAULT_NO_REPLY_TIMEOUT)
			ofono_dbus_signal_property_changed(conn, path,
						OFONO_CALL_FORWARDING_INTERFACE,
						tattr, DBUS_TYPE_UINT16,
						&timeout);
	}

	g_slist_free_full(old, g_free);
	cf->cf_conditions[type] = list;

	if (update_sim == TRUE)
		sim_set_cf_indicator(cf);

	if ((cf->flags & CALL_FORWARDING_FLAG_CPHS_CFF) ||
			cf->cfis_record_id > 0)
		new_cfu = is_cfu_enabled(cf);
	else
		new_cfu = FALSE;

	if (new_cfu != old_cfu) {
		ofono_bool_t status = new_cfu;
		int i;

		/*
		 * Emit signals to mask/unmask conditional cfs on cfu change
		 */
		for (i = 0; i < 4; i++) {
			if (i == CALL_FORWARDING_TYPE_UNCONDITIONAL)
				continue;

			lc = cf_cond_find(cf->cf_conditions[i],
						BEARER_CLASS_VOICE);
			if (lc == NULL)
				continue;

			if (new_cfu)
				number = "";
			else
				number = phone_number_to_string(
							&lc->phone_number);

			ofono_dbus_signal_property_changed(conn, path,
						OFONO_CALL_FORWARDING_INTERFACE,
						cf_type_lut[i],
						DBUS_TYPE_STRING, &number);
		}

		ofono_dbus_signal_property_changed(conn, path,
					OFONO_CALL_FORWARDING_INTERFACE,
					"ForwardingFlagOnSim",
					DBUS_TYPE_BOOLEAN, &status);
	}
}

static inline void property_append_cf_condition(DBusMessageIter *dict, int cls,
						const char *postfix,
						const char *value,
						dbus_uint16_t timeout)
{
	char attr[64];
	char tattr[64];
	int addt = !strcmp(postfix, "NoReply");

	snprintf(attr, sizeof(attr), "%s%s",
			bearer_class_to_string(cls), postfix);

	if (addt)
		snprintf(tattr, sizeof(tattr), "%s%sTimeout",
				bearer_class_to_string(cls), postfix);

	ofono_dbus_dict_append(dict, attr, DBUS_TYPE_STRING, &value);

	if (addt)
		ofono_dbus_dict_append(dict, tattr, DBUS_TYPE_UINT16, &timeout);
}

static void property_append_cf_conditions(DBusMessageIter *dict,
						GSList *cf_list, int mask,
						const char *postfix)
{
	GSList *l;
	int i;
	struct ofono_call_forwarding_condition *cf;
	const char *number;

	for (i = 1, l = cf_list; i <= BEARER_CLASS_PAD; i = i << 1) {
		if (!(mask & i))
			continue;

		while (l && (cf = l->data) && (cf->cls < i))
				l = l->next;

		if (l == NULL || cf->cls != i) {
			property_append_cf_condition(dict, i, postfix, "",
						DEFAULT_NO_REPLY_TIMEOUT);
			continue;
		}

		number = phone_number_to_string(&cf->phone_number);

		property_append_cf_condition(dict, i, postfix, number,
						cf->time);
	}
}

static DBusMessage *cf_get_properties_reply(DBusMessage *msg,
					struct ofono_call_forwarding *cf)
{
	DBusMessage *reply;
	DBusMessageIter iter;
	DBusMessageIter dict;
	int i;
	dbus_bool_t status;
	gboolean cfu_enabled;
	GSList *cf_list;

	reply = dbus_message_new_method_return(msg);
	if (reply == NULL)
		return NULL;

	dbus_message_iter_init_append(reply, &iter);

	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
					OFONO_PROPERTIES_ARRAY_SIGNATURE,
						&dict);

	cfu_enabled = is_cfu_enabled(cf);

	for (i = 0; i < 4; i++) {
		/*
		 * Report conditional cfs as empty when CFU is active
		 */
		if (cfu_enabled && (i != CALL_FORWARDING_TYPE_UNCONDITIONAL))
			cf_list = NULL;
		else
			cf_list = cf->cf_conditions[i];

		property_append_cf_conditions(&dict, cf_list,
						BEARER_CLASS_VOICE,
						cf_type_lut[i]);
	}

	if ((cf->flags & CALL_FORWARDING_FLAG_CPHS_CFF) ||
			cf->cfis_record_id > 0)
		status = cfu_enabled;
	else
		status = FALSE;

	ofono_dbus_dict_append(&dict, "ForwardingFlagOnSim", DBUS_TYPE_BOOLEAN,
					&status);

	dbus_message_iter_close_container(&iter, &dict);

	return reply;
}

static void get_query_cf_callback(const struct ofono_error *error, int total,
			const struct ofono_call_forwarding_condition *list,
			void *data)
{
	struct ofono_call_forwarding *cf = data;

	if (error->type == OFONO_ERROR_TYPE_NO_ERROR) {
		GSList *l = cf_cond_list_create(total, list);

		set_new_cond_list(cf, cf->query_next, l);

		DBG("%s conditions:", cf_type_lut[cf->query_next]);

		cf_cond_list_print(l);

		if (cf->query_next == CALL_FORWARDING_TYPE_NOT_REACHABLE)
			cf->flags |= CALL_FORWARDING_FLAG_CACHED;
	}

	if (cf->query_next == CALL_FORWARDING_TYPE_NOT_REACHABLE) {
		__ofono_dbus_pending_reply(&cf->pending,
				cf_get_properties_reply(cf->pending, cf));
		return;
	}

	cf->query_next++;
	get_query_next_cf_cond(cf);
}

static inline void get_query_next_cf_cond(struct ofono_call_forwarding *cf)
{
	cf->driver->query(cf, cf->query_next, BEARER_CLASS_DEFAULT,
				get_query_cf_callback, cf);
}

static DBusMessage *cf_get_properties(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	struct ofono_call_forwarding *cf = data;
	struct ofono_modem *modem = __ofono_atom_get_modem(cf->atom);

	if ((cf->flags & CALL_FORWARDING_FLAG_CACHED) ||
			ofono_modem_get_online(modem) == FALSE)
		return cf_get_properties_reply(msg, cf);

	if (cf->driver->query == NULL)
		return __ofono_error_not_implemented(msg);

	if (__ofono_call_forwarding_is_busy(cf) ||
			__ofono_ussd_is_busy(cf->ussd))
		return __ofono_error_busy(msg);

	cf->pending = dbus_message_ref(msg);
	cf->query_next = 0;

	get_query_next_cf_cond(cf);

	return NULL;
}

static gboolean cf_condition_enabled_property(struct ofono_call_forwarding *cf,
			const char *property, int *out_type, int *out_cls)
{
	int i;
	int j;
	int len;
	const char *prefix;

	for (i = 1; i <= BEARER_CLASS_VOICE; i = i << 1) {
		prefix = bearer_class_to_string(i);

		len = strlen(prefix);

		if (strncmp(property, prefix, len))
			continue;

		/*
		 * We check the 4 call forwarding types, e.g.
		 * unconditional, busy, no reply, not reachable
		 */
		for (j = 0; j < 4; j++)
			if (!strcmp(property+len, cf_type_lut[j])) {
				*out_type = j;
				*out_cls = i;
				return TRUE;
			}
	}

	return FALSE;
}

static gboolean cf_condition_timeout_property(const char *property,
						int *out_cls)
{
	int i;
	int len;
	const char *prefix;

	for (i = 1; i <= BEARER_CLASS_VOICE; i = i << 1) {
		prefix = bearer_class_to_string(i);

		len = strlen(prefix);

		if (strncmp(property, prefix, len))
			continue;

		if (!strcmp(property+len, "NoReplyTimeout")) {
			*out_cls = i;
			return TRUE;
		}
	}

	return FALSE;
}

static void set_query_cf_callback(const struct ofono_error *error, int total,
			const struct ofono_call_forwarding_condition *list,
			void *data)
{
	struct ofono_call_forwarding *cf = data;

	if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
		ofono_error("Setting succeeded, but query failed");
		cf->flags &= ~CALL_FORWARDING_FLAG_CACHED;
		__ofono_dbus_pending_reply(&cf->pending,
					__ofono_error_failed(cf->pending));
		return;
	}

	if (cf->query_next == cf->query_end)
		__ofono_dbus_pending_reply(&cf->pending,
				dbus_message_new_method_return(cf->pending));

	set_new_cond_list(cf, cf->query_next, cf_cond_list_create(total, list));

	DBG("%s conditions:", cf_type_lut[cf->query_next]);
	cf_cond_list_print(cf->cf_conditions[cf->query_next]);

	if (cf->query_next == cf->query_end)
		return;

	cf->query_next++;
	set_query_next_cf_cond(cf);
}

static void set_query_next_cf_cond(struct ofono_call_forwarding *cf)
{
	cf->driver->query(cf, cf->query_next, BEARER_CLASS_DEFAULT,
				set_query_cf_callback, cf);
}

static void set_property_callback(const struct ofono_error *error, void *data)
{
	struct ofono_call_forwarding *cf = data;

	if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
		DBG("Error occurred during set/erasure");
		__ofono_dbus_pending_reply(&cf->pending,
					__ofono_error_failed(cf->pending));
		return;
	}

	/* Successfully set, query the entire set just in case */
	set_query_next_cf_cond(cf);
}

static DBusMessage *set_property_request(struct ofono_call_forwarding *cf,
						DBusMessage *msg,
						int type, int cls,
						struct ofono_phone_number *ph,
						int timeout)
{
	if (ph->number[0] != '\0' && cf->driver->registration == NULL)
		return __ofono_error_not_implemented(msg);

	if (ph->number[0] == '\0' && cf->driver->erasure == NULL)
		return __ofono_error_not_implemented(msg);

	cf->pending = dbus_message_ref(msg);
	cf->query_next = type;
	cf->query_end = type;

	DBG("Farming off request, will be erasure: %d", ph->number[0] == '\0');

	if (ph->number[0] != '\0')
		cf->driver->registration(cf, type, cls, ph, timeout,
					set_property_callback, cf);
	else
		cf->driver->erasure(cf, type, cls, set_property_callback, cf);

	return NULL;
}

static DBusMessage *cf_set_property(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	struct ofono_call_forwarding *cf = data;
	struct ofono_modem *modem = __ofono_atom_get_modem(cf->atom);
	DBusMessageIter iter;
	DBusMessageIter var;
	const char *property;
	int cls;
	int type;

	if (ofono_modem_get_online(modem) == FALSE)
		return __ofono_error_not_available(msg);

	if (__ofono_call_forwarding_is_busy(cf) ||
			__ofono_ussd_is_busy(cf->ussd))
		return __ofono_error_busy(msg);

	if (!dbus_message_iter_init(msg, &iter))
		return __ofono_error_invalid_args(msg);

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
		return __ofono_error_invalid_args(msg);

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

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
		return __ofono_error_invalid_args(msg);

	dbus_message_iter_recurse(&iter, &var);

	if (cf_condition_timeout_property(property, &cls)) {
		dbus_uint16_t timeout;
		struct ofono_call_forwarding_condition *c;

		type = CALL_FORWARDING_TYPE_NO_REPLY;

		if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_UINT16)
			return __ofono_error_invalid_args(msg);

		dbus_message_iter_get_basic(&var, &timeout);

		if (timeout < 1 || timeout > 30)
			return __ofono_error_invalid_format(msg);


		c = cf_cond_find(cf->cf_conditions[type], cls);
		if (c == NULL)
			return __ofono_error_failed(msg);

		return set_property_request(cf, msg, type, cls,
						&c->phone_number, timeout);
	} else if (cf_condition_enabled_property(cf, property, &type, &cls)) {
		struct ofono_phone_number ph;
		const char *number;
		int timeout;

		ph.number[0] = '\0';
		ph.type = 129;

		if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING)
			return __ofono_error_invalid_args(msg);

		dbus_message_iter_get_basic(&var, &number);

		if (strlen(number) > 0 && !valid_phone_number_format(number))
			return __ofono_error_invalid_format(msg);

		/*
		 * Don't set conditional cfs when cfu is active
		 */
		if (type != CALL_FORWARDING_TYPE_UNCONDITIONAL &&
				number[0] != '\0' && is_cfu_enabled(cf))
			return __ofono_error_not_available(msg);

		if (number[0] != '\0')
			__string_to_phone_number(number, &ph);

		timeout = cf_cond_find_timeout(cf->cf_conditions[type], cls);

		return set_property_request(cf, msg, type, cls, &ph,
						timeout);
	}

	return __ofono_error_invalid_args(msg);
}

static void disable_conditional_callback(const struct ofono_error *error,
						void *data)
{
	struct ofono_call_forwarding *cf = data;

	if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
		DBG("Error occurred during conditional erasure");

		__ofono_dbus_pending_reply(&cf->pending,
					__ofono_error_failed(cf->pending));
		return;
	}

	/* Query the three conditional cf types */
	cf->query_next = CALL_FORWARDING_TYPE_BUSY;
	cf->query_end = CALL_FORWARDING_TYPE_NOT_REACHABLE;
	set_query_next_cf_cond(cf);
}

static void disable_all_callback(const struct ofono_error *error, void *data)
{
	struct ofono_call_forwarding *cf = data;

	if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
		DBG("Error occurred during erasure of all");

		__ofono_dbus_pending_reply(&cf->pending,
					__ofono_error_failed(cf->pending));
		return;
	}

	/* Query all cf types */
	cf->query_next = CALL_FORWARDING_TYPE_UNCONDITIONAL;
	cf->query_end = CALL_FORWARDING_TYPE_NOT_REACHABLE;
	set_query_next_cf_cond(cf);
}

static DBusMessage *cf_disable_all(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	struct ofono_call_forwarding *cf = data;
	const char *strtype;
	int type;

	if (cf->driver->erasure == NULL)
		return __ofono_error_not_implemented(msg);

	if (__ofono_call_forwarding_is_busy(cf) ||
			__ofono_ussd_is_busy(cf->ussd))
		return __ofono_error_busy(msg);

	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &strtype,
					DBUS_TYPE_INVALID) == FALSE)
		return __ofono_error_invalid_args(msg);

	if (!strcmp(strtype, "all") || !strcmp(strtype, ""))
		type = CALL_FORWARDING_TYPE_ALL;
	else if (!strcmp(strtype, "conditional"))
		type = CALL_FORWARDING_TYPE_ALL_CONDITIONAL;
	else
		return __ofono_error_invalid_format(msg);

	cf->pending = dbus_message_ref(msg);

	if (type == CALL_FORWARDING_TYPE_ALL)
		cf->driver->erasure(cf, type, BEARER_CLASS_DEFAULT,
					disable_all_callback, cf);
	else
		cf->driver->erasure(cf, type, BEARER_CLASS_DEFAULT,
					disable_conditional_callback, cf);

	return NULL;
}

static const GDBusMethodTable cf_methods[] = {
	{ GDBUS_ASYNC_METHOD("GetProperties",
				NULL, GDBUS_ARGS({ "properties", "a{sv}" }),
				cf_get_properties) },
	{ GDBUS_ASYNC_METHOD("SetProperty",
			GDBUS_ARGS({ "property", "s" }, { "value", "v" }),
			NULL, cf_set_property) },
	{ GDBUS_ASYNC_METHOD("DisableAll",
			GDBUS_ARGS({ "type", "s" }), NULL,
			cf_disable_all) },
	{ }
};

static const GDBusSignalTable cf_signals[] = {
	{ GDBUS_SIGNAL("PropertyChanged",
			GDBUS_ARGS({ "name", "s" }, { "value", "v" })) },
	{ }
};

static DBusMessage *cf_ss_control_reply(struct ofono_call_forwarding *cf,
					struct cf_ss_request *req)
{
	const char *context = "CallForwarding";
	const char *sig = "(ssa{sv})";
	const char *ss_type = ss_control_type_to_string(req->ss_type);
	const char *cf_type = cf_type_lut[req->cf_type];
	DBusMessageIter iter;
	DBusMessageIter variant;
	DBusMessageIter vstruct;
	DBusMessageIter dict;
	DBusMessage *reply;

	reply = dbus_message_new_method_return(cf->pending);

	dbus_message_iter_init_append(reply, &iter);

	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &context);

	dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, sig,
						&variant);

	dbus_message_iter_open_container(&variant, DBUS_TYPE_STRUCT, NULL,
						&vstruct);

	dbus_message_iter_append_basic(&vstruct, DBUS_TYPE_STRING,
					&ss_type);

	dbus_message_iter_append_basic(&vstruct, DBUS_TYPE_STRING,
					&cf_type);

	dbus_message_iter_open_container(&vstruct, DBUS_TYPE_ARRAY,
				OFONO_PROPERTIES_ARRAY_SIGNATURE, &dict);

	if (req->cf_type == CALL_FORWARDING_TYPE_UNCONDITIONAL ||
		req->cf_type == CALL_FORWARDING_TYPE_ALL)
		property_append_cf_conditions(&dict,
			req->cf_list[CALL_FORWARDING_TYPE_UNCONDITIONAL],
			req->cls,
			cf_type_lut[CALL_FORWARDING_TYPE_UNCONDITIONAL]);

	if (req->cf_type == CALL_FORWARDING_TYPE_NO_REPLY ||
		req->cf_type == CALL_FORWARDING_TYPE_ALL ||
		req->cf_type == CALL_FORWARDING_TYPE_ALL_CONDITIONAL)
		property_append_cf_conditions(&dict,
			req->cf_list[CALL_FORWARDING_TYPE_NO_REPLY],
			req->cls, cf_type_lut[CALL_FORWARDING_TYPE_NO_REPLY]);

	if (req->cf_type == CALL_FORWARDING_TYPE_NOT_REACHABLE ||
		req->cf_type == CALL_FORWARDING_TYPE_ALL ||
		req->cf_type == CALL_FORWARDING_TYPE_ALL_CONDITIONAL)
		property_append_cf_conditions(&dict,
			req->cf_list[CALL_FORWARDING_TYPE_NOT_REACHABLE],
			req->cls,
			cf_type_lut[CALL_FORWARDING_TYPE_NOT_REACHABLE]);

	if (req->cf_type == CALL_FORWARDING_TYPE_BUSY ||
		req->cf_type == CALL_FORWARDING_TYPE_ALL ||
		req->cf_type == CALL_FORWARDING_TYPE_ALL_CONDITIONAL)
		property_append_cf_conditions(&dict,
			req->cf_list[CALL_FORWARDING_TYPE_BUSY],
			req->cls, cf_type_lut[CALL_FORWARDING_TYPE_BUSY]);

	dbus_message_iter_close_container(&vstruct, &dict);

	dbus_message_iter_close_container(&variant, &vstruct);

	dbus_message_iter_close_container(&iter, &variant);

	return reply;
}

static void ss_set_query_cf_callback(const struct ofono_error *error, int total,
			const struct ofono_call_forwarding_condition *list,
			void *data)
{
	struct ofono_call_forwarding *cf = data;
	GSList *l;
	DBusMessage *reply;

	if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
		ofono_error("Query failed with error: %s",
						telephony_error_to_str(error));
		cf->flags &= ~CALL_FORWARDING_FLAG_CACHED;
		reply = __ofono_error_from_error(error, cf->pending);
		__ofono_dbus_pending_reply(&cf->pending, reply);
		return;
	}

	l = cf_cond_list_create(total, list);
	DBG("%s conditions:", cf_type_lut[cf->query_next]);
	cf_cond_list_print(l);

	cf->ss_req->cf_list[cf->query_next] = l;

	if (cf->query_next == cf->query_end) {
		reply = cf_ss_control_reply(cf, cf->ss_req);
		__ofono_dbus_pending_reply(&cf->pending, reply);
		g_free(cf->ss_req);
		cf->ss_req = NULL;
	}

	set_new_cond_list(cf, cf->query_next, l);

	if (cf->query_next != cf->query_end) {
		cf->query_next++;
		ss_set_query_next_cf_cond(cf);
	}
}

static void ss_set_query_next_cf_cond(struct ofono_call_forwarding *cf)
{
	int cls;

	cls = (cf->ss_req->ss_type == SS_CONTROL_TYPE_QUERY) ?
			cf->ss_req->cls : BEARER_CLASS_DEFAULT;

	if (cls == BEARER_CLASS_SS_DEFAULT)
			cls = BEARER_CLASS_DEFAULT;

	cf->driver->query(cf, cf->query_next, cls,
			ss_set_query_cf_callback, cf);
}

static void cf_ss_control_callback(const struct ofono_error *error, void *data)
{
	struct ofono_call_forwarding *cf = data;

	if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {

		DBG("CF ss control set/erasure failed with error: %s",
						telephony_error_to_str(error));
		__ofono_dbus_pending_reply(&cf->pending,
				__ofono_error_from_error(error, cf->pending));
		g_free(cf->ss_req);
		cf->ss_req = NULL;
		return;
	}

	ss_set_query_next_cf_cond(cf);
}

static gboolean cf_ss_control(int type, const char *sc,
				const char *sia, const char *sib,
				const char *sic, const char *dn,
				DBusMessage *msg, void *data)
{
	struct ofono_call_forwarding *cf = data;
	DBusConnection *conn = ofono_dbus_get_connection();
	int cls = BEARER_CLASS_SS_DEFAULT;
	int timeout = DEFAULT_NO_REPLY_TIMEOUT;
	int cf_type;
	DBusMessage *reply;
	struct ofono_phone_number ph;
	void *operation = NULL;

	/* Before we do anything, make sure we're actually initialized */
	if (cf == NULL)
		return FALSE;

	if (__ofono_call_forwarding_is_busy(cf)) {
		reply = __ofono_error_busy(msg);
		g_dbus_send_message(conn, reply);

		return TRUE;
	}

	DBG("Received call forwarding ss control request");

	DBG("type: %d, sc: %s, sia: %s, sib: %s, sic: %s, dn: %s",
		type, sc, sia, sib, sic, dn);

	if (!strcmp(sc, "21"))
		cf_type = CALL_FORWARDING_TYPE_UNCONDITIONAL;
	else if (!strcmp(sc, "67"))
		cf_type = CALL_FORWARDING_TYPE_BUSY;
	else if (!strcmp(sc, "61"))
		cf_type = CALL_FORWARDING_TYPE_NO_REPLY;
	else if (!strcmp(sc, "62"))
		cf_type = CALL_FORWARDING_TYPE_NOT_REACHABLE;
	else if (!strcmp(sc, "002"))
		cf_type = CALL_FORWARDING_TYPE_ALL;
	else if (!strcmp(sc, "004"))
		cf_type = CALL_FORWARDING_TYPE_ALL_CONDITIONAL;
	else
		return FALSE;

	if (strlen(sia) &&
			(type == SS_CONTROL_TYPE_QUERY ||
				type == SS_CONTROL_TYPE_ERASURE ||
				type == SS_CONTROL_TYPE_DEACTIVATION))
		goto error;

	/*
	 * Activation / Registration is figured context specific according to
	 * 22.030 Section 6.5.2 "The UE shall determine from the context
	 * whether, an entry of a single *, activation or registration
	 * was intended."
	 */
	if (type == SS_CONTROL_TYPE_ACTIVATION && strlen(sia) > 0)
		type = SS_CONTROL_TYPE_REGISTRATION;

	if (type == SS_CONTROL_TYPE_REGISTRATION &&
			!valid_phone_number_format(sia))
		goto error;

	if (strlen(sib) > 0) {
		long service_code;
		char *end;

		service_code = strtoul(sib, &end, 10);

		if (end == sib || *end != '\0')
			goto error;

		cls = mmi_service_code_to_bearer_class(service_code);

		if (cls == 0)
			goto error;
	}

	if (strlen(sic) > 0) {
		char *end;

		if  (type != SS_CONTROL_TYPE_REGISTRATION)
			goto error;

		if (cf_type != CALL_FORWARDING_TYPE_ALL &&
			cf_type != CALL_FORWARDING_TYPE_ALL_CONDITIONAL &&
			cf_type != CALL_FORWARDING_TYPE_NO_REPLY)
			goto error;

		timeout = strtoul(sic, &end, 10);

		if (end == sic || *end != '\0')
			goto error;

		if (timeout < 1 || timeout > 30)
			goto error;
	}

	switch (type) {
	case SS_CONTROL_TYPE_REGISTRATION:
		operation = cf->driver->registration;
		break;
	case SS_CONTROL_TYPE_ACTIVATION:
		operation = cf->driver->activation;
		break;
	case SS_CONTROL_TYPE_DEACTIVATION:
		operation = cf->driver->deactivation;
		break;
	case SS_CONTROL_TYPE_ERASURE:
		operation = cf->driver->erasure;
		break;
	case SS_CONTROL_TYPE_QUERY:
		operation = cf->driver->query;
		break;
	}

	if (operation == NULL) {
		reply = __ofono_error_not_implemented(msg);
		g_dbus_send_message(conn, reply);

		return TRUE;
	}

	cf->ss_req = g_try_new0(struct cf_ss_request, 1);

	if (cf->ss_req == NULL) {
		reply = __ofono_error_failed(msg);
		g_dbus_send_message(conn, reply);

		return TRUE;
	}

	cf->ss_req->ss_type = type;
	cf->ss_req->cf_type = cf_type;
	cf->ss_req->cls = cls;

	cf->pending = dbus_message_ref(msg);

	switch (cf->ss_req->cf_type) {
	case CALL_FORWARDING_TYPE_ALL:
		cf->query_next = CALL_FORWARDING_TYPE_UNCONDITIONAL;
		cf->query_end = CALL_FORWARDING_TYPE_NOT_REACHABLE;
		break;
	case CALL_FORWARDING_TYPE_ALL_CONDITIONAL:
		cf->query_next = CALL_FORWARDING_TYPE_BUSY;
		cf->query_end = CALL_FORWARDING_TYPE_NOT_REACHABLE;
		break;
	default:
		cf->query_next = cf->ss_req->cf_type;
		cf->query_end = cf->ss_req->cf_type;
		break;
	}

	/*
	 * Some modems don't understand all classes very well, particularly
	 * the older models.  So if the bearer class is the default, we
	 * just use the more commonly understood value of 7 since BEARER_SMS
	 * is not applicable to CallForwarding conditions according to 22.004
	 * Annex A
	 */
	if (cls == BEARER_CLASS_SS_DEFAULT)
		cls = BEARER_CLASS_DEFAULT;

	switch (cf->ss_req->ss_type) {
	case SS_CONTROL_TYPE_REGISTRATION:
		__string_to_phone_number(sia, &ph);
		cf->driver->registration(cf, cf_type, cls, &ph, timeout,
					cf_ss_control_callback, cf);
		break;
	case SS_CONTROL_TYPE_ACTIVATION:
		cf->driver->activation(cf, cf_type, cls, cf_ss_control_callback,
					cf);
		break;
	case SS_CONTROL_TYPE_DEACTIVATION:
		cf->driver->deactivation(cf, cf_type, cls,
					cf_ss_control_callback, cf);
		break;
	case SS_CONTROL_TYPE_ERASURE:
		cf->driver->erasure(cf, cf_type, cls, cf_ss_control_callback,
					cf);
		break;
	case SS_CONTROL_TYPE_QUERY:
		ss_set_query_next_cf_cond(cf);
		break;
	}

	return TRUE;

error:
	reply = __ofono_error_invalid_format(msg);
	g_dbus_send_message(conn, reply);
	return TRUE;
}

static void cf_register_ss_controls(struct ofono_call_forwarding *cf)
{
	__ofono_ussd_ssc_register(cf->ussd, "21", cf_ss_control, cf, NULL);
	__ofono_ussd_ssc_register(cf->ussd, "67", cf_ss_control, cf, NULL);
	__ofono_ussd_ssc_register(cf->ussd, "61", cf_ss_control, cf, NULL);
	__ofono_ussd_ssc_register(cf->ussd, "62", cf_ss_control, cf, NULL);

	__ofono_ussd_ssc_register(cf->ussd, "002", cf_ss_control, cf, NULL);
	__ofono_ussd_ssc_register(cf->ussd, "004", cf_ss_control, cf, NULL);
}

static void cf_unregister_ss_controls(struct ofono_call_forwarding *cf)
{
	__ofono_ussd_ssc_unregister(cf->ussd, "21");
	__ofono_ussd_ssc_unregister(cf->ussd, "67");
	__ofono_ussd_ssc_unregister(cf->ussd, "61");
	__ofono_ussd_ssc_unregister(cf->ussd, "62");

	__ofono_ussd_ssc_unregister(cf->ussd, "002");
	__ofono_ussd_ssc_unregister(cf->ussd, "004");
}

gboolean __ofono_call_forwarding_is_busy(struct ofono_call_forwarding *cf)
{
	return cf->pending ? TRUE : FALSE;
}

static void sim_cfis_read_cb(int ok, int total_length, int record,
			const unsigned char *data,
			int record_length, void *userdata)
{
	struct ofono_call_forwarding *cf = userdata;
	DBusConnection *conn = ofono_dbus_get_connection();
	const char *path = __ofono_atom_get_path(cf->atom);

	if (!ok || record_length < 16 || total_length < record_length) {
		cf->cfis_record_id = 0;
		return;
	}

	/*
	 * Multiple Subscriber Profile number which can have values 1-4.
	 * Profile id 1 is assumed as the current profile.
	 */
	if (data[0] != 1)
		return;

	cf->cfis_record_id = record;

	if (cf->flags & CALL_FORWARDING_FLAG_CACHED)
		return;

	/*
	 * For now we only support Voice, although Fax & all Data
	 * basic services are applicable as well.
	 */
	if (data[1] & 0x01) {
		int ton_npi;
		int number_len;
		const char *number;
		char attr[64];
		struct ofono_call_forwarding_condition *cond;
		dbus_bool_t status;

		number_len = data[2];
		ton_npi = data[3];

		if (number_len > 11 || ton_npi == 0xff)
			return;

		cond = g_try_new0(struct ofono_call_forwarding_condition, 1);
		if (cond == NULL)
			return;

		status = TRUE;
		cond->status = TRUE;
		cond->cls = BEARER_CLASS_VOICE;
		cond->time = 0;
		cond->phone_number.type = ton_npi;

		sim_extract_bcd_number(data + 4, number_len - 1,
					cond->phone_number.number);
		number = phone_number_to_string(&cond->phone_number);

		snprintf(attr, sizeof(attr), "%s%s",
			bearer_class_to_string(BEARER_CLASS_VOICE),
			cf_type_lut[CALL_FORWARDING_TYPE_UNCONDITIONAL]);

		cf->cf_conditions[CALL_FORWARDING_TYPE_UNCONDITIONAL] =
						g_slist_append(NULL, cond);

		ofono_dbus_signal_property_changed(conn, path,
					OFONO_CALL_FORWARDING_INTERFACE,
					attr, DBUS_TYPE_STRING, &number);

		ofono_dbus_signal_property_changed(conn, path,
					OFONO_CALL_FORWARDING_INTERFACE,
					"ForwardingFlagOnSim",
					DBUS_TYPE_BOOLEAN, &status);
	}
}

static void sim_cphs_cff_read_cb(int ok, int total_length, int record,
				const unsigned char *data,
				int record_length, void *userdata)
{
	struct ofono_call_forwarding *cf = userdata;
	DBusConnection *conn = ofono_dbus_get_connection();
	const char *path = __ofono_atom_get_path(cf->atom);
	dbus_bool_t cfu_voice;

	if (!ok || total_length < 1)
		return;

	cf->flags |= CALL_FORWARDING_FLAG_CPHS_CFF;

	if (cf->flags & CALL_FORWARDING_FLAG_CACHED)
		return;

	/*
	 * For now we only support Voice, although Fax & all Data
	 * basic services are applicable as well.
	 */
	if ((data[0] & 0xf) != 0xA)
		return;

	cfu_voice = TRUE;

	ofono_dbus_signal_property_changed(conn, path,
					OFONO_CALL_FORWARDING_INTERFACE,
					"ForwardingFlagOnSim",
					DBUS_TYPE_BOOLEAN, &cfu_voice);
}

static void call_forwarding_unregister(struct ofono_atom *atom)
{
	struct ofono_call_forwarding *cf = __ofono_atom_get_data(atom);
	const char *path = __ofono_atom_get_path(cf->atom);
	DBusConnection *conn = ofono_dbus_get_connection();
	struct ofono_modem *modem = __ofono_atom_get_modem(cf->atom);

	ofono_modem_remove_interface(modem, OFONO_CALL_FORWARDING_INTERFACE);
	g_dbus_unregister_interface(conn, path,
					OFONO_CALL_FORWARDING_INTERFACE);

	if (cf->sim_context) {
		ofono_sim_context_free(cf->sim_context);
		cf->sim_context = NULL;
	}

	if (cf->ussd)
		cf_unregister_ss_controls(cf);

	if (cf->ussd_watch)
		__ofono_modem_remove_atom_watch(modem, cf->ussd_watch);

	cf->flags = 0;
}

static void sim_cfis_changed(int id, void *userdata)
{
	struct ofono_call_forwarding *cf = userdata;

	if (!(cf->flags & CALL_FORWARDING_FLAG_CACHED))
		return;

	/*
	 * If the values are cached it's because at least one client
	 * requested them and we need to notify them about this
	 * change.  However the authoritative source of current
	 * Call-Forwarding settings is the network operator and the
	 * query can take a noticeable amount of time.  Instead of
	 * sending PropertyChanged, we reregister the Call Forwarding
	 * atom.  The client will invoke GetProperties only if it
	 * is still interested.
	 */
	call_forwarding_unregister(cf->atom);
	ofono_call_forwarding_register(cf);
}

static void sim_read_cf_indicator(struct ofono_call_forwarding *cf)
{
	if (__ofono_sim_service_available(cf->sim,
			SIM_UST_SERVICE_CFIS,
			SIM_SST_SERVICE_CFIS) == TRUE) {
		ofono_sim_read(cf->sim_context, SIM_EFCFIS_FILEID,
				OFONO_SIM_FILE_STRUCTURE_FIXED,
				sim_cfis_read_cb, cf);
		ofono_sim_add_file_watch(cf->sim_context, SIM_EFCFIS_FILEID,
						sim_cfis_changed, cf, NULL);
	} else {
		ofono_sim_read(cf->sim_context, SIM_EF_CPHS_CFF_FILEID,
				OFONO_SIM_FILE_STRUCTURE_TRANSPARENT,
				sim_cphs_cff_read_cb, cf);
		ofono_sim_add_file_watch(cf->sim_context,
						SIM_EF_CPHS_CFF_FILEID,
						sim_cfis_changed, cf, NULL);
	}
}

static void call_forwarding_remove(struct ofono_atom *atom)
{
	struct ofono_call_forwarding *cf = __ofono_atom_get_data(atom);

	DBG("atom: %p", atom);

	if (cf == NULL)
		return;

	if (cf->driver && cf->driver->remove)
		cf->driver->remove(cf);

	cf_clear_all(cf);

	g_free(cf);
}

OFONO_DEFINE_ATOM_CREATE(call_forwarding, OFONO_ATOM_TYPE_CALL_FORWARDING)

static void ussd_watch(struct ofono_atom *atom,
			enum ofono_atom_watch_condition cond, void *data)
{
	struct ofono_call_forwarding *cf = data;

	if (cond == OFONO_ATOM_WATCH_CONDITION_UNREGISTERED) {
		cf->ussd = NULL;
		return;
	}

	cf->ussd = __ofono_atom_get_data(atom);
	cf_register_ss_controls(cf);
}

void ofono_call_forwarding_register(struct ofono_call_forwarding *cf)
{
	DBusConnection *conn = ofono_dbus_get_connection();
	const char *path = __ofono_atom_get_path(cf->atom);
	struct ofono_modem *modem = __ofono_atom_get_modem(cf->atom);

	if (!g_dbus_register_interface(conn, path,
					OFONO_CALL_FORWARDING_INTERFACE,
					cf_methods, cf_signals, NULL, cf,
					NULL)) {
		ofono_error("Could not create %s interface",
				OFONO_CALL_FORWARDING_INTERFACE);

		return;
	}

	ofono_modem_add_interface(modem, OFONO_CALL_FORWARDING_INTERFACE);

	cf->sim = __ofono_atom_find(OFONO_ATOM_TYPE_SIM, modem);
	if (cf->sim) {
		cf->sim_context = ofono_sim_context_create(cf->sim);
		sim_read_cf_indicator(cf);
	}

	cf->ussd_watch = __ofono_modem_add_atom_watch(modem,
					OFONO_ATOM_TYPE_USSD,
					ussd_watch, cf, NULL);

	__ofono_atom_register(cf->atom, call_forwarding_unregister);
}

void ofono_call_forwarding_remove(struct ofono_call_forwarding *cf)
{
	__ofono_atom_free(cf->atom);
}

void ofono_call_forwarding_set_data(struct ofono_call_forwarding *cf,
								void *data)
{
	cf->driver_data = data;
}

void *ofono_call_forwarding_get_data(struct ofono_call_forwarding *cf)
{
	return cf->driver_data;
}
