/*
 * 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.
 *
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#include <hardware/bluetooth.h>
#include <hardware/bt_hh.h>

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

const bthh_interface_t *if_hh = NULL;

SINTMAP(bthh_protocol_mode_t, -1, "(unknown)")
	DELEMENT(BTHH_REPORT_MODE),
	DELEMENT(BTHH_BOOT_MODE),
	DELEMENT(BTHH_UNSUPPORTED_MODE),
ENDMAP

SINTMAP(bthh_report_type_t, -1, "(unknown)")
	DELEMENT(BTHH_INPUT_REPORT),
	DELEMENT(BTHH_OUTPUT_REPORT),
	DELEMENT(BTHH_FEATURE_REPORT),
ENDMAP

SINTMAP(bthh_connection_state_t, -1, "(unknown)")
	DELEMENT(BTHH_CONN_STATE_CONNECTED),
	DELEMENT(BTHH_CONN_STATE_CONNECTING),
	DELEMENT(BTHH_CONN_STATE_DISCONNECTED),
	DELEMENT(BTHH_CONN_STATE_DISCONNECTING),
	DELEMENT(BTHH_CONN_STATE_FAILED_MOUSE_FROM_HOST),
	DELEMENT(BTHH_CONN_STATE_FAILED_KBD_FROM_HOST),
	DELEMENT(BTHH_CONN_STATE_FAILED_TOO_MANY_DEVICES),
	DELEMENT(BTHH_CONN_STATE_FAILED_NO_BTHID_DRIVER),
	DELEMENT(BTHH_CONN_STATE_FAILED_GENERIC),
	DELEMENT(BTHH_CONN_STATE_UNKNOWN),
ENDMAP

SINTMAP(bthh_status_t, -1, "(unknown)")
	DELEMENT(BTHH_OK),
	DELEMENT(BTHH_HS_HID_NOT_READY),
	DELEMENT(BTHH_HS_INVALID_RPT_ID),
	DELEMENT(BTHH_HS_TRANS_NOT_SPT),
	DELEMENT(BTHH_HS_INVALID_PARAM),
	DELEMENT(BTHH_HS_ERROR),
	DELEMENT(BTHH_ERR),
	DELEMENT(BTHH_ERR_SDP),
	DELEMENT(BTHH_ERR_PROTO),
	DELEMENT(BTHH_ERR_DB_FULL),
	DELEMENT(BTHH_ERR_TOD_UNSPT),
	DELEMENT(BTHH_ERR_NO_RES),
	DELEMENT(BTHH_ERR_AUTH_FAILED),
	DELEMENT(BTHH_ERR_HDL),
ENDMAP

static char connected_device_addr[MAX_ADDR_STR_LEN];
/*
 * Callback for connection state change.
 * state will have one of the values from bthh_connection_state_t
 */
static void connection_state_cb(bt_bdaddr_t *bd_addr,
						bthh_connection_state_t state)
{
	char addr[MAX_ADDR_STR_LEN];

	haltest_info("%s: bd_addr=%s connection_state=%s\n", __func__,
					bt_bdaddr_t2str(bd_addr, addr),
					bthh_connection_state_t2str(state));
	if (state == BTHH_CONN_STATE_CONNECTED)
		strcpy(connected_device_addr, addr);
}

/*
 * Callback for virtual unplug api.
 * the status of the virtual unplug
 */
static void virtual_unplug_cb(bt_bdaddr_t *bd_addr, bthh_status_t hh_status)
{
	char addr[MAX_ADDR_STR_LEN];

	haltest_info("%s: bd_addr=%s hh_status=%s\n", __func__,
						bt_bdaddr_t2str(bd_addr, addr),
						bthh_status_t2str(hh_status));
}

/* Callback for Android 5.0 handshake api. */
#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
static void handshake_cb(bt_bdaddr_t *bd_addr, bthh_status_t hh_status)
{
	char addr[MAX_ADDR_STR_LEN];

	haltest_info("%s: bd_addr=%s hh_status=%s\n", __func__,
						bt_bdaddr_t2str(bd_addr, addr),
						bthh_status_t2str(hh_status));
}
#endif

/*
 * Callback for get hid info
 * hid_info will contain attr_mask, sub_class, app_id, vendor_id, product_id,
 * version, ctry_code, len
 */
static void hid_info_cb(bt_bdaddr_t *bd_addr, bthh_hid_info_t hid_info)
{
	char addr[MAX_ADDR_STR_LEN];

	/* TODO: bluedroid does not seem to ever call this callback */
	haltest_info("%s: bd_addr=%s\n", __func__,
						bt_bdaddr_t2str(bd_addr, addr));
}

/*
 * Callback for get/set protocol api.
 * the protocol mode is one of the value from bthh_protocol_mode_t
 */
static void protocol_mode_cb(bt_bdaddr_t *bd_addr, bthh_status_t hh_status,
						bthh_protocol_mode_t mode)
{
	char addr[MAX_ADDR_STR_LEN];

	haltest_info("%s: bd_addr=%s hh_status=%s mode=%s\n", __func__,
					bt_bdaddr_t2str(bd_addr, addr),
					bthh_status_t2str(hh_status),
					bthh_protocol_mode_t2str(mode));
}

/* Callback for get/set_idle_time api. */
static void idle_time_cb(bt_bdaddr_t *bd_addr, bthh_status_t hh_status,
								int idle_rate)
{
	char addr[MAX_ADDR_STR_LEN];

	haltest_info("%s: bd_addr=%s hh_status=%s idle_rate=%d\n", __func__,
				bt_bdaddr_t2str(bd_addr, addr),
				bthh_status_t2str(hh_status), idle_rate);
}


/*
 * Callback for get report api.
 * if status is ok rpt_data contains the report data
 */
static void get_report_cb(bt_bdaddr_t *bd_addr, bthh_status_t hh_status,
						uint8_t *rpt_data, int rpt_size)
{
	char addr[MAX_ADDR_STR_LEN];

	/* TODO: print actual report */
	haltest_info("%s: bd_addr=%s hh_status=%s rpt_size=%d\n", __func__,
					bt_bdaddr_t2str(bd_addr, addr),
					bthh_status_t2str(hh_status), rpt_size);
}

static bthh_callbacks_t bthh_callbacks = {
	.size = sizeof(bthh_callbacks),
	.connection_state_cb = connection_state_cb,
	.hid_info_cb = hid_info_cb,
	.protocol_mode_cb = protocol_mode_cb,
	.idle_time_cb = idle_time_cb,
	.get_report_cb = get_report_cb,
	.virtual_unplug_cb = virtual_unplug_cb,
#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0)
	.handshake_cb = handshake_cb
#endif
};

/* init */

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

	EXEC(if_hh->init, &bthh_callbacks);
}

/* connect */

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

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

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

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

/* disconnect */

/* Same completion as connect_c */
#define disconnect_c connect_c

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

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

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

/* virtual_unplug */

/* Same completion as connect_c */
#define virtual_unplug_c connect_c

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

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

	EXEC(if_hh->virtual_unplug, &addr);
}

/* set_info */

/* Same completion as connect_c */
#define set_info_c connect_c

static void set_info_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;
	bthh_hid_info_t hid_info;

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

	memset(&hid_info, 0, sizeof(hid_info));

	/*
	 * This command is intentionally not supported. See comment from
	 * bt_hid_info() in android/hidhost.c
	 */
	EXEC(if_hh->set_info, &addr, hid_info);
}

/* get_protocol */

static void get_protocol_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	if (argc == 3) {
		*user = connected_device_addr;
		*enum_func = enum_one_string;
	} else if (argc == 4) {
		*user = TYPE_ENUM(bthh_protocol_mode_t);
		*enum_func = enum_defines;
	}
}

static void get_protocol_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;
	bthh_protocol_mode_t protocolMode;

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

	if (argc < 4) {
		haltest_error("No protocol mode specified\n");
		return;
	}
	protocolMode = str2bthh_protocol_mode_t(argv[3]);

	EXEC(if_hh->get_protocol, &addr, protocolMode);
}

/* set_protocol */

/* Same completion as get_protocol_c */
#define set_protocol_c get_protocol_c

static void set_protocol_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;
	bthh_protocol_mode_t protocolMode;

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

	if (argc < 4) {
		haltest_error("No protocol mode specified\n");
		return;
	}
	protocolMode = str2bthh_protocol_mode_t(argv[3]);

	EXEC(if_hh->set_protocol, &addr, protocolMode);
}

/* get_report */

static void get_report_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	if (argc == 3) {
		*user = connected_device_addr;
		*enum_func = enum_one_string;
	} else if (argc == 4) {
		*user = TYPE_ENUM(bthh_report_type_t);
		*enum_func = enum_defines;
	}
}

static void get_report_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;
	bthh_report_type_t reportType;
	uint8_t reportId;
	int bufferSize;

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

	if (argc < 4) {
		haltest_error("No report type specified\n");
		return;
	}
	reportType = str2bthh_report_type_t(argv[3]);

	if (argc < 5) {
		haltest_error("No reportId specified\n");
		return;
	}
	reportId = (uint8_t) atoi(argv[4]);

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

	EXEC(if_hh->get_report, &addr, reportType, reportId, bufferSize);
}

/* set_report */

static void set_report_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	if (argc == 3) {
		*user = connected_device_addr;
		*enum_func = enum_one_string;
	} else if (argc == 4) {
		*user = TYPE_ENUM(bthh_report_type_t);
		*enum_func = enum_defines;
	}
}

static void set_report_p(int argc, const char **argv)
{
	bt_bdaddr_t addr;
	bthh_report_type_t reportType;

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

	if (argc <= 3) {
		haltest_error("No report type specified\n");
		return;
	}
	reportType = str2bthh_report_type_t(argv[3]);

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

	EXEC(if_hh->set_report, &addr, reportType, (char *) argv[4]);
}

/* send_data */

static void send_data_c(int argc, const char **argv, enum_func *enum_func,
								void **user)
{
	if (argc == 3) {
		*user = connected_device_addr;
		*enum_func = enum_one_string;
	}
}

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

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

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

	EXEC(if_hh->send_data, &addr, (char *) argv[3]);
}

/* cleanup */

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

	EXECV(if_hh->cleanup);
}

/* Methods available in bthh_interface_t */
static struct method methods[] = {
	STD_METHOD(init),
	STD_METHODCH(connect, "<addr>"),
	STD_METHODCH(disconnect, "<addr>"),
	STD_METHODCH(virtual_unplug, "<addr>"),
	STD_METHODCH(set_info, "<addr>"),
	STD_METHODCH(get_protocol, "<addr> <mode>"),
	STD_METHODCH(set_protocol, "<addr> <mode>"),
	STD_METHODCH(get_report, "<addr> <type> <report_id> <size>"),
	STD_METHODCH(set_report, "<addr> <type> <hex_encoded_report>"),
	STD_METHODCH(send_data, "<addr> <hex_encoded_data>"),
	STD_METHOD(cleanup),
	END_METHOD
};

const struct interface hh_if = {
	.name = "hidhost",
	.methods = methods
};
