/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <sys/socket.h>
#include <syslog.h>

#include "cras_bt_device.h"
#include "cras_telephony.h"
#include "cras_hfp_ag_profile.h"
#include "cras_hfp_slc.h"
#include "cras_system_state.h"

#define SLC_BUF_SIZE_BYTES 256

/* Indicator update command response and indicator indices.
 * Note that indicator index starts from '1'.
 */
#define BATTERY_IND_INDEX		1
#define SIGNAL_IND_INDEX		2
#define SERVICE_IND_INDEX		3
#define CALL_IND_INDEX			4
#define CALLSETUP_IND_INDEX		5
#define CALLHELD_IND_INDEX		6
#define INDICATOR_UPDATE_RSP		\
	"+CIND: "			\
	"(\"battchg\",(0-5)),"		\
	"(\"signal\",(0-5)),"		\
	"(\"service\",(0,1)),"		\
	"(\"call\",(0,1)),"		\
	"(\"callsetup\",(0-3)),"	\
	"(\"callheld\",(0-2)),"		\
	"(\"roam\",(0,1))"		\
	""
/* Mode values for standard event reporting activation/deactivation AT
 * command AT+CMER. Used for indicator events reporting in HFP. */
#define FORWARD_UNSOLICIT_RESULT_CODE	3

/* Handle object to hold required info to initialize and maintain
 * an HFP service level connection.
 * Args:
 *    buf - Buffer hold received commands.
 *    buf_read_idx - Read index for buf.
 *    buf_write_idx - Write index for buf.
 *    rfcomm_fd - File descriptor for the established RFCOMM connection.
 *    init_cb - Callback to be triggered when an SLC is initialized.
 *    initialized - The service level connection is fully initilized of not.
 *    cli_active - Calling line identification notification is enabled or not.
 *    battery - Current battery level of AG stored in SLC.
 *    signal - Current signal strength of AG stored in SLC.
 *    service - Current service availability of AG stored in SLC.
 *    callheld - Current callheld status of AG stored in SLC.
 *    ind_event_report - Activate status of indicator events reporting.
 *    telephony - A reference of current telephony handle.
 *    device - The associated bt device.
 */
struct hfp_slc_handle {
	char buf[SLC_BUF_SIZE_BYTES];
	int buf_read_idx;
	int buf_write_idx;

	int is_hsp;
	int rfcomm_fd;
	hfp_slc_init_cb init_cb;
	hfp_slc_disconnect_cb disconnect_cb;
	int initialized;
	int cli_active;
	int battery;
	int signal;
	int service;
	int callheld;
	int ind_event_report;
	struct cras_bt_device *device;

	struct cras_telephony_handle *telephony;
};

/* AT command exchanges between AG(Audio gateway) and HF(Hands-free device) */
struct at_command {
	const char *cmd;
	int (*callback) (struct hfp_slc_handle *handle, const char *cmd);
};

/* Sends a response or command to HF */
static int hfp_send(struct hfp_slc_handle *handle, const char *buf)
{
	int written, err, len;

	if (handle->rfcomm_fd < 0)
		return -EIO;

	/* Message start and end with "\r\n". refer to spec 4.33. */
	err = write(handle->rfcomm_fd, "\r\n", 2);
	if (err < 0)
		return -errno;

	len = strlen(buf);
	written = 0;
	while (written < len) {
		err = write(handle->rfcomm_fd,
			    buf + written, len - written);
		if (err < 0)
			return -errno;
		written += err;
	}

	err = write(handle->rfcomm_fd, "\r\n", 2);
	if (err < 0)
		return -errno;

	return 0;
}

/* Sends a response for indicator event reporting. */
static int hfp_send_ind_event_report(struct hfp_slc_handle *handle,
				     int ind_index,
				     int value)
{
	char cmd[64];

	if (handle->is_hsp || !handle->ind_event_report)
		return 0;

	snprintf(cmd, 64, "+CIEV: %d,%d", ind_index, value);
	return hfp_send(handle, cmd);
}

/* Sends calling line identification unsolicited result code and
 * standard call waiting notification. */
static int hfp_send_calling_line_identification(struct hfp_slc_handle *handle,
						const char *number,
						int type)
{
	char cmd[64];

	if (handle->is_hsp)
		return 0;

	if (handle->telephony->call) {
		snprintf(cmd, 64, "+CCWA: \"%s\",%d", number, type);
	} else {
		snprintf(cmd, 64, "+CLIP: \"%s\",%d", number, type);
	}
	return hfp_send(handle, cmd);
}

/* ATA command to accept an incoming call. Mandatory support per spec 4.13. */
static int answer_call(struct hfp_slc_handle *handle, const char *cmd)
{
	int rc;
	rc = hfp_send(handle, "OK");
	if (rc)
		return rc;

	return cras_telephony_event_answer_call();
}

/* AT+CCWA command to enable the "Call Waiting notification" function.
 * Mandatory support per spec 4.21. */
static int call_waiting_notify(struct hfp_slc_handle *handle, const char *buf)
{
	return hfp_send(handle, "OK");
}

/* AT+CLIP command to enable the "Calling Line Identification notification"
 * function. Mandatory per spec 4.23.
 */
static int cli_notification(struct hfp_slc_handle *handle, const char *cmd)
{
	handle->cli_active = (cmd[8] == '1');
	return hfp_send(handle, "OK");
}

/* ATDdd...dd command to place call with supplied number, or ATD>nnn...
 * command to dial the number stored at memory location. Mandatory per
 * spec 4.18 and 4.19.
 */
static int dial_number(struct hfp_slc_handle *handle, const char *cmd)
{
	int rc, cmd_len;

	cmd_len = strlen(cmd);

	if (cmd[3] == '>') {
		/* Handle memory dial. Extract memory location from command
		 * ATD>nnn...; and lookup. */
		int memory_location;
		memory_location = strtol(cmd + 4, NULL, 0);
		if (handle->telephony->dial_number == NULL || memory_location != 1)
			return hfp_send(handle, "ERROR");
	}
	else {
		/* ATDddddd; Store dial number to the only memory slot. */
		cras_telephony_store_dial_number(cmd_len - 3 - 1, cmd + 3);
	}

	rc = hfp_send(handle, "OK");
	if (rc)
		return rc;

	handle->telephony->callsetup = 2;
	return hfp_send_ind_event_report(handle, CALLSETUP_IND_INDEX, 2);
}

/* AT+VTS command to generate a DTMF code. Mandatory per spec 4.27. */
static int dtmf_tone(struct hfp_slc_handle *handle, const char *buf)
{
	return hfp_send(handle, "OK");
}

/* AT+CMER command enables the registration status update function in AG.
 * The service level connection is consider initialized when successfully
 * responded OK to the AT+CMER command. Mandatory support per spec 4.4.
 */
static int event_reporting(struct hfp_slc_handle *handle, const char *cmd)
{
	char *tokens, *mode, *tmp;
	int err = 0;

	/* AT+CMER=[<mode>[,<keyp>[,<disp>[,<ind> [,<bfr>]]]]]
	 * Parse <ind>, the only token we care about.
	 */
	tokens = strdup(cmd);
	strtok(tokens, "=");

	mode = strtok(NULL, ",");
	tmp = strtok(NULL, ",");
	tmp = strtok(NULL, ",");
	tmp = strtok(NULL, ",");

	/* mode = 3 for forward unsolicited result codes.
	 * AT+CMER=3,0,0,1 activates “indicator events reporting”.
	 * The service level connection is considered established after
	 * successfully responded with OK, regardless of the indicator
	 * events reporting status.
	 */
	if (!mode || !tmp) {
		syslog(LOG_ERR, "Invalid event reporting” cmd %s", cmd);
		err = -EINVAL;
		goto event_reporting_err;
	}
	if (atoi(mode) == FORWARD_UNSOLICIT_RESULT_CODE)
		handle->ind_event_report = atoi(tmp);

	err = hfp_send(handle, "OK");
	if (err) {
		syslog(LOG_ERR, "Error sending response for command %s", cmd);
		goto event_reporting_err;
	}

	/* Consider the Service Level Connection to be fully initialized,
	 * and thereby established, after successfully responded with OK.
	 */
	if (!handle->initialized) {
		handle->initialized = 1;
		if (handle->init_cb)
			handle->init_cb(handle);
	}

event_reporting_err:
	free(tokens);
	return err;
}

/* AT+CMEE command to set the "Extended Audio Gateway Error Result Code".
 * Mandatory per spec 4.9.
 */
static int extended_errors(struct hfp_slc_handle *handle, const char *buf)
{
	return hfp_send(handle, "OK");
}

/* AT+CKPD command to handle the user initiated action from headset profile
 * device.
 */
static int key_press(struct hfp_slc_handle *handle, const char *buf)
{
	hfp_send(handle, "OK");

	/* Release the call and connection. */
	if (handle->telephony->call || handle->telephony->callsetup) {
		cras_telephony_event_terminate_call();
		handle->disconnect_cb(handle);
		return -EIO;
	}
	return 0;
}

/* AT+BLDN command to re-dial the last number. Mandatory support
 * per spec 4.20.
 */
static int last_dialed_number(struct hfp_slc_handle *handle, const char *buf)
{
	int rc;

	if (!handle->telephony->dial_number)
		return hfp_send(handle, "ERROR");

	rc = hfp_send(handle, "OK");
	if (rc)
		return rc;

	handle->telephony->callsetup = 2;
	return hfp_send_ind_event_report(handle, CALLSETUP_IND_INDEX, 2);
}

/* AT+CLCC command to query list of current calls. Mandatory support
 * per spec 4.31.
 *
 * +CLCC: <idx>,<direction>,<status>,<mode>,<multiparty>
 */
static int list_current_calls(struct hfp_slc_handle *handle, const char *cmd)
{
	char buf[64];

	int idx = 1;
	int rc;
	/* Fake the call list base on callheld and call status
	 * since we have no API exposed to manage call list.
	 * This is a hack to pass qualification test which ask us to
	 * handle the basic case that one call is active and
	 * the other is on hold. */
	if (handle->telephony->callheld)
	{
		snprintf(buf, 64, "+CLCC: %d,1,1,0,0", idx++);
		rc = hfp_send(handle, buf);
		if (rc)
			return rc;
	}

	if (handle->telephony->call)
	{
		snprintf(buf, 64, "+CLCC: %d,1,0,0,0", idx++);
		rc = hfp_send(handle, buf);
		if (rc)
			return rc;
	}

	return hfp_send(handle, "OK");
}

/* AT+COPS command to query currently selected operator or set name format.
 * Mandatory support per spec 4.8.
 */
static int operator_selection(struct hfp_slc_handle *handle, const char *buf)
{
	int rc;
	if (buf[7] == '?')
	{
		/* HF sends AT+COPS? command to find current network operator.
		 * AG responds with +COPS:<mode>,<format>,<operator>, where
		 * the mode=0 means automatic for network selection. If no
		 * operator is selected, <format> and <operator> are omitted.
		 */
		rc = hfp_send(handle, "+COPS: 0");
		if (rc)
			return rc;
	}
	return hfp_send(handle, "OK");
}

/* AT+CIND command retrieves the supported indicator and its corresponding
 * range and order index or read current status of indicators. Mandatory
 * support per spec 4.2.
 */
static int report_indicators(struct hfp_slc_handle *handle, const char *cmd)
{
	int err;
	char buf[64];

	if (cmd[7] == '=') {
		/* Indicator update test command "AT+CIND=?" */
		err = hfp_send(handle, INDICATOR_UPDATE_RSP);
	} else {
		/* Indicator update read command "AT+CIND?".
		 * Respond with current status of AG indicators,
		 * the values must be listed in the indicator order declared
		 * in INDICATOR_UPDATE_RSP.
		 * +CIND: <signal>,<service>,<call>,
		 *        <callsetup>,<callheld>,<roam>
		 */
		snprintf(buf, 64, "+CIND: %d,%d,%d,%d,%d,%d,0",
			handle->battery,
			handle->signal,
			handle->service,
			handle->telephony->call,
			handle->telephony->callsetup,
			handle->telephony->callheld
			);
		err = hfp_send(handle, buf);
	}

	if (err < 0)
		return err;

	return hfp_send(handle, "OK");
}

/* AT+BIA command to change the subset of indicators that shall be
 * sent by the AG. It is okay to ignore this command here since we
 * don't do event reporting(CMER).
 */
static int indicator_activation(struct hfp_slc_handle *handle, const char *cmd)
{
	/* AT+BIA=[[<indrep 1>][,[<indrep 2>][,...[,[<indrep n>]]]]] */
	syslog(LOG_ERR, "Bluetooth indicator activation command %s", cmd);
	return hfp_send(handle, "OK");
}

/* AT+VGM and AT+VGS command reports the current mic and speaker gain
 * level respectively. Optional support per spec 4.28.
 */
static int signal_gain_setting(struct hfp_slc_handle *handle,
			       const char *cmd)
{
	int gain;

	if (strlen(cmd) < 8) {
		syslog(LOG_ERR, "Invalid gain setting command %s", cmd);
		return -EINVAL;
	}

	/* Map 0 to the smallest non-zero scale 6/100, and 15 to
	 * 100/100 full. */
	if (cmd[5] == 'S') {
		gain = atoi(&cmd[7]);
		cras_bt_device_update_hardware_volume(handle->device,
						      (gain + 1) * 100 / 16);
	}

	return hfp_send(handle, "OK");
}

/* AT+CNUM command to query the subscriber number. Mandatory support
 * per spec 4.30.
 */
static int subscriber_number(struct hfp_slc_handle *handle, const char *buf)
{
	return hfp_send(handle, "OK");
}

/* AT+BRSF command notifies the HF(Hands-free device) supported features
 * and retrieves the AG(Audio gateway) supported features. Mandatory
 * support per spec 4.2.
 */
static int supported_features(struct hfp_slc_handle *handle, const char *cmd)
{
	int err;
	char response[128];
	if (strlen(cmd) < 9)
		return -EINVAL;

	/* AT+BRSF=<feature> command received, ignore the HF supported feature
	 * for now. Respond with +BRSF:<feature> to notify mandatory supported
	 * features in AG(audio gateway).
	 */
	snprintf(response, 128, "+BRSF: %u", HFP_SUPPORTED_FEATURE);
	err = hfp_send(handle, response);
	if (err < 0)
		return err;

	return hfp_send(handle, "OK");
}

int hfp_event_speaker_gain(struct hfp_slc_handle *handle, int gain)
{
	char command[128];

	/* Normailize gain value to 0-15 */
	gain = gain * 15 / 100;
	snprintf(command, 128, "+VGS=%d", gain);

	return hfp_send(handle, command);
}

/* AT+CHUP command to terminate current call. Mandatory support
 * per spec 4.15.
 */
static int terminate_call(struct hfp_slc_handle *handle, const char *cmd)
{
	int rc;
	rc = hfp_send(handle, "OK");
	if (rc)
		return rc;

	return cras_telephony_event_terminate_call();
}

/* AT commands to support in order to conform HFP specification.
 *
 * An initialized service level connection is the pre-condition for all
 * call related procedures. Note that for the call related commands,
 * we are good to just respond with a dummy "OK".
 *
 * The procedure to establish a service level connection is described below:
 *
 * 1. HF notifies AG about its own supported features and AG responds
 * with its supported feature.
 *
 * HF(hands-free)                             AG(audio gateway)
 *                     AT+BRSF=<HF supported feature> -->
 *                 <-- +BRSF:<AG supported feature>
 *                 <-- OK
 *
 * 2. HF retrieves the information about the indicators supported in AG.
 *
 * HF(hands-free)                             AG(audio gateway)
 *                     AT+CIND=? -->
 *                 <-- +CIND:...
 *                 <-- OK
 *
 * 3. The HF requests the current status of the indicators in AG.
 *
 * HF(hands-free)                             AG(audio gateway)
 *                     AT+CIND -->
 *                 <-- +CIND:...
 *                 <-- OK
 *
 * 4. HF requests enabling indicator status update in the AG.
 *
 * HF(hands-free)                             AG(audio gateway)
 *                     AT+CMER= -->
 *                 <-- OK
 */
static struct at_command at_commands[] = {
	{ "ATA", answer_call },
	{ "ATD", dial_number },
	{ "AT+BIA", indicator_activation },
	{ "AT+BLDN", last_dialed_number },
	{ "AT+BRSF", supported_features },
	{ "AT+CCWA", call_waiting_notify },
	{ "AT+CHUP", terminate_call },
	{ "AT+CIND", report_indicators },
	{ "AT+CKPD", key_press },
	{ "AT+CLCC", list_current_calls },
	{ "AT+CLIP", cli_notification },
	{ "AT+CMEE", extended_errors },
	{ "AT+CMER", event_reporting },
	{ "AT+CNUM", subscriber_number },
	{ "AT+COPS", operator_selection },
	{ "AT+VG", signal_gain_setting },
	{ "AT+VTS", dtmf_tone },
	{ 0 }
};

static int handle_at_command(struct hfp_slc_handle *slc_handle,
			     const char *cmd) {
	struct at_command *atc;

	for (atc = at_commands; atc->cmd; atc++)
		if (!strncmp(cmd, atc->cmd, strlen(atc->cmd)))
			return atc->callback(slc_handle, cmd);

	syslog(LOG_ERR, "AT command %s not supported", cmd);
	return hfp_send(slc_handle, "ERROR");
}

static void slc_watch_callback(void *arg)
{
	struct hfp_slc_handle *handle = (struct hfp_slc_handle *)arg;
	ssize_t bytes_read;
	int err;

	bytes_read = read(handle->rfcomm_fd,
			  &handle->buf[handle->buf_write_idx],
			  SLC_BUF_SIZE_BYTES - handle->buf_write_idx - 1);
	if (bytes_read < 0) {
		syslog(LOG_ERR, "Error reading slc command %s",
		       strerror(errno));
		handle->disconnect_cb(handle);
		return;
	}
	handle->buf_write_idx += bytes_read;
	handle->buf[handle->buf_write_idx] = '\0';

	while (handle->buf_read_idx != handle->buf_write_idx) {
		char *end_char;
		end_char = strchr(&handle->buf[handle->buf_read_idx], '\r');
		if (end_char == NULL)
			break;

		*end_char = '\0';
		err = handle_at_command(handle,
					&handle->buf[handle->buf_read_idx]);
		if (err < 0)
			return;

		/* Shift the read index */
		handle->buf_read_idx = 1 + end_char - handle->buf;
		if (handle->buf_read_idx == handle->buf_write_idx) {
			handle->buf_read_idx = 0;
			handle->buf_write_idx = 0;
		}
	}

	/* Handle the case when buffer is full and no command found. */
	if (handle->buf_write_idx == SLC_BUF_SIZE_BYTES - 1) {
		if (handle->buf_read_idx) {
			memmove(handle->buf,
				&handle->buf[handle->buf_read_idx],
				handle->buf_write_idx - handle->buf_read_idx);
			handle->buf_write_idx -= handle->buf_read_idx;
			handle->buf_read_idx = 0;
		} else {
			syslog(LOG_ERR,
			       "Parse SLC command error, clean up buffer");
			handle->buf_write_idx = 0;
		}
	}

	return;
}

/* Exported interfaces */

struct hfp_slc_handle *hfp_slc_create(int fd,
				      int is_hsp,
				      struct cras_bt_device *device,
				      hfp_slc_init_cb init_cb,
				      hfp_slc_disconnect_cb disconnect_cb)
{
	struct hfp_slc_handle *handle;

	handle = (struct hfp_slc_handle*) calloc(1, sizeof(*handle));
	if (!handle)
		return NULL;

	handle->rfcomm_fd = fd;
	handle->is_hsp = is_hsp;
	handle->device = device;
	handle->init_cb = init_cb;
	handle->disconnect_cb = disconnect_cb;
	handle->cli_active = 0;
	handle->battery = 5;
	handle->signal = 5;
	handle->service = 1;
	handle->ind_event_report = 0;
	handle->telephony = cras_telephony_get();

	cras_system_add_select_fd(handle->rfcomm_fd,
				  slc_watch_callback, handle);

	return handle;
}

void hfp_slc_destroy(struct hfp_slc_handle *slc_handle)
{
	cras_system_rm_select_fd(slc_handle->rfcomm_fd);
	close(slc_handle->rfcomm_fd);
	free(slc_handle);
}

int hfp_set_call_status(struct hfp_slc_handle *handle, int call)
{
	int old_call = handle->telephony->call;

	if (old_call == call)
		return 0;

	handle->telephony->call = call;
	return hfp_event_update_call(handle);
}

/* Procedure to setup a call when AG sees incoming call.
 *
 * HF(hands-free)                             AG(audio gateway)
 *                                                     <-- Incoming call
 *                 <-- +CIEV: (callsetup = 1)
 *                 <-- RING (ALERT)
 */
int hfp_event_incoming_call(struct hfp_slc_handle *handle,
			    const char *number,
			    int type)
{
	int rc;

	if (handle->is_hsp)
		return 0;

	if (handle->cli_active) {
		rc = hfp_send_calling_line_identification(handle, number, type);
		if (rc)
			return rc;
	}

	if (handle->telephony->call)
		return 0;
	else
		return hfp_send(handle, "RING");
}

int hfp_event_update_call(struct hfp_slc_handle *handle)
{
	return hfp_send_ind_event_report(handle, CALL_IND_INDEX,
					 handle->telephony->call);
}

int hfp_event_update_callsetup(struct hfp_slc_handle *handle)
{
	return hfp_send_ind_event_report(handle, CALLSETUP_IND_INDEX,
					 handle->telephony->callsetup);
}

int hfp_event_update_callheld(struct hfp_slc_handle *handle)
{
	return hfp_send_ind_event_report(handle, CALLHELD_IND_INDEX,
					 handle->telephony->callheld);
}

int hfp_event_set_battery(struct hfp_slc_handle *handle, int level)
{
	handle->battery = level;
	return hfp_send_ind_event_report(handle, BATTERY_IND_INDEX, level);
}

int hfp_event_set_signal(struct hfp_slc_handle *handle, int level)
{
	handle->signal = level;
	return hfp_send_ind_event_report(handle, SIGNAL_IND_INDEX, level);
}

int hfp_event_set_service(struct hfp_slc_handle *handle, int avail)
{
	/* Convert to 0 or 1.
	 * Since the value must be either 1 or 0. (service presence or not) */
	handle->service = !!avail;
	return hfp_send_ind_event_report(handle, SERVICE_IND_INDEX, avail);
}