/* Play MP3 file from flash

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.

   Demo video: https://www.bilibili.com/video/BV1ekRAYVEZ1/
*/

#include <stdio.h>
#include <inttypes.h>

#include "esp_log.h"

#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_event_iface.h"
#include "audio_error.h"
#include "tone_stream.h"
#include "i2s_stream.h"
#include "mp3_decoder.h"
#include "driver/gpio.h"

#include "touch_button.h"
#include "iot_button.h"
#include "touch_sensor_lowlevel.h"

#include "led_driver.h"

#include "board.h"

#if __has_include("audio_tone_uri.h")
    #include "audio_tone_uri.h"
#else
    #error "please refer the README, and then make the tone file"
#endif

static const char *TAG = "main";

#define TOUCH_CHANNEL_1        (3)
#define TOUCH_CHANNEL_2        (9)
#define TOUCH_CHANNEL_3        (13)
#define TOUCH_CHANNEL_4        (14)

#define LIGHT_TOUCH_THRESHOLD  (0.15)
#define HEAVY_TOUCH_THRESHOLD  (0.4)

audio_pipeline_handle_t pipeline;
audio_element_handle_t tone_stream_reader, i2s_stream_writer, mp3_decoder;

static void led_task(void *arg)
{
    /* Configure the LED strip and obtain a handle */
    led_strip_handle_t led_strip = led_create();
    led_set_mode(1);
    vTaskDelay(1000);
    led_set_mode(0);

    while(1) {
        /* Run the LED animation based on LED configuration and weather data */
        ESP_ERROR_CHECK(led_animations_start(led_strip));
    }

    vTaskDelete(NULL);
}

static void touch_event_nose(void *arg, void *data)
{
    button_event_t event = iot_button_get_event(arg);
    ESP_LOGI(TAG, "touch event nose: %s", iot_button_get_event_str(event));

    audio_pipeline_stop(pipeline);
    audio_pipeline_wait_for_stop(pipeline);
    audio_pipeline_reset_ringbuffer(pipeline);
    audio_pipeline_reset_elements(pipeline);
    audio_pipeline_reset_items_state(pipeline);

    audio_element_set_uri(tone_stream_reader, tone_uri[TONE_TYPE_TOUCH_NOSE]);
    audio_pipeline_run(pipeline);
}

static void touch_event_nose_long(void *arg, void *data)
{
    button_event_t event = iot_button_get_event(arg);
    ESP_LOGI(TAG, "touch event nose long: %s", iot_button_get_event_str(event));

    audio_pipeline_stop(pipeline);
    audio_pipeline_wait_for_stop(pipeline);
    audio_pipeline_reset_ringbuffer(pipeline);
    audio_pipeline_reset_elements(pipeline);
    audio_pipeline_reset_items_state(pipeline);

    audio_element_set_uri(tone_stream_reader, tone_uri[TONE_TYPE_SCREAMING]);
    audio_pipeline_run(pipeline);
}

static void touch_event_hat(void *arg, void *data)
{
    button_event_t event = iot_button_get_event(arg);
    ESP_LOGI(TAG, "touch event hat: %s", iot_button_get_event_str(event));

    static tone_type_t hat_state = TONE_TYPE_HAT_1;
    audio_pipeline_stop(pipeline);
    audio_pipeline_wait_for_stop(pipeline);
    audio_pipeline_reset_ringbuffer(pipeline);
    audio_pipeline_reset_elements(pipeline);
    audio_pipeline_reset_items_state(pipeline);

    audio_element_set_uri(tone_stream_reader, tone_uri[hat_state]);
    audio_pipeline_run(pipeline);

     // Toggle between two tone states and update LED mode
    if (hat_state == TONE_TYPE_CAPYBARA_SONG_1) {
        // Turn on LED
        led_set_mode(4);
        hat_state = TONE_TYPE_HAT_1;
    } else {
        hat_state = TONE_TYPE_CAPYBARA_SONG_1;
    }
    
}

static void touch_event_belly(void *arg, void *data)
{
    button_event_t event = iot_button_get_event(arg);
    ESP_LOGI(TAG, "touch event hat: %s", iot_button_get_event_str(event));

    static int audio_belly_count_g = 0;

    audio_pipeline_stop(pipeline);
    audio_pipeline_wait_for_stop(pipeline);
    audio_pipeline_reset_ringbuffer(pipeline);
    audio_pipeline_reset_elements(pipeline);
    audio_pipeline_reset_items_state(pipeline);

    audio_element_set_uri(tone_stream_reader, tone_uri[audio_belly_count_g]);

    audio_pipeline_run(pipeline);

    // Update the counter for next playback
    if (audio_belly_count_g < 3) {
        audio_belly_count_g++;
    } else if (audio_belly_count_g == 3) {
        audio_belly_count_g = TONE_TYPE_SCREAMING;
    } else {
        audio_belly_count_g = 0;
    }
}

static void touch_task(void *arg)
{
    // Register all touch channel
    uint32_t touch_channel_list[] = {TOUCH_CHANNEL_1, TOUCH_CHANNEL_2, TOUCH_CHANNEL_3, TOUCH_CHANNEL_4};
    int total_channel_num = sizeof(touch_channel_list) / sizeof(touch_channel_list[0]);

    // calloc channel_type for every button from the list
    touch_lowlevel_type_t *channel_type = calloc(total_channel_num, sizeof(touch_lowlevel_type_t));
    assert(channel_type);
    for (int i = 0; i  < total_channel_num; i++) {
        channel_type[i] = TOUCH_LOWLEVEL_TYPE_TOUCH;
    }

    touch_lowlevel_config_t low_config = {
        .channel_num = total_channel_num,
        .channel_list = touch_channel_list,
        .channel_type = channel_type,
    };
    esp_err_t ret = touch_sensor_lowlevel_create(&low_config);
    assert(ret == ESP_OK);
    free(channel_type);

    /* ============================= Init touch IO3 ============================= */ 
    const button_config_t btn_cfg = {
        .short_press_time = 300,
        .long_press_time = 2000,
    };
    button_touch_config_t touch_cfg_1 = {
        .touch_channel = touch_channel_list[0],
        .channel_threshold = LIGHT_TOUCH_THRESHOLD,
        .skip_lowlevel_init = true,
    };

    /* Create button for nose */
    button_handle_t btn_nose = NULL;
    ret = iot_button_new_touch_button_device(&btn_cfg, &touch_cfg_1, &btn_nose);
    assert(ret == ESP_OK);

    // Create button for nose heavy press (if neeeded)
    // touch_cfg_1.channel_threshold = HEAVY_TOUCH_THRESHOLD;
    // button_handle_t btn_heavy_1 = NULL;
    // ret = iot_button_new_touch_button_device(&btn_cfg, &touch_cfg_1, &btn_heavy_1);
    // assert(ret == ESP_OK);

    /* ============================= Init touch IO9 ============================= */ 
    button_touch_config_t touch_cfg_2 = {
        .touch_channel = touch_channel_list[1],
        .channel_threshold = LIGHT_TOUCH_THRESHOLD,
        .skip_lowlevel_init = true,
    };

    /* Create button for hat */
    button_handle_t btn_hat = NULL;
    ret = iot_button_new_touch_button_device(&btn_cfg, &touch_cfg_2, &btn_hat);
    assert(ret == ESP_OK);

    /* ============================= Init touch IO13 ============================= */ 
    button_touch_config_t touch_cfg_3 = {
        .touch_channel = touch_channel_list[2],
        .channel_threshold = LIGHT_TOUCH_THRESHOLD,
        .skip_lowlevel_init = true,
    };

    /* Create light press button */
    button_handle_t btn_belly = NULL;
    ret = iot_button_new_touch_button_device(&btn_cfg, &touch_cfg_3, &btn_belly);
    assert(ret == ESP_OK);

    /* ========================== Register touch callback ========================== */ 
    // Register touch callback for nose
    iot_button_register_cb(btn_nose, BUTTON_PRESS_DOWN, NULL, touch_event_nose, NULL);
    iot_button_register_cb(btn_nose, BUTTON_LONG_PRESS_START, NULL, touch_event_nose_long, NULL);

    // Register touch callback for hat
    iot_button_register_cb(btn_hat, BUTTON_PRESS_DOWN, NULL, touch_event_hat, NULL);

    // Register touch callback for belly
    iot_button_register_cb(btn_belly, BUTTON_PRESS_DOWN, NULL, touch_event_belly, NULL);

    touch_sensor_lowlevel_start();

    while (1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main(void)
{
    xTaskCreate(touch_task, "touch_task", 1024 * 5, NULL, 5, NULL);
    xTaskCreate(led_task, "led_task", 1024 * 3, NULL, 5, NULL);

    esp_log_level_set("*", ESP_LOG_WARN);
    esp_log_level_set(TAG, ESP_LOG_INFO);

    ESP_LOGI(TAG, "[ 1 ] Start codec chip");
    audio_board_handle_t board_handle = audio_board_init();
    audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_DECODE, AUDIO_HAL_CTRL_START);
    audio_hal_set_volume(board_handle->audio_hal, 80);

    ESP_LOGI(TAG, "[2.0] Create audio pipeline for playback");
    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    pipeline = audio_pipeline_init(&pipeline_cfg);
    AUDIO_NULL_CHECK(TAG, pipeline, return);

    ESP_LOGI(TAG, "[2.1] Create tone stream to read data from flash");
    tone_stream_cfg_t tone_cfg = TONE_STREAM_CFG_DEFAULT();
    tone_cfg.type = AUDIO_STREAM_READER;
    tone_stream_reader = tone_stream_init(&tone_cfg);
    AUDIO_NULL_CHECK(TAG, tone_stream_reader, return);

    ESP_LOGI(TAG, "[2.2] Create i2s stream to write data to codec chip");
#if defined CONFIG_ESP32_C3_LYRA_V2_BOARD
    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_PDM_TX_CFG_DEFAULT();
#else
    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
#endif
    i2s_cfg.type = AUDIO_STREAM_WRITER;
    i2s_stream_writer = i2s_stream_init(&i2s_cfg);
    AUDIO_NULL_CHECK(TAG, i2s_stream_writer, return);

    ESP_LOGI(TAG, "[2.3] Create mp3 decoder to decode mp3 file");
    mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
    mp3_decoder = mp3_decoder_init(&mp3_cfg);
    AUDIO_NULL_CHECK(TAG, mp3_decoder, return);

    ESP_LOGI(TAG, "[2.4] Register all elements to audio pipeline");
    audio_pipeline_register(pipeline, tone_stream_reader, "tone");
    audio_pipeline_register(pipeline, mp3_decoder, "mp3");
    audio_pipeline_register(pipeline, i2s_stream_writer, "i2s");

    ESP_LOGI(TAG, "[2.5] Link it together [flash]-->tone_stream-->mp3_decoder-->i2s_stream-->[codec_chip]");
    const char *link_tag[3] = {"tone", "mp3", "i2s"};
    audio_pipeline_link(pipeline, &link_tag[0], 3);

    ESP_LOGI(TAG, "[2.6] Set up  uri (file as tone_stream, mp3 as mp3 decoder, and default output is i2s)");
    audio_element_set_uri(tone_stream_reader, tone_uri[TONE_TYPE_SCREAMING]);

    ESP_LOGI(TAG, "[ 3 ] Set up event listener");
    audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
    audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);

    ESP_LOGI(TAG, "[3.1] Listening event from all elements of pipeline");
    audio_pipeline_set_listener(pipeline, evt);

    ESP_LOGI(TAG, "[ 4 ] Start audio_pipeline");
    audio_pipeline_run(pipeline);

    ESP_LOGI(TAG, "[ 4 ] Listen for all pipeline events");
    while (1) {
        audio_event_iface_msg_t msg = { 0 };
        esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret);
            continue;
        }

        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) mp3_decoder
            && msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
            audio_element_info_t music_info = {0};
            audio_element_getinfo(mp3_decoder, &music_info);

            ESP_LOGI(TAG, "[ * ] Receive music info from mp3 decoder, sample_rates=%d, bits=%d, ch=%d",
                     music_info.sample_rates, music_info.bits, music_info.channels);

            i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
            continue;
        }

        /* Stop when the last pipeline element (i2s_stream_writer in this case) receives stop event */
        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) i2s_stream_writer
            && msg.cmd == AEL_MSG_CMD_REPORT_STATUS
            && (((int)msg.data == AEL_STATUS_STATE_FINISHED))) {
            // Turn off LED
            led_set_mode(0);
        }
    }

    ESP_LOGI(TAG, "[ 5 ] Stop audio_pipeline");
    audio_pipeline_stop(pipeline);
    audio_pipeline_wait_for_stop(pipeline);
    audio_pipeline_terminate(pipeline);

    audio_pipeline_unregister(pipeline, tone_stream_reader);
    audio_pipeline_unregister(pipeline, i2s_stream_writer);
    audio_pipeline_unregister(pipeline, mp3_decoder);

    /* Terminal the pipeline before removing the listener */
    audio_pipeline_remove_listener(pipeline);

    /* Make sure audio_pipeline_remove_listener & audio_event_iface_remove_listener are called before destroying event_iface */
    audio_event_iface_destroy(evt);

    /* Release all resources */
    audio_pipeline_deinit(pipeline);
    audio_element_deinit(tone_stream_reader);
    audio_element_deinit(i2s_stream_writer);
    audio_element_deinit(mp3_decoder);
}
