#ifndef PLATFORM_NATIVE

#include "drivers/Esp32DriverRGBW.h"

#include <driver/gpio.h>
#include <driver/rmt.h>

// Timing constants
static constexpr uint16_t DIVIDER = 4;
static constexpr double RMT_DURATION_NS = 12.5; // Minimum time of a single RMT duration based on clock ns
static constexpr uint32_t T0H = 300;
static constexpr uint32_t T0L = 900;
static constexpr uint32_t T1H = 600;
static constexpr uint32_t T1L = 600;
static constexpr uint32_t TRS = 80000;
static constexpr rmt_item32_t bit0Data = {uint32_t(T0H / (RMT_DURATION_NS * DIVIDER)), 1, uint32_t(T0L / (RMT_DURATION_NS * DIVIDER)), 0};
static constexpr rmt_item32_t bit1Data = {uint32_t(T1H / (RMT_DURATION_NS * DIVIDER)), 1, uint32_t(T1L / (RMT_DURATION_NS * DIVIDER)), 0};

// Function registered at the RMT driver that converts regular uint8_t values containing r,g,b,w values
// to rmt_item32_t for each bit. (8 bit -> 32 * 8 bit)
static void IRAM_ATTR uint8ToRmtAdaptor(const void *src, rmt_item32_t *dest, size_t srcSize,
                                        size_t wantedNum, size_t *translatedSize, size_t *itemNum)
{
  if (src == NULL || dest == NULL)
  {
    *translatedSize = 0;
    *itemNum = 0;
    return;
  }

  size_t size = 0;
  size_t num = 0;
  uint8_t *psrc = (uint8_t *)src;
  rmt_item32_t *pdest = dest;
  while (size < srcSize && num < wantedNum)
  {
    for (int i = 0; i < 8; i++)
    {
      // MSB first
      const bool isBitSet = *psrc & (1 << (7 - i));
      pdest->val = isBitSet ? bit1Data.val : bit0Data.val;
      num++;
      pdest++;
    }
    size++;
    psrc++;
  }
  *translatedSize = size;
  *itemNum = num;
}

void Esp32DriverRGBW::begin(int gpio, int rmtChannel)
{
  rmtChannel_ = rmtChannel;
  transmitting_ = false;

  rmt_config_t rmt_tx;
  rmt_tx.channel = static_cast<rmt_channel_t>(rmtChannel);
  rmt_tx.gpio_num = static_cast<gpio_num_t>(gpio);
  rmt_tx.rmt_mode = RMT_MODE_TX;
  rmt_tx.mem_block_num = 1;
  rmt_tx.clk_div = DIVIDER;
  rmt_tx.tx_config.loop_en = false;
  rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
  rmt_tx.tx_config.carrier_en = false;
  rmt_tx.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
  rmt_tx.tx_config.idle_output_en = true;

  ESP_ERROR_CHECK(rmt_config(&rmt_tx));
  ESP_ERROR_CHECK(rmt_driver_install(rmt_tx.channel, 0, 0));

  rmt_translator_init((rmt_channel_t)rmtChannel, uint8ToRmtAdaptor);
}

void Esp32DriverRGBW::end()
{
  ESP_ERROR_CHECK(rmt_driver_uninstall((rmt_channel_t)rmtChannel_));
}

void Esp32DriverRGBW::writeSync(const uint32_t *rgbwData, int numLeds)
{
  waitForTransmissionToFinish();
  auto data = reinterpret_cast<const uint8_t *>(rgbwData);
  ESP_ERROR_CHECK(rmt_write_sample((rmt_channel_t)rmtChannel_, data, numLeds * 4, true));
}

void Esp32DriverRGBW::writeAsync(const uint32_t *rgbwData, int numLeds)
{
  waitForTransmissionToFinish();
  auto data = reinterpret_cast<const uint8_t *>(rgbwData);
  ESP_ERROR_CHECK(rmt_write_sample((rmt_channel_t)rmtChannel_, data, numLeds * 4, false));
  transmitting_ = true;
}

bool Esp32DriverRGBW::waitForTransmissionToFinish(int waitMs)
{
  if (!transmitting_)
    return true;
  auto ret = rmt_wait_tx_done((rmt_channel_t)rmtChannel_, waitMs / portTICK_PERIOD_MS);
  if (ret == ESP_OK)
  {
    transmitting_ = false;
    return true;
  }
  else
    return false;
}

#endif