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

#include <cutils/properties.h>

#include "hal-log.h"
#include "hal.h"
#include "hal-msg.h"
#include "ipc-common.h"
#include "hal-ipc.h"
#include "hal-utils.h"

#define MODE_PROPERTY_NAME "persist.sys.bluetooth.mode"

static const bt_callbacks_t *bt_hal_cbacks = NULL;

#define enum_prop_to_hal(prop, hal_prop, type) do { \
	static type e; \
	prop.val = &e; \
	prop.len = sizeof(e); \
	e = *((uint8_t *) (hal_prop->val)); \
} while (0)

#define enum_prop_from_hal(prop, hal_len, hal_val, enum_type) do { \
	enum_type e; \
	if (prop->len != sizeof(e)) { \
		error("invalid HAL property %u (%u vs %zu), aborting ", \
					prop->type, prop->len, sizeof(e)); \
		exit(EXIT_FAILURE); \
	} \
	memcpy(&e, prop->val, sizeof(e)); \
	*((uint8_t *) hal_val) = e; /* enums are mapped to 1 byte */ \
	*hal_len = 1; \
} while (0)

static void handle_adapter_state_changed(void *buf, uint16_t len)
{
	struct hal_ev_adapter_state_changed *ev = buf;

	DBG("state: %s", bt_state_t2str(ev->state));

	if (bt_hal_cbacks->adapter_state_changed_cb)
		bt_hal_cbacks->adapter_state_changed_cb(ev->state);
}

static void adapter_props_to_hal(bt_property_t *send_props,
					struct hal_property *prop,
					uint8_t num_props, uint16_t len)
{
	void *buf = prop;
	uint8_t i;

	for (i = 0; i < num_props; i++) {
		if (sizeof(*prop) + prop->len > len) {
			error("invalid adapter properties(%zu > %u), aborting",
					sizeof(*prop) + prop->len, len);
			exit(EXIT_FAILURE);
		}

		send_props[i].type = prop->type;

		switch (prop->type) {
		case HAL_PROP_ADAPTER_TYPE:
			enum_prop_to_hal(send_props[i], prop,
							bt_device_type_t);
			break;
		case HAL_PROP_ADAPTER_SCAN_MODE:
			enum_prop_to_hal(send_props[i], prop,
							bt_scan_mode_t);
			break;
		case HAL_PROP_ADAPTER_SERVICE_REC:
		default:
			send_props[i].len = prop->len;
			send_props[i].val = prop->val;
			break;
		}

		DBG("prop[%d]: %s", i, btproperty2str(&send_props[i]));

		len -= sizeof(*prop) + prop->len;
		buf += sizeof(*prop) + prop->len;
		prop = buf;
	}

	if (!len)
		return;

	error("invalid adapter properties (%u bytes left), aborting", len);
	exit(EXIT_FAILURE);
}

static void adapter_prop_from_hal(const bt_property_t *property, uint8_t *type,
						uint16_t *len, void *val)
{
	/* type match IPC type */
	*type = property->type;

	switch (property->type) {
	case HAL_PROP_ADAPTER_SCAN_MODE:
		enum_prop_from_hal(property, len, val, bt_scan_mode_t);
		break;
	default:
		*len = property->len;
		memcpy(val, property->val, property->len);
		break;
	}
}

static void device_props_to_hal(bt_property_t *send_props,
				struct hal_property *prop, uint8_t num_props,
				uint16_t len)
{
	void *buf = prop;
	uint8_t i;

	for (i = 0; i < num_props; i++) {
		if (sizeof(*prop) + prop->len > len) {
			error("invalid device properties (%zu > %u), aborting",
					sizeof(*prop) + prop->len, len);
			exit(EXIT_FAILURE);
		}

		send_props[i].type = prop->type;

		switch (prop->type) {
		case HAL_PROP_DEVICE_TYPE:
			enum_prop_to_hal(send_props[i], prop,
							bt_device_type_t);
			break;
		case HAL_PROP_DEVICE_VERSION_INFO:
		{
			static bt_remote_version_t e;
			const struct hal_prop_device_info *p;

			send_props[i].val = &e;
			send_props[i].len = sizeof(e);

			p = (struct hal_prop_device_info *) prop->val;

			e.manufacturer = p->manufacturer;
			e.sub_ver = p->sub_version;
			e.version = p->version;
		}
			break;
		case HAL_PROP_DEVICE_SERVICE_REC:
		{
			static bt_service_record_t e;
			const struct hal_prop_device_service_rec *p;

			send_props[i].val = &e;
			send_props[i].len = sizeof(e);

			p = (struct hal_prop_device_service_rec *) prop->val;

			memset(&e, 0, sizeof(e));
			memcpy(&e.channel, &p->channel, sizeof(e.channel));
			memcpy(e.uuid.uu, p->uuid, sizeof(e.uuid.uu));
			memcpy(e.name, p->name, p->name_len);
		}
			break;
		default:
			send_props[i].len = prop->len;
			send_props[i].val = prop->val;
			break;
		}

		len -= sizeof(*prop) + prop->len;
		buf += sizeof(*prop) + prop->len;
		prop = buf;

		DBG("prop[%d]: %s", i, btproperty2str(&send_props[i]));
	}

	if (!len)
		return;

	error("invalid device properties (%u bytes left), aborting", len);
	exit(EXIT_FAILURE);
}

static void handle_adapter_props_changed(void *buf, uint16_t len)
{
	struct hal_ev_adapter_props_changed *ev = buf;
	bt_property_t props[ev->num_props];

	DBG("");

	if (!bt_hal_cbacks->adapter_properties_cb)
		return;

	len -= sizeof(*ev);
	adapter_props_to_hal(props, ev->props, ev->num_props, len);

	bt_hal_cbacks->adapter_properties_cb(ev->status, ev->num_props, props);
}

static void handle_bond_state_change(void *buf, uint16_t len)
{
	struct hal_ev_bond_state_changed *ev = buf;
	bt_bdaddr_t *addr = (bt_bdaddr_t *) ev->bdaddr;

	DBG("state %u", ev->state);

	if (bt_hal_cbacks->bond_state_changed_cb)
		bt_hal_cbacks->bond_state_changed_cb(ev->status, addr,
								ev->state);
}

static void handle_pin_request(void *buf, uint16_t len)
{
	struct hal_ev_pin_request *ev = buf;
	/* Those are declared as packed, so it's safe to assign pointers */
	bt_bdaddr_t *addr = (bt_bdaddr_t *) ev->bdaddr;
	bt_bdname_t *name = (bt_bdname_t *) ev->name;

	DBG("");

	if (bt_hal_cbacks->pin_request_cb)
		bt_hal_cbacks->pin_request_cb(addr, name, ev->class_of_dev);
}

static void handle_ssp_request(void *buf, uint16_t len)
{
	struct hal_ev_ssp_request *ev = buf;
	/* Those are declared as packed, so it's safe to assign pointers */
	bt_bdaddr_t *addr = (bt_bdaddr_t *) ev->bdaddr;
	bt_bdname_t *name = (bt_bdname_t *) ev->name;

	DBG("");

	if (bt_hal_cbacks->ssp_request_cb)
		bt_hal_cbacks->ssp_request_cb(addr, name, ev->class_of_dev,
							ev->pairing_variant,
							ev->passkey);
}

void bt_thread_associate(void)
{
	if (bt_hal_cbacks->thread_evt_cb)
		bt_hal_cbacks->thread_evt_cb(ASSOCIATE_JVM);
}

void bt_thread_disassociate(void)
{
	if (bt_hal_cbacks->thread_evt_cb)
		bt_hal_cbacks->thread_evt_cb(DISASSOCIATE_JVM);
}

static bool interface_ready(void)
{
	return bt_hal_cbacks != NULL;
}

static void handle_discovery_state_changed(void *buf, uint16_t len)
{
	struct hal_ev_discovery_state_changed *ev = buf;

	DBG("");

	if (bt_hal_cbacks->discovery_state_changed_cb)
		bt_hal_cbacks->discovery_state_changed_cb(ev->state);
}

static void handle_device_found(void *buf, uint16_t len)
{
	struct hal_ev_device_found *ev = buf;
	bt_property_t props[ev->num_props];

	DBG("");

	if (!bt_hal_cbacks->device_found_cb)
		return;

	len -= sizeof(*ev);
	device_props_to_hal(props, ev->props, ev->num_props, len);

	bt_hal_cbacks->device_found_cb(ev->num_props, props);
}

static void handle_device_state_changed(void *buf, uint16_t len)
{
	struct hal_ev_remote_device_props *ev = buf;
	bt_property_t props[ev->num_props];

	DBG("");

	if (!bt_hal_cbacks->remote_device_properties_cb)
		return;

	len -= sizeof(*ev);
	device_props_to_hal(props, ev->props, ev->num_props, len);

	bt_hal_cbacks->remote_device_properties_cb(ev->status,
						(bt_bdaddr_t *)ev->bdaddr,
						ev->num_props, props);
}

static void handle_acl_state_changed(void *buf, uint16_t len)
{
	struct hal_ev_acl_state_changed *ev = buf;
	bt_bdaddr_t *addr = (bt_bdaddr_t *) ev->bdaddr;

	DBG("state %u", ev->state);

	if (bt_hal_cbacks->acl_state_changed_cb)
		bt_hal_cbacks->acl_state_changed_cb(ev->status, addr,
								ev->state);
}

static void handle_dut_mode_receive(void *buf, uint16_t len)
{
	struct hal_ev_dut_mode_receive *ev = buf;

	DBG("");

	if (len != sizeof(*ev) + ev->len) {
		error("invalid dut mode receive event (%u), aborting", len);
		exit(EXIT_FAILURE);
	}

	if (bt_hal_cbacks->dut_mode_recv_cb)
		bt_hal_cbacks->dut_mode_recv_cb(ev->opcode, ev->data, ev->len);
}

static void handle_le_test_mode(void *buf, uint16_t len)
{
	struct hal_ev_le_test_mode *ev = buf;

	DBG("");

	if (bt_hal_cbacks->le_test_mode_cb)
		bt_hal_cbacks->le_test_mode_cb(ev->status, ev->num_packets);
}

/*
 * handlers will be called from notification thread context,
 * index in table equals to 'opcode - HAL_MINIMUM_EVENT'
 */
static const struct hal_ipc_handler ev_handlers[] = {
	{	/* HAL_EV_ADAPTER_STATE_CHANGED */
		.handler = handle_adapter_state_changed,
		.var_len = false,
		.data_len = sizeof(struct hal_ev_adapter_state_changed)
	},
	{	/* HAL_EV_ADAPTER_PROPS_CHANGED */
		.handler = handle_adapter_props_changed,
		.var_len = true,
		.data_len = sizeof(struct hal_ev_adapter_props_changed) +
						sizeof(struct hal_property),
	},
	{	/* HAL_EV_REMOTE_DEVICE_PROPS */
		.handler = handle_device_state_changed,
		.var_len = true,
		.data_len = sizeof(struct hal_ev_remote_device_props) +
						sizeof(struct hal_property),
	},
	{	/* HAL_EV_DEVICE_FOUND */
		.handler = handle_device_found,
		.var_len = true,
		.data_len = sizeof(struct hal_ev_device_found) +
						sizeof(struct hal_property),
	},
	{	/* HAL_EV_DISCOVERY_STATE_CHANGED */
		.handler = handle_discovery_state_changed,
		.var_len = false,
		.data_len = sizeof(struct hal_ev_discovery_state_changed),
	},
	{	/* HAL_EV_PIN_REQUEST */
		.handler = handle_pin_request,
		.var_len = false,
		.data_len = sizeof(struct hal_ev_pin_request),
	},
	{	/* HAL_EV_SSP_REQUEST */
		.handler = handle_ssp_request,
		.var_len = false,
		.data_len = sizeof(struct hal_ev_ssp_request),
	},
	{	/* HAL_EV_BOND_STATE_CHANGED */
		.handler = handle_bond_state_change,
		.var_len = false,
		.data_len = sizeof(struct hal_ev_bond_state_changed),
	},
	{	/* HAL_EV_ACL_STATE_CHANGED */
		.handler = handle_acl_state_changed,
		.var_len = false,
		.data_len = sizeof(struct hal_ev_acl_state_changed),
	},
	{	/* HAL_EV_DUT_MODE_RECEIVE */
		.handler = handle_dut_mode_receive,
		.var_len = true,
		.data_len = sizeof(struct hal_ev_dut_mode_receive),
	},
	{	/* HAL_EV_LE_TEST_MODE */
		.handler = handle_le_test_mode,
		.var_len = false,
		.data_len = sizeof(struct hal_ev_le_test_mode),
	}
};

static uint8_t get_mode(void)
{
	char value[PROPERTY_VALUE_MAX];

	if (property_get(MODE_PROPERTY_NAME, value, "") > 0 &&
					(!strcasecmp(value, "bredr")))
		return HAL_MODE_BREDR;

	if (property_get(MODE_PROPERTY_NAME, value, "") > 0 &&
					(!strcasecmp(value, "le")))
		return HAL_MODE_LE;

	return HAL_MODE_DEFAULT;
}

static int init(bt_callbacks_t *callbacks)
{
	struct hal_cmd_register_module cmd;
	int status;

	DBG("");

	if (interface_ready())
		return BT_STATUS_DONE;

	bt_hal_cbacks = callbacks;

	hal_ipc_register(HAL_SERVICE_ID_BLUETOOTH, ev_handlers,
				sizeof(ev_handlers)/sizeof(ev_handlers[0]));

	if (!hal_ipc_init()) {
		bt_hal_cbacks = NULL;
		return BT_STATUS_FAIL;
	}

	cmd.service_id = HAL_SERVICE_ID_BLUETOOTH;
	cmd.mode = get_mode();

	status = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE,
					sizeof(cmd), &cmd, NULL, NULL, NULL);
	if (status != BT_STATUS_SUCCESS) {
		error("Failed to register 'bluetooth' service");
		goto fail;
	}

	cmd.service_id = HAL_SERVICE_ID_SOCKET;
	cmd.mode = HAL_MODE_DEFAULT;

	status = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE,
					sizeof(cmd), &cmd, NULL, NULL, NULL);
	if (status != BT_STATUS_SUCCESS) {
		error("Failed to register 'socket' service");
		goto fail;
	}

	return status;

fail:
	hal_ipc_cleanup();
	bt_hal_cbacks = NULL;

	hal_ipc_unregister(HAL_SERVICE_ID_BLUETOOTH);

	return status;
}

static int enable(void)
{
	DBG("");

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_ENABLE, 0, NULL, 0,
								NULL, NULL);
}

static int disable(void)
{
	DBG("");

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_DISABLE, 0, NULL, 0,
								NULL, NULL);
}

static void cleanup(void)
{
	DBG("");

	if (!interface_ready())
		return;

	hal_ipc_cleanup();

	bt_hal_cbacks = NULL;

	hal_ipc_unregister(HAL_SERVICE_ID_BLUETOOTH);
}

static int get_adapter_properties(void)
{
	DBG("");

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_GET_ADAPTER_PROPS,
						0, NULL, 0, NULL, NULL);
}

static int get_adapter_property(bt_property_type_t type)
{
	struct hal_cmd_get_adapter_prop cmd;

	DBG("prop: %s (%d)", bt_property_type_t2str(type), type);

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	/* type match IPC type */
	cmd.type = type;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_GET_ADAPTER_PROP,
					sizeof(cmd), &cmd, 0, NULL, NULL);
}

static int set_adapter_property(const bt_property_t *property)
{
	char buf[IPC_MTU];
	struct hal_cmd_set_adapter_prop *cmd = (void *) buf;
	size_t len;

	DBG("prop: %s", btproperty2str(property));

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	adapter_prop_from_hal(property, &cmd->type, &cmd->len, cmd->val);

	len = sizeof(*cmd) + cmd->len;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_SET_ADAPTER_PROP,
						len, cmd, 0, NULL, NULL);
}

static int get_remote_device_properties(bt_bdaddr_t *remote_addr)
{
	struct hal_cmd_get_remote_device_props cmd;

	DBG("bdaddr: %s", bdaddr2str(remote_addr));

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	memcpy(cmd.bdaddr, remote_addr, sizeof(cmd.bdaddr));

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH,
					HAL_OP_GET_REMOTE_DEVICE_PROPS,
					sizeof(cmd), &cmd, 0, NULL, NULL);
}

static int get_remote_device_property(bt_bdaddr_t *remote_addr,
						bt_property_type_t type)
{
	struct hal_cmd_get_remote_device_prop cmd;

	DBG("bdaddr: %s prop: %s", bdaddr2str(remote_addr),
						bt_property_type_t2str(type));

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	memcpy(cmd.bdaddr, remote_addr, sizeof(cmd.bdaddr));

	/* type match IPC type */
	cmd.type = type;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH,
					HAL_OP_GET_REMOTE_DEVICE_PROP,
					sizeof(cmd), &cmd, 0, NULL, NULL);
}

static int set_remote_device_property(bt_bdaddr_t *remote_addr,
						const bt_property_t *property)
{
	char buf[IPC_MTU];
	struct hal_cmd_set_remote_device_prop *cmd = (void *) buf;
	size_t len;

	DBG("bdaddr: %s prop: %s", bdaddr2str(remote_addr),
				bt_property_type_t2str(property->type));

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	memcpy(cmd->bdaddr, remote_addr, sizeof(cmd->bdaddr));

	/* type match IPC type */
	cmd->type = property->type;
	cmd->len = property->len;
	memcpy(cmd->val, property->val, property->len);

	len = sizeof(*cmd) + cmd->len;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH,
					HAL_OP_SET_REMOTE_DEVICE_PROP,
					len, cmd, 0, NULL, NULL);
}

static int get_remote_service_record(bt_bdaddr_t *remote_addr, bt_uuid_t *uuid)
{
	struct hal_cmd_get_remote_service_rec cmd;

	DBG("bdaddr: %s", bdaddr2str(remote_addr));

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	memcpy(cmd.bdaddr, remote_addr, sizeof(cmd.bdaddr));
	memcpy(cmd.uuid, uuid, sizeof(cmd.uuid));

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH,
					HAL_OP_GET_REMOTE_SERVICE_REC,
					sizeof(cmd), &cmd, 0, NULL, NULL);
}

static int get_remote_services(bt_bdaddr_t *remote_addr)
{
	struct hal_cmd_get_remote_services cmd;

	DBG("bdaddr: %s", bdaddr2str(remote_addr));

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	memcpy(cmd.bdaddr, remote_addr, sizeof(cmd.bdaddr));

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH,
			HAL_OP_GET_REMOTE_SERVICES, sizeof(cmd), &cmd, 0,
			NULL, NULL);
}

static int start_discovery(void)
{
	DBG("");

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH,
				HAL_OP_START_DISCOVERY, 0, NULL, 0,
				NULL, NULL);
}

static int cancel_discovery(void)
{
	DBG("");

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH,
				HAL_OP_CANCEL_DISCOVERY, 0, NULL, 0,
				NULL, NULL);
}

static int create_bond(const bt_bdaddr_t *bd_addr)
{
	struct hal_cmd_create_bond cmd;

	DBG("bdaddr: %s", bdaddr2str(bd_addr));

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr));

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_CREATE_BOND,
					sizeof(cmd), &cmd, 0, NULL, NULL);
}

static int cancel_bond(const bt_bdaddr_t *bd_addr)
{
	struct hal_cmd_cancel_bond cmd;

	DBG("bdaddr: %s", bdaddr2str(bd_addr));

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr));

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_CANCEL_BOND,
					sizeof(cmd), &cmd, 0, NULL, NULL);
}

static int remove_bond(const bt_bdaddr_t *bd_addr)
{
	struct hal_cmd_remove_bond cmd;

	DBG("bdaddr: %s", bdaddr2str(bd_addr));

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr));

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_REMOVE_BOND,
					sizeof(cmd), &cmd, 0, NULL, NULL);
}

static int pin_reply(const bt_bdaddr_t *bd_addr, uint8_t accept,
				uint8_t pin_len, bt_pin_code_t *pin_code)
{
	struct hal_cmd_pin_reply cmd;

	DBG("bdaddr: %s", bdaddr2str(bd_addr));

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr));
	cmd.accept = accept;
	cmd.pin_len = pin_len;
	memcpy(cmd.pin_code, pin_code, sizeof(cmd.pin_code));

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_PIN_REPLY,
					sizeof(cmd), &cmd, 0, NULL, NULL);
}

static int ssp_reply(const bt_bdaddr_t *bd_addr, bt_ssp_variant_t variant,
					uint8_t accept, uint32_t passkey)
{
	struct hal_cmd_ssp_reply cmd;

	DBG("bdaddr: %s", bdaddr2str(bd_addr));

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr));
	/* type match IPC type */
	cmd.ssp_variant = variant;
	cmd.accept = accept;
	cmd.passkey = passkey;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_SSP_REPLY,
					sizeof(cmd), &cmd, 0, NULL, NULL);
}

static const void *get_profile_interface(const char *profile_id)
{
	DBG("%s", profile_id);

	if (!interface_ready())
		return NULL;

	if (!strcmp(profile_id, BT_PROFILE_SOCKETS_ID))
		return bt_get_socket_interface();

	if (!strcmp(profile_id, BT_PROFILE_HIDHOST_ID))
		return bt_get_hidhost_interface();

	if (!strcmp(profile_id, BT_PROFILE_PAN_ID))
		return bt_get_pan_interface();

	if (!strcmp(profile_id, BT_PROFILE_ADVANCED_AUDIO_ID))
		return bt_get_a2dp_interface();

	if (!strcmp(profile_id, BT_PROFILE_AV_RC_ID))
		return bt_get_avrcp_interface();

	if (!strcmp(profile_id, BT_PROFILE_HANDSFREE_ID))
		return bt_get_handsfree_interface();

	if (!strcmp(profile_id, BT_PROFILE_GATT_ID))
		return bt_get_gatt_interface();

	if (!strcmp(profile_id, BT_PROFILE_HEALTH_ID))
		return bt_get_health_interface();

	return NULL;
}

static int dut_mode_configure(uint8_t enable)
{
	struct hal_cmd_dut_mode_conf cmd;

	DBG("enable %u", enable);

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	cmd.enable = enable;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_DUT_MODE_CONF,
					sizeof(cmd), &cmd, 0, NULL, NULL);
}

static int dut_mode_send(uint16_t opcode, uint8_t *buf, uint8_t buf_len)
{
	char cmd_buf[IPC_MTU];
	struct hal_cmd_dut_mode_send *cmd = (void *) cmd_buf;
	size_t len;

	DBG("opcode %u len %u", opcode, buf_len);

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	cmd->opcode = opcode;
	cmd->len = buf_len;
	memcpy(cmd->data, buf, cmd->len);

	len = sizeof(*cmd) + cmd->len;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_DUT_MODE_SEND,
						len, cmd, 0, NULL, NULL);
}

static int le_test_mode(uint16_t opcode, uint8_t *buf, uint8_t buf_len)
{
	char cmd_buf[IPC_MTU];
	struct hal_cmd_le_test_mode *cmd = (void *) cmd_buf;
	size_t len;

	DBG("opcode %u len %u", opcode, buf_len);

	if (!interface_ready())
		return BT_STATUS_NOT_READY;

	cmd->opcode = opcode;
	cmd->len = buf_len;
	memcpy(cmd->data, buf, cmd->len);

	len = sizeof(*cmd) + cmd->len;

	return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_LE_TEST_MODE,
						len, cmd, 0, NULL, NULL);
}

static int config_hci_snoop_log(uint8_t enable)
{
	const char *property;

	DBG("enable %u", enable);

	property = enable ? "bluetooth.start" : "bluetooth.stop";

	if (property_set(property, "snoop") < 0) {
		error("Failed to set %s=snoop", property);
		return BT_STATUS_FAIL;
	}

	return BT_STATUS_SUCCESS;
}

static const bt_interface_t bluetooth_if = {
	.size = sizeof(bt_interface_t),
	.init = init,
	.enable = enable,
	.disable = disable,
	.cleanup = cleanup,
	.get_adapter_properties = get_adapter_properties,
	.get_adapter_property = get_adapter_property,
	.set_adapter_property = set_adapter_property,
	.get_remote_device_properties = get_remote_device_properties,
	.get_remote_device_property = get_remote_device_property,
	.set_remote_device_property = set_remote_device_property,
	.get_remote_service_record = get_remote_service_record,
	.get_remote_services = get_remote_services,
	.start_discovery = start_discovery,
	.cancel_discovery = cancel_discovery,
	.create_bond = create_bond,
	.remove_bond = remove_bond,
	.cancel_bond = cancel_bond,
	.pin_reply = pin_reply,
	.ssp_reply = ssp_reply,
	.get_profile_interface = get_profile_interface,
	.dut_mode_configure = dut_mode_configure,
	.dut_mode_send = dut_mode_send,
	.le_test_mode = le_test_mode,
	.config_hci_snoop_log = config_hci_snoop_log,
};

static const bt_interface_t *get_bluetooth_interface(void)
{
	DBG("");

	return &bluetooth_if;
}

static int close_bluetooth(struct hw_device_t *device)
{
	DBG("");

	cleanup();

	free(device);

	return 0;
}

static int open_bluetooth(const struct hw_module_t *module, char const *name,
					struct hw_device_t **device)
{
	bluetooth_device_t *dev = malloc(sizeof(bluetooth_device_t));

	DBG("");

	memset(dev, 0, sizeof(bluetooth_device_t));
	dev->common.tag = HARDWARE_DEVICE_TAG;
	dev->common.version = 0;
	dev->common.module = (struct hw_module_t *) module;
	dev->common.close = close_bluetooth;
	dev->get_bluetooth_interface = get_bluetooth_interface;

	*device = (struct hw_device_t *) dev;

	return 0;
}

static struct hw_module_methods_t bluetooth_module_methods = {
	.open = open_bluetooth,
};

struct hw_module_t HAL_MODULE_INFO_SYM = {
	.tag = HARDWARE_MODULE_TAG,
	.version_major = 1,
	.version_minor = 0,
	.id = BT_HARDWARE_MODULE_ID,
	.name = "BlueZ Bluetooth stack",
	.author = "Intel Corporation",
	.methods = &bluetooth_module_methods
};
