#include <Arduino.h>
#include "driver/i2s.h"
#include "I2SSampler.h"
#include <HTTPClient.h>
#include <WiFi.h>

#define I2S_SERVER_URL "http://192.168.0.245:5003/i2s_samples"
#define SSID "Pretty_Fly_For_WiFi"
#define PASSWORD "vanilleistdasbesteEIS2"


/*
I2S Interface is used for communication of serial digital audio files between integradet circuits. The interface at least consists
out of three signal wires, the Clock (SCK), Data (SD) and the Word-Select-Wire (WS). The Word-Select-Wire defines the timespan of a
serial sended data word. The Clock defines the sampling rate or frequency. The communication always takes place in one direction. 
*/

// i2s config - this is set up to read from the left channel
i2s_config_t i2s_config = {
    // I2S work mode 
    // Master Mode, RX Mode 
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    // I2S sample rate
    .sample_rate = 44100, // 44,1kHz
    // I2S bits per sample
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // 16 Bit
    // I2S channel format type
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // only left channel used
    // I2S communication format
    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S),
    // flags used to allocate the interrupt
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    // I2S DMA Buffer Count
    .dma_buf_count = 4, // 4 Buffer used
    // I2S DMA Buffer Length
    .dma_buf_len = 1024, // length of one Buffer
    // I2S using APLL as main I2S clock, enable it to get accurate clock
    .use_apll = false,
    // I2S auto clear tx descriptor if there is underflow condition (helps in avoiding noise in case of data unavailability)
    .tx_desc_auto_clear = false,
    // I2S using fixed MCLK output. If use_apll = true and fixed_mclk > 0, then clock output for i2s is fixed and equa to the fixed_mclk value
    .fixed_mclk = 0};

// i2s pins
i2s_pin_config_t i2s_pins = {
    // BCK in out pin
    .bck_io_num = GPIO_NUM_32, // Seriel Clock pin --> has the frequency of (sample rate * bits per channel * number of channels)
    // WS in out pin
    .ws_io_num = GPIO_NUM_25, // Word Select pin --> 1 for the right sound channel and 0 for the left sound channel
    // DATA out pin
    .data_out_num = I2S_PIN_NO_CHANGE, // no data output used
    // DATA in pin
    .data_in_num = GPIO_NUM_33}; // Serial Data pin --> on this pin data gets transmitted

// create an object pointer
I2SSampler *i2s_sampler = NULL;
WiFiClient *wifi_client = NULL;
HTTPClient *http_client = NULL;

void setup() {
  Serial.begin(115200);

  // launch WiFi
  WiFi.mode(WIFI_STA);
  WiFi.begin(SSID, PASSWORD);
  if (WiFi.waitForConnectResult() != WL_CONNECTED)
  {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }
  Serial.println("Started up");
  Serial.println(WiFi.localIP());
  // setup the HTTP Client
  wifi_client = new WiFiClient();
  http_client = new HTTPClient();
  

  // set up i2s to read from microphone
  i2s_sampler = new I2SSampler(); // allocate dynamic memory //DELETE????

  // set up the i2s sample writer task
  // TaskHandle_t is a type by which taks are referenced. A call to xTaskCreate returns an TaskHandke_t
  // variable as a pointer that can than be used as an parameter to delete the task
  TaskHandle_t writer_task_handle;
  BaseType_t xReturned;
  // Create a new task and add it to the list of tasks that are ready to run. If a task is created using xTaskCreate()
  // then the required RAM gets automatically allocated from HEAP.
  xReturned = xTaskCreate(i2sWriterTask, "I2S Writer Task", 4096, i2s_sampler, 1, &writer_task_handle);
  // i2sWriterTask       = Function that implements the task
  // "I2S Writer Task"   = Text name for the task
  // 4096                = Stack size in words, not byte fo use as the task's stack // BIG ENOUGH???
  // i2s_sampler         = Parameter passed into the task
  // 1                   = Priority at which the task is created
  // &writer_task_handle = Used to pass out the created task's handle
  if(xReturned == pdPASS)
  {
    // the task was created
     Serial.println("Writer Task got created!");
  }

  // start sampling from i2s device
  i2s_sampler->start(I2S_NUM_1, i2s_pins, i2s_config, 32768, writer_task_handle);
  // I2S_NUM_1  = I2S Peripherial 1
  // i2s_pins   = i2s_pin_config_t
  // i2s_config = i2s_config_t
  // 32768      = Buffer size in bytes
  // writer_task_handle 
}

// Task to write samples to 
void i2sWriterTask(void *param)
{
  I2SSampler *sampler = (I2SSampler *)param;
  // Time is measured in 'ticks' - which is the number of times the tick interrupt has executed since the kernel 
  // was started. The tick count is held in the variable of type TickType_t
  // pdMS_TO_TICKS converts a time in milliseconds to a time in ticks.
  const TickType_t xMaxBlockTime = pdMS_TO_TICKS(100);
  while (true)
  {
    // wait for some samples to save
    // ulTaskNotifyTake() is a macro intended for use when a task notification is used as a faster and lighter
    // binary sempahore alternative
    // gets notification from task when audio buffer is full (xTaskNotify) to save data
    // it returns the value of the notification value 
    uint32_t ulNotificationValue = ulTaskNotifyTake(pdTRUE, xMaxBlockTime);
    // pdTrue        = clear the notofication value (set to 0) before exiting
    // xMaxBlockTime = the maximum time to wait in the Blocked state for a notification to be received
    // A task can only take the semaphore if it is available, and the semaphore is only available if its count is 1
    // The ulTaskNotifyTake() function's xClearOnExit parameter is set to pdTRUE so the count value is returned to zero
    // each time the notification is taken (emulating a binary semaphore)                
    if (ulNotificationValue > 0)
    {
       // Do something with the sample (has to be done in 100ms)

       // Buffer where Data is located, current type is int16_t, check for casting!!! 
       //(uint8_t *)sampler->getCapturedAudioBuffer();

        // HTTP Post Requests
        // URL of the website where the Post request should be done
        http_client->begin(*wifi_client, I2S_SERVER_URL);
        // Make HTTP request header to specify the media type of the body of the request
        http_client->addHeader("content-type", "application/octet-stream");
        // send the HTTP request with POST --> sending information from buffer
        int httpResponseCode = http_client->POST((uint8_t *)sampler->getCapturedAudioBuffer(), sampler->getBufferSizeInBytes());
        Serial.println("HTTP response code: " + httpResponseCode);
        http_client->end();

    }
  }
}


void loop() {
  // nothing to be done here
}
