/main/bt.c
#include <string.h>
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gap_bt_api.h"
#include "esp_a2dp_api.h"
#include "esp_avrc_api.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "audio.h"
#include "bt.h"
#include "gfx.h"
#include "play.h"

void _bt_set_mode(bt_mode m);

bt_mode mode = BT_INACTIVE;
esp_bd_addr_t pairing_peer = {0};
char bt_addr_buf[18];
bt_playback_state playback_state = {0};

char* bt_addr_str(esp_bd_addr_t addr) {
	snprintf(bt_addr_buf, 18, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
	return bt_addr_buf;
}

void print_hex(uint8_t* v, int len) {
	for (int i = 0; i < len; i++) {
		printf("%02x ", v[i]);
	}
}

char* arr_to_str(void* val, int len) {
	char* s = (char*) malloc(len + 1);
	memcpy(s, val, len);
	s[len] = 0;

	return s;
}

int max(int a, int b) {
	return a > b ? a : b;
}

void set_last_connected(esp_bd_addr_t* bda) {
	const char TAG[] = "set_last_connected";
	nvs_handle_t h;
	esp_err_t ret;

	ret = nvs_open("bluetooth", NVS_READWRITE, &h);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not open NVS: %s", esp_err_to_name(ret));
		return;
	}

	ret = nvs_set_blob(h, "lastconn", bda, sizeof(esp_bd_addr_t));
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not set last connected in NVS: %s", esp_err_to_name(ret));
		goto bt_set_last_connected_cleanup;
	}

	ret = nvs_commit(h);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not commit NVS: %s", esp_err_to_name(ret));
	}

	ESP_LOGI(TAG, "Set last connected device to %s", bt_addr_str(*bda));

bt_set_last_connected_cleanup:
	nvs_close(h);
}

int get_last_connected(esp_bd_addr_t* addr) {
	const char TAG[] = "get_last_connected";
	nvs_handle_t h;
	esp_err_t ret;

	ret = nvs_open("bluetooth", NVS_READONLY, &h);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not open NVS: %s", esp_err_to_name(ret));
		return 0;
	}

	size_t len = sizeof(esp_bd_addr_t);
	ret = (bt_mode) nvs_get_blob(h, "lastconn", (uint8_t*) addr, &len);
	if (ret == ESP_ERR_NVS_NOT_FOUND) {
		nvs_close(h);
		return 0;
	} else if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not retrieve last connected from NVS: %s", esp_err_to_name(ret));
		nvs_close(h);
		return 0;
	}

	if (len != sizeof(esp_bd_addr_t)) {
		ESP_LOGE(TAG, "Short read for last connected: %d bytes", len);
		return 0;
	}

	nvs_close(h);
	return 1;
}

void a2dp_connection_state_event(struct a2d_conn_stat_param* p) {
	char* event_str;
	char* extra = "";

	switch (p->state) {
	case ESP_A2D_CONNECTION_STATE_DISCONNECTED:
		event_str = "Disconnected from";
		extra = p->disc_rsn == ESP_A2D_DISC_RSN_NORMAL ? " (normal)" : " (abnormal)";
		break;
	case ESP_A2D_CONNECTION_STATE_CONNECTED:
		event_str = "Connected to";
		set_last_connected(&p->remote_bda);
		play_set_media_type(PLAY_MEDIA_BLUETOOTH);
		break;
	case ESP_A2D_CONNECTION_STATE_CONNECTING:
		event_str = "Connecting to";
		break;
	case ESP_A2D_CONNECTION_STATE_DISCONNECTING:
		event_str = "Disconnecting from";
		break;
	default:
		event_str = "Unknown event from";
	}

	ESP_LOGI("BT", "%s %s%s", event_str, bt_addr_str(p->remote_bda), extra);
}

void a2dp_audio_state_event(struct a2d_audio_stat_param *p) {
	int r;

	switch (p->state) {
	case ESP_A2D_AUDIO_STATE_STARTED:
		ESP_LOGI("BT", "Started audio from %s", bt_addr_str(p->remote_bda));
		r = audio_open();
		if (!r) {
			ESP_LOGE("BT", "Could not open audio for BT stream");
		}
		break;
	case ESP_A2D_AUDIO_STATE_STOPPED:
		ESP_LOGI("BT", "Stopped audio from %s", bt_addr_str(p->remote_bda));
		audio_close();
		break;
	case ESP_A2D_AUDIO_STATE_REMOTE_SUSPEND:
		ESP_LOGI("BT", "Remote suspend audio from %s", bt_addr_str(p->remote_bda));
		audio_close();
		break;
	}
}

void a2dp_callback(esp_a2d_cb_event_t event, esp_a2d_cb_param_t* param) {
	switch (event) {
	case ESP_A2D_CONNECTION_STATE_EVT:
		a2dp_connection_state_event(&param->conn_stat);
		break;
	case ESP_A2D_AUDIO_STATE_EVT:
		a2dp_audio_state_event(&param->audio_stat);
		break;
	case ESP_A2D_AUDIO_CFG_EVT:
		// param->audio_cfg;
	case ESP_A2D_MEDIA_CTRL_ACK_EVT:
		// param->media_ctrl_stat;
	default:
		ESP_LOGI(__func__, "%s evt %d", __func__, event);
	}
}

void grab_metadata() {
	esp_err_t ret;
	
	ret = esp_avrc_ct_send_metadata_cmd(1, ESP_AVRC_MD_ATTR_TITLE | ESP_AVRC_MD_ATTR_ARTIST | ESP_AVRC_MD_ATTR_PLAYING_TIME);
	if (ret != ESP_OK) {
		ESP_LOGE("BT", "Could not send metadata request: %s", esp_err_to_name(ret));
	}
}

void a2dp_data_callback(const uint8_t *data, uint32_t len) {
	audio_send((uint16_t*)data, len / 2);
}

void avrc_callback(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t* param) {
	const char TAG[] = "avrc_callback";
	char *text;
	esp_err_t ret;
	int len;

	switch (event) {
	case ESP_AVRC_CT_CONNECTION_STATE_EVT:
		ESP_LOGI(TAG, "connection state connected:%d addr:%s", param->conn_stat.connected, bt_addr_str(param->conn_stat.remote_bda));
		if (param->conn_stat.connected) {
			grab_metadata();
		}
		break;
	case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
		// param->psth_rsp
		ESP_LOGI(TAG, "passthrough response tl:%d key_code:%d, key_state:%d", param->psth_rsp.tl, param->psth_rsp.key_code, param->psth_rsp.key_state);
		break;
	case ESP_AVRC_CT_METADATA_RSP_EVT:
		// param->meta_rsp
		text = arr_to_str(param->meta_rsp.attr_text, param->meta_rsp.attr_length);
		ESP_LOGI(TAG, "metadata response attr_id:%d text:%s", param->meta_rsp.attr_id, text);
		gfx_data_t* gd = gfx_acquire_data(SCENE_NO_CHANGE);
		switch(param->meta_rsp.attr_id) {
		case ESP_AVRC_MD_ATTR_TITLE:
			len = max(param->meta_rsp.attr_length, META_STR_MAX);
			memcpy(gd->playing_meta.title, param->meta_rsp.attr_text, len);
			gd->playing_meta.title[META_STR_MAX - 1] = 0;
			break;
		case ESP_AVRC_MD_ATTR_ARTIST:
			len = max(param->meta_rsp.attr_length, META_STR_MAX);
			memcpy(gd->playing_meta.artist, param->meta_rsp.attr_text, len);
			gd->playing_meta.artist[META_STR_MAX - 1] = 0;
			break;
		case ESP_AVRC_MD_ATTR_PLAYING_TIME:
			gd->playing_meta.track_length = atoi(text) / 1000;
			break;
		}
		gfx_release_data();
		free(text);

		break;
	case ESP_AVRC_CT_PLAY_STATUS_RSP_EVT:
		// ???
		ESP_LOGI(TAG, "play status response");
		break;
	case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
		// param->change_ntf
		switch (param->change_ntf.event_id) {
		case ESP_AVRC_RN_VOLUME_CHANGE:
			ESP_LOGI(TAG, "volume change:%d", param->change_ntf.event_parameter.volume);
			break;
		case ESP_AVRC_RN_PLAY_STATUS_CHANGE:
			text = "unknown";
			switch (param->change_ntf.event_parameter.playback) {
			case ESP_AVRC_PLAYBACK_STOPPED:
				text = "stopped";
				break;
			case ESP_AVRC_PLAYBACK_PLAYING:
				text = "playing";
				break;
			case ESP_AVRC_PLAYBACK_PAUSED:
				text = "paused";
				break;
			case ESP_AVRC_PLAYBACK_FWD_SEEK:
				text = "fwd seek";
				break;
			case ESP_AVRC_PLAYBACK_REV_SEEK:
				text = "rev seek";
				break;
			case ESP_AVRC_PLAYBACK_ERROR:
				text = "error";
				break;
			}
			ESP_LOGI(TAG, "playback status changed:%s", text);
			grab_metadata();

			playback_state.state = param->change_ntf.event_parameter.playback;

			ret = esp_avrc_ct_send_register_notification_cmd(1, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0);
			if (ret != ESP_OK) {
				ESP_LOGE(TAG, "Could not register play status change");
			}
			break;
		case ESP_AVRC_RN_TRACK_CHANGE:
			ESP_LOGI(TAG, "track change:");
			print_hex(param->change_ntf.event_parameter.elm_id, 8);
			printf("\n");
			grab_metadata();

			ret = esp_avrc_ct_send_register_notification_cmd(3, ESP_AVRC_RN_TRACK_CHANGE, 0);
			if (ret != ESP_OK) {
				ESP_LOGE(TAG, "Could not register track change");
			}
			break;
		case ESP_AVRC_RN_PLAY_POS_CHANGED:
			ESP_LOGI(TAG, "play position change:%d", param->change_ntf.event_parameter.play_pos);
			ret = esp_avrc_ct_send_register_notification_cmd(2, ESP_AVRC_RN_PLAY_POS_CHANGED, 1);
			if (ret != ESP_OK) {
				ESP_LOGE(TAG, "Could not register play position change");
			}
			break;
		case ESP_AVRC_RN_BATTERY_STATUS_CHANGE:
			ESP_LOGI(TAG, "battery status change:%d", param->change_ntf.event_parameter.batt);
			break;
		}
		break;
	case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
		// param->rmt_feats
		ESP_LOGI(TAG, "remote features feat_mask:%08x tg_feat_flag:%04x addr:%s", param->rmt_feats.feat_mask, param->rmt_feats.tg_feat_flag, bt_addr_str(param->rmt_feats.remote_bda));
		if (param->rmt_feats.feat_mask & ESP_AVRC_FEAT_RCTG) {
			esp_avrc_ct_send_get_rn_capabilities_cmd(0);
		}
		break;
	case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT:
		// param->get_rn_caps_rsp
		ESP_LOGI(TAG, "remote notification capabilities count:%d set:%04x", param->get_rn_caps_rsp.cap_count, param->get_rn_caps_rsp.evt_set.bits);

		esp_avrc_rn_evt_cap_mask_t* events = &param->get_rn_caps_rsp.evt_set;

		if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, events, ESP_AVRC_RN_PLAY_STATUS_CHANGE)) {
			ret = esp_avrc_ct_send_register_notification_cmd(1, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0);
			if (ret != ESP_OK) {
				ESP_LOGE(TAG, "Could not register play status change");
			}
			ESP_LOGI(TAG, "play status notification registered");
		} else {
			ESP_LOGI(TAG, "play status not supported");
		}

		if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, events, ESP_AVRC_RN_PLAY_POS_CHANGED)) {
			ret = esp_avrc_ct_send_register_notification_cmd(2, ESP_AVRC_RN_PLAY_POS_CHANGED, 1);
			if (ret != ESP_OK) {
				ESP_LOGE(TAG, "Could not register play position change");
			}
			ESP_LOGI(TAG, "play position notification registered");
		} else {
			ESP_LOGI(TAG, "play position not supported");
		}

		if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, events, ESP_AVRC_RN_VOLUME_CHANGE)) {
			ret = esp_avrc_ct_send_register_notification_cmd(3, ESP_AVRC_RN_VOLUME_CHANGE, 0);
			if (ret != ESP_OK) {
				ESP_LOGE(TAG, "Could not register volume change");
			}
			ESP_LOGI(TAG, "volume change notification registered");
		} else {
			ESP_LOGI(TAG, "volume change not supported");
		}

		if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, events, ESP_AVRC_RN_TRACK_CHANGE)) {
			ret = esp_avrc_ct_send_register_notification_cmd(3, ESP_AVRC_RN_TRACK_CHANGE, 0);
			if (ret != ESP_OK) {
				ESP_LOGE(TAG, "Could not register track change");
			}
			ESP_LOGI(TAG, "track change notification registered");
		} else {
			ESP_LOGI(TAG, "track change not supported");
		}

		break;
	case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT:
		// param->set_volume_rsp
		ESP_LOGI(TAG, "absolute volume response volume:%d", param->set_volume_rsp.volume);
		break;
	}
}

void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) {
	const char TAG[] = "bt_app_gap_cb";

	switch (event) {
		case ESP_BT_GAP_DISC_RES_EVT: {
			struct disc_res_param* p = &param->disc_res;
			ESP_LOGI(TAG, "discovered %s", bt_addr_str(p->bda));
			for (int i = 0; i < p->num_prop; i++) {
				esp_bt_gap_dev_prop_t *dp = &p->prop[i];
				char *s;
				uint32_t v;

				switch (dp->type) {
				case ESP_BT_GAP_DEV_PROP_BDNAME:
					s = arr_to_str(dp->val, dp->len);
					printf("\tname: %s\n", s);
					free(s);
					break;
				case ESP_BT_GAP_DEV_PROP_COD:
					v = *(uint32_t*)dp->val;
					printf("\tCoD: %d (%08x)\n", v, v);
					break;
				case ESP_BT_GAP_DEV_PROP_RSSI:
					printf("\tRSSI: %d\n", *(uint8_t*)dp->val);
					break;
				case ESP_BT_GAP_DEV_PROP_EIR:
					printf("\tEIR: ");
					print_hex(dp->val, dp->len);
					printf("\n");
					break;
				}
			}
			break;
		}
		case ESP_BT_GAP_AUTH_CMPL_EVT: {
			if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
				ESP_LOGI(TAG, "auth success: %s", param->auth_cmpl.device_name);
			} else {
				ESP_LOGE(TAG, "auth failed: %d", param->auth_cmpl.stat);
			}
			break;
		}
		case ESP_BT_GAP_PIN_REQ_EVT: {
			ESP_LOGI(TAG, "PIN REQ: min_16_digit:%d", param->pin_req.min_16_digit);
			if (param->pin_req.min_16_digit) {
				esp_bt_pin_code_t pin_code = {0};
				esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
			} else {
				esp_bt_pin_code_t pin_code = {'1','2','3','4', 0};
				esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
			}
			break;
		}
		case ESP_BT_GAP_CFM_REQ_EVT:
			ESP_LOGI(TAG, "confirm %d", param->cfm_req.num_val);
			memcpy(pairing_peer, param->cfm_req.bda, sizeof(esp_bd_addr_t));
			gfx_data_t* gd = gfx_acquire_data(SCENE_NO_CHANGE);
			gd->bt.confirm = param->cfm_req.num_val;
			gfx_release_data();
			break;
		case ESP_BT_GAP_KEY_NOTIF_EVT:
			ESP_LOGI(TAG, "passkey: %d", param->key_notif.passkey);
			break;
		case ESP_BT_GAP_KEY_REQ_EVT:
			ESP_LOGI(TAG, "enter passkey!");
			break;
		case ESP_BT_GAP_MODE_CHG_EVT:
			ESP_LOGI(TAG, "mode change: %d", param->mode_chg.mode);
			break;
		default:
			ESP_LOGI(TAG, "event %d\n", event);
	}
}

int bt_init() {
	const char TAG[] = "bt_start";
	esp_err_t ret;

	ret = esp_bt_controller_mem_release(ESP_BT_MODE_BLE);
	ESP_ERROR_CHECK(ret);

	esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
	ret = esp_bt_controller_init(&bt_cfg);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not initialize BT controller: %s", esp_err_to_name(ret));
		return 0;
	}

	ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not enable BT controller: %s", esp_err_to_name(ret));
		return 0;
	}

	ESP_LOGI(TAG, "BT controller enabled");

	ret = esp_bluedroid_init();
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not initialize bluedroid: %s", esp_err_to_name(ret));
		return 0;
	}

	ret = esp_bluedroid_enable();
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not enable bluedroid: %s", esp_err_to_name(ret));
		return 0;
	}

	ESP_LOGI(TAG, "Bluedroid enabled");

	ret = esp_bt_dev_set_device_name("Clock Radio");
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not set device name: %s", esp_err_to_name(ret));
		return 0;
	}

	esp_bt_gap_register_callback(bt_app_gap_cb);

	esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
	esp_bt_gap_set_security_param(ESP_BT_SP_IOCAP_MODE, &iocap, sizeof(esp_bt_io_cap_t));
	esp_bt_gap_set_pin(ESP_BT_PIN_TYPE_VARIABLE, 0, NULL);

	ret = esp_avrc_ct_register_callback(avrc_callback);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not register AVRC callback: %s", esp_err_to_name(ret));
		return 0;
	}

	ret = esp_avrc_ct_init();
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not initialize AVRC: %s", esp_err_to_name(ret));
		return 0;
	}

	esp_a2d_register_callback(&a2dp_callback);
	esp_a2d_sink_register_data_callback(&a2dp_data_callback);
	ret = esp_a2d_sink_init();
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not initialize A2DP sink: %s", esp_err_to_name(ret));
		return 0;
	}

	nvs_handle_t h;
	ret = nvs_open("bluetooth", NVS_READONLY, &h);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not open NVS: %s", esp_err_to_name(ret));
		return 0;
	}

	uint8_t v;
	ret = (bt_mode) nvs_get_u8(h, "mode", &v);
	if (ret == ESP_ERR_NVS_NOT_FOUND) {
		_bt_set_mode(BT_INACTIVE);
	} else if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could retrieve mode from NVS: %s", esp_err_to_name(ret));
		_bt_set_mode(BT_INACTIVE);
	} else {
		ESP_LOGI(TAG, "Restored mode to %d", v);
		_bt_set_mode((bt_mode)v);
	}

	nvs_close(h);

	esp_bd_addr_t bda = {0};
	if (get_last_connected(&bda)) {
		ret = esp_a2d_sink_connect(bda);
		if (ret != ESP_OK) {
			ESP_LOGE(TAG, "Error connecting to %s", bt_addr_str(bda));
		}
	}

	return 1;
}

void bt_discover() {
	esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
}

bt_mode bt_get_mode() {
	return mode;
}

void _bt_set_mode(bt_mode m) {
	const char TAG[] = "bt_set_mode";
	esp_err_t ret = 0;

	mode = m;
	switch (mode) {
	case BT_INACTIVE:
		ret = esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
		break;
	case BT_ACTIVE:
		ret = esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
		break;
	case BT_DISCOVERABLE:
		ret = esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
		break;
	}

	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not set scan mode: %s", esp_err_to_name(ret));
		return;
	}
		
	gfx_data_t *gd = gfx_acquire_data(SCENE_NO_CHANGE);
	gd->bt.mode = mode;
	gfx_release_data();
}

void bt_set_mode(bt_mode m) {
	const char TAG[] = "bt_set_mode";
	esp_err_t ret;

	_bt_set_mode(m);

	nvs_handle_t h;
	ret = nvs_open("bluetooth", NVS_READWRITE, &h);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not open NVS: %s", esp_err_to_name(ret));
		return;
	}

	ret = nvs_set_u8(h, "mode", (uint8_t)m);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not set mode in NVS: %s", esp_err_to_name(ret));
		goto bt_set_mode_cleanup;
	}

	ret = nvs_commit(h);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not commit NVS: %s", esp_err_to_name(ret));
	}

bt_set_mode_cleanup:
	nvs_close(h);
}

void bt_confirm_pair(int accept) {
	esp_bt_gap_ssp_confirm_reply(pairing_peer, accept);
	memset(pairing_peer, 0, sizeof(esp_bd_addr_t));

	gfx_data_t *gd = gfx_acquire_data(SCENE_NO_CHANGE);
	gd->bt.confirm = 0;
	gfx_release_data();
}

void bt_send_key(esp_avrc_pt_cmd_t cmd, esp_avrc_pt_cmd_state_t state) {
	esp_avrc_ct_send_passthrough_cmd(1, cmd, state);
}

bt_playback_state* bt_get_playback_state() {
	return &playback_state;
}