#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "esp_log.h"
#include "nvs.h"
#include "nvs_flash.h"

#include "esp_gap_bt_api.h"
#include "esp_a2dp_api.h"

#include "H2201_dispatcher.h"
#include "H2201_i2s.h"
#include "H2201_a2dp.h"
#include "H2201_bluetooth.h"

static const char *h2201_a2dp_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"};
static const char *h2201_a2dp_audio_state_str[] = {"Suspended", "Stopped", "Started"};

size_t bda_size = sizeof(esp_bd_addr_t);
uint8_t last_connection[sizeof(esp_bd_addr_t)];

void h2201_a2dp_get_connection()
{
    nvs_handle my_handle;
    if (nvs_open("connected_bda", NVS_READONLY, &my_handle) != ESP_OK)
    {
        ESP_LOGE(H2201_A2DP_TAG, "NVS OPEN ERROR");
    }
    if (nvs_get_blob(my_handle, "last_bda", last_connection, &bda_size) != ESP_OK)
    {
        ESP_LOGE(H2201_A2DP_TAG, "NVS read ERROR");
    }
    nvs_close(my_handle);

    ESP_LOGI(H2201_A2DP_TAG, "get last connection: [%02x:%02x:%02x:%02x:%02x:%02x]", last_connection[0], last_connection[1], last_connection[2], last_connection[3], last_connection[4], last_connection[5]);
}

void h2201_a2dp_store_connection(uint8_t bda[])
{
    ESP_LOGI(H2201_A2DP_TAG, "set connection: [%02x:%02x:%02x:%02x:%02x:%02x]", bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);

    // same value, nothing to store
    if (memcmp(bda, last_connection, bda_size) == 0)
    {
        ESP_LOGD(H2201_A2DP_TAG, "bda not changed!");
        return;
    }

    nvs_handle my_handle;
    if (nvs_open("connected_bda", NVS_READWRITE, &my_handle) != ESP_OK)
    {
        ESP_LOGE(H2201_A2DP_TAG, "NVS OPEN ERROR");
    }
    if (nvs_set_blob(my_handle, "last_bda", bda, bda_size) != ESP_OK)
    {
        ESP_LOGE(H2201_A2DP_TAG, "NVS WRITE ERROR");
    }
    else
    {
        if (nvs_commit(my_handle) != ESP_OK)
        {
            ESP_LOGE(H2201_A2DP_TAG, "NVS COMMIT ERROR");
        }
    }
    nvs_close(my_handle);
    memcpy(last_connection, bda, bda_size);
}

void h2201_a2dp_remove_connection()
{
    ESP_LOGI(H2201_A2DP_TAG, "remove connection");
    uint8_t bda[] = {0, 0, 0, 0, 0, 0, 0, 0};
    h2201_a2dp_store_connection(bda);
}


void h2201_a2dp_eventhandler(uint16_t event, void *p_param)
{
    ESP_LOGI(H2201_A2DP_TAG, "%s event %d", __func__, event);
    
    esp_a2d_cb_param_t *a2d = (esp_a2d_cb_param_t *)(p_param);
    switch (event)
    {
    case ESP_A2D_CONNECTION_STATE_EVT: // connection state changed event
    {
        uint8_t *bda = a2d->conn_stat.remote_bda;
        ESP_LOGI(H2201_A2DP_TAG, "A2DP connection state: %s, [%02x:%02x:%02x:%02x:%02x:%02x]", h2201_a2dp_conn_state_str[a2d->conn_stat.state], bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
        if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED)
        {
            esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
            h2201_i2s_task_shut_down();

            if (a2d->conn_stat.disc_rsn == ESP_A2D_DISC_RSN_NORMAL)
            {
                h2201_a2dp_remove_connection();
            }
            else
            {
                esp_a2d_sink_connect(last_connection);
            }
        }
        else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED)
        {
            esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
            h2201_i2s_task_start_up();

            h2201_a2dp_store_connection(a2d->conn_stat.remote_bda);
        }
        break;
    }

    case ESP_A2D_AUDIO_STATE_EVT: // audio stream transmission state changed event
    {
        ESP_LOGI(H2201_A2DP_TAG, "A2DP audio state: %s", h2201_a2dp_audio_state_str[a2d->audio_stat.state]);
        break;
    }

    case ESP_A2D_AUDIO_CFG_EVT: // audio codec is configured
    {
        ESP_LOGI(H2201_A2DP_TAG, "A2DP audio stream configuration, codec type %d", a2d->audio_cfg.mcc.type);
        // for now only SBC stream is supported
        if (a2d->audio_cfg.mcc.type == ESP_A2D_MCT_SBC)
        {
            int sample_rate = 16000;
            char oct0 = a2d->audio_cfg.mcc.cie.sbc[0];
            if (oct0 & (0x01 << 6))
            {
                sample_rate = 32000;
            }
            else if (oct0 & (0x01 << 5))
            {
                sample_rate = 44100;
            }
            else if (oct0 & (0x01 << 4))
            {
                sample_rate = 48000;
            }
            h2201_i2s_set_samplerate(sample_rate);
            // h2201_i2s_set_samplerate(48000);

            ESP_LOGI(H2201_A2DP_TAG, "Configure audio player %x-%x-%x-%x",
                     a2d->audio_cfg.mcc.cie.sbc[0],
                     a2d->audio_cfg.mcc.cie.sbc[1],
                     a2d->audio_cfg.mcc.cie.sbc[2],
                     a2d->audio_cfg.mcc.cie.sbc[3]);
            ESP_LOGI(H2201_A2DP_TAG, "Audio player configured, sample rate=%d", sample_rate);
        }
        break;
    }

    case ESP_A2D_PROF_STATE_EVT: // indicate a2dp init&deinit complete
    {
        if (ESP_A2D_INIT_SUCCESS == a2d->a2d_prof_stat.init_state)
        {
            ESP_LOGI(H2201_A2DP_TAG, "A2DP PROF STATE: Init Complete");
            h2201_a2dp_get_connection();
            // esp_a2d_sink_connect(last_connection);
        }
        else
        {
            ESP_LOGI(H2201_A2DP_TAG, "A2DP PROF STATE: Deinit Complete");
        }
        break;
    }

    default:
        ESP_LOGE(H2201_A2DP_TAG, "%s unhandled evt %d", __func__, event);
        break;
    }
}

/* callback for A2DP sink */
void h2201_a2dp_callback(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
{
    h2201_dispatcher_work_dispatch(h2201_a2dp_eventhandler, event, param, sizeof(esp_a2d_cb_param_t), NULL);
}

void h2201_a2dp_create_sink(void)
{
    esp_a2d_register_callback(&h2201_a2dp_callback);
    esp_a2d_sink_register_data_callback(h2201_i2s_write_ringbuf);
    esp_a2d_sink_init();
}