/*
 * 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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <errno.h>

#include <glib.h>

#include <ofono/handsfree-audio.h>

#include "ofono.h"
#include "common.h"
#include "hfp.h"
#include "gatserver.h"
#include "gatppp.h"

#define RING_TIMEOUT 3

#define CVSD_OFFSET 0
#define MSBC_OFFSET 1
#define CODECS_COUNT (MSBC_OFFSET + 1)

struct hfp_codec_info {
	unsigned char type;
	ofono_bool_t supported;
};

struct ofono_emulator {
	struct ofono_atom *atom;
	enum ofono_emulator_type type;
	GAtServer *server;
	GAtPPP *ppp;
	int l_features;
	int r_features;
	GSList *indicators;
	guint callsetup_source;
	int pns_id;
	struct ofono_handsfree_card *card;
	struct hfp_codec_info r_codecs[CODECS_COUNT];
	unsigned char selected_codec;
	unsigned char negotiated_codec;
	unsigned char proposed_codec;
	ofono_emulator_codec_negotiation_cb codec_negotiation_cb;
	void *codec_negotiation_data;
	ofono_bool_t bac_received;
	bool slc : 1;
	unsigned int events_mode : 2;
	bool events_ind : 1;
	unsigned int cmee_mode : 2;
	bool clip : 1;
	bool ccwa : 1;
	bool ddr_active : 1;
};

struct indicator {
	char *name;
	int value;
	int min;
	int max;
	gboolean deferred;
	gboolean active;
	gboolean mandatory;
};

static void emulator_debug(const char *str, void *data)
{
	ofono_info("%s: %s\n", (char *)data, str);
}

static void emulator_disconnect(gpointer user_data)
{
	struct ofono_emulator *em = user_data;

	DBG("%p", em);

	ofono_emulator_remove(em);
}

static void ppp_connect(const char *iface, const char *local,
			const char *remote,
			const char *dns1, const char *dns2,
			gpointer user_data)
{
	DBG("Network Device: %s\n", iface);
	DBG("IP Address: %s\n", local);
	DBG("Remote IP Address: %s\n", remote);
	DBG("Primary DNS Server: %s\n", dns1);
	DBG("Secondary DNS Server: %s\n", dns2);
}

static void cleanup_ppp(struct ofono_emulator *em)
{
	DBG("");

	g_at_ppp_unref(em->ppp);
	em->ppp = NULL;

	__ofono_private_network_release(em->pns_id);
	em->pns_id = 0;

	g_at_server_resume(em->server);
	g_at_server_send_final(em->server, G_AT_SERVER_RESULT_NO_CARRIER);
}

static void ppp_disconnect(GAtPPPDisconnectReason reason, gpointer user_data)
{
	struct ofono_emulator *em = user_data;

	cleanup_ppp(em);
}

static void ppp_suspend(gpointer user_data)
{
	struct ofono_emulator *em = user_data;

	DBG("");

	g_at_server_resume(em->server);
}

static void suspend_server(gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	GAtIO *io = g_at_server_get_io(em->server);

	g_at_server_suspend(em->server);

	if (g_at_ppp_listen(em->ppp, io) == FALSE)
		cleanup_ppp(em);
}

static void request_private_network_cb(
			const struct ofono_private_network_settings *pns,
			void *data)
{
	struct ofono_emulator *em = data;
	GAtIO *io = g_at_server_get_io(em->server);

	if (pns == NULL)
		goto error;

	em->ppp = g_at_ppp_server_new_full(pns->server_ip, pns->fd);
	if (em->ppp == NULL) {
		close(pns->fd);
		goto badalloc;
	}

	g_at_ppp_set_server_info(em->ppp, pns->peer_ip,
					pns->primary_dns, pns->secondary_dns);

	g_at_ppp_set_acfc_enabled(em->ppp, TRUE);
	g_at_ppp_set_pfc_enabled(em->ppp, TRUE);

	g_at_ppp_set_credentials(em->ppp, "", "");
	g_at_ppp_set_debug(em->ppp, emulator_debug, "PPP");

	g_at_ppp_set_connect_function(em->ppp, ppp_connect, em);
	g_at_ppp_set_disconnect_function(em->ppp, ppp_disconnect, em);
	g_at_ppp_set_suspend_function(em->ppp, ppp_suspend, em);

	g_at_server_send_intermediate(em->server, "CONNECT");
	g_at_io_set_write_done(io, suspend_server, em);

	return;

badalloc:
	__ofono_private_network_release(em->pns_id);

error:
	em->pns_id = 0;
	g_at_server_send_final(em->server, G_AT_SERVER_RESULT_ERROR);
}

static gboolean dial_call(struct ofono_emulator *em, const char *dial_str)
{
	char c = *dial_str;

	DBG("dial call %s", dial_str);

	if (c == '*' || c == '#' || c == 'T' || c == 't') {
		if (__ofono_private_network_request(request_private_network_cb,
						&em->pns_id, em) == FALSE)
			return FALSE;
	}

	return TRUE;
}

static void dial_cb(GAtServer *server, GAtServerRequestType type,
				GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	GAtResultIter iter;
	const char *dial_str;

	DBG("");

	if (type != G_AT_SERVER_REQUEST_TYPE_SET)
		goto error;

	g_at_result_iter_init(&iter, result);

	if (!g_at_result_iter_next(&iter, ""))
		goto error;

	dial_str = g_at_result_iter_raw_line(&iter);
	if (!dial_str)
		goto error;

	if (em->ppp)
		goto error;

	if (!dial_call(em, dial_str))
		goto error;

	return;

error:
	g_at_server_send_final(em->server, G_AT_SERVER_RESULT_ERROR);
}

static void dun_ath_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	GAtResultIter iter;
	int val;

	DBG("");

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto error;

		if (g_at_result_iter_next_number(&iter, &val) == FALSE)
			goto error;

		if (val != 0)
			goto error;

		/* Fall through */

	case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY:
		if (em->ppp == NULL)
			goto error;

		g_at_ppp_unref(em->ppp);
		em->ppp = NULL;

		__ofono_private_network_release(em->pns_id);
		em->pns_id = 0;

		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;

	default:
error:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void resume_ppp(gpointer user_data)
{
	struct ofono_emulator *em = user_data;

	g_at_server_suspend(em->server);
	g_at_ppp_resume(em->ppp);
}

static void dun_ato_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	GAtIO *io = g_at_server_get_io(em->server);
	GAtResultIter iter;
	int val;

	DBG("");

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto error;

		if (g_at_result_iter_next_number(&iter, &val) == FALSE)
			goto error;

		if (val != 0)
			goto error;

		/* Fall through */
	case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY:
		if (em->ppp == NULL)
			goto error;

		g_at_server_send_intermediate(em->server, "CONNECT");
		g_at_io_set_write_done(io, resume_ppp, em);
		break;

	default:
error:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static struct indicator *find_indicator(struct ofono_emulator *em,
						const char *name, int *index)
{
	GSList *l;
	int i;

	for (i = 1, l = em->indicators; l; l = l->next, i++) {
		struct indicator *ind = l->data;

		if (g_str_equal(ind->name, name) == FALSE)
			continue;

		if (index)
			*index = i;

		return ind;
	}

	return NULL;
}

static struct ofono_call *find_call_with_status(struct ofono_emulator *em,
								int status)
{
	struct ofono_modem *modem = __ofono_atom_get_modem(em->atom);
	struct ofono_voicecall *vc;

	vc = __ofono_atom_find(OFONO_ATOM_TYPE_VOICECALL, modem);
	if (vc == NULL)
		return NULL;

	return __ofono_voicecall_find_call_with_status(vc, status);
}

static void notify_deferred_indicators(GAtServer *server, void *user_data)
{
	struct ofono_emulator *em = user_data;
	int i;
	char buf[64];
	GSList *l;
	struct indicator *ind;

	for (i = 1, l = em->indicators; l; l = l->next, i++) {
		ind = l->data;

		if (!ind->deferred)
			continue;

		if (em->events_mode == 3 && em->events_ind && em->slc &&
				ind->active) {
			sprintf(buf, "+CIEV: %d,%d", i, ind->value);
			g_at_server_send_unsolicited(em->server, buf);
		}

		ind->deferred = FALSE;
	}
}

static gboolean notify_ccwa(void *user_data)
{
	struct ofono_emulator *em = user_data;
	struct ofono_call *c;
	const char *phone;
	/*
	 * '+CCWA: "+",' + phone number + phone type on 3 digits max
	 * + terminating null
	 */
	char str[OFONO_MAX_PHONE_NUMBER_LENGTH + 14 + 1];

	if ((em->type == OFONO_EMULATOR_TYPE_HFP && em->slc == FALSE) ||
			!em->ccwa)
		goto end;

	c = find_call_with_status(em, CALL_STATUS_WAITING);

	if (c && c->clip_validity == CLIP_VALIDITY_VALID) {
		phone = phone_number_to_string(&c->phone_number);
		sprintf(str, "+CCWA: \"%s\",%d", phone, c->phone_number.type);

		g_at_server_send_unsolicited(em->server, str);
	} else
		g_at_server_send_unsolicited(em->server, "+CCWA: \"\",128");

end:
	em->callsetup_source = 0;

	return FALSE;
}

static gboolean notify_ring(void *user_data)
{
	struct ofono_emulator *em = user_data;
	struct ofono_call *c;
	const char *phone;
	/*
	 * '+CLIP: "+",' + phone number + phone type on 3 digits max
	 * + terminating null
	 */
	char str[OFONO_MAX_PHONE_NUMBER_LENGTH + 14 + 1];

	if (em->type == OFONO_EMULATOR_TYPE_HFP && em->slc == FALSE)
		return TRUE;

	g_at_server_send_unsolicited(em->server, "RING");

	if (!em->clip)
		return TRUE;

	c = find_call_with_status(em, CALL_STATUS_INCOMING);

	if (c == NULL)
		return TRUE;

	switch (c->clip_validity) {
	case CLIP_VALIDITY_VALID:
		phone = phone_number_to_string(&c->phone_number);
		sprintf(str, "+CLIP: \"%s\",%d", phone, c->phone_number.type);
		g_at_server_send_unsolicited(em->server, str);
		break;

	case CLIP_VALIDITY_WITHHELD:
		g_at_server_send_unsolicited(em->server, "+CLIP: \"\",128");
		break;
	}

	return TRUE;
}

static void brsf_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	GAtResultIter iter;
	int val;
	char buf[16];

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto fail;

		if (g_at_result_iter_next_number(&iter, &val) == FALSE)
			goto fail;

		if (val < 0 || val > 0xffff)
			goto fail;

		em->r_features = val;

		sprintf(buf, "+BRSF: %d", em->l_features);
		g_at_server_send_info(em->server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;

	default:
fail:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void cind_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	GSList *l;
	struct indicator *ind;
	gsize size;
	int len;
	char *buf;
	char *tmp;

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		/*
		 * "+CIND: " + terminating null + number of indicators *
		 * (max of 3 digits in the value + separator)
		 */
		size = 7 + 1 + (g_slist_length(em->indicators) * 4);
		buf = g_try_malloc0(size);
		if (buf == NULL)
			goto fail;

		len = sprintf(buf, "+CIND: ");
		tmp = buf + len;

		for (l = em->indicators; l; l = l->next) {
			ind = l->data;
			len = sprintf(tmp, "%s%d",
					l == em->indicators ? "" : ",",
					ind->value);
			tmp = tmp + len;
		}

		g_at_server_send_info(em->server, buf, TRUE);
		g_free(buf);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;

	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		/*
		 * '+CIND: ' + terminating null + number of indicators *
		 * ( indicator name + '("",(000,000))' + separator)
		 */
		size = 8;

		for (l = em->indicators; l; l = l->next) {
			ind = l->data;
			size += strlen(ind->name) + 15;
		}

		buf = g_try_malloc0(size);
		if (buf == NULL)
			goto fail;

		len = sprintf(buf, "+CIND: ");
		tmp = buf + len;

		for (l = em->indicators; l; l = l->next) {
			ind = l->data;
			len = sprintf(tmp, "%s(\"%s\",(%d%c%d))",
					l == em->indicators ? "" : ",",
					ind->name, ind->min,
					(ind->max - ind->min) == 1 ? ',' : '-',
					ind->max);
			tmp = tmp + len;
		}

		g_at_server_send_info(server, buf, TRUE);
		g_free(buf);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;

	default:
fail:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void cmer_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	char buf[32];

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		sprintf(buf, "+CMER: %d,0,0,%d,0", em->events_mode,
						em->events_ind);
		g_at_server_send_info(em->server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;

	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		sprintf(buf, "+CMER: (0,3),(0),(0),(0,1),(0)");
		g_at_server_send_info(em->server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;

	case G_AT_SERVER_REQUEST_TYPE_SET:
	{
		GAtResultIter iter;
		int mode = em->events_mode;
		int ind = em->events_ind;
		int val;

		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto fail;

		/* mode */
		if (!g_at_result_iter_next_number_default(&iter, mode, &mode))
			goto fail;

		if (mode != 0 && mode != 3)
			goto fail;

		/* keyp */
		if (!g_at_result_iter_next_number_default(&iter, 0, &val)) {
			if (!g_at_result_iter_skip_next(&iter))
				goto done;
			goto fail;
		}

		if (val != 0)
			goto fail;

		/* disp */
		if (!g_at_result_iter_next_number_default(&iter, 0, &val)) {
			if (!g_at_result_iter_skip_next(&iter))
				goto done;
			goto fail;
		}

		if (val != 0)
			goto fail;

		/* ind */
		if (!g_at_result_iter_next_number_default(&iter, ind, &ind)) {
			if (!g_at_result_iter_skip_next(&iter))
				goto done;
			goto fail;
		}

		if (ind != 0 && ind != 1)
			goto fail;

		/* bfr */
		if (!g_at_result_iter_next_number_default(&iter, 0, &val)) {
			if (!g_at_result_iter_skip_next(&iter))
				goto done;
			goto fail;
		}

		if (val != 0)
			goto fail;

		/* check that bfr is last parameter */
		if (g_at_result_iter_skip_next(&iter))
			goto fail;

done:
		em->events_mode = mode;
		em->events_ind = ind;

		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);

		__ofono_emulator_slc_condition(em,
					OFONO_EMULATOR_SLC_CONDITION_CMER);
		break;
	}

	default:
fail:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void clip_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	GAtResultIter iter;
	int val;

	if (em->slc == FALSE)
		goto fail;

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto fail;

		if (!g_at_result_iter_next_number(&iter, &val))
			goto fail;

		if (val != 0 && val != 1)
			goto fail;

		/* check this is last parameter */
		if (g_at_result_iter_skip_next(&iter))
			goto fail;

		em->clip = val;

		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;

	default:
fail:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
	}
}

static void ccwa_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	GAtResultIter iter;
	int val;
	struct indicator *call_ind;
	struct indicator *cs_ind;

	if (em->slc == FALSE)
		goto fail;

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto fail;

		if (!g_at_result_iter_next_number(&iter, &val))
			goto fail;

		if (val != 0 && val != 1)
			goto fail;

		/* check this is last parameter */
		if (g_at_result_iter_skip_next(&iter))
			goto fail;

		call_ind = find_indicator(em, OFONO_EMULATOR_IND_CALL, NULL);
		cs_ind = find_indicator(em, OFONO_EMULATOR_IND_CALLSETUP, NULL);

		if (cs_ind->value == OFONO_EMULATOR_CALLSETUP_INCOMING &&
				call_ind->value == OFONO_EMULATOR_CALL_ACTIVE &&
				em->ccwa == FALSE && val == 1)
			em->callsetup_source = g_timeout_add_seconds(0,
							notify_ccwa, em);

		em->ccwa = val;

		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;

	default:
fail:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
	}
}

static void cmee_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	GAtResultIter iter;
	int val;
	char buf[16];

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto fail;

		if (g_at_result_iter_next_number(&iter, &val) == FALSE)
			goto fail;

		if (val != 0 && val != 1)
			goto fail;

		em->cmee_mode = val;

		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;

	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		sprintf(buf, "+CMEE: %d", em->cmee_mode);
		g_at_server_send_info(em->server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;

	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		/* HFP only support 0 and 1 */
		sprintf(buf, "+CMEE: (0,1)");
		g_at_server_send_info(em->server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;

	default:
fail:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void bia_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
	{
		GAtResultIter iter;
		GSList *l;
		int val;

		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto fail;

		/* check validity of the request */
		while (g_at_result_iter_next_number_default(&iter, 0, &val))
			if (val != 0 &&  val != 1)
				goto fail;

		/* Check that we have no non-numbers in the stream */
		if (g_at_result_iter_skip_next(&iter) == TRUE)
			goto fail;

		/* request is valid, update the indicator activation status */
		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto fail;

		for (l = em->indicators; l; l = l->next) {
			struct indicator *ind = l->data;

			if (g_at_result_iter_next_number_default(&iter,
						ind->active, &val) == FALSE)
				break;

			if (ind->mandatory == TRUE)
				continue;

			ind->active = val;
		}

		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	}

	default:
fail:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void bind_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	char buf[128];

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		g_at_server_send_info(em->server, "+BIND: 1,1", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);

		__ofono_emulator_slc_condition(em,
					OFONO_EMULATOR_SLC_CONDITION_BIND);
		break;

	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		sprintf(buf, "+BIND: (1)");
		g_at_server_send_info(em->server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;

	case G_AT_SERVER_REQUEST_TYPE_SET:
	{
		GAtResultIter iter;
		int hf_indicator;
		int num_hf_indicators = 0;

		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto fail;

		/* check validity of the request */
		while (num_hf_indicators < 20 &&
				g_at_result_iter_next_number(&iter,
							&hf_indicator)) {
			if (hf_indicator > 0xffff)
				goto fail;

			num_hf_indicators += 1;
		}

		/* Check that we have nothing extra in the stream */
		if (g_at_result_iter_skip_next(&iter) == TRUE)
			goto fail;

		/* request is valid, update the indicator activation status */
		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto fail;

		while (g_at_result_iter_next_number(&iter, &hf_indicator))
			ofono_info("HF supports indicator: 0x%04x",
					hf_indicator);

		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);

		break;
	}

	default:
fail:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void biev_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
	{
		GAtResultIter iter;
		int hf_indicator;
		int val;

		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto fail;

		if (g_at_result_iter_next_number(&iter, &hf_indicator) == FALSE)
			goto fail;

		if (hf_indicator != HFP_HF_INDICATOR_ENHANCED_SAFETY)
			goto fail;

		if (em->ddr_active == FALSE)
			goto fail;

		if (g_at_result_iter_next_number(&iter, &val) == FALSE)
			goto fail;

		if (val < 0 || val > 1)
			goto fail;

		ofono_info("Enhanced Safety indicator: %d", val);

		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	}

	default:
fail:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void finish_codec_negotiation(struct ofono_emulator *em,
			int err)
{
	if (em->codec_negotiation_cb == NULL)
		return;

	em->codec_negotiation_cb(err, em->codec_negotiation_data);

	em->codec_negotiation_cb = NULL;
	em->codec_negotiation_data = NULL;
}

static void bac_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	GAtResultIter iter;
	int val;

	DBG("");

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			goto fail;

		/*
		 * CVSD codec is mandatory and must come first.
		 * See HFP v1.6 4.34.1
		 */
		if (g_at_result_iter_next_number(&iter, &val) == FALSE ||
				val != HFP_CODEC_CVSD)
			goto fail;

		em->bac_received = TRUE;

		em->negotiated_codec = 0;
		em->r_codecs[CVSD_OFFSET].supported = TRUE;

		while (g_at_result_iter_next_number(&iter, &val)) {
			switch (val) {
			case HFP_CODEC_MSBC:
				em->r_codecs[MSBC_OFFSET].supported = TRUE;
				break;
			default:
				DBG("Unsupported HFP codec %d", val);
				break;
			}
		}

		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);

		/*
		 * If we're currently in the process of selecting a codec
		 * we have to restart that now
		 */
		if (em->proposed_codec) {
			em->proposed_codec = 0;
			ofono_emulator_start_codec_negotiation(em, NULL, NULL);
		}

		break;

	default:
fail:
		DBG("Process AT+BAC failed");
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);

		finish_codec_negotiation(em, -EIO);

		break;
	}
}

static void connect_sco(struct ofono_emulator *em)
{
	int err;

	DBG("");

	if (em->card == NULL) {
		finish_codec_negotiation(em, -EINVAL);
		return;
	}

	err = ofono_handsfree_card_connect_sco(em->card);
	if (err == 0) {
		finish_codec_negotiation(em, 0);
		return;
	}

	/* If we have another codec we can try then lets do that */
	if (em->negotiated_codec != HFP_CODEC_CVSD) {
		em->selected_codec = HFP_CODEC_CVSD;
		ofono_emulator_start_codec_negotiation(em,
					em->codec_negotiation_cb,
					em->codec_negotiation_data);
		return;
	}

	finish_codec_negotiation(em, -EIO);
}

static void bcs_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;
	GAtResultIter iter;
	int val;

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_result_iter_init(&iter, result);

		if (!g_at_result_iter_next(&iter, ""))
			break;

		if (!g_at_result_iter_next_number(&iter, &val))
			break;

		if (em->proposed_codec != val) {
			em->proposed_codec = 0;
			break;
		}

		em->proposed_codec = 0;
		em->negotiated_codec = val;

		DBG("negotiated codec %d", val);

		if (em->card != NULL)
			ofono_handsfree_card_set_codec(em->card,
							em->negotiated_codec);

		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);

		connect_sco(em);

		return;
	default:
		break;
	}

	finish_codec_negotiation(em, -EIO);

	g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
}

static void bcc_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *result, gpointer user_data)
{
	struct ofono_emulator *em = user_data;

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);

		if (!em->negotiated_codec) {
			ofono_emulator_start_codec_negotiation(em, NULL, NULL);
			return;
		}

		connect_sco(em);

		return;
	default:
		break;
	}

	g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
}

static void emulator_add_indicator(struct ofono_emulator *em, const char* name,
					int min, int max, int dflt,
					gboolean mandatory)
{
	struct indicator *ind;

	ind = g_try_new0(struct indicator, 1);
	if (ind == NULL) {
		ofono_error("Unable to allocate indicator structure");
		return;
	}

	ind->name = l_strdup(name);
	ind->min = min;
	ind->max = max;
	ind->value = dflt;
	ind->active = TRUE;
	ind->mandatory = mandatory;

	em->indicators = g_slist_append(em->indicators, ind);
}

static void emulator_unregister(struct ofono_atom *atom)
{
	struct ofono_emulator *em = __ofono_atom_get_data(atom);
	GSList *l;

	DBG("%p", em);

	if (em->callsetup_source) {
		g_source_remove(em->callsetup_source);
		em->callsetup_source = 0;
	}

	for (l = em->indicators; l; l = l->next) {
		struct indicator *ind = l->data;

		l_free(ind->name);
		g_free(ind);
	}

	g_slist_free(em->indicators);
	em->indicators = NULL;

	g_at_ppp_unref(em->ppp);
	em->ppp = NULL;

	if (em->pns_id > 0) {
		__ofono_private_network_release(em->pns_id);
		em->pns_id = 0;
	}

	g_at_server_unref(em->server);
	em->server = NULL;

	ofono_handsfree_card_remove(em->card);
	em->card = NULL;
}

void ofono_emulator_register(struct ofono_emulator *em, int fd)
{
	GIOChannel *io;

	DBG("%p, %d", em, fd);

	if (fd < 0)
		return;

	io = g_io_channel_unix_new(fd);

	em->server = g_at_server_new(io);
	if (em->server == NULL)
		return;

	g_io_channel_unref(io);

	g_at_server_set_debug(em->server, emulator_debug, "Server");
	g_at_server_set_disconnect_function(em->server,
						emulator_disconnect, em);
	g_at_server_set_finish_callback(em->server, notify_deferred_indicators,
						em);

	if (em->type == OFONO_EMULATOR_TYPE_HFP) {
		em->ddr_active = true;

		emulator_add_indicator(em, OFONO_EMULATOR_IND_SERVICE, 0, 1, 0,
									FALSE);
		emulator_add_indicator(em, OFONO_EMULATOR_IND_CALL, 0, 1, 0,
									TRUE);
		emulator_add_indicator(em, OFONO_EMULATOR_IND_CALLSETUP, 0, 3,
								0, TRUE);
		emulator_add_indicator(em, OFONO_EMULATOR_IND_CALLHELD, 0, 2,
								0, TRUE);
		emulator_add_indicator(em, OFONO_EMULATOR_IND_SIGNAL, 0, 5, 0,
									FALSE);
		emulator_add_indicator(em, OFONO_EMULATOR_IND_ROAMING, 0, 1, 0,
									FALSE);
		emulator_add_indicator(em, OFONO_EMULATOR_IND_BATTERY, 0, 5, 5,
									FALSE);

		g_at_server_register(em->server, "+BRSF", brsf_cb, em, NULL);
		g_at_server_register(em->server, "+CIND", cind_cb, em, NULL);
		g_at_server_register(em->server, "+CMER", cmer_cb, em, NULL);
		g_at_server_register(em->server, "+CLIP", clip_cb, em, NULL);
		g_at_server_register(em->server, "+CCWA", ccwa_cb, em, NULL);
		g_at_server_register(em->server, "+CMEE", cmee_cb, em, NULL);
		g_at_server_register(em->server, "+BIA", bia_cb, em, NULL);
		g_at_server_register(em->server, "+BIND", bind_cb, em, NULL);
		g_at_server_register(em->server, "+BIEV", biev_cb, em, NULL);
		g_at_server_register(em->server, "+BAC", bac_cb, em, NULL);
		g_at_server_register(em->server, "+BCC", bcc_cb, em, NULL);
		g_at_server_register(em->server, "+BCS", bcs_cb, em, NULL);
	}

	__ofono_atom_register(em->atom, emulator_unregister);

	switch (em->type) {
	case OFONO_EMULATOR_TYPE_DUN:
		g_at_server_register(em->server, "D", dial_cb, em, NULL);
		g_at_server_register(em->server, "H", dun_ath_cb, em, NULL);
		g_at_server_register(em->server, "O", dun_ato_cb, em, NULL);
		break;
	case OFONO_EMULATOR_TYPE_HFP:
		g_at_server_set_echo(em->server, FALSE);
		break;
	default:
		break;
	}
}

static void emulator_remove(struct ofono_atom *atom)
{
	struct ofono_emulator *em = __ofono_atom_get_data(atom);

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

	g_free(em);
}

struct ofono_emulator *ofono_emulator_create(struct ofono_modem *modem,
						enum ofono_emulator_type type)
{
	struct ofono_emulator *em;
	enum ofono_atom_type atom_t;

	DBG("modem: %p, type: %d", modem, type);

	if (type == OFONO_EMULATOR_TYPE_DUN)
		atom_t = OFONO_ATOM_TYPE_EMULATOR_DUN;
	else if (type == OFONO_EMULATOR_TYPE_HFP)
		atom_t = OFONO_ATOM_TYPE_EMULATOR_HFP;
	else
		return NULL;

	em = g_try_new0(struct ofono_emulator, 1);

	if (em == NULL)
		return NULL;

	em->type = type;
	em->l_features |= HFP_AG_FEATURE_3WAY;
	em->l_features |= HFP_AG_FEATURE_REJECT_CALL;
	em->l_features |= HFP_AG_FEATURE_ENHANCED_CALL_STATUS;
	em->l_features |= HFP_AG_FEATURE_ENHANCED_CALL_CONTROL;
	em->l_features |= HFP_AG_FEATURE_EXTENDED_RES_CODE;
	em->l_features |= HFP_AG_FEATURE_HF_INDICATORS;
	em->l_features |= HFP_AG_FEATURE_CODEC_NEGOTIATION;
	em->events_mode = 3;	/* default mode is forwarding events */
	em->cmee_mode = 0;	/* CME ERROR disabled by default */

	em->atom = __ofono_modem_add_atom_offline(modem, atom_t,
							emulator_remove, em);

	return em;
}

void ofono_emulator_remove(struct ofono_emulator *em)
{
	__ofono_atom_free(em->atom);
}

void ofono_emulator_send_final(struct ofono_emulator *em,
				const struct ofono_error *final)
{
	char buf[256];

	/*
	 * TODO: Handle various CMEE modes and report error strings from
	 * common.c
	 */
	switch (final->type) {
	case OFONO_ERROR_TYPE_CMS:
		sprintf(buf, "+CMS ERROR: %d", final->error);
		g_at_server_send_ext_final(em->server, buf);
		break;

	case OFONO_ERROR_TYPE_CME:
		switch (em->cmee_mode) {
		case 1:
			sprintf(buf, "+CME ERROR: %d", final->error);
			break;

		case 2:
			sprintf(buf, "+CME ERROR: %s",
						telephony_error_to_str(final));
			break;

		default:
			goto failure;
		}

		g_at_server_send_ext_final(em->server, buf);
		break;

	case OFONO_ERROR_TYPE_NO_ERROR:
		g_at_server_send_final(em->server, G_AT_SERVER_RESULT_OK);
		break;

	case OFONO_ERROR_TYPE_CEER:
	case OFONO_ERROR_TYPE_SIM:
	case OFONO_ERROR_TYPE_FAILURE:
	case OFONO_ERROR_TYPE_ERRNO:
failure:
		g_at_server_send_final(em->server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

void ofono_emulator_send_unsolicited(struct ofono_emulator *em,
					const char *result)
{
	g_at_server_send_unsolicited(em->server, result);
}

void ofono_emulator_send_intermediate(struct ofono_emulator *em,
					const char *result)
{
	g_at_server_send_intermediate(em->server, result);
}

void ofono_emulator_send_info(struct ofono_emulator *em, const char *line,
				ofono_bool_t last)
{
	g_at_server_send_info(em->server, line, last);
}

struct handler {
	ofono_emulator_request_cb_t cb;
	void *data;
	ofono_destroy_func destroy;
	struct ofono_emulator *em;
};

struct ofono_emulator_request {
	GAtResultIter iter;
	enum ofono_emulator_request_type type;
};

static void handler_proxy(GAtServer *server, GAtServerRequestType type,
					GAtResult *result, gpointer userdata)
{
	struct handler *h = userdata;
	struct ofono_emulator_request req;

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY:
		req.type = OFONO_EMULATOR_REQUEST_TYPE_COMMAND_ONLY;
		break;
	case G_AT_SERVER_REQUEST_TYPE_SET:
		req.type = OFONO_EMULATOR_REQUEST_TYPE_SET;
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		req.type = OFONO_EMULATOR_REQUEST_TYPE_QUERY;
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		req.type = OFONO_EMULATOR_REQUEST_TYPE_SUPPORT;
	}

	g_at_result_iter_init(&req.iter, result);
	g_at_result_iter_next(&req.iter, "");

	h->cb(h->em, &req, h->data);
}

static void handler_proxy_need_slc(GAtServer *server,
					GAtServerRequestType type,
					GAtResult *result, gpointer userdata)
{
	struct handler *h = userdata;

	if (h->em->slc == FALSE) {
		g_at_server_send_final(h->em->server, G_AT_SERVER_RESULT_ERROR);
		return;
	}

	handler_proxy(server, type, result, userdata);
}

static void handler_proxy_chld(GAtServer *server, GAtServerRequestType type,
				GAtResult *result, gpointer userdata)
{
	struct handler *h = userdata;

	if (h->em->slc == FALSE && type != G_AT_SERVER_REQUEST_TYPE_SUPPORT) {
		g_at_server_send_final(h->em->server, G_AT_SERVER_RESULT_ERROR);
		return;
	}

	handler_proxy(server, type, result, userdata);
}

static void handler_destroy(gpointer userdata)
{
	struct handler *h = userdata;

	if (h->destroy)
		h->destroy(h->data);

	g_free(h);
}

ofono_bool_t ofono_emulator_add_handler(struct ofono_emulator *em,
					const char *prefix,
					ofono_emulator_request_cb_t cb,
					void *data, ofono_destroy_func destroy)
{
	struct handler *h;
	GAtServerNotifyFunc func = handler_proxy;

	h = g_new0(struct handler, 1);
	h->cb = cb;
	h->data = data;
	h->destroy = destroy;
	h->em = em;

	if (em->type == OFONO_EMULATOR_TYPE_HFP) {
		func = handler_proxy_need_slc;

		if (!strcmp(prefix, "+CHLD"))
			func = handler_proxy_chld;
	}

	if (g_at_server_register(em->server, prefix, func, h,
					handler_destroy) == TRUE)
		return TRUE;

	g_free(h);

	return FALSE;
}

ofono_bool_t ofono_emulator_remove_handler(struct ofono_emulator *em,
						const char *prefix)
{
	return g_at_server_unregister(em->server, prefix);
}

ofono_bool_t ofono_emulator_request_next_string(
					struct ofono_emulator_request *req,
					const char **str)
{
	return g_at_result_iter_next_string(&req->iter, str);
}

ofono_bool_t ofono_emulator_request_next_number(
					struct ofono_emulator_request *req,
					int *number)
{
	return g_at_result_iter_next_number(&req->iter, number);
}

const char *ofono_emulator_request_get_raw(struct ofono_emulator_request *req)
{
	return g_at_result_iter_raw_line(&req->iter);
}

enum ofono_emulator_request_type ofono_emulator_request_get_type(
					struct ofono_emulator_request *req)
{
	return req->type;
}

void ofono_emulator_set_indicator(struct ofono_emulator *em,
					const char *name, int value)
{
	int i;
	char buf[20];
	struct indicator *ind;
	struct indicator *call_ind;
	struct indicator *cs_ind;
	gboolean call;
	gboolean callsetup;
	gboolean waiting;

	ind = find_indicator(em, name, &i);

	if (ind == NULL || ind->value == value || value < ind->min
			|| value > ind->max)
		return;

	ind->value = value;

	call_ind = find_indicator(em, OFONO_EMULATOR_IND_CALL, NULL);
	cs_ind = find_indicator(em, OFONO_EMULATOR_IND_CALLSETUP, NULL);

	call = ind == call_ind;
	callsetup = ind == cs_ind;

	/*
	 * When callsetup indicator goes to Incoming and there is an active
	 * call a +CCWA should be sent before +CIEV
	 */
	waiting = (callsetup && value == OFONO_EMULATOR_CALLSETUP_INCOMING &&
			call_ind->value == OFONO_EMULATOR_CALL_ACTIVE);

	if (waiting)
		notify_ccwa(em);

	if (em->events_mode == 3 && em->events_ind && em->slc && ind->active) {
		if (!g_at_server_command_pending(em->server)) {
			sprintf(buf, "+CIEV: %d,%d", i, ind->value);
			g_at_server_send_unsolicited(em->server, buf);
		} else
			ind->deferred = TRUE;
	}

	/*
	 * Ring timer should be started when:
	 * - callsetup indicator is set to Incoming and there is no active call
	 *   (not a waiting call)
	 * - or call indicator is set to inactive while callsetup is already
	 *   set to Incoming.
	 * In those cases, a first RING should be sent just after the +CIEV
	 * Ring timer should be stopped for all other values of callsetup
	 */
	if (waiting)
		return;

	/* Call state went from active/held + waiting -> incoming */
	if (call && value == OFONO_EMULATOR_CALL_INACTIVE &&
			cs_ind->value == OFONO_EMULATOR_CALLSETUP_INCOMING)
		goto start_ring;

	if (!callsetup)
		return;

	if (value != OFONO_EMULATOR_CALLSETUP_INCOMING) {
		if (em->callsetup_source > 0) {
			g_source_remove(em->callsetup_source);
			em->callsetup_source = 0;
		}

		return;
	}

start_ring:
	notify_ring(em);
	em->callsetup_source = g_timeout_add_seconds(RING_TIMEOUT,
							notify_ring, em);
}

void __ofono_emulator_set_indicator_forced(struct ofono_emulator *em,
						const char *name, int value)
{
	int i;
	struct indicator *ind;
	char buf[20];

	ind = find_indicator(em, name, &i);

	if (ind == NULL || value < ind->min || value > ind->max)
		return;

	ind->value = value;

	if (em->events_mode == 3 && em->events_ind && em->slc && ind->active) {
		if (!g_at_server_command_pending(em->server)) {
			sprintf(buf, "+CIEV: %d,%d", i, ind->value);
			g_at_server_send_unsolicited(em->server, buf);
		} else
			ind->deferred = TRUE;
	}
}

void __ofono_emulator_slc_condition(struct ofono_emulator *em,
					enum ofono_emulator_slc_condition cond)
{
	if (em->slc == TRUE)
		return;

	switch (cond) {
	case OFONO_EMULATOR_SLC_CONDITION_CMER:
		if ((em->r_features & HFP_HF_FEATURE_3WAY) &&
				(em->l_features & HFP_AG_FEATURE_3WAY))
			return;
		/* Fall Through */

	case OFONO_EMULATOR_SLC_CONDITION_CHLD:
		if ((em->r_features & HFP_HF_FEATURE_HF_INDICATORS) &&
				(em->l_features & HFP_HF_FEATURE_HF_INDICATORS))
			return;
		/* Fall Through */

	case OFONO_EMULATOR_SLC_CONDITION_BIND:
		ofono_info("SLC reached");
		em->slc = TRUE;

		ofono_handsfree_card_register(em->card);

	default:
		break;
	}
}

void ofono_emulator_set_hf_indicator_active(struct ofono_emulator *em,
						int indicator,
						ofono_bool_t active)
{
	char buf[64];

	if (!(em->l_features & HFP_HF_FEATURE_HF_INDICATORS))
		return;

	if (!(em->r_features & HFP_HF_FEATURE_HF_INDICATORS))
		return;

	if (indicator != HFP_HF_INDICATOR_ENHANCED_SAFETY)
		return;

	em->ddr_active = active;

	sprintf(buf, "+BIND: %d,%d", HFP_HF_INDICATOR_ENHANCED_SAFETY, active);
	g_at_server_send_unsolicited(em->server, buf);
}

void ofono_emulator_set_handsfree_card(struct ofono_emulator *em,
					struct ofono_handsfree_card *card)
{
	if (em == NULL)
		return;

	em->card = card;
}

static unsigned char select_codec(struct ofono_emulator *em)
{
	if (ofono_handsfree_audio_has_wideband() &&
			em->r_codecs[MSBC_OFFSET].supported)
		return HFP_CODEC_MSBC;

	/* CVSD is mandatory for both sides */
	return HFP_CODEC_CVSD;
}

int ofono_emulator_start_codec_negotiation(struct ofono_emulator *em,
			ofono_emulator_codec_negotiation_cb cb, void *data)
{
	char buf[64];
	unsigned char codec;

	if (em == NULL)
		return -EINVAL;

	if (cb != NULL && em->codec_negotiation_cb != NULL)
		return -EALREADY;

	if (em->proposed_codec > 0)
		return -EALREADY;

	if (!em->bac_received || em->negotiated_codec > 0) {
		/*
		 * Report we're done even if we don't have done any
		 * negotiation as the other side may have to clean up.
		 */
		if (cb)
			cb(0, data);

		/*
		 * If we didn't received any +BAC during the SLC setup the
		 * remote side doesn't support codec negotiation and we can
		 * directly connect our card. Otherwise if we got +BAC and
		 * already have a negotiated codec we can proceed here
		 * without doing any negotiation again.
		 */
		ofono_handsfree_card_connect_sco(em->card);

		return 0;
	}

	if (em->selected_codec > 0) {
		codec = em->selected_codec;
		em->selected_codec = 0;
		goto done;
	}

	codec = select_codec(em);
	if (!codec) {
		DBG("Failed to select HFP codec");
		return -EINVAL;
	}

done:
	em->proposed_codec = codec;

	em->codec_negotiation_cb = cb;
	em->codec_negotiation_data = data;

	snprintf(buf, 64, "+BCS: %d", em->proposed_codec);
	g_at_server_send_unsolicited(em->server, buf);

	return 0;
}
