#include <stdio.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "esp_system.h"
#include "esp_spi_flash.h"
#include "esp_log.h"

#include "driver/i2s.h"
#include "esp_heap_caps.h"

#define CONFIG_I2S_CH                 0

  //// Rev 1
  // sample rate: bits/second
  //#define CONFIG_SAMPLE_RATE            44100
  #define CONFIG_SAMPLE_RATE            16000

  #define CONFIG_BIT_SAMPLE             32
  #define CONFIG_BITS_PER_SAMPLE_TYPE   I2S_BITS_PER_SAMPLE_32BIT
  // Number of frames to sample, and the resulting sample size
  #define SAMPLE_SIZE_FRAMES  1024
  #define SAMPLE_SIZE         ((CONFIG_BIT_SAMPLE/8)*SAMPLE_SIZE_FRAMES)

#define NUM_CHANNELS        (1) // For mono recording only!
// sample rate: bytes/second
#define BYTE_RATE           ((CONFIG_SAMPLE_RATE * (CONFIG_BIT_SAMPLE / 8)) * NUM_CHANNELS)

#define WAVE_HEADER_SIZE        44

static uint8_t localMemory[130000];

static void generate_wav_header(char* wav_header, uint32_t wav_size, uint32_t sample_rate)
{

    // See this for reference: http://soundfile.sapp.org/doc/WaveFormat/
    uint32_t file_size = wav_size + WAVE_HEADER_SIZE - 8;
    uint32_t byte_rate = BYTE_RATE;

    const char set_wav_header[] = {
        'R','I','F','F', // ChunkID
        file_size, file_size >> 8, file_size >> 16, file_size >> 24, // ChunkSize
        'W','A','V','E', // Format
        'f','m','t',' ', // Subchunk1ID
        0x10, 0x00, 0x00, 0x00, // Subchunk1Size (16 for PCM)
        0x01, 0x00, // AudioFormat (1 for PCM)
        0x01, 0x00, // NumChannels (1 channel)
        sample_rate, sample_rate >> 8, sample_rate >> 16, sample_rate >> 24, // SampleRate
        byte_rate, byte_rate >> 8, byte_rate >> 16, byte_rate >> 24, // ByteRate
        0x02, 0x00, // BlockAlign
        CONFIG_BIT_SAMPLE, 0x00, // BitsPerSample
        'd','a','t','a', // Subchunk2ID
        wav_size, wav_size >> 8, wav_size >> 16, wav_size >> 24, // Subchunk2Size
    };

    memcpy(wav_header, set_wav_header, sizeof(set_wav_header));
}

uint8_t *TriMicrophoneRecord(uint32_t recTimeSec, uint32_t *pAudioSize)
{
    // Use POSIX and C standard library functions to work with files.

    uint32_t flash_rec_bytes = BYTE_RATE * recTimeSec;
    uint32_t bytesTotal = flash_rec_bytes + WAVE_HEADER_SIZE;
    size_t largestFreeBlock = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT);
    printf("Largest malloc free block %u\n", largestFreeBlock);
    uint8_t *newmem;
    if ((bytesTotal > largestFreeBlock) && (bytesTotal <= 130000))
      newmem = localMemory;
    else
      newmem = malloc(bytesTotal);
    if (newmem == NULL) {
      printf("Could not allocate %lu bytes for recording %lu seconds.\n",
                (unsigned long)bytesTotal, (unsigned long)recTimeSec);
      return NULL;
    }

    generate_wav_header((char *)newmem, flash_rec_bytes, CONFIG_SAMPLE_RATE);

    printf("Start recording total audio bytes: %d\n", flash_rec_bytes);
    // Start recording
    uint32_t flash_wr_size = WAVE_HEADER_SIZE;
    while (flash_wr_size < bytesTotal) {
      // Read the RAW samples from the microphone
      // REV1: Our 32 bit of samples, only the first 24 bits are real.
      //       The last 8 will always be 0.  Let caller take care of that.
      size_t bytes_read = 0;
      size_t bytes_to_read = SAMPLE_SIZE;
      if ((bytesTotal - flash_wr_size) < SAMPLE_SIZE)
        bytes_to_read = bytesTotal - flash_wr_size;
      i2s_read(CONFIG_I2S_CH, (char *)&newmem[flash_wr_size],
               bytes_to_read, &bytes_read, 100);
      flash_wr_size += bytes_read;
      //printf("here %lu\n", (unsigned long)flash_wr_size);
      fflush(stdout);
    }

  printf("Recording done!\n");

  *pAudioSize = flash_wr_size;

  return newmem; // SUCCESS
}

void TriMicrophoneRecordFreeMemory(void *newmem)
{
  if (newmem != localMemory)
    heap_caps_free(newmem);
}

bool TriMicrophoneInit(void)
{
    // Our part has LR grounded, which means it only outputs its signal
    // in the left frame of I2S frame.

    //// REV1: Set the I2S configuration as I2S and 32bits per sample
    // I2S_COMM_FORMAT_STAND_I2S: ICS-43434 is Philips standard
    i2s_config_t i2s_config = {
        .mode = I2S_MODE_MASTER | I2S_MODE_RX,
        .sample_rate = CONFIG_SAMPLE_RATE,
        .bits_per_sample = CONFIG_BITS_PER_SAMPLE_TYPE,
        .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
        //.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
        //.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2,
        .use_apll = false,
        .dma_buf_count = 4,
        .dma_buf_len = SAMPLE_SIZE_FRAMES,
        //.dma_buf_len = 128,
        .tx_desc_auto_clear = true,
        //.tx_desc_auto_clear = false,
        //.fixed_mclk = 0
    };

    // Set the pinout configuration
    // Note that we are controlling a ICS-43434.  L/R is tied low
    i2s_pin_config_t pin_config = {
        .mck_io_num = I2S_PIN_NO_CHANGE,
        .bck_io_num = 4,
        .ws_io_num = 33,
        .data_out_num = I2S_PIN_NO_CHANGE,
        .data_in_num = 36,
    };

    // Call driver installation function before any I2S R/W operation.
    if (i2s_driver_install(CONFIG_I2S_CH, &i2s_config, 0, NULL) != ESP_OK) {
      printf("i2s_driver_install FAIL\n");
      return false;
    }
    if (i2s_set_pin(CONFIG_I2S_CH, &pin_config) != ESP_OK) {
      printf("i2s_set_pin FAIL\n");
      return false;
    }

    // Allocated DMA buffers and starts I2S
    if (i2s_set_clk(CONFIG_I2S_CH, CONFIG_SAMPLE_RATE, 
                    CONFIG_BITS_PER_SAMPLE_TYPE, I2S_CHANNEL_MONO)
        != ESP_OK)
    {
      printf("i2s_set_clk FAIL\n");
      return false;
    }

  return true; // SUCCESS
}

void BWLogHexBuffer(const uint8_t *bufP, uint32_t len, const char *prefixString)
{
  if ((bufP == NULL) || (len == 0)) {
    printf("ERR: %sEmptyBuffer\n", prefixString ? prefixString : "");
    return;
  }

  for (uint32_t lineByteCount = 0; lineByteCount < len; lineByteCount+=16) {
    #define BWLOG_ONELINE_SIZE      80
    char oneLine[BWLOG_ONELINE_SIZE];
    snprintf(oneLine, BWLOG_ONELINE_SIZE, "%s0x%04x ",
                    prefixString ? prefixString : "", (unsigned)lineByteCount);
    for (uint8_t byteCount = 0; byteCount < 16; ++byteCount) {
      if (lineByteCount+byteCount >= len)
        break; // We're done with this line

      #define BWLOG_ONEVALUE_SIZE      (4+1)
      char oneValue[BWLOG_ONEVALUE_SIZE];
      snprintf(oneValue, BWLOG_ONEVALUE_SIZE, "%02x ", *bufP);
      strcat(oneLine, oneValue);
      ++bufP;
    }
    printf("%s\n", oneLine);
  }
}

void app_main(void)
{
    /* Print chip information */
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    printf("ESP32: %d CPU cores, WiFi%s%s, silicon rev %d, %dMB %s flash\n",
            chip_info.cores,
            (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
            (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "",
            chip_info.revision, spi_flash_get_chip_size() / (1024 * 1024),
            (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");

  TriMicrophoneInit();
  uint32_t audioSize = 0;
  uint8_t *audio = TriMicrophoneRecord(1, &audioSize);
  if (audio) {
    printf("Record returned %lu bytes\n", (unsigned long)audioSize);
  
    // Output the header
    BWLogHexBuffer(audio, WAVE_HEADER_SIZE, "Header: ");
    BWLogHexBuffer(&audio[WAVE_HEADER_SIZE], (audioSize-WAVE_HEADER_SIZE), "  ");

    TriMicrophoneRecordFreeMemory(audio);
  } else {
    printf("Recording failed\n");
  }

    // Stop I2S driver and destroy
    ESP_ERROR_CHECK( i2s_driver_uninstall(CONFIG_I2S_CH) );
}
