/*
 * Copyright 2024 Google LLC
 *
 * 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 "mfg_command.h"

#include "applib/app_launch_reason.h"
#include "applib/app_watch_info.h"
#include "console/prompt.h"
#include "kernel/util/standby.h"
#include "mfg/mfg_info.h"
#include "process_management/app_install_manager.h"
#include "process_management/app_manager.h"

void command_enter_standby(void) {
  enter_standby(RebootReasonCode_MfgShutdown);
}

void command_color_read(void) {
  char buffer[10];
  prompt_send_response_fmt(buffer, sizeof(buffer), "%d", mfg_info_get_watch_color());
}

void command_color_write(const char* color_num) {
  char *end;
  int color = strtol(color_num, &end, 10);

  if (*end) {
    prompt_send_response("Invalid color");
    return;
  }

  mfg_info_set_watch_color(color);

  const WatchInfoColor written_color = mfg_info_get_watch_color();
  if (written_color == color) {
    prompt_send_response("OK");
  } else {
    prompt_send_response("ERROR");
  }
}

void command_disp_offset_read(void) {
  char buffer[16];
  prompt_send_response_fmt(buffer, sizeof(buffer), "X: %"PRId16" Y: %"PRId16,
                           mfg_info_get_disp_offsets().x,
                           mfg_info_get_disp_offsets().y);
}

void command_disp_offset_write(const char* offset_x_str, const char* offset_y_str) {
  char *nonnumeric_x, *nonnumeric_y;
  int8_t offset_x = strtol(offset_x_str, &nonnumeric_x, 10);
  if (*nonnumeric_x) {
    prompt_send_response("Invalid x offset");
  }

  int8_t offset_y = strtol(offset_y_str, &nonnumeric_y, 10);
  if (*nonnumeric_y) {
    prompt_send_response("Invalid y offset");
  }

  if (!*nonnumeric_x && !*nonnumeric_y) {
    mfg_info_set_disp_offsets((GPoint) {offset_x, offset_y});
  }
}

void command_rtcfreq_read(void) {
  char buffer[10];
  prompt_send_response_fmt(buffer, sizeof(buffer), "%"PRIu32, mfg_info_get_rtc_freq());
}

void command_rtcfreq_write(const char* rtc_freq_string) {
  char *end;
  uint32_t rtc_freq = strtol(rtc_freq_string, &end, 10);

  if (*end) {
    prompt_send_response("Invalid rtcfreq");
    return;
  }

  mfg_info_set_rtc_freq(rtc_freq);
}

void command_model_read(void) {
  char model_buffer[MFG_INFO_MODEL_STRING_LENGTH];
  mfg_info_get_model(model_buffer);

  // Just send it straight out, as it's already null-terminated
  prompt_send_response(model_buffer);
}

void command_model_write(const char* model) {
  // mfg_info_set_model will truncate if the string is too long, so no need to check
  mfg_info_set_model(model);
  char written_model[MFG_INFO_MODEL_STRING_LENGTH];
  mfg_info_get_model(written_model);
  if (!strncmp(model, written_model, MFG_INFO_MODEL_STRING_LENGTH)) {
    prompt_send_response("OK");
  } else {
    prompt_send_response("ERROR");
  }
}

#if BOOTLOADER_TEST_STAGE1
#include "bootloader_test_bin.auto.h"

#include "drivers/flash.h"
#include "flash_region/flash_region.h"
#include "system/firmware_storage.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/reset.h"
#include "util/crc32.h"
#include "util/legacy_checksum.h"
static void prv_bootloader_test_copy(uint32_t flash_addr, uint32_t flash_end) {
  const uint8_t *bin;
  size_t size;

  bin = s_bootloader_test_stage2;
  size = sizeof(s_bootloader_test_stage2);
  flash_region_erase_optimal_range(flash_addr,
                                   flash_addr,
                                   flash_addr+size+sizeof(FirmwareDescription),
                                   flash_end);

  FirmwareDescription fw_desc = {
    .description_length = sizeof(FirmwareDescription),
    .firmware_length = size,
    .checksum = 0,
  };
#if CAPABILITY_HAS_DEFECTIVE_FW_CRC
  fw_desc.checksum = legacy_defective_checksum_memory(bin, size);
#else
  fw_desc.checksum = crc32(CRC32_INIT, bin, size);
#endif

  flash_write_bytes((uint8_t *)&fw_desc, flash_addr, sizeof(FirmwareDescription));
  flash_write_bytes(bin, flash_addr+sizeof(FirmwareDescription), size);
}

#define BLTEST_LOG(x...) pbl_log(LOG_LEVEL_ALWAYS, __FILE__, __LINE__, x)

void command_bootloader_test(const char *dest_type) {
  prompt_command_finish();

  BLTEST_LOG("BOOTLOADER TEST STAGE 1");
  boot_bit_set(BOOT_BIT_FW_STABLE);

  BLTEST_LOG("STAGE 1 -- Setting test boot bits");
  boot_bit_clear(BOOT_BIT_BOOTLOADER_TEST_A | BOOT_BIT_BOOTLOADER_TEST_B);
  boot_bit_set(BOOT_BIT_BOOTLOADER_TEST_A);

  bool as_fw = true;
  if (strcmp(dest_type, "prf") == 0) {
    as_fw = false;
  }
  if (as_fw) {
    BLTEST_LOG("STAGE 1 -- Copying STAGE 2 to scratch");
    prv_bootloader_test_copy(FLASH_REGION_FIRMWARE_SCRATCH_BEGIN,
                             FLASH_REGION_FIRMWARE_SCRATCH_END);

    BLTEST_LOG("STAGE 1 -- Marking new FW boot bit");
    boot_bit_set(BOOT_BIT_NEW_FW_AVAILABLE);
  } else {
    BLTEST_LOG("STAGE 1 -- Copying STAGE 2 to PRF");
    flash_prf_set_protection(false);
    prv_bootloader_test_copy(FLASH_REGION_SAFE_FIRMWARE_BEGIN, FLASH_REGION_SAFE_FIRMWARE_END);

    BLTEST_LOG("STAGE 1 -- Marking PRF boot bit");
    boot_bit_set(BOOT_BIT_FORCE_PRF);
  }

  BLTEST_LOG("STAGE 1 -- Rebooting");
  RebootReason reason = { RebootReasonCode_PrfReset, 0 };
  reboot_reason_set(&reason);
  system_hard_reset();
}
#else
void command_bootloader_test(void) {
  prompt_send_response("Not configured for bootloader test");
}
#endif
