// Basic Config
#include "globals.h"
#include "reset.h"

// Conversion factor for micro seconds to seconds
#define uS_TO_S_FACTOR 1000000ULL

// RTC_NOINIT_ATTR -> keeps value after a software restart or system crash
RTC_NOINIT_ATTR runmode_t RTC_runmode;
RTC_NOINIT_ATTR uint32_t RTC_restarts;
// RTC_DATA_ATTR -> keeps value after a wakeup from sleep
RTC_DATA_ATTR struct timeval sleep_start_time;
RTC_DATA_ATTR int64_t RTC_millis = 0;

struct timeval sleep_stop_time;

void reset_rtc_vars(void) {
  RTC_runmode = RUNMODE_POWERCYCLE;
  RTC_restarts = 0;
}

#if (HAS_TIME)
void adjust_wakeup(uint32_t *wakeuptime) {
  // only adjust wakeup if we have a valid time
  if ((timeSource == _unsynced) ||
      (sntp_get_sync_status() == SNTP_SYNC_STATUS_IN_PROGRESS)) {
    ESP_LOGI(TAG, "Syncwakeup: No valid time for sync");
    return;
  }

  time_t now;
  time(&now);

  // 1..3600 seconds between next wakeup time and following top-of-hour
  uint16_t shift_sec = 3600 - (now + *wakeuptime) % 3600;

  if (shift_sec <= SYNCWAKEUP) {
    *wakeuptime += shift_sec; // delay wakeup to catch top-of-hour
    ESP_LOGI(TAG, "Syncwakeup: Wakeup %hu sec postponed", shift_sec);
  } else if (shift_sec >= (3600 - SYNCWAKEUP)) {
    *wakeuptime = 3600 - shift_sec; // shorten wake up to next top-of-hour
    ESP_LOGI(TAG, "Syncwakeup: Wakeup %hu sec preponed", shift_sec);
  } else
    ESP_LOGI(TAG, "Syncwakeup: Wakeup keeping unshifted");
}
#endif

void do_reset(bool warmstart) {
  if (warmstart) {
    ESP_LOGI(TAG, "restarting device (warmstart)");
  } else {
#if (HAS_LORA)
    if (RTC_runmode == RUNMODE_NORMAL) {
      LMIC_shutdown();
    }
#endif
    reset_rtc_vars();
    ESP_LOGI(TAG, "restarting device (coldstart)");
  }
  esp_restart();
}

void do_after_reset(void) {
  struct timeval sleep_stop_time;
  int64_t sleep_time_ms;

  // read (and initialize on first run) runtime settings from NVRAM
  loadConfig();

  // set time zone to user value from paxcounter.conf
#ifdef TIME_SYNC_TIMEZONE
  setenv("TZ", TIME_SYNC_TIMEZONE, 1);
  tzset();
  ESP_LOGD(TAG, "Timezone set to %s", TIME_SYNC_TIMEZONE);
#endif

  switch (rtc_get_reset_reason(0)) {
  case RESET_REASON_CHIP_POWER_ON:
  case RESET_REASON_SYS_BROWN_OUT:
    reset_rtc_vars();
    break;

  case RESET_REASON_CPU0_SW:
    // keep previous set runmode (update / normal / maintenance)
    RTC_restarts++;
    break;

  case RESET_REASON_CORE_DEEP_SLEEP:
    // calculate time spent in deep sleep
    gettimeofday(&sleep_stop_time, NULL);
    sleep_time_ms = ((int64_t)sleep_stop_time.tv_sec * 1000000L +
                     (int64_t)sleep_stop_time.tv_usec -
                     (int64_t)sleep_start_time.tv_sec * 1000000L -
                     (int64_t)sleep_start_time.tv_usec) /
                    1000LL;
    RTC_millis += sleep_time_ms; // increment system monotonic time
    ESP_LOGI(TAG, "Time spent in deep sleep: %llu ms", sleep_time_ms);
    // do we have a valid time? -> set global variable
    timeSource = timeIsValid(sleep_stop_time.tv_sec) ? _set : _unsynced;
    // set wakeup state, not if we have pending OTA update
    if (RTC_runmode == RUNMODE_SLEEP)
      RTC_runmode = RUNMODE_WAKEUP;
    break;

  default:
    RTC_runmode = RUNMODE_POWERCYCLE;
    RTC_restarts++;
    break;
  }
}

void enter_deepsleep(uint32_t wakeup_sec, gpio_num_t wakeup_gpio) {
  ESP_LOGI(TAG, "Preparing to sleep...");

  RTC_runmode = RUNMODE_SLEEP;

  // validate wake up pin, if we have
  if (!GPIO_IS_VALID_GPIO(wakeup_gpio))
    wakeup_gpio = GPIO_NUM_MAX;
  // stop further enqueuing of senddata and MAC processing
  libpax_counter_stop();

  // switch off any power consuming hardware
#if (HAS_SDS011)
  sds011_sleep();
#endif

  // wait a while (max 100 sec) to clear send queues
  ESP_LOGI(TAG, "Waiting until send queues are empty...");
  for (int i = 100; i > 0; i--) {
    if (allQueuesEmtpy())
      break;
    vTaskDelay(pdMS_TO_TICKS(1000));
  }

// wait up to 100secs until LMIC is idle
#if (HAS_LORA)
  lora_waitforidle(100);
#endif // (HAS_LORA)

// shutdown MQTT safely
#ifdef HAS_MQTT
  mqtt_deinit();
#endif

// shutdown SPI safely
#ifdef HAS_SPI
  spi_deinit();
#endif

// save LMIC state to RTC RAM
#if (HAS_LORA)
  SaveLMICToRTC(wakeup_sec);
#endif // (HAS_LORA)

// set display to power save mode
#ifdef HAS_DISPLAY
  dp_shutdown();
#endif

// reduce power if has PMU or VEXT
#ifdef HAS_PMU
  AXP192_power(pmu_power_sleep);
#elif EXT_POWER_SW
  digitalWrite(EXT_POWER_SW, EXT_POWER_OFF);
#endif

  // halt interrupts accessing i2c bus
  mask_user_IRQ();

  // shutdown i2c bus
  i2c_deinit();

#if (HAS_TIME)
  if (cfg.wakesync && cfg.sleepcycle)
    adjust_wakeup(&wakeup_sec);
#endif

  // configure wakeup sources
  // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html

  // set up RTC wakeup timer, if we have
  if (wakeup_sec > 0) {
    esp_sleep_enable_timer_wakeup(wakeup_sec * uS_TO_S_FACTOR);
  }

  // set wakeup gpio, if we have
  if (wakeup_gpio != GPIO_NUM_MAX) {
    rtc_gpio_isolate(wakeup_gpio); // minimize deep sleep current
    esp_sleep_enable_ext1_wakeup(1ULL << wakeup_gpio, ESP_EXT1_WAKEUP_ALL_LOW);
  }

  // time stamp sleep start time and save system monotonic time. Deep sleep.
  gettimeofday(&sleep_start_time, NULL);
  RTC_millis += esp_timer_get_time() / 1000LL;
  ESP_LOGI(TAG, "Going to sleep, good bye.");

// flush & close sd card, if we have
#if (HAS_SDCARD)
  sdcard_close();
#endif

  esp_deep_sleep_start();
}

uint64_t uptime() {
  return (uint64_t)(RTC_millis + esp_timer_get_time() / 1000LL);
}
