// Local Mesen-S feasibility host. Callers must verify SHA-256 before this ABI.
// The existing upstream SHA-1 implementation is an additional identity guard.
#include "stdafx.h"
#include "sha1.h"
#include "libretro.h"
#include <array>
#include <exception>

#ifdef _WIN32
#define API extern "C" __declspec(dllexport)
#else
#define API extern "C"
#endif

extern "C" { unsigned char mesen_probe_ipl[64] = {}; }
static bool initialized, loaded, frame_ok, state_boundary;
static bool fresh_candidate;
static std::string error;
static std::vector<uint8_t> rgba;
static std::vector<uint8_t> cartridge;
static std::array<float, 16384> pcm;
static size_t pcm_frames;
static unsigned width, height;
static uint16_t inputs[2];
static double sample_rate, fps;
std::string mesen_probe_capture_state();
unsigned mesen_probe_state_schema();
static std::string pending_state;
static constexpr unsigned max_state_bytes = 16 * 1024 * 1024;

static bool environment(unsigned command, void* data) {
  switch(command) {
  case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
  case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
    *static_cast<const char**>(data) = ".";
    return true;
  case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
    return *static_cast<retro_pixel_format*>(data) == RETRO_PIXEL_FORMAT_XRGB8888;
  case RETRO_ENVIRONMENT_GET_VARIABLE: {
    auto* variable = static_cast<retro_variable*>(data);
    const std::pair<const char*, const char*> options[] = {
      {"mesen-s_ramstate", "All 0s"}, {"mesen-s_region", "NTSC"},
      {"mesen-s_ntsc_filter", "Disabled"}, {"mesen-s_overclock", "None"},
      {"mesen-s_cubic_interpolation", "disabled"}, {"mesen-s_blend_high_res", "disabled"},
      {"mesen-s_hle_coprocessor", "disabled"}, {"mesen-s_overscan_vertical", "None"},
      {"mesen-s_overscan_horizontal", "None"}, {"mesen-s_aspect_ratio", "No Stretching"}
    };
    for(const auto& option : options) if(!strcmp(variable->key, option.first)) {
      variable->value = option.second;
      return true;
    }
    variable->value = nullptr;
    return false;
  }
  case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
  case RETRO_ENVIRONMENT_GET_FASTFORWARDING:
    *static_cast<bool*>(data) = false;
    return true;
  case RETRO_ENVIRONMENT_GET_CAN_DUPE:
    *static_cast<bool*>(data) = true;
    return true;
  case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO: {
    auto* av = static_cast<retro_system_av_info*>(data);
    sample_rate = av->timing.sample_rate;
    fps = av->timing.fps;
    return true;
  }
  case RETRO_ENVIRONMENT_SET_VARIABLES:
  case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
  case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS:
  case RETRO_ENVIRONMENT_SET_GEOMETRY:
  case RETRO_ENVIRONMENT_SET_MEMORY_MAPS:
    return true;
  default: return false;
  }
}

static void video(const void* data, unsigned w, unsigned h, size_t pitch) {
  if(!data) return; // libretro duplicate: retain previous pixels.
  if(w == 0 || h == 0 || w > 1024 || h > 512 || pitch < w * 4) {
    error = "Invalid core video dimensions";
    return;
  }
  width = w; height = h; rgba.resize(w * h * 4);
  for(unsigned y = 0; y < h; ++y) for(unsigned x = 0; x < w; ++x) {
    uint32_t pixel;
    memcpy(&pixel, static_cast<const uint8_t*>(data) + y * pitch + x * 4, 4);
    auto* out = rgba.data() + (y * w + x) * 4;
    out[0] = pixel >> 16; out[1] = pixel >> 8; out[2] = pixel; out[3] = 255;
  }
}

static size_t audio(const int16_t* samples, size_t frames) {
  if(frames > pcm.size() / 2 - pcm_frames) {
    error = "Core exceeded bounded PCM buffer";
    return frames; // Avoid upstream retry loop; the frame is marked failed.
  }
  for(size_t n = 0; n < frames * 2; ++n) pcm[pcm_frames * 2 + n] = samples[n] / 32768.0f;
  pcm_frames += frames;
  return frames;
}

static int16_t input(unsigned port, unsigned device, unsigned index, unsigned id) {
  static constexpr uint16_t masks[] = {
    0x8000, 0x4000, 0x2000, 0x1000, 0x0800, 0x0400,
    0x0200, 0x0100, 0x0080, 0x0040, 0x0020, 0x0010
  };
  return port < 2 && device == RETRO_DEVICE_JOYPAD && index == 0 && id < 12
    ? (inputs[port] & masks[id]) != 0 : 0;
}

API void sfc_unload() {
  pending_state.clear();
  if(loaded) retro_unload_game();
  loaded = false;
  if(initialized) retro_deinit();
  initialized = false; frame_ok = false; state_boundary = false; fresh_candidate = false;
  cartridge.clear(); rgba.clear(); pcm_frames = 0; width = height = 0;
  inputs[0] = inputs[1] = 0;
}

API int sfc_load(uint8_t* rom, unsigned rom_size, uint8_t* ipl, unsigned ipl_size) {
  sfc_unload(); error.clear();
#ifdef MESEN_SNES_LAB
  // The standalone local lab pins ROM identity in its explicit manifest. Mesen
  // handles cartridge mapping; this separate build bounds the input only.
  if(!rom || rom_size < 32768 || rom_size > 16u * 1024 * 1024) {
    error = "Unsupported bounded SNES cartridge size"; return 0;
  }
#else
#ifdef MESEN_NATIVE_MENU
  const char* expected_rom_sha1 = MESEN_NATIVE_ROM_SHA1;
#else
  const char* expected_rom_sha1 = "EAFEF16AA34E12E99D1A8BBE138B0A0E39DBBEF1";
#endif
  if(!rom || rom_size != 524288 ||
      SHA1::GetHash(rom, rom_size) != expected_rom_sha1) {
    error = "Unsupported ROM identity"; return 0;
  }
#endif
  if(!ipl || ipl_size != 64 ||
      SHA1::GetHash(ipl, ipl_size) != "97E352553E94242AE823547CD853EECDA55C20F0") {
    error = "Unsupported IPL identity"; return 0;
  }
  try {
    memcpy(mesen_probe_ipl, ipl, 64);
    cartridge.assign(rom, rom + rom_size);
    retro_set_environment(environment);
    retro_init(); initialized = true;
    retro_set_video_refresh(video);
    retro_set_audio_sample_batch(audio);
    retro_set_input_poll([]() {});
    retro_set_input_state(input);
    retro_game_info game = {
#ifdef MESEN_SNES_LAB
      "local-lab.sfc",
#else
      "smash-tv.sfc",
#endif
      cartridge.data(), cartridge.size(), nullptr};
    loaded = retro_load_game(&game);
    if(!loaded) { error = "Core rejected game"; sfc_unload(); return 0; }
    retro_system_av_info av = {};
    retro_get_system_av_info(&av);
    sample_rate = av.timing.sample_rate; fps = av.timing.fps;
    state_boundary = true;
    fresh_candidate = true;
    return 1;
  } catch(const std::exception& e) { error = e.what(); sfc_unload(); return 0; }
}

API void sfc_run_frame() {
  fresh_candidate = false; // Any execution attempt consumes restore eligibility.
  pending_state.clear();
  frame_ok = false;
  if(!loaded) { error = "Load the supported game first"; return; }
  if(!state_boundary) { error = "Reload after a failed core frame"; return; }
  if(pcm_frames) { error = "Drain PCM before running another frame"; return; }
  state_boundary = false;
  error.clear();
  try { retro_run(); frame_ok = error.empty(); }
  catch(const std::exception& e) { error = e.what(); }
  state_boundary = frame_ok;
}
API int sfc_frame_ok() { return frame_ok; }
API unsigned sfc_state_schema() { return mesen_probe_state_schema(); }
API const char* sfc_error() { return error.c_str(); }
API const uint8_t* sfc_pixels() { return rgba.data(); }
API unsigned sfc_width() { return width; }
API unsigned sfc_height() { return height; }
API unsigned sfc_audio_frames() { return static_cast<unsigned>(pcm_frames); }
API double sfc_sample_rate() { return sample_rate; }
API double sfc_fps() { return fps; }
API void sfc_set_input(unsigned port, unsigned buttons) { if(port < 2) inputs[port] = buttons; }
#ifdef MESEN_SNES_LAB
// Physical WRAM only (CPU banks 7E-7F), copied without bus/hardware side effects.
// Reads are available only at the same drained boundary used for snapshots.
API unsigned sfc_debug_memory_size() {
  if(!loaded || retro_get_memory_size(RETRO_MEMORY_SYSTEM_RAM) != 0x20000 ||
      !retro_get_memory_data(RETRO_MEMORY_SYSTEM_RAM)) return 0;
  return 0x20000;
}
API unsigned sfc_debug_read(unsigned offset, uint8_t* destination, unsigned size) {
  const unsigned length = 0x20000;
  if(!loaded || !state_boundary || pcm_frames || !destination || !size || size > 4096 ||
      offset > length || size > length - offset || sfc_debug_memory_size() != length) return 0;
  const auto* ram = static_cast<const uint8_t*>(retro_get_memory_data(RETRO_MEMORY_SYSTEM_RAM));
  memcpy(destination, ram + offset, size); return size;
}
#endif
#ifdef MESEN_NATIVE_MENU
// The experiment exposes only its reserved 256-byte mailbox. No arbitrary RAM
// access or new API exists in the original pinned production core.
static uint8_t* native_mailbox(unsigned offset, unsigned size) {
  const unsigned base = 0x1ff00, length = 256;
  if(!loaded || !state_boundary || pcm_frames || offset > length || size > length - offset ||
      retro_get_memory_size(RETRO_MEMORY_SYSTEM_RAM) < base + length) return nullptr;
  auto* ram = static_cast<uint8_t*>(retro_get_memory_data(RETRO_MEMORY_SYSTEM_RAM));
  return ram ? ram + base + offset : nullptr;
}
API unsigned sfc_native_read(unsigned offset, uint8_t* destination, unsigned size) {
  auto* source = native_mailbox(offset, size);
  if(!source || !destination || !size) return 0;
  memcpy(destination, source, size); return size;
}
API unsigned sfc_native_write(unsigned offset, const uint8_t* source, unsigned size) {
  auto* destination = native_mailbox(offset, size);
  if(!destination || !source || !size) return 0;
  memcpy(destination, source, size); return size;
}
#endif
API unsigned sfc_drain_audio(float* out, unsigned capacity) {
  if(!out || capacity < pcm_frames) { error = "PCM output buffer too small"; return 0; }
  unsigned count = static_cast<unsigned>(pcm_frames);
  memcpy(out, pcm.data(), count * 2 * sizeof(float)); pcm_frames = 0;
  return count;
}

// Call together at a paused/drained guest-frame boundary. No import API is
// exposed: arbitrary core-state deserialization still needs separate hardening.
API unsigned sfc_state_size() {
  pending_state.clear(); error.clear();
  if(!loaded || !state_boundary || pcm_frames) { error = "Save requires a valid, drained frame boundary"; return 0; }
  try {
    pending_state = mesen_probe_capture_state();
    if(pending_state.empty() || pending_state.size() > max_state_bytes) {
      pending_state.clear(); error = "Invalid core state size"; return 0;
    }
    return static_cast<unsigned>(pending_state.size());
  } catch(const std::exception& e) { error = e.what(); return 0; }
}
API unsigned sfc_save_state(uint8_t* out, unsigned capacity) {
  if(!out || pending_state.empty() || capacity < pending_state.size()) {
    error = "State output buffer too small or snapshot unavailable"; return 0;
  }
  memcpy(out, pending_state.data(), pending_state.size());
  error.clear();
  return static_cast<unsigned>(pending_state.size());
}

#ifdef MESEN_STATE_RESTORE
// One attempt per freshly loaded candidate. Never deserialize into the retained
// original, and never resume a failed candidate. The host owns envelope checks
// and worker termination: this function cannot interrupt a hung guest frame.
API int sfc_restore_candidate(const uint8_t* bytes, unsigned size) {
  if(!loaded || !fresh_candidate || !state_boundary || pcm_frames) {
    error = "Restore requires a fresh candidate"; return 0;
  }
  fresh_candidate = false; state_boundary = false; frame_ok = false;
  pending_state.clear(); rgba.clear(); width = height = 0; pcm_frames = 0;
  inputs[0] = inputs[1] = 0;
  if(!bytes || !size || size > max_state_bytes) {
    error = "Invalid candidate state length"; return 0;
  }
  try {
    if(!retro_unserialize(bytes, size)) {
      error = "Candidate state rejected; discard this machine"; return 0;
    }
    state_boundary = true; error.clear(); return 1;
  } catch(const std::exception& e) { error = e.what(); return 0; }
}
#endif
