#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_system.h"
#include "audio.h"
#include "esp_log.h"
#include "string.h"

#define TAG "AUDIO"
typedef struct play_async_payload_t
{
    char mp3_name[50];
} play_async_payload_t;

void play_spiffs_audio(char *path);
void envelope_volume_to(double volume);

static xQueueHandle play_async_queue;

EventGroupHandle_t play_event;
const int music_playing_event = BIT0;

static bool audio_player_initialized;

static void play_audio_task(void *params)
{
    play_async_payload_t play_async_payload;
    while (true)
    {
        xQueueReceive(play_async_queue, &play_async_payload, portMAX_DELAY);

        xEventGroupSetBits(play_event, music_playing_event);
        char full_path[60];
        sprintf(full_path, "/spiffs/%s", play_async_payload.mp3_name);
        double volume_val = get_volume();
        envelope_volume_to(volume_val);
        play_spiffs_audio(full_path);
        xEventGroupClearBits(play_event, music_playing_event);
    }
}

void play_audio_init()
{
    play_async_queue = xQueueCreate(10, sizeof(play_async_payload_t));
    play_event = xEventGroupCreate();
    xTaskCreatePinnedToCore(play_audio_task, "play_audio_task", 1024 * 4, NULL, 5, NULL, 0);
    audio_player_initialized = true;
}

void play_audio_async(char *mp3_name)
{
    if (!audio_player_initialized)
    {
        ESP_LOGE(TAG, "Audio player is not initialized. please run play_audio_init() first");
        return;
    }
    if (strlen(mp3_name) > 50)
    {
        ESP_LOGE(TAG, "audio file name must not exceed 50");
        return;
    }
    play_async_payload_t play_async_payload;
    strcpy(play_async_payload.mp3_name, mp3_name);
    if (!xQueueSend(play_async_queue, &play_async_payload, 0))
    {
        ESP_LOGE(TAG, "Too many mp3's queued");
    }
}

bool is_playing(int *number_of_mp3_in_queue)
{
    int mp3_count = uxQueueMessagesWaiting(play_async_queue);
    bool is_playing = xEventGroupGetBits(play_event);
    *number_of_mp3_in_queue = mp3_count;
    return mp3_count || is_playing;
}

void play_audio_sync(char *mp3_name)
{
    play_audio_async(mp3_name);
    int number_of_mps = 0;
    while (is_playing(&number_of_mps))
    {
        vTaskDelay(pdMS_TO_TICKS(50));
    }
}

static xSemaphoreHandle play_in_loop_kill_notification_bin_sem;

void start_play_in_loop_task(void *params)
{
    char *mp3_name = params;
    while (true)
    {
        play_audio_sync(mp3_name);

        if (xSemaphoreTake(play_in_loop_kill_notification_bin_sem, 0))
        {
            play_in_loop_kill_notification_bin_sem = NULL;
            vTaskDelete(NULL);
        }
    }
}

void start_play_in_loop(char *mp3_name)
{
    if (!play_in_loop_kill_notification_bin_sem)
    {
        play_in_loop_kill_notification_bin_sem = xSemaphoreCreateBinary();
        xTaskCreate(start_play_in_loop_task, "start_play_in_loop_task", 1024 * 4, mp3_name, 5, NULL);
    }
    else
    {
        ESP_LOGE(TAG, "loop already playing");
    }
}

void stop_play_in_loop(void)
{
    if (play_in_loop_kill_notification_bin_sem == NULL)
        return;
    if (!xSemaphoreGive(play_in_loop_kill_notification_bin_sem))
    {
        ESP_LOGE("AUDIO", "COULD NOT KILL TASK");
    }
}

double get_volume()
{
    nvs_handle handle;
    esp_err_t err = nvs_open("audio", NVS_READONLY, &handle);
    if (err == ESP_ERR_NVS_NOT_FOUND)
    {
        return 1;
    }
    ESP_ERROR_CHECK(err);
    double volume_val = 0;
    int8_t volume_int = 0;
    err = nvs_get_i8(handle, "vol", &volume_int);
    volume_val = (double)volume_int / (double)100;
    if (err == ESP_ERR_NVS_NOT_FOUND)
    {
        set_volume(1);
        volume_val = 1;
    }
    else if (err != ESP_OK)
    {
        ESP_ERROR_CHECK(err);
    }
    return volume_val;
}

void set_volume(double level)
{
    nvs_handle handle;
    ESP_ERROR_CHECK(nvs_open("audio", NVS_READWRITE, &handle));
    int8_t volume_int = (double)level * (double)100;
    ESP_ERROR_CHECK(nvs_set_i8(handle, "vol", volume_int));
    envelope_volume_to(level);
    ESP_ERROR_CHECK(nvs_commit(handle));
    nvs_close(handle);
}
