/*
 * 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 <string.h>
#include <inttypes.h>

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

static hw_device_t *bt_device;
const bt_interface_t *if_bluetooth;

#define VERIFY_PROP_TYPE_ARG(n, typ) \
	do { \
		if (n < argc) \
			typ = str2btpropertytype(argv[n]); \
		else { \
			haltest_error("No property type specified\n"); \
			return;\
		} \
	} while (0)

static bt_scan_mode_t str2btscanmode(const char *str)
{
	bt_scan_mode_t v = str2bt_scan_mode_t(str);

	if ((int) v != -1)
		return v;

	haltest_warn("WARN: %s cannot convert %s\n", __func__, str);
	return (bt_scan_mode_t) atoi(str);
}

static bt_ssp_variant_t str2btsspvariant(const char *str)
{
	bt_ssp_variant_t v = str2bt_ssp_variant_t(str);

	if ((int) v != -1)
		return v;

	haltest_warn("WARN: %s cannot convert %s\n", __func__, str);
	return (bt_ssp_variant_t) atoi(str);
}

static bt_property_type_t str2btpropertytype(const char *str)
{
	bt_property_type_t v = str2bt_property_type_t(str);

	if ((int) v != -1)
		return v;

	haltest_warn("WARN: %s cannot convert %s\n", __func__, str);
	return (bt_property_type_t) atoi(str);
}

static void dump_properties(int num_properties, bt_property_t *properties)
{
	int i;

	for (i = 0; i < num_properties; i++) {
		/*
		 * properities sometimes come unaligned hence memcp to
		 * aligned buffer
		 */
		bt_property_t prop;
		memcpy(&prop, properties + i, sizeof(prop));

		haltest_info("prop: %s\n", btproperty2str(&prop));
	}
}

/* Cache for remote devices, stored in sorted array */
static bt_bdaddr_t *remote_devices = NULL;
static int remote_devices_cnt = 0;
static int remote_devices_capacity = 0;

/* Adds address to remote device set so it can be used in tab completion */
void add_remote_device(const bt_bdaddr_t *addr)
{
	int i;

	if (remote_devices == NULL) {
		remote_devices = malloc(4 * sizeof(bt_bdaddr_t));
		remote_devices_cnt = 0;
		if (remote_devices == NULL) {
			remote_devices_capacity = 0;
			return;
		}

		remote_devices_capacity = 4;
	}

	/* Array is sorted, search for right place */
	for (i = 0; i < remote_devices_cnt; ++i) {
		int res = memcmp(&remote_devices[i], addr, sizeof(*addr));

		if (res == 0)
			return; /* Already added */
		else if (res > 0)
			break;
	}

	/* Realloc space if needed */
	if (remote_devices_cnt >= remote_devices_capacity) {
		bt_bdaddr_t *tmp;

		remote_devices_capacity *= 2;
		/*
		 * Save reference to previously allocated memory block so that
		 * it can be freed in case realloc fails.
		 */
		tmp = remote_devices;

		remote_devices = realloc(remote_devices, sizeof(bt_bdaddr_t) *
						remote_devices_capacity);
		if (remote_devices == NULL) {
			free(tmp);
			remote_devices_capacity = 0;
			remote_devices_cnt = 0;
			return;
		}
	}

	if (i < remote_devices_cnt)
		memmove(remote_devices + i + 1, remote_devices + i,
				(remote_devices_cnt - i) * sizeof(bt_bdaddr_t));
	remote_devices[i] = *addr;
	remote_devices_cnt++;
}

const char *enum_devices(void *v, int i)
{
	static char buf[MAX_ADDR_STR_LEN];

	if (i >= remote_devices_cnt)
		return NULL;

	bt_bdaddr_t2str(&remote_devices[i], buf);
	return buf;
}

static void add_remote_device_from_props(int num_properties,
						const bt_property_t *properties)
{
	int i;

	for (i = 0; i < num_properties; i++) {
		/*
		 * properities sometimes come unaligned hence memcp to
		 * aligned buffer
		 */
		bt_property_t property;

		memcpy(&property, properties + i, sizeof(property));
		if (property.type == BT_PROPERTY_BDADDR)
			add_remote_device((bt_bdaddr_t *) property.val);
	}
}

bool close_hw_bt_dev(void)
{
	if (!bt_device)
		return false;

	bt_device->close(bt_device);
	return true;
}

static void adapter_state_changed_cb(bt_state_t state)
{
	haltest_info("%s: state=%s\n", __func__, bt_state_t2str(state));
}

static void adapter_properties_cb(bt_status_t status, int num_properties,
						bt_property_t *properties)
{
	haltest_info("%s: status=%s num_properties=%d\n", __func__,
				bt_status_t2str(status), num_properties);

	dump_properties(num_properties, properties);
}

static void remote_device_properties_cb(bt_status_t status,
					bt_bdaddr_t *bd_addr,
					int num_properties,
					bt_property_t *properties)
{
	haltest_info("%s: status=%s bd_addr=%s num_properties=%d\n", __func__,
			bt_status_t2str(status), bdaddr2str(bd_addr),
			num_properties);

	add_remote_device(bd_addr);

	dump_properties(num_properties, properties);
}

static void device_found_cb(int num_properties, bt_property_t *properties)
{
	haltest_info("%s: num_properties=%d\n", __func__, num_properties);

	add_remote_device_from_props(num_properties, properties);

	dump_properties(num_properties, properties);
}

static void discovery_state_changed_cb(bt_discovery_state_t state)
{
	haltest_info("%s: state=%s\n", __func__,
					bt_discovery_state_t2str(state));
}

/*
 * Buffer for remote addres that came from one of bind request.
 * It's stored for command completion.
 */
static char last_remote_addr[MAX_ADDR_STR_LEN];
static bt_ssp_variant_t last_ssp_variant = (bt_ssp_variant_t) -1;

static bt_bdaddr_t pin_request_addr;
static void pin_request_answer(char *reply)
{
	bt_pin_code_t pin;
	int accept = 0;
	int pin_len = strlen(reply);

	if (pin_len > 0) {
		accept = 1;
		if (pin_len > 16)
			pin_len = 16;
		memcpy(&pin.pin, reply, pin_len);
	}

	EXEC(if_bluetooth->pin_reply, &pin_request_addr, accept, pin_len, &pin);
}

static void pin_request_cb(bt_bdaddr_t *remote_bd_addr, bt_bdname_t *bd_name,
#if ANDROID_VERSION >= PLATFORM_VER(6, 0, 1)
							uint32_t cod, bool u)
#else
								uint32_t cod)
#endif
{
	/* Store for command completion */
	bt_bdaddr_t2str(remote_bd_addr, last_remote_addr);
	pin_request_addr = *remote_bd_addr;

	haltest_info("%s: remote_bd_addr=%s bd_name=%s cod=%06x\n", __func__,
					last_remote_addr, bd_name->name, cod);
	terminal_prompt_for("Enter pin: ", pin_request_answer);
}

/* Variables to store information from ssp_request_cb used for ssp_reply */
static bt_bdaddr_t ssp_request_addr;
static bt_ssp_variant_t ssp_request_variant;
static uint32_t ssp_request_pask_key;

/* Called when user hit enter on prompt for confirmation */
static void ssp_request_yes_no_answer(char *reply)
{
	int accept = *reply == 0 || *reply == 'y' || *reply == 'Y';

	if_bluetooth->ssp_reply(&ssp_request_addr, ssp_request_variant, accept,
							ssp_request_pask_key);
}

static void ssp_request_cb(bt_bdaddr_t *remote_bd_addr, bt_bdname_t *bd_name,
				uint32_t cod, bt_ssp_variant_t pairing_variant,
				uint32_t pass_key)
{
	static char prompt[50];

	/* Store for command completion */
	bt_bdaddr_t2str(remote_bd_addr, last_remote_addr);
	last_ssp_variant = pairing_variant;

	haltest_info("%s: remote_bd_addr=%s bd_name=%s cod=%06x pairing_variant=%s pass_key=%d\n",
			__func__, last_remote_addr, bd_name->name, cod,
			bt_ssp_variant_t2str(pairing_variant), pass_key);

	switch (pairing_variant) {
	case BT_SSP_VARIANT_PASSKEY_CONFIRMATION:
		sprintf(prompt, "Does other device show %d [Y/n] ?", pass_key);

		ssp_request_addr = *remote_bd_addr;
		ssp_request_variant = pairing_variant;
		ssp_request_pask_key = pass_key;

		terminal_prompt_for(prompt, ssp_request_yes_no_answer);
		break;
	case BT_SSP_VARIANT_CONSENT:
		sprintf(prompt, "Consent pairing [Y/n] ?");

		ssp_request_addr = *remote_bd_addr;
		ssp_request_variant = pairing_variant;
		ssp_request_pask_key = 0;

		terminal_prompt_for(prompt, ssp_request_yes_no_answer);
		break;
	case BT_SSP_VARIANT_PASSKEY_ENTRY:
	case BT_SSP_VARIANT_PASSKEY_NOTIFICATION:
	default:
		haltest_info("Not automatically handled\n");
		break;
	}
}

static void bond_state_changed_cb(bt_status_t status,
						bt_bdaddr_t *remote_bd_addr,
						bt_bond_state_t state)
{
	haltest_info("%s: status=%s remote_bd_addr=%s state=%s\n", __func__,
			bt_status_t2str(status), bdaddr2str(remote_bd_addr),
			bt_bond_state_t2str(state));
}

static void acl_state_changed_cb(bt_status_t status,
						bt_bdaddr_t *remote_bd_addr,
						bt_acl_state_t state)
{
	haltest_info("%s: status=%s remote_bd_addr=%s state=%s\n", __func__,
			bt_status_t2str(status), bdaddr2str(remote_bd_addr),
			bt_acl_state_t2str(state));
}

static void thread_evt_cb(bt_cb_thread_evt evt)
{
	haltest_info("%s: evt=%s\n", __func__, bt_cb_thread_evt2str(evt));
}

static void dut_mode_recv_cb(uint16_t opcode, uint8_t *buf, uint8_t len)
{
	haltest_info("%s\n", __func__);
}

static void le_test_mode_cb(bt_status_t status, uint16_t num_packets)
{
	haltest_info("%s %s %d\n", __func__, bt_status_t2str(status),
								num_packets);
}

#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
static void energy_info_cb(bt_activity_energy_info *energy_info)
{
	haltest_info("%s status=%s, ctrl_state=0x%02X, tx_time=0x%jx,"
			"rx_time=0x%jx, idle_time=0x%jx, energu_used=0x%jx\n",
			__func__, bt_status_t2str(energy_info->status),
			energy_info->ctrl_state, energy_info->tx_time,
			energy_info->rx_time, energy_info->idle_time,
			energy_info->energy_used);
}
#endif

static bt_callbacks_t bt_callbacks = {
	.size = sizeof(bt_callbacks),
	.adapter_state_changed_cb = adapter_state_changed_cb,
	.adapter_properties_cb = adapter_properties_cb,
	.remote_device_properties_cb = remote_device_properties_cb,
	.device_found_cb = device_found_cb,
	.discovery_state_changed_cb = discovery_state_changed_cb,
	.pin_request_cb = pin_request_cb,
	.ssp_request_cb = ssp_request_cb,
	.bond_state_changed_cb = bond_state_changed_cb,
	.acl_state_changed_cb = acl_state_changed_cb,
	.thread_evt_cb = thread_evt_cb,
	.dut_mode_recv_cb = dut_mode_recv_cb,
	.le_test_mode_cb = le_test_mode_cb,
#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
	.energy_info_cb = energy_info_cb,
#endif
};

#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
static alarm_cb alarm_cb_p = NULL;
static void *alarm_cb_p_data = NULL;

static bool set_wake_alarm(uint64_t delay_millis, bool should_wake, alarm_cb cb,
								void *data)
{
	haltest_info("%s: delay %"PRIu64" should_wake %u cb %p data %p\n",
				__func__, delay_millis, should_wake, cb, data);

	/* TODO call alarm callback after specified delay */
	alarm_cb_p = cb;
	alarm_cb_p_data = data;

	return true;
}

static int acquire_wake_lock(const char *lock_name)
{
	haltest_info("%s: %s\n", __func__, lock_name);

	return BT_STATUS_SUCCESS;
}

static int release_wake_lock(const char *lock_name)
{
	haltest_info("%s: %s\n", __func__, lock_name);

	return BT_STATUS_SUCCESS;
}

static bt_os_callouts_t bt_os_callouts = {
	.size = sizeof(bt_os_callouts),
	.set_wake_alarm = set_wake_alarm,
	.acquire_wake_lock = acquire_wake_lock,
	.release_wake_lock = release_wake_lock,
};
#endif

static void init_p(int argc, const char **argv)
{
	int err;
	const hw_module_t *module;

	err = hw_get_module(BT_HARDWARE_MODULE_ID, &module);
	if (err) {
		haltest_error("he_get_module returned %d\n", err);
		return;
	}

	err = module->methods->open(module, BT_HARDWARE_MODULE_ID, &bt_device);
	if (err) {
		haltest_error("module->methods->open returned %d\n", err);
		return;
	}

	if_bluetooth =
		((bluetooth_device_t *) bt_device)->get_bluetooth_interface();
	if (!if_bluetooth) {
		haltest_error("get_bluetooth_interface returned NULL\n");
		return;
	}

	EXEC(if_bluetooth->init, &bt_callbacks);

#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
	EXEC(if_bluetooth->set_os_callouts, &bt_os_callouts);
#endif
}

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

	EXECV(if_bluetooth->cleanup);

	if_bluetooth = NULL;
}

static void enable_p(int argc, const char **argv)
{
	RETURN_IF_NULL(if_bluetooth);

#if ANDROID_VERSION >= PLATFORM_VER(6, 0, 1)
	EXEC(if_bluetooth->enable, false);
#else
	EXEC(if_bluetooth->enable);
#endif
}
#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
static void read_energy_info_p(int argc, const char **argv)
{
	RETURN_IF_NULL(if_bluetooth);

	EXEC(if_bluetooth->read_energy_info);
}

#define get_connection_state_c complete_addr_c

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

	RETURN_IF_NULL(if_bluetooth);

	VERIFY_ADDR_ARG(2, &addr);

	haltest_info("if_bluetooth->get_connection_state : %d\n",
				if_bluetooth->get_connection_state(&addr));
}
#endif

static void disable_p(int argc, const char **argv)
{
	RETURN_IF_NULL(if_bluetooth);

	EXEC(if_bluetooth->disable);
}

static void get_adapter_properties_p(int argc, const char **argv)
{
	RETURN_IF_NULL(if_bluetooth);

	EXEC(if_bluetooth->get_adapter_properties);
}

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

static void get_adapter_property_p(int argc, const char **argv)
{
	int type;

	RETURN_IF_NULL(if_bluetooth);
	VERIFY_PROP_TYPE_ARG(2, type);

	EXEC(if_bluetooth->get_adapter_property, type);
}

static const char * const names[] = {
	"BT_PROPERTY_BDNAME",
	"BT_PROPERTY_ADAPTER_SCAN_MODE",
	"BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT",
	NULL
};

static void set_adapter_property_c(int argc, const char **argv,
					enum_func *enum_func, void **user)
{
	if (argc == 3) {
		*user = (void *) names;
		*enum_func = enum_strings;
	} else if (argc == 4) {
		if (0 == strcmp(argv[2], "BT_PROPERTY_ADAPTER_SCAN_MODE")) {
			*user = TYPE_ENUM(bt_scan_mode_t);
			*enum_func = enum_defines;
		}
	}
}

static void set_adapter_property_p(int argc, const char **argv)
{
	bt_property_t property;
	bt_scan_mode_t mode;
	int timeout;

	RETURN_IF_NULL(if_bluetooth);
	VERIFY_PROP_TYPE_ARG(2, property.type);

	if (argc <= 3) {
		haltest_error("No property value specified\n");
		return;
	}
	switch (property.type) {
	case BT_PROPERTY_BDNAME:
		property.len = strlen(argv[3]) + 1;
		property.val = (char *) argv[3];
		break;

	case BT_PROPERTY_ADAPTER_SCAN_MODE:
		mode = str2btscanmode(argv[3]);
		property.len = sizeof(bt_scan_mode_t);
		property.val = &mode;
		break;

	case BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT:
		timeout = atoi(argv[3]);
		property.val = &timeout;
		property.len = sizeof(timeout);
		break;

	case BT_PROPERTY_BDADDR:
	case BT_PROPERTY_UUIDS:
	case BT_PROPERTY_CLASS_OF_DEVICE:
	case BT_PROPERTY_TYPE_OF_DEVICE:
	case BT_PROPERTY_SERVICE_RECORD:
	case BT_PROPERTY_ADAPTER_BONDED_DEVICES:
	case BT_PROPERTY_REMOTE_FRIENDLY_NAME:
	case BT_PROPERTY_REMOTE_RSSI:
	case BT_PROPERTY_REMOTE_VERSION_INFO:
	case BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP:
#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
	case BT_PROPERTY_LOCAL_LE_FEATURES:
#endif
	default:
		haltest_error("Invalid property %s\n", argv[3]);
		return;
	}

	EXEC(if_bluetooth->set_adapter_property, &property);
}

/* This function is to be used for completion methods that need only address */
static void complete_addr_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	if (argc == 3) {
		*user = NULL;
		*enum_func = enum_devices;
	}
}

/* Just addres to complete, use complete_addr_c */
#define get_remote_device_properties_c complete_addr_c

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

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

	EXEC(if_bluetooth->get_remote_device_properties, &addr);
}

static void get_remote_device_property_c(int argc, const char **argv,
							enum_func *enum_func,
							void **user)
{
	if (argc == 3) {
		*user = NULL;
		*enum_func = enum_devices;
	} else if (argc == 4) {
		*user = TYPE_ENUM(bt_property_type_t);
		*enum_func = enum_defines;
	}
}

static void get_remote_device_property_p(int argc, const char **argv)
{
	bt_property_type_t type;
	bt_bdaddr_t addr;

	RETURN_IF_NULL(if_bluetooth);
	VERIFY_ADDR_ARG(2, &addr);
	VERIFY_PROP_TYPE_ARG(3, type);

	EXEC(if_bluetooth->get_remote_device_property, &addr, type);
}

/*
 * Same completion as for get_remote_device_property_c can be used for
 * set_remote_device_property_c. No need to create separate function.
 */
#define set_remote_device_property_c get_remote_device_property_c

static void set_remote_device_property_p(int argc, const char **argv)
{
	bt_property_t property;
	bt_bdaddr_t addr;

	RETURN_IF_NULL(if_bluetooth);
	VERIFY_ADDR_ARG(2, &addr);
	VERIFY_PROP_TYPE_ARG(3, property.type);

	switch (property.type) {
	case BT_PROPERTY_REMOTE_FRIENDLY_NAME:
		property.len = strlen(argv[4]);
		property.val = (char *) argv[4];
		break;
	case BT_PROPERTY_BDNAME:
	case BT_PROPERTY_BDADDR:
	case BT_PROPERTY_UUIDS:
	case BT_PROPERTY_CLASS_OF_DEVICE:
	case BT_PROPERTY_TYPE_OF_DEVICE:
	case BT_PROPERTY_SERVICE_RECORD:
	case BT_PROPERTY_ADAPTER_SCAN_MODE:
	case BT_PROPERTY_ADAPTER_BONDED_DEVICES:
	case BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT:
	case BT_PROPERTY_REMOTE_RSSI:
	case BT_PROPERTY_REMOTE_VERSION_INFO:
	case BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP:
#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
	case BT_PROPERTY_LOCAL_LE_FEATURES:
#endif
	default:
		return;
	}

	EXEC(if_bluetooth->set_remote_device_property, &addr, &property);
}

/* For now uuid is not autocompleted. Use routine for complete_addr_c */
#define get_remote_service_record_c complete_addr_c

static void get_remote_service_record_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;
	bt_uuid_t uuid;

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

	if (argc <= 3) {
		haltest_error("No uuid specified\n");
		return;
	}

	str2bt_uuid_t(argv[3], &uuid);

	EXEC(if_bluetooth->get_remote_service_record, &addr, &uuid);
}

/* Just addres to complete, use complete_addr_c */
#define get_remote_services_c complete_addr_c

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

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

	EXEC(if_bluetooth->get_remote_services, &addr);
}

static void start_discovery_p(int argc, const char **argv)
{
	RETURN_IF_NULL(if_bluetooth);

	EXEC(if_bluetooth->start_discovery);
}

static void cancel_discovery_p(int argc, const char **argv)
{
	RETURN_IF_NULL(if_bluetooth);

	EXEC(if_bluetooth->cancel_discovery);
}

/* Just addres to complete, use complete_addr_c */
#define create_bond_c complete_addr_c

static void create_bond_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;
#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
	int transport;
#endif

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

#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
	if (argc < 4)
		transport = BT_TRANSPORT_UNKNOWN;
	else
		transport = atoi(argv[3]);

	EXEC(if_bluetooth->create_bond, &addr, transport);
#else
	EXEC(if_bluetooth->create_bond, &addr);
#endif
}

/* Just addres to complete, use complete_addr_c */
#define remove_bond_c complete_addr_c

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

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

	EXEC(if_bluetooth->remove_bond, &addr);
}

/* Just addres to complete, use complete_addr_c */
#define cancel_bond_c complete_addr_c

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

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

	EXEC(if_bluetooth->cancel_bond, &addr);
}

static void pin_reply_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	static const char *const completions[] = { last_remote_addr, NULL };

	if (argc == 3) {
		*user = (void *) completions;
		*enum_func = enum_strings;
	}
}

static void pin_reply_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;
	bt_pin_code_t pin;
	int pin_len = 0;
	int accept = 0;

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

	if (argc > 3) {
		accept = 1;
		pin_len = strlen(argv[3]);
		memcpy(pin.pin, argv[3], pin_len);
	}

	EXEC(if_bluetooth->pin_reply, &addr, accept, pin_len, &pin);
}

static void ssp_reply_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	if (argc == 3) {
		*user = last_remote_addr;
		*enum_func = enum_one_string;
	} else if (argc == 5) {
		*user = "1";
		*enum_func = enum_one_string;
	} else if (argc == 4) {
		if (-1 != (int) last_ssp_variant) {
			*user = (void *) bt_ssp_variant_t2str(last_ssp_variant);
			*enum_func = enum_one_string;
		} else {
			*user = TYPE_ENUM(bt_ssp_variant_t);
			*enum_func = enum_defines;
		}
	}
}

static void ssp_reply_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;
	bt_ssp_variant_t var;
	int accept;
	int passkey;

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

	if (argc < 4) {
		haltest_error("No ssp variant specified\n");
		return;
	}

	var = str2btsspvariant(argv[3]);
	if (argc < 5) {
		haltest_error("No accept value specified\n");
		return;
	}

	accept = atoi(argv[4]);
	passkey = 0;

	if (accept && var == BT_SSP_VARIANT_PASSKEY_ENTRY && argc >= 5)
		passkey = atoi(argv[4]);

	EXEC(if_bluetooth->ssp_reply, &addr, var, accept, passkey);
}

static void get_profile_interface_c(int argc, const char **argv,
					enum_func *enum_func, void **user)
{
	static const char *const profile_ids[] = {
		BT_PROFILE_HANDSFREE_ID,
		BT_PROFILE_ADVANCED_AUDIO_ID,
		BT_PROFILE_HEALTH_ID,
		BT_PROFILE_SOCKETS_ID,
		BT_PROFILE_HIDHOST_ID,
		BT_PROFILE_PAN_ID,
		BT_PROFILE_GATT_ID,
		BT_PROFILE_AV_RC_ID,
#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
		BT_PROFILE_HANDSFREE_CLIENT_ID,
		BT_PROFILE_MAP_CLIENT_ID,
		BT_PROFILE_AV_RC_CTRL_ID,
		BT_PROFILE_ADVANCED_AUDIO_SINK_ID,
#endif
		NULL
	};

	if (argc == 3) {
		*user = (void *) profile_ids;
		*enum_func = enum_strings;
	}
}

static void get_profile_interface_p(int argc, const char **argv)
{
	const char *id;
	const void **pif = NULL;

	RETURN_IF_NULL(if_bluetooth);
	if (argc <= 2) {
		haltest_error("No interface specified\n");
		return;
	}

	id = argv[2];

	if (strcmp(BT_PROFILE_HANDSFREE_ID, id) == 0)
		pif = (const void **) &if_hf;
	else if (strcmp(BT_PROFILE_ADVANCED_AUDIO_ID, id) == 0)
		pif = (const void **) &if_av;
	else if (strcmp(BT_PROFILE_HEALTH_ID, id) == 0)
		pif = (const void **) &if_hl;
	else if (strcmp(BT_PROFILE_SOCKETS_ID, id) == 0)
		pif = (const void **) &if_sock;
	else if (strcmp(BT_PROFILE_HIDHOST_ID, id) == 0)
		pif = (const void **) &if_hh;
	else if (strcmp(BT_PROFILE_PAN_ID, id) == 0)
		pif = (const void **) &if_pan;
	else if (strcmp(BT_PROFILE_AV_RC_ID, id) == 0)
		pif = (const void **) &if_rc;
	else if (strcmp(BT_PROFILE_GATT_ID, id) == 0)
		pif = (const void **) &if_gatt;
#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
	else if (strcmp(BT_PROFILE_AV_RC_CTRL_ID, id) == 0)
		pif = (const void **) &if_rc_ctrl;
	else if (strcmp(BT_PROFILE_HANDSFREE_CLIENT_ID, id) == 0)
		pif = (const void **) &if_hf_client;
	else if (strcmp(BT_PROFILE_MAP_CLIENT_ID, id) == 0)
		pif = (const void **) &if_mce;
	else if (strcmp(BT_PROFILE_ADVANCED_AUDIO_SINK_ID, id) == 0)
		pif = (const void **) &if_av_sink;
#endif
	else
		haltest_error("%s is not correct for get_profile_interface\n",
									id);

	if (pif != NULL) {
		*pif = if_bluetooth->get_profile_interface(id);
		haltest_info("get_profile_interface(%s) : %p\n", id, *pif);
	}
}

static void dut_mode_configure_p(int argc, const char **argv)
{
	uint8_t mode;

	RETURN_IF_NULL(if_bluetooth);

	if (argc <= 2) {
		haltest_error("No dut mode specified\n");
		return;
	}

	mode = strtol(argv[2], NULL, 0);

	EXEC(if_bluetooth->dut_mode_configure, mode);
}

static void dut_mode_send_p(int argc, const char **argv)
{
	haltest_error("not implemented\n");
}

static void le_test_mode_p(int argc, const char **argv)
{
	haltest_error("not implemented\n");
}

static void config_hci_snoop_log_p(int argc, const char **argv)
{
	uint8_t mode;

	RETURN_IF_NULL(if_bluetooth);

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

	mode = strtol(argv[2], NULL, 0);

	EXEC(if_bluetooth->config_hci_snoop_log, mode);
}

static struct method methods[] = {
	STD_METHOD(init),
	STD_METHOD(cleanup),
	STD_METHOD(enable),
	STD_METHOD(disable),
	STD_METHOD(get_adapter_properties),
	STD_METHODCH(get_adapter_property, "<prop_type>"),
	STD_METHODCH(set_adapter_property, "<prop_type> <prop_value>"),
	STD_METHODCH(get_remote_device_properties, "<addr>"),
	STD_METHODCH(get_remote_device_property, "<addr> <property_type>"),
	STD_METHODCH(set_remote_device_property,
					"<addr> <property_type> <value>"),
	STD_METHODCH(get_remote_service_record, "<addr> <uuid>"),
	STD_METHODCH(get_remote_services, "<addr>"),
	STD_METHOD(start_discovery),
	STD_METHOD(cancel_discovery),
#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
	STD_METHODCH(create_bond, "<addr> [<transport>]"),
	STD_METHOD(read_energy_info),
	STD_METHODCH(get_connection_state, "<addr>"),
#else
	STD_METHODCH(create_bond, "<addr>"),
#endif
	STD_METHODCH(remove_bond, "<addr>"),
	STD_METHODCH(cancel_bond, "<addr>"),
	STD_METHODCH(pin_reply, "<address> [<pin>]"),
	STD_METHODCH(ssp_reply, "<address> <ssp_veriant> 1|0 [<passkey>]"),
	STD_METHODCH(get_profile_interface, "<profile id>"),
	STD_METHODH(dut_mode_configure, "<dut mode>"),
	STD_METHOD(dut_mode_send),
	STD_METHOD(le_test_mode),
	STD_METHODH(config_hci_snoop_log, "<mode>"),
	END_METHOD
};

const struct interface bluetooth_if = {
	.name = "bluetooth",
	.methods = methods
};
