#include "cmsis_nvic.h"
#include "hal_dma.h"
#include "hal_sensor_eng.h"
#include "hal_timer.h"
#include "hal_trace.h"
#include "plat_addr_map.h"
#include CHIP_SPECIFIC_HDR(reg_sensor_eng)
#include "reg_timer.h"
#include "string.h"
#ifdef RTOS
#include "cmsis_os.h"
#endif

#define MAX_RX_FRAME_NUM (5)

static struct SENSOR_ENG_T *const sensor_eng[] = {
    (struct SENSOR_ENG_T *)SENSOR_ENG_BASE,
};

static struct DUAL_TIMER_T *const sensor_eng_timer =
    (struct DUAL_TIMER_T *)MCU_TIMER2_BASE;

static struct HAL_DMA_DESC_T tx_dma_desc[1];
static struct HAL_DMA_DESC_T rx_dma_desc[MAX_RX_FRAME_NUM];

static HAL_SENSOR_ENG_HANDLER_T sensor_eng_handler;

static uint8_t dma_rx_chan;

static uint8_t *dma_rx_buf;
static uint32_t dma_rx_len;
static uint32_t prev_rx_pos;

void hal_sensor_process_rx_buffer(void) {
  if (dma_rx_len == 0) {
    return;
  }

  uint8_t *dma_buf_active = (uint8_t *)hal_dma_get_cur_dst_addr(dma_rx_chan);
  uint32_t result_len = 0;
  uint8_t *curr_pos;

  curr_pos = dma_rx_buf + prev_rx_pos;

  // TRACE(2,"dma_buf_active:%08x curr_pos:%08x", dma_buf_active, curr_pos);

  if (dma_buf_active < curr_pos) {
    result_len = dma_rx_buf + dma_rx_len - curr_pos;
    // TRACE(2,"1:result_len:%08x, curr_pos:%08x", result_len, curr_pos);
    // DUMP8("%02x ", curr_pos, result_len);
    if (sensor_eng_handler) {
      sensor_eng_handler(HAL_SENSOR_ENGINE_ID_0, HAL_SENSOR_ENGINE_DEVICE_I2C0,
                         curr_pos, result_len);
    }
    curr_pos = dma_rx_buf;
  }

  if (curr_pos < dma_buf_active) {
    result_len = dma_buf_active - curr_pos;
    // TRACE(2,"2:result_len:%08x, curr_pos:%08x", result_len, curr_pos);
    // DUMP8("%02x ", curr_pos, result_len);
    if (sensor_eng_handler) {
      sensor_eng_handler(HAL_SENSOR_ENGINE_ID_0, HAL_SENSOR_ENGINE_DEVICE_I2C0,
                         curr_pos, result_len);
    }
    curr_pos = dma_buf_active;
  }

  prev_rx_pos = curr_pos - dma_rx_buf;
}

static void hal_sensor_eng_irq_handler(void) {
  // TRACE(2,"%s: irq=0x%08x", __func__, sensor_eng[0]->SENSOR_INTR_STS);

  sensor_eng[0]->SENSOR_INTR_CLR = SENSOR_INTR_CLEAR;

  hal_sensor_process_rx_buffer();
}

static void hal_sensor_eng_irq_enable(void) {
  NVIC_SetVector(SENSOR_WKUP_IRQn, (uint32_t)hal_sensor_eng_irq_handler);
  NVIC_SetPriority(SENSOR_WKUP_IRQn, IRQ_PRIORITY_HIGH);
  NVIC_ClearPendingIRQ(SENSOR_WKUP_IRQn);
  NVIC_EnableIRQ(SENSOR_WKUP_IRQn);
}

static void hal_sensor_eng_irq_disable(void) {
  NVIC_DisableIRQ(SENSOR_WKUP_IRQn);
}

static void hal_sensor_eng_timer_stop(void) {
  sensor_eng_timer->timer[0].Control = 0;
  sensor_eng_timer->timer[0].IntClr = 1;
}

static void hal_sensor_eng_timer_open(uint32_t ticks) {
  if (ticks > 1) {
    ticks -= 1;
  } else {
    ticks = 1;
  }
  sensor_eng_timer->timer[0].Load = ticks;
  sensor_eng_timer->timer[0].Control =
      TIMER_CTRL_MODE_PERIODIC | TIMER_CTRL_INTEN | TIMER_CTRL_PRESCALE_DIV_1 |
      TIMER_CTRL_SIZE_32_BIT;
}

static void hal_sensor_eng_timer_start(void) {
  sensor_eng_timer->timer[0].Control |= TIMER_CTRL_EN;
}

int hal_sensor_engine_open(const struct HAL_SENSOR_ENGINE_CFG_T *cfg) {
  struct HAL_DMA_CH_CFG_T rx_dma_cfg;
  enum HAL_DMA_RET_T dret;
  uint32_t i;
  uint32_t sx_sel;

  ASSERT(cfg->id == HAL_SENSOR_ENGINE_ID_0, "Bad sensor engine id: %d",
         cfg->id);
  ASSERT(cfg->device == HAL_SENSOR_ENGINE_DEVICE_I2C0,
         "Bad sensor engine device: %d", cfg->device);
  ASSERT(cfg->rx_burst_cnt <= MAX_RX_FRAME_NUM, "Bad rx_burst_cnt: %d",
         cfg->rx_burst_cnt);
  ASSERT(cfg->tx_dma_cfg, "Bad tx_dma_cfg");

  sensor_eng_handler = cfg->handler;
  dma_rx_chan = cfg->rx_dma_cfg->ch;
  dma_rx_buf = (uint8_t *)cfg->rx_dma_cfg->dst;
  dma_rx_len = cfg->rx_burst_len * cfg->rx_burst_cnt;
  prev_rx_pos = 0;

  dret =
      hal_dma_init_desc(&tx_dma_desc[0], cfg->tx_dma_cfg, &tx_dma_desc[0], 0);
  ASSERT(dret == HAL_DMA_OK, "SensorEng: Failed to init tx desc 0");

  dret = hal_gpdma_sg_start(&tx_dma_desc[0], cfg->tx_dma_cfg);
  ASSERT(dret == HAL_DMA_OK, "SensorEng: Failed to start tx dma");

  if (dma_rx_len) {
    ASSERT(cfg->rx_dma_cfg, "Bad rx_dma_cfg");

    rx_dma_cfg = *cfg->rx_dma_cfg;
    for (i = 0; i < cfg->rx_burst_cnt; i++) {
      rx_dma_cfg.dst = (uint32_t)(dma_rx_buf + cfg->rx_burst_len * i);
      if (i + 1 == cfg->rx_burst_cnt) {
        dret =
            hal_dma_init_desc(&rx_dma_desc[i], &rx_dma_cfg, &rx_dma_desc[0], 0);
      } else {
        dret = hal_dma_init_desc(&rx_dma_desc[i], &rx_dma_cfg,
                                 &rx_dma_desc[i + 1], 0);
      }
      ASSERT(dret == HAL_DMA_OK, "SensorEng: Failed to init rx desc %d", i);
    }
    hal_gpdma_sg_start(&rx_dma_desc[0], cfg->rx_dma_cfg);
    ASSERT(dret == HAL_DMA_OK, "SensorEng: Failed to start rx dma");
  }

  hal_cmu_clock_enable(HAL_CMU_MOD_H_SENSOR_ENG);
  hal_cmu_reset_clear(HAL_CMU_MOD_H_SENSOR_ENG);
  hal_cmu_clock_enable(HAL_CMU_MOD_O_TIMER2);
  hal_cmu_clock_enable(HAL_CMU_MOD_P_TIMER2);
  hal_cmu_reset_clear(HAL_CMU_MOD_O_TIMER2);
  hal_cmu_reset_clear(HAL_CMU_MOD_P_TIMER2);

  sensor_eng[0]->SLV0_CONFIG_REG = SLV_I2C_SPI_SEL(cfg->device) |
                                   SLV_SLV_DEV_ID(cfg->device_address) |
                                   SLV_ENABLE;
  if (dma_rx_len) {
    // RX LLI = 1
    sensor_eng[0]->GLOBAL_CFG1_REG =
        SET_BITFIELD(sensor_eng[0]->GLOBAL_CFG1_REG, NUM_OF_RX_LLI0, 1);
  }
  // TX LLI = 1
  sensor_eng[0]->GLOBAL_CFG3_REG =
      SET_BITFIELD(sensor_eng[0]->GLOBAL_CFG3_REG, NUM_OF_TX_LLI0, 1);
  if (cfg->trigger_type == HAL_SENSOR_ENGINE_TRIGGER_GPIO) {
    sensor_eng[0]->SLV0_INTR_MASK = ~(1 << cfg->trigger_gpio);
  }

  if (cfg->data_to_vad) {
    sx_sel = cfg->id;
  } else {
    sx_sel = GET_BITFIELD(sensor_eng[0]->GLOBAL_CFG0_REG, GBL_CODEC_SX_SEL);
  }
  sensor_eng[0]->GLOBAL_CFG0_REG =
      GBL_CODEC_SX_SEL(sx_sel) | GBL_SENSORHUB_EN |
      (cfg->trigger_type == HAL_SENSOR_ENGINE_TRIGGER_GPIO ? GBL_TRIGGER_TYPE
                                                           : 0);

  if (cfg->trigger_type == HAL_SENSOR_ENGINE_TRIGGER_TIMER) {
    hal_sensor_eng_timer_open(US_TO_TICKS(cfg->period_us));
    hal_sensor_eng_timer_start();
  }

  hal_sensor_eng_irq_enable();

  return 0;
}

int hal_sensor_engine_close(enum HAL_SENSOR_ENGINE_ID_T id) {
  uint32_t cnt;

  hal_sensor_eng_irq_disable();

  hal_sensor_eng_timer_stop();

  sensor_eng[0]->GLOBAL_CFG0_REG &= ~GBL_SENSORHUB_EN;

  // Wait until sensor engine becomes idle
  cnt = 0;
  while ((sensor_eng[0]->SENSOR_STATUS & SENSOR_ENG_FSM_MASK) && cnt < 10) {
    osDelay(1);
    cnt++;
  }
  if (sensor_eng[0]->SENSOR_STATUS & SENSOR_ENG_FSM_MASK) {
    ASSERT(false, "%s: Sensor engine cannot become idle: 0x%08X", __func__,
           sensor_eng[0]->SENSOR_STATUS);
  }

  sensor_eng[0]->SLV0_INTR_MASK = ~0UL;

  dma_rx_len = 0;

  hal_cmu_reset_set(HAL_CMU_MOD_P_TIMER2);
  hal_cmu_reset_set(HAL_CMU_MOD_O_TIMER2);
  hal_cmu_clock_disable(HAL_CMU_MOD_P_TIMER2);
  hal_cmu_clock_disable(HAL_CMU_MOD_O_TIMER2);
  hal_cmu_reset_set(HAL_CMU_MOD_H_SENSOR_ENG);
  hal_cmu_clock_disable(HAL_CMU_MOD_H_SENSOR_ENG);

  return 0;
}

#if 0
#include "hal_i2c.h"

#define READ_BUF_BURST_CNT 5
#define TRIGGER_TIMER_US 500000
#define LOCK_INTR_CNT 3
#define TRIGGER_GPIO_PIN HAL_GPIO_PIN_P0_0

static void hal_sensor_eng_external_input_pin_irqhandler(enum HAL_GPIO_PIN_T pin)
{
    bool gpio_lvl = 0;
    gpio_lvl = hal_gpio_pin_get_val(pin);
    TRACE(2,"hal_sensor_eng_external_irqhandler io=%d val=%d", pin, gpio_lvl);
}

static void sensor_timer_test_handler(enum HAL_I2C_ID_T id, const uint8_t *buf, uint32_t len)
{
    TRACE(7,"%s: id=%d len=%u %02X-%02X-%02X-%02x", __FUNCTION__, id, len, buf[0], buf[1], buf[2], buf[3]);
}

static void sensor_gpio_test_handler(enum HAL_I2C_ID_T id, const uint8_t *buf, uint32_t len)
{
    TRACE(7,"%s: id=%d len=%u %02X-%02X-%02X-%02x", __FUNCTION__, id, len, buf[0], buf[1], buf[2], buf[3]);
}

static int sensor_eng_test(enum HAL_SENSOR_ENGINE_TRIGGER_T trigger_type)
{
    static uint8_t write_buf[3];
    static uint8_t read_buf[READ_BUF_BURST_CNT][sizeof(write_buf) * 2];

    int ret;
    struct HAL_I2C_CONFIG_T cfg;
    struct HAL_I2C_SENSOR_ENGINE_CONFIG_T se_cfg;
    enum HAL_I2C_ID_T id;
    uint32_t i;

    id = HAL_I2C_ID_0;

    for (i = 0; i < sizeof(write_buf); i++) {
        write_buf[i] = i;
    }

    memset(&cfg, 0, sizeof(cfg));
    cfg.mode = HAL_I2C_API_MODE_SENSOR_ENGINE;
    cfg.as_master = 1;
    cfg.use_dma = 1;
    cfg.use_sync = 1;
    cfg.speed = 100000;
    ret = hal_i2c_open(id, &cfg);
    if (ret) {
        TRACE(2,"%s: i2c open failed %d", __func__, ret);
        return ret;
    }

    memset(&se_cfg, 0, sizeof(se_cfg));
    se_cfg.id = HAL_SENSOR_ENGINE_ID_0;
    se_cfg.trigger_type = trigger_type;
    if (trigger_type == HAL_SENSOR_ENGINE_TRIGGER_TIMER) {
        se_cfg.period_us = TRIGGER_TIMER_US;
        se_cfg.handler = sensor_timer_test_handler;
    } else {
        se_cfg.trigger_gpio = TRIGGER_GPIO_PIN;
        se_cfg.handler = sensor_gpio_test_handler;
    }
    se_cfg.target_addr = 0x17;
    se_cfg.write_buf = &write_buf[0];
    se_cfg.write_txn_len = 1;
    se_cfg.read_buf = &read_buf[0][0];
    se_cfg.read_txn_len = 2;
    se_cfg.txn_cnt = sizeof(write_buf);
    se_cfg.read_burst_cnt = READ_BUF_BURST_CNT;

    hal_i2c_sensor_engine_start(id, &se_cfg);

    uint32_t lock = int_lock();
    while (1) {
        hal_sys_timer_delay(US_TO_TICKS(TRIGGER_TIMER_US) * LOCK_INTR_CNT);
        TRACE_IMM(2,"%s: delay %u ms", __func__, (TRIGGER_TIMER_US / 1000 * LOCK_INTR_CNT));
        hal_sensor_process_rx_buffer();
    }
    int_unlock(lock);

    return 0;
}

int hal_sensor_eng_periodic_timer_test(void)
{
    sensor_eng_test(HAL_SENSOR_ENGINE_TRIGGER_TIMER);
    return 0;
}

int hal_sensor_eng_external_input_test(void)
{
    static const struct HAL_IOMUX_PIN_FUNCTION_MAP ext_pin[] = {
        {TRIGGER_GPIO_PIN, HAL_IOMUX_FUNC_GPIO, HAL_IOMUX_PIN_VOLTAGE_VIO, HAL_IOMUX_PIN_PULLUP_ENABLE},
    };
    struct HAL_GPIO_IRQ_CFG_T gpiocfg;

    sensor_eng_test(HAL_SENSOR_ENGINE_TRIGGER_GPIO);

    hal_iomux_init((struct HAL_IOMUX_PIN_FUNCTION_MAP *)ext_pin, sizeof(ext_pin)/sizeof(struct HAL_IOMUX_PIN_FUNCTION_MAP));
    hal_gpio_pin_set_dir(TRIGGER_GPIO_PIN, HAL_GPIO_DIR_IN, 1);

    gpiocfg.irq_enable = true;
    gpiocfg.irq_debounce = false;
    gpiocfg.irq_polarity = HAL_GPIO_IRQ_POLARITY_HIGH_RISING;
    gpiocfg.irq_handler = hal_sensor_eng_external_input_pin_irqhandler;
    gpiocfg.irq_type = HAL_GPIO_IRQ_TYPE_EDGE_SENSITIVE;

    hal_gpio_setup_irq(TRIGGER_GPIO_PIN, &gpiocfg);

    TRACE(5,"SLV0:%08x CFG1:0x%08x CFG3:0x%08x CFG0:0x%08x INTR_MASK:0x%08x",
        sensor_eng[0]->SLV0_CONFIG_REG,
        sensor_eng[0]->GLOBAL_CFG1_REG,
        sensor_eng[0]->GLOBAL_CFG3_REG,
        sensor_eng[0]->GLOBAL_CFG0_REG,
        sensor_eng[0]->SLV0_INTR_MASK);
    return 0;
}
#endif
