/*
 * Copyright (C) 2013 Intel Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include "if-main.h"
#include "../hal-utils.h"

const bthf_interface_t *if_hf = NULL;

SINTMAP(bthf_at_response_t, -1, "(unknown)")
	DELEMENT(BTHF_AT_RESPONSE_ERROR),
	DELEMENT(BTHF_AT_RESPONSE_OK),
ENDMAP

SINTMAP(bthf_connection_state_t, -1, "(unknown)")
	DELEMENT(BTHF_CONNECTION_STATE_DISCONNECTED),
	DELEMENT(BTHF_CONNECTION_STATE_CONNECTING),
	DELEMENT(BTHF_CONNECTION_STATE_CONNECTED),
	DELEMENT(BTHF_CONNECTION_STATE_SLC_CONNECTED),
	DELEMENT(BTHF_CONNECTION_STATE_DISCONNECTING),
ENDMAP

SINTMAP(bthf_audio_state_t, -1, "(unknown)")
	DELEMENT(BTHF_AUDIO_STATE_DISCONNECTED),
	DELEMENT(BTHF_AUDIO_STATE_CONNECTING),
	DELEMENT(BTHF_AUDIO_STATE_CONNECTED),
	DELEMENT(BTHF_AUDIO_STATE_DISCONNECTING),
ENDMAP

SINTMAP(bthf_vr_state_t, -1, "(unknown)")
	DELEMENT(BTHF_VR_STATE_STOPPED),
	DELEMENT(BTHF_VR_STATE_STARTED),
ENDMAP

SINTMAP(bthf_volume_type_t, -1, "(unknown)")
	DELEMENT(BTHF_VOLUME_TYPE_SPK),
	DELEMENT(BTHF_VOLUME_TYPE_MIC),
ENDMAP

SINTMAP(bthf_nrec_t, -1, "(unknown)")
	DELEMENT(BTHF_NREC_STOP),
	DELEMENT(BTHF_NREC_START),
ENDMAP

SINTMAP(bthf_chld_type_t, -1, "(unknown)")
	DELEMENT(BTHF_CHLD_TYPE_RELEASEHELD),
	DELEMENT(BTHF_CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD),
	DELEMENT(BTHF_CHLD_TYPE_HOLDACTIVE_ACCEPTHELD),
	DELEMENT(BTHF_CHLD_TYPE_ADDHELDTOCONF),
ENDMAP

/* Network Status */
SINTMAP(bthf_network_state_t, -1, "(unknown)")
	DELEMENT(BTHF_NETWORK_STATE_NOT_AVAILABLE),
	DELEMENT(BTHF_NETWORK_STATE_AVAILABLE),
ENDMAP

/* Service type */
SINTMAP(bthf_service_type_t, -1, "(unknown)")
	DELEMENT(BTHF_SERVICE_TYPE_HOME),
	DELEMENT(BTHF_SERVICE_TYPE_ROAMING),
ENDMAP

SINTMAP(bthf_call_state_t, -1, "(unknown)")
	DELEMENT(BTHF_CALL_STATE_ACTIVE),
	DELEMENT(BTHF_CALL_STATE_HELD),
	DELEMENT(BTHF_CALL_STATE_DIALING),
	DELEMENT(BTHF_CALL_STATE_ALERTING),
	DELEMENT(BTHF_CALL_STATE_INCOMING),
	DELEMENT(BTHF_CALL_STATE_WAITING),
	DELEMENT(BTHF_CALL_STATE_IDLE),
ENDMAP

SINTMAP(bthf_call_direction_t, -1, "(unknown)")
	DELEMENT(BTHF_CALL_DIRECTION_OUTGOING),
	DELEMENT(BTHF_CALL_DIRECTION_INCOMING),
ENDMAP

SINTMAP(bthf_call_mode_t, -1, "(unknown)")
	DELEMENT(BTHF_CALL_TYPE_VOICE),
	DELEMENT(BTHF_CALL_TYPE_DATA),
	DELEMENT(BTHF_CALL_TYPE_FAX),
ENDMAP

SINTMAP(bthf_call_mpty_type_t, -1, "(unknown)")
	DELEMENT(BTHF_CALL_MPTY_TYPE_SINGLE),
	DELEMENT(BTHF_CALL_MPTY_TYPE_MULTI),
ENDMAP

SINTMAP(bthf_call_addrtype_t, -1, "(unknown)")
	DELEMENT(BTHF_CALL_ADDRTYPE_UNKNOWN),
	DELEMENT(BTHF_CALL_ADDRTYPE_INTERNATIONAL),
ENDMAP

/* Callbacks */

static char last_addr[MAX_ADDR_STR_LEN];

/*
 * Callback for connection state change.
 * state will have one of the values from BtHfConnectionState
 */
static void connection_state_cb(bthf_connection_state_t state,
							bt_bdaddr_t *bd_addr)
{
	haltest_info("%s: state=%s bd_addr=%s\n", __func__,
					bthf_connection_state_t2str(state),
					bt_bdaddr_t2str(bd_addr, last_addr));
}

/*
 * Callback for audio connection state change.
 * state will have one of the values from BtHfAudioState
 */
static void audio_state_cb(bthf_audio_state_t state, bt_bdaddr_t *bd_addr)
{
	haltest_info("%s: state=%s bd_addr=%s\n", __func__,
					bthf_audio_state_t2str(state),
					bt_bdaddr_t2str(bd_addr, last_addr));
}

/*
 * Callback for VR connection state change.
 * state will have one of the values from BtHfVRState
 */
static void vr_cmd_cb(bthf_vr_state_t state)
{
	haltest_info("%s: state=%s\n", __func__, bthf_vr_state_t2str(state));
}

/* Callback for answer incoming call (ATA) */
static void answer_call_cmd_cb(void)
{
	haltest_info("%s\n", __func__);
}

/* Callback for disconnect call (AT+CHUP) */
static void hangup_call_cmd_cb(void)
{
	haltest_info("%s\n", __func__);
}

/*
 * Callback for disconnect call (AT+CHUP)
 * type will denote Speaker/Mic gain (BtHfVolumeControl).
 */
static void volume_cmd_cb(bthf_volume_type_t type, int volume)
{
	haltest_info("%s: type=%s volume=%d\n", __func__,
					bthf_volume_type_t2str(type), volume);
}

/*
 * Callback for dialing an outgoing call
 * If number is NULL, redial
 */
static void dial_call_cmd_cb(char *number)
{
	haltest_info("%s: number=%s\n", __func__, number);
}

/*
 * Callback for sending DTMF tones
 * tone contains the dtmf character to be sent
 */
static void dtmf_cmd_cb(char tone)
{
	haltest_info("%s: tone=%d\n", __func__, tone);
}

/*
 * Callback for enabling/disabling noise reduction/echo cancellation
 * value will be 1 to enable, 0 to disable
 */
static void nrec_cmd_cb(bthf_nrec_t nrec)
{
	haltest_info("%s: nrec=%s\n", __func__, bthf_nrec_t2str(nrec));
}

/*
 * Callback for call hold handling (AT+CHLD)
 * value will contain the call hold command (0, 1, 2, 3)
 */
static void chld_cmd_cb(bthf_chld_type_t chld)
{
	haltest_info("%s: chld=%s\n", __func__, bthf_chld_type_t2str(chld));
}

/* Callback for CNUM (subscriber number) */
static void cnum_cmd_cb(void)
{
	haltest_info("%s\n", __func__);
}

/* Callback for indicators (CIND) */
static void cind_cmd_cb(void)
{
	haltest_info("%s\n", __func__);
}

/* Callback for operator selection (COPS) */
static void cops_cmd_cb(void)
{
	haltest_info("%s\n", __func__);
}

/* Callback for call list (AT+CLCC) */
static void clcc_cmd_cb(void)
{
	haltest_info("%s\n", __func__);
}

/*
 * Callback for unknown AT command recd from HF
 * at_string will contain the unparsed AT string
 */
static void unknown_at_cmd_cb(char *at_string)
{
	haltest_info("%s: at_string=%s\n", __func__, at_string);
}

/* Callback for keypressed (HSP) event. */
static void key_pressed_cmd_cb(void)
{
	haltest_info("%s\n", __func__);
}

static bthf_callbacks_t hf_cbacks = {

	.size = sizeof(hf_cbacks),
	.connection_state_cb = connection_state_cb,
	.audio_state_cb = audio_state_cb,
	.vr_cmd_cb = vr_cmd_cb,
	.answer_call_cmd_cb = answer_call_cmd_cb,
	.hangup_call_cmd_cb = hangup_call_cmd_cb,
	.volume_cmd_cb = volume_cmd_cb,
	.dial_call_cmd_cb = dial_call_cmd_cb,
	.dtmf_cmd_cb = dtmf_cmd_cb,
	.nrec_cmd_cb = nrec_cmd_cb,
	.chld_cmd_cb = chld_cmd_cb,
	.cnum_cmd_cb = cnum_cmd_cb,
	.cind_cmd_cb = cind_cmd_cb,
	.cops_cmd_cb = cops_cmd_cb,
	.clcc_cmd_cb = clcc_cmd_cb,
	.unknown_at_cmd_cb = unknown_at_cmd_cb,
	.key_pressed_cmd_cb = key_pressed_cmd_cb,
};

/* init */

static void init_p(int argc, const char **argv)
{
	RETURN_IF_NULL(if_hf);

	EXEC(if_hf->init, &hf_cbacks);
}

/* connect */

static void connect_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	if (argc == 3) {
		*user = NULL;
		*enum_func = enum_devices;
	}
}

static void connect_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;

	RETURN_IF_NULL(if_hf);
	VERIFY_ADDR_ARG(2, &addr);

	EXEC(if_hf->connect, &addr);
}

/* disconnect */

/*
 * This completion function will be used for several methods
 * returning recently connected address
 */
static void connected_addr_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	if (argc == 3) {
		*user = last_addr;
		*enum_func = enum_one_string;
	}
}

/* Map completion to connected_addr_c */
#define disconnect_c connected_addr_c

static void disconnect_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;

	RETURN_IF_NULL(if_hf);
	VERIFY_ADDR_ARG(2, &addr);

	EXEC(if_hf->disconnect, &addr);
}

/* create an audio connection */

/* Map completion to connected_addr_c */
#define connect_audio_c connected_addr_c

static void connect_audio_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;

	RETURN_IF_NULL(if_hf);
	VERIFY_ADDR_ARG(2, &addr);

	EXEC(if_hf->connect_audio, &addr);
}

/* close the audio connection */

/* Map completion to connected_addr_c */
#define disconnect_audio_c connected_addr_c

static void disconnect_audio_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;

	RETURN_IF_NULL(if_hf);
	VERIFY_ADDR_ARG(2, &addr);

	EXEC(if_hf->disconnect_audio, &addr);
}

/* start voice recognition */

static void start_voice_recognition_p(int argc, const char **argv)
{
	RETURN_IF_NULL(if_hf);

	EXEC(if_hf->start_voice_recognition);
}

/* stop voice recognition */

static void stop_voice_recognition_p(int argc, const char **argv)
{
	RETURN_IF_NULL(if_hf);

	EXEC(if_hf->stop_voice_recognition);
}

/* volume control */

static void volume_control_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	if (argc == 3) {
		*user = TYPE_ENUM(bthf_volume_type_t);
		*enum_func = enum_defines;
	}
}

static void volume_control_p(int argc, const char **argv)
{
	bthf_volume_type_t type;
	int volume;

	RETURN_IF_NULL(if_hf);

	/* volume type */
	if (argc <= 2) {
		haltest_error("No volume type specified\n");
		return;
	}
	type = str2bthf_volume_type_t(argv[2]);

	/* volume */
	if (argc <= 3) {
		haltest_error("No volume specified\n");
		return;
	}
	volume = atoi(argv[3]);

	EXEC(if_hf->volume_control, type, volume);
}

/* Combined device status change notification */

static void device_status_notification_c(int argc, const char **argv,
							enum_func *enum_func,
							void **user)
{
	if (argc == 3) {
		*user = TYPE_ENUM(bthf_network_state_t);
		*enum_func = enum_defines;
	} else if (argc == 4) {
		*user = TYPE_ENUM(bthf_service_type_t);
		*enum_func = enum_defines;
	}
}

static void device_status_notification_p(int argc, const char **argv)
{
	bthf_network_state_t ntk_state;
	bthf_service_type_t svc_type;
	int signal;
	int batt_chg;

	RETURN_IF_NULL(if_hf);

	/* network state */
	if (argc <= 2) {
		haltest_error("No network state specified\n");
		return;
	}
	ntk_state = str2bthf_network_state_t(argv[2]);

	/* service type */
	if (argc <= 3) {
		haltest_error("No service type specified\n");
		return;
	}
	svc_type = str2bthf_service_type_t(argv[3]);

	/* signal */
	if (argc <= 4) {
		haltest_error("No signal specified\n");
		return;
	}
	signal = atoi(argv[4]);

	/* batt_chg */
	if (argc <= 5) {
		haltest_error("No batt_chg specified\n");
		return;
	}
	batt_chg = atoi(argv[5]);

	EXEC(if_hf->device_status_notification, ntk_state, svc_type, signal,
								batt_chg);
}

/* Response for COPS command */

static void cops_response_p(int argc, const char **argv)
{
	RETURN_IF_NULL(if_hf);

	/* response */
	if (argc <= 2) {
		haltest_error("No cops specified\n");
		return;
	}

	EXEC(if_hf->cops_response, argv[2]);
}

/* Response for CIND command */

static void cind_response_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	if (argc == 6) {
		*user = TYPE_ENUM(bthf_call_state_t);
		*enum_func = enum_defines;
	}
}

static void cind_response_p(int argc, const char **argv)
{
	int svc;
	int num_active;
	int num_held;
	bthf_call_state_t call_setup_state;
	int signal;
	int roam;
	int batt_chg;

	RETURN_IF_NULL(if_hf);

	/* svc */
	if (argc <= 2) {
		haltest_error("No service specified\n");
		return;
	}
	svc = atoi(argv[2]);

	/* num active */
	if (argc <= 3) {
		haltest_error("No num active specified\n");
		return;
	}
	num_active = atoi(argv[3]);

	/* num held */
	if (argc <= 4) {
		haltest_error("No num held specified\n");
		return;
	}
	num_held = atoi(argv[4]);

	/* call setup state */
	if (argc <= 5) {
		haltest_error("No call setup state specified\n");
		return;
	}
	call_setup_state = str2bthf_call_state_t(argv[5]);

	/* signal */
	if (argc <= 6) {
		haltest_error("No signal specified\n");
		return;
	}
	signal = atoi(argv[6]);

	/* roam */
	if (argc <= 7) {
		haltest_error("No roam specified\n");
		return;
	}
	roam = atoi(argv[7]);

	/* batt_chg */
	if (argc <= 8) {
		haltest_error("No batt_chg specified\n");
		return;
	}
	batt_chg = atoi(argv[8]);

	EXEC(if_hf->cind_response, svc, num_active, num_held, call_setup_state,
							signal, roam, batt_chg);
}

/* Pre-formatted AT response, typically in response to unknown AT cmd */

static void formatted_at_response_p(int argc, const char **argv)
{
	RETURN_IF_NULL(if_hf);

	/* response */
	if (argc <= 2) {
		haltest_error("No response specified\n");
		return;
	}

	EXEC(if_hf->formatted_at_response, argv[2]);
}

/* at_response */

static void at_response_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	if (argc == 3) {
		*user = TYPE_ENUM(bthf_at_response_t);
		*enum_func = enum_defines;
	}
}

static void at_response_p(int argc, const char **argv)
{
	bthf_at_response_t response_code;
	int error_code = 0;

	RETURN_IF_NULL(if_hf);

	/* response type */
	if (argc <= 2) {
		haltest_error("No response specified\n");
		return;
	}
	response_code = str2bthf_at_response_t(argv[2]);

	/* error code */
	if (argc >= 3)
		error_code = atoi(argv[3]);

	EXEC(if_hf->at_response, response_code, error_code);
}

/* response for CLCC command */

static void clcc_response_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	if (argc == 4) {
		*user = TYPE_ENUM(bthf_call_direction_t);
		*enum_func = enum_defines;
	} else if (argc == 5) {
		*user = TYPE_ENUM(bthf_call_state_t);
		*enum_func = enum_defines;
	} else if (argc == 6) {
		*user = TYPE_ENUM(bthf_call_mode_t);
		*enum_func = enum_defines;
	} else if (argc == 7) {
		*user = TYPE_ENUM(bthf_call_mpty_type_t);
		*enum_func = enum_defines;
	} else if (argc == 9) {
		*user = TYPE_ENUM(bthf_call_addrtype_t);
		*enum_func = enum_defines;
	}
}

static void clcc_response_p(int argc, const char **argv)
{
	int index;
	bthf_call_direction_t dir;
	bthf_call_state_t state;
	bthf_call_mode_t mode;
	bthf_call_mpty_type_t mpty;
	const char *number;
	bthf_call_addrtype_t type;

	RETURN_IF_NULL(if_hf);

	/* index */
	if (argc <= 2) {
		haltest_error("No index specified\n");
		return;
	}
	index = atoi(argv[2]);

	/* direction */
	if (argc <= 3) {
		haltest_error("No direction specified\n");
		return;
	}
	dir = str2bthf_call_direction_t(argv[3]);

	/* call state */
	if (argc <= 4) {
		haltest_error("No call state specified\n");
		return;
	}
	state = str2bthf_call_state_t(argv[4]);

	/* call mode */
	if (argc <= 5) {
		haltest_error("No mode specified\n");
		return;
	}
	mode = str2bthf_call_mode_t(argv[5]);

	/* call mpty type */
	if (argc <= 6) {
		haltest_error("No mpty type specified\n");
		return;
	}
	mpty = str2bthf_call_mpty_type_t(argv[6]);

	/* number */
	if (argc <= 7) {
		haltest_error("No number specified\n");
		return;
	}
	number = argv[7];

	/* call mpty type */
	if (argc <= 8) {
		haltest_error("No address type specified\n");
		return;
	}
	type = str2bthf_call_addrtype_t(argv[8]);

	EXEC(if_hf->clcc_response, index, dir, state, mode, mpty, number,
									type);
}

/* phone state change */

static void phone_state_change_c(int argc, const char **argv,
					enum_func *enum_func, void **user)
{
	if (argc == 5) {
		*user = TYPE_ENUM(bthf_call_state_t);
		*enum_func = enum_defines;
	} else if (argc == 7) {
		*user = TYPE_ENUM(bthf_call_addrtype_t);
		*enum_func = enum_defines;
	}
}

static void phone_state_change_p(int argc, const char **argv)
{
	int num_active;
	int num_held;
	bthf_call_state_t call_setup_state;
	const char *number;
	bthf_call_addrtype_t type;

	RETURN_IF_NULL(if_hf);

	/* num_active */
	if (argc <= 2) {
		haltest_error("No num_active specified\n");
		return;
	}
	num_active = atoi(argv[2]);

	/* num_held */
	if (argc <= 3) {
		haltest_error("No num_held specified\n");
		return;
	}
	num_held = atoi(argv[3]);

	/* setup state */
	if (argc <= 4) {
		haltest_error("No call setup state specified\n");
		return;
	}
	call_setup_state = str2bthf_call_state_t(argv[4]);

	/* number */
	if (argc <= 5) {
		haltest_error("No number specified\n");
		return;
	}
	number = argv[5];

	/* call mpty type */
	if (argc <= 6) {
		haltest_error("No address type specified\n");
		return;
	}
	type = str2bthf_call_addrtype_t(argv[6]);

	EXEC(if_hf->phone_state_change, num_active, num_held, call_setup_state,
								number, type);
}

/* cleanup */

static void cleanup_p(int argc, const char **argv)
{
	RETURN_IF_NULL(if_hf);

	EXECV(if_hf->cleanup);
	if_hf = NULL;
}

static struct method methods[] = {
	STD_METHOD(init),
	STD_METHODCH(connect, "<addr>"),
	STD_METHODCH(disconnect, "<addr>"),
	STD_METHODCH(connect_audio, "<addr>"),
	STD_METHODCH(disconnect_audio, "<addr>"),
	STD_METHOD(start_voice_recognition),
	STD_METHOD(stop_voice_recognition),
	STD_METHODCH(volume_control, "<vol_type> <volume>"),
	STD_METHODCH(device_status_notification,
			"<ntk_state> <svt_type> <signal> <batt_chg>"),
	STD_METHODH(cops_response, "<cops string>"),
	STD_METHODCH(cind_response,
			"<svc> <num_active> <num_held> <setup_state> <signal> <roam> <batt_chg>"),
	STD_METHODH(formatted_at_response, "<at_response>"),
	STD_METHODCH(at_response, "<response_code> [<error_code>]"),
	STD_METHODCH(clcc_response,
			"<index> <direction> <state> <mode> <mpty> <number> <type>"),
	STD_METHODCH(phone_state_change,
			"<num_active> <num_held> <setup_state> <number> <type>"),
	STD_METHOD(cleanup),
	END_METHOD
};

const struct interface hf_if = {
	.name = "handsfree",
	.methods = methods
};
