// 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 "sdkconfig.h"
#if defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE) || defined(CONFIG_ESP_WIFI_REMOTE_ENABLED)

#include "esp32-hal-hosted.h"
#include "esp32-hal-log.h"
#include "esp32-hal.h"
#include "pins_arduino.h"

#include "esp_hosted.h"
#include "esp_hosted_transport_config.h"
// extern esp_err_t esp_hosted_init();
// extern esp_err_t esp_hosted_deinit();

static bool hosted_initialized = false;
static bool hosted_ble_active = false;
static bool hosted_wifi_active = false;

static sdio_pin_config_t sdio_pin_config = {
#ifdef BOARD_HAS_SDIO_ESP_HOSTED
  .pin_clk = BOARD_SDIO_ESP_HOSTED_CLK,
  .pin_cmd = BOARD_SDIO_ESP_HOSTED_CMD,
  .pin_d0 = BOARD_SDIO_ESP_HOSTED_D0,
  .pin_d1 = BOARD_SDIO_ESP_HOSTED_D1,
  .pin_d2 = BOARD_SDIO_ESP_HOSTED_D2,
  .pin_d3 = BOARD_SDIO_ESP_HOSTED_D3,
  .pin_reset = BOARD_SDIO_ESP_HOSTED_RESET
#else
  .pin_clk = CONFIG_ESP_SDIO_PIN_CLK,
  .pin_cmd = CONFIG_ESP_SDIO_PIN_CMD,
  .pin_d0 = CONFIG_ESP_SDIO_PIN_D0,
  .pin_d1 = CONFIG_ESP_SDIO_PIN_D1,
  .pin_d2 = CONFIG_ESP_SDIO_PIN_D2,
  .pin_d3 = CONFIG_ESP_SDIO_PIN_D3,
  .pin_reset = CONFIG_ESP_SDIO_GPIO_RESET_SLAVE
#endif
};

static esp_hosted_coprocessor_fwver_t slave_version_struct = {.major1 = 0, .minor1 = 0, .patch1 = 0};
static esp_hosted_coprocessor_fwver_t host_version_struct = {
  .major1 = ESP_HOSTED_VERSION_MAJOR_1, .minor1 = ESP_HOSTED_VERSION_MINOR_1, .patch1 = ESP_HOSTED_VERSION_PATCH_1
};

static bool hostedInit();
static bool hostedDeinit();

void hostedGetHostVersion(uint32_t *major, uint32_t *minor, uint32_t *patch) {
  *major = host_version_struct.major1;
  *minor = host_version_struct.minor1;
  *patch = host_version_struct.patch1;
}

void hostedGetSlaveVersion(uint32_t *major, uint32_t *minor, uint32_t *patch) {
  *major = slave_version_struct.major1;
  *minor = slave_version_struct.minor1;
  *patch = slave_version_struct.patch1;
}

bool hostedHasUpdate() {
  if (!hosted_initialized) {
    log_e("ESP-Hosted is not initialized");
    return false;
  }

  uint32_t host_version = ESP_HOSTED_VERSION_VAL(host_version_struct.major1, host_version_struct.minor1, host_version_struct.patch1);
  uint32_t slave_version = 0;

  esp_err_t ret = esp_hosted_get_coprocessor_fwversion(&slave_version_struct);
  if (ret != ESP_OK) {
    log_e("Could not get slave firmware version: %s", esp_err_to_name(ret));
  } else {
    slave_version = ESP_HOSTED_VERSION_VAL(slave_version_struct.major1, slave_version_struct.minor1, slave_version_struct.patch1);
  }

  log_i("Host firmware version: %" PRIu32 ".%" PRIu32 ".%" PRIu32, host_version_struct.major1, host_version_struct.minor1, host_version_struct.patch1);
  log_i("Slave firmware version: %" PRIu32 ".%" PRIu32 ".%" PRIu32, slave_version_struct.major1, slave_version_struct.minor1, slave_version_struct.patch1);

  // compare major.minor only
  // slave_version &= 0xFFFFFF00;
  // host_version &= 0xFFFFFF00;

  if (host_version == slave_version) {
    log_i("Versions Match!");
  } else if (host_version > slave_version) {
    log_w("Version on Host is NEWER than version on co-processor");
    log_w("Update URL: %s", hostedGetUpdateURL());
    return true;
  } else {
    log_w("Version on Host is OLDER than version on co-processor");
  }
  return false;
}

char *hostedGetUpdateURL() {
  // https://espressif.github.io/arduino-esp32/hosted/esp32c6-v1.2.3.bin
  static char url[92] = {0};
  snprintf(
    url, 92, "https://espressif.github.io/arduino-esp32/hosted/%s-v%" PRIu32 ".%" PRIu32 ".%" PRIu32 ".bin", CONFIG_ESP_HOSTED_IDF_SLAVE_TARGET,
    host_version_struct.major1, host_version_struct.minor1, host_version_struct.patch1
  );
  return url;
}

bool hostedBeginUpdate() {
  if (!hosted_initialized) {
    log_e("ESP-Hosted is not initialized");
    return false;
  }

  esp_err_t err = esp_hosted_slave_ota_begin();
  if (err != ESP_OK) {
    log_e("Failed to begin Update: %s", esp_err_to_name(err));
  }
  return err == ESP_OK;
}

bool hostedWriteUpdate(uint8_t *buf, uint32_t len) {
  if (!hosted_initialized) {
    log_e("ESP-Hosted is not initialized");
    return false;
  }

  esp_err_t err = esp_hosted_slave_ota_write(buf, len);
  if (err != ESP_OK) {
    log_e("Failed to write Update: %s", esp_err_to_name(err));
  }
  return err == ESP_OK;
}

bool hostedEndUpdate() {
  if (!hosted_initialized) {
    log_e("ESP-Hosted is not initialized");
    return false;
  }

  esp_err_t err = esp_hosted_slave_ota_end();
  if (err != ESP_OK) {
    log_e("Failed to end Update: %s", esp_err_to_name(err));
  }
  return err == ESP_OK;
}

bool hostedActivateUpdate() {
  if (!hosted_initialized) {
    log_e("ESP-Hosted is not initialized");
    return false;
  }

  // Activate can fail on older firmwares and that is not critical
  uint32_t slave_version = ESP_HOSTED_VERSION_VAL(slave_version_struct.major1, slave_version_struct.minor1, slave_version_struct.patch1);
  uint32_t min_version = ESP_HOSTED_VERSION_VAL(2, 6, 0);

  if (slave_version < min_version) {
    // Silence messages caused by earlier versions
    esp_log_level_set("rpc_core", ESP_LOG_NONE);
  }

  esp_err_t err = esp_hosted_slave_ota_activate();

  // Any further communication will result in logged errors
  esp_log_level_set("sdmmc_io", ESP_LOG_NONE);
  esp_log_level_set("H_SDIO_DRV", ESP_LOG_NONE);

  if (err != ESP_OK && slave_version >= min_version) {
    log_e("Failed to activate Update: %s", esp_err_to_name(err));
    return false;
  }
  return true;
}

static bool hostedInit() {
  if (!hosted_initialized) {
    log_i("Initializing ESP-Hosted");
    log_d(
      "SDIO pins: clk=%d, cmd=%d, d0=%d, d1=%d, d2=%d, d3=%d, rst=%d", sdio_pin_config.pin_clk, sdio_pin_config.pin_cmd, sdio_pin_config.pin_d0,
      sdio_pin_config.pin_d1, sdio_pin_config.pin_d2, sdio_pin_config.pin_d3, sdio_pin_config.pin_reset
    );
    hosted_initialized = true;
    struct esp_hosted_sdio_config conf = INIT_DEFAULT_HOST_SDIO_CONFIG();
    conf.pin_clk.pin = sdio_pin_config.pin_clk;
    conf.pin_cmd.pin = sdio_pin_config.pin_cmd;
    conf.pin_d0.pin = sdio_pin_config.pin_d0;
    conf.pin_d1.pin = sdio_pin_config.pin_d1;
    conf.pin_d2.pin = sdio_pin_config.pin_d2;
    conf.pin_d3.pin = sdio_pin_config.pin_d3;
    conf.pin_reset.pin = sdio_pin_config.pin_reset;
    esp_err_t err = esp_hosted_sdio_set_config(&conf);
    if (err != ESP_OK) {  //&& err != ESP_ERR_NOT_ALLOWED) { // uncomment when second init is fixed
      log_e("esp_hosted_sdio_set_config failed: %s", esp_err_to_name(err));
      return false;
    }
    err = esp_hosted_init();
    if (err != ESP_OK) {
      log_e("esp_hosted_init failed: %s", esp_err_to_name(err));
      hosted_initialized = false;
      return false;
    }
    log_i("ESP-Hosted initialized!");
    err = esp_hosted_connect_to_slave();
    if (err != ESP_OK) {
      log_e("esp_hosted_connect_to_slave failed: %s", esp_err_to_name(err));
      hosted_initialized = false;
      return false;
    }
    hostedHasUpdate();
    return true;
  }

  // Attach pins to PeriMan here
  // Slave chip model is CONFIG_IDF_SLAVE_TARGET
  // sdio_pin_config.pin_clk
  // sdio_pin_config.pin_cmd
  // sdio_pin_config.pin_d0
  // sdio_pin_config.pin_d1
  // sdio_pin_config.pin_d2
  // sdio_pin_config.pin_d3
  // sdio_pin_config.pin_reset

  return true;
}

static bool hostedDeinit() {
  if (!hosted_initialized) {
    log_e("ESP-Hosted is not initialized");
    return false;
  }

  if (esp_hosted_deinit() != ESP_OK) {
    log_e("esp_hosted_deinit failed!");
    return false;
  }

  hosted_initialized = false;
  return true;
}

bool hostedInitBLE() {
  log_i("Initializing ESP-Hosted for BLE");
  if (!hostedInit()) {
    return false;
  }

  uint32_t slave_version = ESP_HOSTED_VERSION_VAL(slave_version_struct.major1, slave_version_struct.minor1, slave_version_struct.patch1);
  uint32_t min_version = ESP_HOSTED_VERSION_VAL(2, 6, 0);
  if (slave_version >= min_version) {
    esp_err_t err = esp_hosted_bt_controller_init();
    if (err != ESP_OK) {
      log_e("esp_hosted_bt_controller_init failed: %s", esp_err_to_name(err));
      return false;
    }
    err = esp_hosted_bt_controller_enable();
    if (err != ESP_OK) {
      log_e("esp_hosted_bt_controller_enable failed: %s", esp_err_to_name(err));
      return false;
    }
  }
  hosted_ble_active = true;
  return true;
}

bool hostedInitWiFi() {
  log_i("Initializing ESP-Hosted for WiFi");
  hosted_wifi_active = true;
  return hostedInit();
}

bool hostedDeinitBLE() {
  log_i("Deinitializing ESP-Hosted for BLE");
  uint32_t slave_version = ESP_HOSTED_VERSION_VAL(slave_version_struct.major1, slave_version_struct.minor1, slave_version_struct.patch1);
  uint32_t min_version = ESP_HOSTED_VERSION_VAL(2, 6, 0);
  if (slave_version >= min_version) {
    esp_err_t err = esp_hosted_bt_controller_disable();
    if (err != ESP_OK) {
      log_e("esp_hosted_bt_controller_disable failed: %s", esp_err_to_name(err));
      return false;
    }
    err = esp_hosted_bt_controller_deinit(false);
    if (err != ESP_OK) {
      log_e("esp_hosted_bt_controller_deinit failed: %s", esp_err_to_name(err));
      return false;
    }
  }
  hosted_ble_active = false;
  if (!hosted_wifi_active) {
    return hostedDeinit();
  } else {
    log_i("ESP-Hosted is still being used by Wi-Fi. Skipping deinit.");
    return true;
  }
}

bool hostedDeinitWiFi() {
  log_i("Deinitializing ESP-Hosted for WiFi");
  hosted_wifi_active = false;
  if (!hosted_ble_active) {
    return hostedDeinit();
  } else {
    log_i("ESP-Hosted is still being used by BLE. Skipping deinit.");
    return true;
  }
}

bool hostedSetPins(int8_t clk, int8_t cmd, int8_t d0, int8_t d1, int8_t d2, int8_t d3, int8_t rst) {
  if (clk < 0 || cmd < 0 || d0 < 0 || d1 < 0 || d2 < 0 || d3 < 0 || rst < 0) {
    log_e("All SDIO pins must be defined");
    return false;
  }

  if (hosted_initialized) {
    int8_t current_clk, current_cmd, current_d0, current_d1, current_d2, current_d3, current_rst;
    hostedGetPins(&current_clk, &current_cmd, &current_d0, &current_d1, &current_d2, &current_d3, &current_rst);
    log_e("SDIO pins must be set before ESP-Hosted is initialized");
    log_e(
      "Current pins used: clk=%d, cmd=%d, d0=%d, d1=%d, d2=%d, d3=%d, rst=%d", current_clk, current_cmd, current_d0, current_d1, current_d2, current_d3,
      current_rst
    );
    return false;
  }

  sdio_pin_config.pin_clk = clk;
  sdio_pin_config.pin_cmd = cmd;
  sdio_pin_config.pin_d0 = d0;
  sdio_pin_config.pin_d1 = d1;
  sdio_pin_config.pin_d2 = d2;
  sdio_pin_config.pin_d3 = d3;
  sdio_pin_config.pin_reset = rst;
  return true;
}

void hostedGetPins(int8_t *clk, int8_t *cmd, int8_t *d0, int8_t *d1, int8_t *d2, int8_t *d3, int8_t *rst) {
  *clk = sdio_pin_config.pin_clk;
  *cmd = sdio_pin_config.pin_cmd;
  *d0 = sdio_pin_config.pin_d0;
  *d1 = sdio_pin_config.pin_d1;
  *d2 = sdio_pin_config.pin_d2;
  *d3 = sdio_pin_config.pin_d3;
  *rst = sdio_pin_config.pin_reset;
}

bool hostedIsBLEActive() {
  return hosted_ble_active;
}

bool hostedIsWiFiActive() {
  return hosted_wifi_active;
}

bool hostedIsInitialized() {
  return hosted_initialized;
}

#endif /* defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE) || defined(CONFIG_ESP_WIFI_REMOTE_ENABLED) */
