//_____________________________________________________________________________
//
// Copyright (C) 2019                   Mobsya                   CH-1020 Renens
//_____________________________________________________________________________
//
// PROJECT   Thymio-III
//_____________________________________________________________________________
//
//! \file    codec.c
//! \brief   This module provides the useful functions to use the audio codec
//!
//! \author  Vincent Gonet
//!
//! \license This project is released under the GNU Lesser General Public License
//_____________________________________________________________________________

//-----------------------------------------------------------------------------
// Include Section
//-----------------------------------------------------------------------------

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <sys/stat.h>
#include <sys/unistd.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
//#include "freertos/portmacro.h"
//#include "freertos/timers.h"

#include "esp_log.h"

#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_event_iface.h"
#include "audio_mem.h"
#include "audio_common.h"
//#include "audio_hal.h"

#include "i2s_stream.h"
#include "spiffs_stream.h"

#include "mp3_decoder.h"

#include "wav_encoder.h"
#include "wav_decoder.h"

#include "filter_resample.h"

#include "esp_peripherals.h"
#include "periph_spiffs.h"

#include "driver/ledc.h"

#include "codec.h"

#include "board.h"
#include "es8374.h"

//-----------------------------------------------------------------------------
// Constants/Macros Definitions
//-----------------------------------------------------------------------------

#define MCLK_FREQUENCY_Hz    20000000

#define RECORD_RATE             48000
#define RECORD_CHANNEL              1  //!< Mono = 1
#define RECORD_BITS                16

#define SAVE_FILE_RATE           8000
#define SAVE_FILE_CHANNEL           1  //!< Mono = 1
#define SAVE_FILE_BITS             16

#define DEFAULT_RECORDER_TASK_STACK (4*1024)
#define DEFAULT_RECORDER_TASK_PRIO (5)

#define DEFAULT_ESP_PERIPH_STACK_SIZE      (4*1024)
#define DEFAULT_ESP_PERIPH_TASK_PRIO       (5)
#define DEFAULT_ESP_PERIPH_TASK_CORE       (0)

#define DEFAULT_ESP_PERIPH_SET_CONFIG() {\
    .task_stack         = DEFAULT_ESP_PERIPH_STACK_SIZE,   \
    .task_prio          = DEFAULT_ESP_PERIPH_TASK_PRIO,    \
    .task_core          = DEFAULT_ESP_PERIPH_TASK_CORE,    \
}

#define AUDIO_HAL_ES8374_DEFAULT(){                     \
        .adc_input  = AUDIO_HAL_ADC_INPUT_LINE2,        \
        .dac_output = AUDIO_HAL_DAC_OUTPUT_LINE1,       \
        .codec_mode = AUDIO_HAL_CODEC_MODE_BOTH,        \
        .i2s_iface = {                                  \
            .mode = AUDIO_HAL_MODE_SLAVE,               \
            .fmt = AUDIO_HAL_I2S_NORMAL,                \
            .samples = AUDIO_HAL_48K_SAMPLES,           \
            .bits = AUDIO_HAL_BIT_LENGTH_16BITS,        \
        },                                              \
};

#define AUDIO_CODEC_DEFAULT_CONFIG(){                   \
        .adc_input  = AUDIO_HAL_ADC_INPUT_LINE1,        \
        .dac_output = AUDIO_HAL_DAC_OUTPUT_ALL,         \
        .codec_mode = AUDIO_HAL_CODEC_MODE_BOTH,        \
        .i2s_iface = {                                  \
            .mode = AUDIO_HAL_MODE_SLAVE,               \
            .fmt = AUDIO_HAL_I2S_NORMAL,                \
            .samples = AUDIO_HAL_48K_SAMPLES,           \
            .bits = AUDIO_HAL_BIT_LENGTH_16BITS,        \
        },                                              \
};

//-----------------------------------------------------------------------------
// Types Definitions
//-----------------------------------------------------------------------------

typedef enum
{
  E_Extension_MP3,
  E_Extension_WAV
} T_Extension;

struct audio_board_handle
{
  audio_hal_handle_t audio_hal; /*!< audio hardware abstract layer handle */
};

typedef struct audio_board_handle* audio_board_handle_t;

typedef enum
{
  RECORDER_EVENT_NONE = 0,
  RECORDER_EVENT_RECORD,
  RECORDER_EVENT_STOP,
  RECORDER_EVENT_PAUSE,
  RECORDER_EVENT_RESUME,
} T_RecorderEvent;

typedef struct WAVAudioRecorder* T_WAVRecorderHandle;
typedef esp_err_t (*WAVRecorderEvent)(T_WAVRecorderHandle ap, T_RecorderEvent event);

typedef struct
{
  int task_stack;
  int task_prio;
  WAVRecorderEvent event_handler;
} T_WAVRecorderConfig;

typedef struct WAVAudioRecorder
{
  audio_pipeline_handle_t Pipeline;
  audio_element_handle_t I2SStream;
  audio_element_handle_t SPIFFSStream;
  audio_element_handle_t Encoder;
  audio_element_handle_t Filter;
  audio_event_iface_handle_t Evt;
  audio_hal_handle_t Hal;
  bool Run;
  bool Recording;
  WAVRecorderEvent EventHandler;
} T_WAVRecorder;

//-----------------------------------------------------------------------------
// Exported Global Data
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Private Data
//-----------------------------------------------------------------------------

static const char* Tag = "codec";

static TaskHandle_t WAVRecorderTask = NULL;

static int16_t FileIndex = -1;
static uint16_t RecordingDuration_s = 0;

static esp_periph_set_handle_t Set;

audio_hal_func_t AUDIO_CODEC_ES8374_DEFAULT_HANDLE =
{
  .audio_codec_initialize   = ES8374_Init,
  .audio_codec_deinitialize = ES8374_Deinit,
  .audio_codec_ctrl         = ES8374_ControlState,
  .audio_codec_config_iface = ES8374_ConfigureI2S,
  .audio_codec_set_volume   = ES8374_SetVoiceVolume,
  .audio_codec_get_volume   = ES8374_GetVoiceVolume
};

static audio_board_handle_t board_handle = 0;

static T_WAVRecorderHandle WAVRecorder = NULL;

//-----------------------------------------------------------------------------
// Private Functions Prototypes
//-----------------------------------------------------------------------------

static audio_board_handle_t Init(void);

static audio_hal_handle_t InitCodec(void);

static void InitSPIFFS(void);

static T_WAVRecorderHandle InitWAVRecorder(T_WAVRecorderConfig* config);

static audio_element_handle_t CreateSPIFFSStream(int sample_rates, int bits, int channels, audio_stream_type_t type);

static audio_element_handle_t CreateI2SStream(int sample_rates, int bits, int channels, audio_stream_type_t type);

static audio_element_handle_t CreateFilter(int source_rate, int source_channel, int dest_rate, int dest_channel, audio_codec_type_t type);

static audio_element_handle_t CreateWAVEncoder(void);

static void GenerateMasterClock(uint32_t clock_Hz);

static void SelectFileSystemFile(char** file, int16_t index, T_Extension extension);

static void EraseFileSystemFile(char* file);

static void RunWAVRecorderTask(void* arg);

static esp_err_t SendWAVRecorderEvent(T_WAVRecorderHandle recorder, T_RecorderEvent event);

static esp_err_t RecordWAV(T_WAVRecorderHandle ap, const char* url);

static esp_err_t StopWAVRecord(T_WAVRecorderHandle ap);

esp_err_t HandleWAVRecorderEvent(T_WAVRecorderHandle ap, T_RecorderEvent event);

//-----------------------------------------------------------------------------
// Inline Code Definition
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Functions Implementation
//-----------------------------------------------------------------------------

void Codec_Init(void)
{
  GenerateMasterClock(MCLK_FREQUENCY_Hz);

  InitSPIFFS();

  ESP_LOGI(Tag, "[2] Start codec chip");
  board_handle = Init();
  audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_BOTH, AUDIO_HAL_CTRL_START);

  T_WAVRecorderConfig WAVRecorderConfig =
  {
    .event_handler = HandleWAVRecorderEvent,
  };

  WAVRecorder = InitWAVRecorder(&WAVRecorderConfig);
}

//_____________________________________________________________________________

void Codec_RecordWAVFile(int16_t index, uint16_t duration_s)
{
  char* file;

  RecordingDuration_s = duration_s;

  FileIndex = index;

  SelectFileSystemFile(&file, FileIndex, E_Extension_WAV);

  RecordWAV(WAVRecorder, file);
}

//_____________________________________________________________________________

void Codec_SetVolume(int16_t volume)
{
  ES8374_SetVoiceVolume(volume);
}

//_____________________________________________________________________________

static audio_board_handle_t Init(void)
{
  if (board_handle)
  {
    ESP_LOGW(Tag, "The board has already been initialized!");
    return board_handle;
  }

  board_handle = (audio_board_handle_t) audio_calloc(1, sizeof(struct audio_board_handle));
  AUDIO_MEM_CHECK(Tag, board_handle, return NULL);
  board_handle->audio_hal = InitCodec();

  return board_handle;
}

//_____________________________________________________________________________

static audio_hal_handle_t InitCodec(void)
{
  audio_hal_codec_config_t audio_codec_cfg = AUDIO_HAL_ES8374_DEFAULT();
  audio_hal_handle_t codec_hal = audio_hal_init(&audio_codec_cfg, &AUDIO_CODEC_ES8374_DEFAULT_HANDLE);
  AUDIO_NULL_CHECK(Tag, codec_hal, return NULL);
  return codec_hal;
}

//_____________________________________________________________________________

static void InitSPIFFS(void)
{
  // Initialize peripherals management
  esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
  Set = esp_periph_set_init(&periph_cfg);

  ESP_LOGI(Tag, "[1] Mount spiffs");
  // Initialize Spiffs peripheral
  periph_spiffs_cfg_t spiffs_cfg =
  {
    .root = "/spiffs",
    .partition_label = NULL,
    .max_files = 5,
    .format_if_mount_failed = true
  };

  esp_periph_handle_t spiffs_handle = periph_spiffs_init(&spiffs_cfg);

  // Start spiffs
  esp_periph_start(Set, spiffs_handle);

  // Wait until spiffs is mounted
  while (!periph_spiffs_is_mounted(spiffs_handle))
  {
    vTaskDelay(500 / portTICK_PERIOD_MS);
  }

  ESP_LOGI(Tag, "[1.1] SPIFFS is mounted");
}

//_____________________________________________________________________________

static T_WAVRecorderHandle InitWAVRecorder(T_WAVRecorderConfig* config)
{
  T_WAVRecorderHandle ap = calloc(1, sizeof(T_WAVRecorder));
  AUDIO_MEM_CHECK(Tag, ap, NULL);

  ESP_LOGI(Tag, "[1] Start audio codec chip");
  //board_handle = Init();
  //audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_DECODE, AUDIO_HAL_CTRL_START);
  ap->Hal = board_handle->audio_hal;
  AUDIO_MEM_CHECK(Tag, ap->Hal, goto _audio_init_failed);

  ESP_LOGI(Tag, "[2.0] Create audio pipeline for record");
  audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
  ap->Pipeline = audio_pipeline_init(&pipeline_cfg);
  AUDIO_MEM_CHECK(Tag, ap->Pipeline, goto _audio_init_failed);

  ESP_LOGI(Tag, "[2.1] Create I2S stream to write audio data to codec chip");
  ap->I2SStream = CreateI2SStream(RECORD_RATE, RECORD_BITS, RECORD_CHANNEL, AUDIO_STREAM_READER);

  ESP_LOGI(Tag, "[2.2] Create filter to convert to 8 [kHz]");
  ap->Filter = CreateFilter(RECORD_RATE, RECORD_CHANNEL, SAVE_FILE_RATE, SAVE_FILE_CHANNEL, AUDIO_CODEC_TYPE_ENCODER);

  ESP_LOGI(Tag, "[2.3] Create WAV encoder to encode WAV format");
  ap->Encoder = CreateWAVEncoder();
  AUDIO_MEM_CHECK(Tag, ap->Encoder, goto _audio_init_failed);

  ESP_LOGI(Tag, "[2.4] Create spiffs stream to write data to spi flash");
  ap->SPIFFSStream = CreateSPIFFSStream(SAVE_FILE_RATE, SAVE_FILE_BITS, SAVE_FILE_CHANNEL, AUDIO_STREAM_WRITER);
  AUDIO_MEM_CHECK(Tag, ap->SPIFFSStream, goto _audio_init_failed);

  ESP_LOGI(Tag, "[2.5] Register all elements to audio pipeline");
  audio_pipeline_register(ap->Pipeline, ap->I2SStream, "i2s_reader");
  audio_pipeline_register(ap->Pipeline, ap->Filter, "filter_downsample");
  audio_pipeline_register(ap->Pipeline, ap->Encoder, "wav_encoder");
  audio_pipeline_register(ap->Pipeline, ap->SPIFFSStream, "file_writer");

  ESP_LOGI(Tag, "[2.6] Link it together [codec_chip]-->i2s_stream-->wav_encoder-->spiffs_stream-->[flash]");
  audio_pipeline_link(ap->Pipeline, (const char *[]) {"i2s_reader", "filter_downsample", "wav_encoder", "file_writer"}, 4);

  ESP_LOGI(Tag, "[3.0] Setup event listener");
  audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
  ap->Evt = audio_event_iface_init(&evt_cfg);

  ESP_LOGI(Tag, "[3.1] Listening event from peripherals");
  audio_event_iface_set_listener(esp_periph_set_get_event_iface(Set), ap->Evt);

  ESP_LOGI(Tag, "[3.2] Listening event from pipeline");
  audio_pipeline_set_listener(ap->Pipeline, ap->Evt);

  ap->Run = true;
  ap->Recording = false;
  ap->EventHandler = config->event_handler;

  int task_stack = config->task_stack ? config->task_stack : DEFAULT_RECORDER_TASK_STACK;
  int task_prio = config->task_prio ? config->task_prio : DEFAULT_RECORDER_TASK_PRIO;

  //if (xTaskCreate(RunWAVRecorderTask, "replayer", task_stack, ap, task_prio, NULL) != pdTRUE)
  if (xTaskCreatePinnedToCore(RunWAVRecorderTask, "recorder", task_stack, ap, task_prio, &WAVRecorderTask, 0) != pdTRUE)
  {
    ESP_LOGE(Tag, "Error creating the Recorder task");
    goto _audio_init_failed;
  }

  return ap;
_audio_init_failed:
  return NULL;
}

//_____________________________________________________________________________

static audio_element_handle_t CreateSPIFFSStream(int sample_rates, int bits, int channels, audio_stream_type_t type)
{
  spiffs_stream_cfg_t spiffs_cfg = SPIFFS_STREAM_CFG_DEFAULT();
  spiffs_cfg.type = type;

  audio_element_handle_t spiffs_stream = spiffs_stream_init(&spiffs_cfg);
  mem_assert(spiffs_stream);

  audio_element_info_t writer_info = {0};
  audio_element_getinfo(spiffs_stream, &writer_info);
  writer_info.bits = bits;
  writer_info.channels = channels;
  writer_info.sample_rates = sample_rates;
  audio_element_setinfo(spiffs_stream, &writer_info);

  return spiffs_stream;
}

//_____________________________________________________________________________

static audio_element_handle_t CreateI2SStream(int sample_rates, int bits, int channels, audio_stream_type_t type)
{
  i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
  i2s_cfg.type = type;
  i2s_cfg.i2s_config.channel_format = I2S_CHANNEL_FMT_ALL_RIGHT;
  i2s_cfg.i2s_config.sample_rate = sample_rates;

  audio_element_handle_t i2s_stream = i2s_stream_init(&i2s_cfg);
  mem_assert(i2s_stream);

  audio_element_info_t i2s_info = {0};
  audio_element_getinfo(i2s_stream, &i2s_info);
  i2s_info.bits = bits;
  i2s_info.channels = channels;
  i2s_info.sample_rates = sample_rates;
  audio_element_setinfo(i2s_stream, &i2s_info);

  return i2s_stream;
}

//_____________________________________________________________________________

static audio_element_handle_t CreateFilter(int source_rate, int source_channel, int dest_rate, int dest_channel, audio_codec_type_t type)
{
  rsp_filter_cfg_t rsp_cfg = DEFAULT_RESAMPLE_FILTER_CONFIG();
  rsp_cfg.src_rate = source_rate;
  rsp_cfg.src_ch = source_channel;
  rsp_cfg.dest_rate = dest_rate;
  rsp_cfg.dest_ch = dest_channel;
  rsp_cfg.type = type;

  return rsp_filter_init(&rsp_cfg);
}

//_____________________________________________________________________________

static audio_element_handle_t CreateWAVEncoder(void)
{
  wav_encoder_cfg_t wav_cfg = DEFAULT_WAV_ENCODER_CONFIG();

  return wav_encoder_init(&wav_cfg);
}

//_____________________________________________________________________________

static void GenerateMasterClock(uint32_t clock_Hz)
{
  ledc_timer_config_t ledc_timer =
  {
    .speed_mode = LEDC_HIGH_SPEED_MODE,
    .timer_num  = LEDC_TIMER_0,
    .bit_num    = 2,
    .freq_hz    = clock_Hz
  };

  ledc_timer_config(&ledc_timer);

  ledc_channel_config_t ledc_channel =
  {
    .channel    = LEDC_CHANNEL_0,
    .gpio_num   = I2S_MCLK_PIN,
    .speed_mode = LEDC_HIGH_SPEED_MODE,
    .timer_sel  = LEDC_TIMER_0,
    .duty       = 2
  };

  ledc_channel_config(&ledc_channel);
}

//_____________________________________________________________________________

static void SelectFileSystemFile(char** file, int16_t index, T_Extension extension)
{
  char path[18] = "/spiffs/";
  char fileName[4];
  char type[6];

  switch (extension)
  {
    case E_Extension_MP3:
      strcpy(type, ".mp3\0");
      break;

    case E_Extension_WAV:
      strcpy(type, ".wav\0");
      break;

    default:
      // Do nothing
      break;
  }

  itoa(index, fileName, 10);  // Convert the index (in base 10) to a string

  strcat(path, fileName);
  strcat(path, type);

  *file = malloc(sizeof(path));  // Allocated memory

  strcpy(*file, path);

  printf("Selected file: %s\n", *file);
}

//_____________________________________________________________________________

static void EraseFileSystemFile(char* file)
{
  struct stat st;

  // Check that the file exists
  if (stat(file, &st) == 0)
  {
    unlink(file);
  }
}

//_____________________________________________________________________________

static void RunWAVRecorderTask(void* arg)
{
  T_WAVRecorderHandle ap = (T_WAVRecorderHandle) arg;
  bool finished = false;
  static bool started = false;

  i2s_stream_set_clk(ap->I2SStream, RECORD_RATE, RECORD_BITS, RECORD_CHANNEL);

  ESP_LOGE(Tag, "[ 6 ] Listen for all pipeline events, record for %d Seconds", RecordingDuration_s);
  uint16_t second_recorded = 0u;

  while (ap->Run)
  {
    audio_event_iface_msg_t msg;

    esp_err_t ret = audio_event_iface_listen(ap->Evt, &msg, portMAX_DELAY);

    if (ret != ESP_OK)
    {
      ESP_LOGE(Tag, "[ * ] Event interface error : %d", ret);
      continue;
    }

    if (!started)
    {
      if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT
          && msg.source == (void*) ap->Encoder
          && msg.cmd == AEL_MSG_CMD_REPORT_STATUS
          && (int)msg.data == AEL_STATUS_STATE_RUNNING)
      {
        ESP_LOGE(Tag, "[ * ] SEND RECORD");
        started = true;
        SendWAVRecorderEvent(ap, RECORDER_EVENT_RECORD);
        continue;
      }
    }
    else
    {
      second_recorded++;

      ESP_LOGE(Tag, "[ * ] Recording ... %d", second_recorded);

      if (second_recorded >= RecordingDuration_s)
      {
        finished = true;
      }

      if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT
          && msg.source == (void*) ap->Encoder
          && msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO)
      {
        audio_element_info_t music_info = {0};
        audio_element_getinfo(ap->Encoder, &music_info);

        ESP_LOGE(Tag, "[ * ] Receive music info from WAV encoder, sample_rates=%d, bits=%d, ch=%d",
                 music_info.sample_rates, music_info.bits, music_info.channels);

        audio_element_setinfo(ap->I2SStream, &music_info);
        //i2s_stream_set_clk(ap->I2SStream, music_info.sample_rates, music_info.bits, music_info.channels);
        continue;
      }

      if (started && finished)
      {
        ESP_LOGI(Tag, "Stop pipeline");
        ESP_LOGE(Tag, "[ * ] SEND STOP");
        SendWAVRecorderEvent(ap, RECORDER_EVENT_STOP);
        audio_pipeline_stop(ap->Pipeline);
        audio_pipeline_wait_for_stop(ap->Pipeline);
        audio_element_reset_state(ap->Filter);
        audio_element_reset_state(ap->SPIFFSStream);
        audio_element_reset_state(ap->Encoder);
        audio_element_reset_state(ap->I2SStream);
        audio_pipeline_reset_ringbuffer(ap->Pipeline);
        audio_pipeline_reset_items_state(ap->Pipeline);
        audio_pipeline_terminate(ap->Pipeline);
        ap->Recording = false;

        finished = false;
        started = false;
        second_recorded = 0u;
        RecordingDuration_s = 0u;

        continue;
      }
    }

    vTaskDelay(1000 / portTICK_PERIOD_MS);
    continue;
  }

  vTaskDelete(WAVRecorderTask);
}

//_____________________________________________________________________________

static esp_err_t SendWAVRecorderEvent(T_WAVRecorderHandle recorder, T_RecorderEvent event)
{
  if (recorder->EventHandler)
  {
    return recorder->EventHandler(recorder, event);
  }

  return ESP_OK;
}

//_____________________________________________________________________________

static esp_err_t RecordWAV(T_WAVRecorderHandle ap, const char* url)
{
  StopWAVRecord(ap);

  if (url)
  {
    audio_element_set_uri(ap->SPIFFSStream, url);
    audio_pipeline_run(ap->Pipeline);
    ap->Recording = true;
  }

  return ESP_OK;
}

//_____________________________________________________________________________

static esp_err_t StopWAVRecord(T_WAVRecorderHandle ap)
{
  if (ap->Recording)
  {
    ESP_LOGE(Tag, "STOP WAV RECORDER");
    audio_pipeline_stop(ap->Pipeline);
    audio_pipeline_wait_for_stop(ap->Pipeline);
    audio_element_reset_state(ap->SPIFFSStream);
    audio_element_reset_state(ap->Encoder);
    audio_element_reset_state(ap->I2SStream);
    audio_pipeline_reset_ringbuffer(ap->Pipeline);
    audio_pipeline_reset_items_state(ap->Pipeline);
    audio_pipeline_terminate(ap->Pipeline);
    ap->Recording = false;
  }

  return ESP_OK;
}

//_____________________________________________________________________________

esp_err_t HandleWAVRecorderEvent(T_WAVRecorderHandle ap, T_RecorderEvent event)
{
  if (event == RECORDER_EVENT_RECORD)
  {
    ESP_LOGE(Tag, "HANDLE WAV RECORD %d SECONDS", RecordingDuration_s);
  }

  if (event == RECORDER_EVENT_STOP)
  {
    ESP_LOGE(Tag, "HANDLE WAV RECORD STOP");
  }

  if (event == RECORDER_EVENT_PAUSE)
  {
    ESP_LOGE(Tag, "HANDLE WAV RECORD PAUSE");
  }

  return ESP_OK;
}
