// Copyright 2015-2025 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "esp32-hal-i2c.h"

#if SOC_I2C_SUPPORTED
#include "esp_idf_version.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)
#include "esp32-hal.h"
#if !CONFIG_DISABLE_HAL_LOCKS
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#endif
#include "esp_attr.h"
#include "esp_system.h"
#include "soc/soc_caps.h"
#include "driver/i2c_master.h"
#include "esp32-hal-periman.h"

typedef volatile struct {
  bool initialized;
  uint32_t frequency;
#if !CONFIG_DISABLE_HAL_LOCKS
  SemaphoreHandle_t lock;
#endif
  int8_t scl;
  int8_t sda;
  i2c_master_bus_handle_t bus_handle;
  i2c_master_dev_handle_t dev_handles[128];
} i2c_bus_t;

static i2c_bus_t bus[SOC_I2C_NUM];

static bool i2cDetachBus(void *bus_i2c_num) {
  uint8_t i2c_num = (int)bus_i2c_num - 1;
  if (!bus[i2c_num].initialized) {
    return true;
  }
  esp_err_t err = i2cDeinit(i2c_num);
  if (err != ESP_OK) {
    log_e("i2cDeinit failed with error: %d", err);
    return false;
  }
  return true;
}

void *i2cBusHandle(uint8_t i2c_num) {
  if (i2c_num >= SOC_I2C_NUM) {
    return NULL;
  }
  return bus[i2c_num].bus_handle;
}

bool i2cIsInit(uint8_t i2c_num) {
  if (i2c_num >= SOC_I2C_NUM) {
    return false;
  }
  return bus[i2c_num].initialized;
}

esp_err_t i2cInit(uint8_t i2c_num, int8_t sda, int8_t scl, uint32_t frequency) {
  esp_err_t ret = ESP_OK;
  if (i2c_num >= SOC_I2C_NUM) {
    return ESP_ERR_INVALID_ARG;
  }
#if !CONFIG_DISABLE_HAL_LOCKS
  if (bus[i2c_num].lock == NULL) {
    bus[i2c_num].lock = xSemaphoreCreateMutex();
    if (bus[i2c_num].lock == NULL) {
      log_e("xSemaphoreCreateMutex failed");
      return ESP_ERR_NO_MEM;
    }
  }
  //acquire lock
  if (xSemaphoreTake(bus[i2c_num].lock, portMAX_DELAY) != pdTRUE) {
    log_e("could not acquire lock");
    return ESP_FAIL;
  }
#endif
  if (bus[i2c_num].initialized) {
    log_e("bus is already initialized");
    ret = ESP_FAIL;
    goto init_fail;
  }

  if (!frequency) {
    frequency = 100000UL;
  } else if (frequency > 1000000UL) {
    frequency = 1000000UL;
  }

  perimanSetBusDeinit(ESP32_BUS_TYPE_I2C_MASTER_SDA, i2cDetachBus);
  perimanSetBusDeinit(ESP32_BUS_TYPE_I2C_MASTER_SCL, i2cDetachBus);

  if (!perimanClearPinBus(sda) || !perimanClearPinBus(scl)) {
    ret = ESP_FAIL;
    goto init_fail;
  }

  log_i("Initializing I2C Master: num=%u sda=%d scl=%d freq=%" PRIu32, i2c_num, sda, scl, frequency);

  i2c_master_bus_handle_t bus_handle = NULL;
  i2c_master_bus_config_t bus_config;
  memset(&bus_config, 0, sizeof(i2c_master_bus_config_t));
  bus_config.i2c_port = (i2c_port_num_t)i2c_num;
  bus_config.sda_io_num = (gpio_num_t)sda;
  bus_config.scl_io_num = (gpio_num_t)scl;
#if SOC_LP_I2C_SUPPORTED
  if (i2c_num >= SOC_HP_I2C_NUM) {
    bus_config.lp_source_clk = LP_I2C_SCLK_DEFAULT;
  } else
#endif
  {
    bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
  }
  bus_config.glitch_ignore_cnt = 7;
  bus_config.intr_priority = 0;      // auto
  bus_config.trans_queue_depth = 0;  // only valid in asynchronous transaction, which Arduino does not use
  bus_config.flags.enable_internal_pullup = 1;
#if SOC_I2C_SUPPORT_SLEEP_RETENTION
  bus_config.flags.allow_pd = 1;  // backup/restore the I2C registers before/after entering/exist sleep mode
#endif

  ret = i2c_new_master_bus(&bus_config, &bus_handle);
  if (ret != ESP_OK) {
    log_e("i2c_new_master_bus failed: [%d] %s", ret, esp_err_to_name(ret));
  } else {
    bus[i2c_num].initialized = true;
    bus[i2c_num].frequency = frequency;
    bus[i2c_num].scl = scl;
    bus[i2c_num].sda = sda;
    bus[i2c_num].bus_handle = bus_handle;
    for (uint8_t i = 0; i < 128; i++) {
      bus[i2c_num].dev_handles[i] = NULL;
    }
    if (!perimanSetPinBus(sda, ESP32_BUS_TYPE_I2C_MASTER_SDA, (void *)(i2c_num + 1), i2c_num, -1)
        || !perimanSetPinBus(scl, ESP32_BUS_TYPE_I2C_MASTER_SCL, (void *)(i2c_num + 1), i2c_num, -1)) {
#if !CONFIG_DISABLE_HAL_LOCKS
      //release lock so that i2cDetachBus can execute i2cDeinit
      xSemaphoreGive(bus[i2c_num].lock);
#endif
      i2cDetachBus((void *)(i2c_num + 1));
      return ESP_FAIL;
    }
  }

  // Silence messages coming from the IDF driver
  esp_log_level_set("i2c.master", ESP_LOG_NONE);

init_fail:
#if !CONFIG_DISABLE_HAL_LOCKS
  //release lock
  xSemaphoreGive(bus[i2c_num].lock);
#endif
  return ret;
}

esp_err_t i2cDeinit(uint8_t i2c_num) {
  esp_err_t err = ESP_FAIL;
  if (i2c_num >= SOC_I2C_NUM) {
    return ESP_ERR_INVALID_ARG;
  }
#if !CONFIG_DISABLE_HAL_LOCKS
  //acquire lock
  if (bus[i2c_num].lock == NULL || xSemaphoreTake(bus[i2c_num].lock, portMAX_DELAY) != pdTRUE) {
    log_e("could not acquire lock");
    return err;
  }
#endif
  if (!bus[i2c_num].initialized) {
    log_e("bus is not initialized");
  } else {
    // remove devices from the bus
    for (uint8_t i = 0; i < 128; i++) {
      if (bus[i2c_num].dev_handles[i] != NULL) {
        err = i2c_master_bus_rm_device(bus[i2c_num].dev_handles[i]);
        bus[i2c_num].dev_handles[i] = NULL;
        if (err != ESP_OK) {
          log_e("i2c_master_bus_rm_device failed: [%d] %s", err, esp_err_to_name(err));
        }
      }
    }
    err = i2c_del_master_bus(bus[i2c_num].bus_handle);
    if (err != ESP_OK) {
      log_e("i2c_del_master_bus failed: [%d] %s", err, esp_err_to_name(err));
    } else {
      bus[i2c_num].initialized = false;
      perimanClearPinBus(bus[i2c_num].scl);
      perimanClearPinBus(bus[i2c_num].sda);
      bus[i2c_num].scl = -1;
      bus[i2c_num].sda = -1;
      bus[i2c_num].bus_handle = NULL;
    }
  }
#if !CONFIG_DISABLE_HAL_LOCKS
  //release lock
  xSemaphoreGive(bus[i2c_num].lock);
#endif
  return err;
}

static esp_err_t i2cAddDeviceIfNeeded(uint8_t i2c_num, uint16_t address) {
  esp_err_t ret = ESP_OK;
  if (bus[i2c_num].dev_handles[address] == NULL) {
    i2c_master_dev_handle_t dev_handle = NULL;
    i2c_device_config_t dev_config;
    memset(&dev_config, 0, sizeof(i2c_device_config_t));
    dev_config.dev_addr_length = I2C_ADDR_BIT_LEN_7;  // Arduino supports only 7bit addresses
    dev_config.device_address = address;
    dev_config.scl_speed_hz = bus[i2c_num].frequency;
    dev_config.scl_wait_us = 0;
    dev_config.flags.disable_ack_check = 0;

    ret = i2c_master_bus_add_device(bus[i2c_num].bus_handle, &dev_config, &dev_handle);
    if (ret != ESP_OK) {
      log_e("i2c_master_bus_add_device failed: [%d] %s", ret, esp_err_to_name(ret));
    } else {
      bus[i2c_num].dev_handles[address] = dev_handle;
      log_v("added device: bus=%u addr=0x%x handle=%p", i2c_num, address, dev_handle);
    }
  }
  return ret;
}

esp_err_t i2cWrite(uint8_t i2c_num, uint16_t address, const uint8_t *buff, size_t size, uint32_t timeOutMillis) {
  esp_err_t ret = ESP_FAIL;
  // i2c_cmd_handle_t cmd = NULL;
  if (i2c_num >= SOC_I2C_NUM) {
    return ESP_ERR_INVALID_ARG;
  }
  if (address >= 128) {
    log_e("Only 7bit I2C addresses are supported");
    return ESP_ERR_INVALID_ARG;
  }
#if !CONFIG_DISABLE_HAL_LOCKS
  //acquire lock
  if (bus[i2c_num].lock == NULL || xSemaphoreTake(bus[i2c_num].lock, portMAX_DELAY) != pdTRUE) {
    log_e("could not acquire lock");
    return ret;
  }
#endif
  if (!bus[i2c_num].initialized) {
    log_e("bus is not initialized");
    goto end;
  }

  if (size == 0) {
    // Probe device
    ret = i2c_master_probe(bus[i2c_num].bus_handle, address, timeOutMillis);
    if (ret != ESP_OK) {
      log_v("i2c_master_probe failed: [%d] %s", ret, esp_err_to_name(ret));
    }
  } else {
    // writing data to device
    ret = i2cAddDeviceIfNeeded(i2c_num, address);
    if (ret != ESP_OK) {
      goto end;
    }

    log_v("i2c_master_transmit: bus=%u addr=0x%x handle=%p size=%lu", i2c_num, address, bus[i2c_num].dev_handles[address], (unsigned long)size);
    ret = i2c_master_transmit(bus[i2c_num].dev_handles[address], buff, size, timeOutMillis);
    if (ret != ESP_OK) {
      log_e("i2c_master_transmit failed: [%d] %s", ret, esp_err_to_name(ret));
      goto end;
    }

    // wait for transactions to finish (is it needed with sync transactions?)
    // ret = i2c_master_bus_wait_all_done(bus[i2c_num].bus_handle, timeOutMillis);
    // if (ret != ESP_OK) {
    //   log_e("i2c_master_bus_wait_all_done failed: [%d] %s", ret, esp_err_to_name(ret));
    //   goto end;
    // }
  }

end:
#if !CONFIG_DISABLE_HAL_LOCKS
  //release lock
  xSemaphoreGive(bus[i2c_num].lock);
#endif
  return ret;
}

esp_err_t i2cRead(uint8_t i2c_num, uint16_t address, uint8_t *buff, size_t size, uint32_t timeOutMillis, size_t *readCount) {
  esp_err_t ret = ESP_FAIL;
  *readCount = 0;
  if (i2c_num >= SOC_I2C_NUM) {
    return ESP_ERR_INVALID_ARG;
  }
#if !CONFIG_DISABLE_HAL_LOCKS
  //acquire lock
  if (bus[i2c_num].lock == NULL || xSemaphoreTake(bus[i2c_num].lock, portMAX_DELAY) != pdTRUE) {
    log_e("could not acquire lock");
    return ret;
  }
#endif
  if (!bus[i2c_num].initialized) {
    log_e("bus is not initialized");
    goto end;
  }

  ret = i2cAddDeviceIfNeeded(i2c_num, address);
  if (ret != ESP_OK) {
    goto end;
  }

  log_v("i2c_master_receive: bus=%u addr=0x%x handle=%p size=%lu", i2c_num, address, bus[i2c_num].dev_handles[address], (unsigned long)size);
  ret = i2c_master_receive(bus[i2c_num].dev_handles[address], buff, size, timeOutMillis);
  if (ret != ESP_OK) {
    log_e("i2c_master_receive failed: [%d] %s", ret, esp_err_to_name(ret));
    goto end;
  }

  // wait for transactions to finish (is it needed with sync transactions?)
  // ret = i2c_master_bus_wait_all_done(bus[i2c_num].bus_handle, timeOutMillis);
  // if (ret != ESP_OK) {
  //   log_e("i2c_master_bus_wait_all_done failed: [%d] %s", ret, esp_err_to_name(ret));
  //   goto end;
  // }
  *readCount = size;

end:
#if !CONFIG_DISABLE_HAL_LOCKS
  //release lock
  xSemaphoreGive(bus[i2c_num].lock);
#endif
  return ret;
}

esp_err_t i2cWriteReadNonStop(
  uint8_t i2c_num, uint16_t address, const uint8_t *wbuff, size_t wsize, uint8_t *rbuff, size_t rsize, uint32_t timeOutMillis, size_t *readCount
) {
  esp_err_t ret = ESP_FAIL;
  *readCount = 0;
  if (i2c_num >= SOC_I2C_NUM) {
    return ESP_ERR_INVALID_ARG;
  }
#if !CONFIG_DISABLE_HAL_LOCKS
  //acquire lock
  if (bus[i2c_num].lock == NULL || xSemaphoreTake(bus[i2c_num].lock, portMAX_DELAY) != pdTRUE) {
    log_e("could not acquire lock");
    return ret;
  }
#endif
  if (!bus[i2c_num].initialized) {
    log_e("bus is not initialized");
    goto end;
  }

  ret = i2cAddDeviceIfNeeded(i2c_num, address);
  if (ret != ESP_OK) {
    goto end;
  }

  log_v(
    "i2c_master_transmit_receive: bus=%u addr=0x%x handle=%p write=%lu read=%lu", i2c_num, address, bus[i2c_num].dev_handles[address], (unsigned long)wsize,
    (unsigned long)rsize
  );
  ret = i2c_master_transmit_receive(bus[i2c_num].dev_handles[address], wbuff, wsize, rbuff, rsize, timeOutMillis);
  if (ret != ESP_OK) {
    log_e("i2c_master_transmit_receive failed: [%d] %s", ret, esp_err_to_name(ret));
    goto end;
  }

  // wait for transactions to finish (is it needed with sync transactions?)
  // ret = i2c_master_bus_wait_all_done(bus[i2c_num].bus_handle, timeOutMillis);
  // if (ret != ESP_OK) {
  //   log_e("i2c_master_bus_wait_all_done failed: [%d] %s", ret, esp_err_to_name(ret));
  //   goto end;
  // }
  *readCount = rsize;

end:
#if !CONFIG_DISABLE_HAL_LOCKS
  //release lock
  xSemaphoreGive(bus[i2c_num].lock);
#endif
  return ret;
}

esp_err_t i2cSetClock(uint8_t i2c_num, uint32_t frequency) {
  esp_err_t ret = ESP_FAIL;
  if (i2c_num >= SOC_I2C_NUM) {
    return ESP_ERR_INVALID_ARG;
  }
#if !CONFIG_DISABLE_HAL_LOCKS
  //acquire lock
  if (bus[i2c_num].lock == NULL || xSemaphoreTake(bus[i2c_num].lock, portMAX_DELAY) != pdTRUE) {
    log_e("could not acquire lock");
    return ret;
  }
#endif
  if (!bus[i2c_num].initialized) {
    log_e("bus is not initialized");
    goto end;
  }
  if (bus[i2c_num].frequency == frequency) {
    ret = ESP_OK;
    goto end;
  }
  if (!frequency) {
    frequency = 100000UL;
  } else if (frequency > 1000000UL) {
    frequency = 1000000UL;
  }

  bus[i2c_num].frequency = frequency;

  // loop through devices, remove them and then re-add them with the new frequency
  for (uint8_t i = 0; i < 128; i++) {
    if (bus[i2c_num].dev_handles[i] != NULL) {
      ret = i2c_master_bus_rm_device(bus[i2c_num].dev_handles[i]);
      if (ret != ESP_OK) {
        log_e("i2c_master_bus_rm_device failed: [%d] %s", ret, esp_err_to_name(ret));
        goto end;
      } else {
        bus[i2c_num].dev_handles[i] = NULL;
        ret = i2cAddDeviceIfNeeded(i2c_num, i);
        if (ret != ESP_OK) {
          goto end;
        }
      }
    }
  }

end:
#if !CONFIG_DISABLE_HAL_LOCKS
  //release lock
  xSemaphoreGive(bus[i2c_num].lock);
#endif
  return ret;
}

esp_err_t i2cGetClock(uint8_t i2c_num, uint32_t *frequency) {
  if (i2c_num >= SOC_I2C_NUM) {
    return ESP_ERR_INVALID_ARG;
  }
  if (!bus[i2c_num].initialized) {
    log_e("bus is not initialized");
    return ESP_FAIL;
  }
  *frequency = bus[i2c_num].frequency;
  return ESP_OK;
}

#endif /* ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) */
#endif /* SOC_I2C_SUPPORTED */
