#include "app.h"
#include "lua/game_engine.h"
#include "lua/media_manager.h"
#include "lua/persistence.h"
#include "lua/wherigo.h"
#include "lua/wherigo_completion.h"
#include "ui/start_screen.h"

#ifdef __APPLE__
#include <objc/message.h>
#include <objc/objc.h>
#endif

#include <cartridge/parser.h>
#include <wx/dir.h>
#include <wx/filename.h>
#include <wx/stdpaths.h>

extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}

bool cApp::OnInit()
{
  // Initialize image handlers for JPEG, PNG, GIF, etc.
  wxInitAllImageHandlers();

  // Show start frame
  auto *startFrame = new cStartScreen();
  startFrame->Show(true);

#ifdef __APPLE__
  // Force the app to process itself into the foreground (Raise() alone is not enough on macOS)
  using MsgFn = id(*)(id, SEL);
  using ActivateFn = void(*)(id, SEL, BOOL);
  id nsApp = reinterpret_cast<MsgFn>(objc_msgSend)(
      reinterpret_cast<id>(objc_getClass("NSApplication")),
      sel_registerName("sharedApplication"));
  reinterpret_cast<ActivateFn>(objc_msgSend)(
      nsApp, sel_registerName("activateIgnoringOtherApps:"), YES);
#else
  startFrame->Raise();
#endif

  return true;
}

bool cApp::loadCartridge(const std::string &filePath) {
  // Unload previous cartridge if any
  unloadCartridge();

  m_cartridgePath = filePath;

  // Parse cartridge
  m_cartridge = cartridge::parseFile(filePath);
  if (!m_cartridge) {
    wxLogError("Failed to parse cartridge: %s", filePath);
    return false;
  }

  return initLuaState();
}

bool cApp::initLuaState() {
  const auto luac = m_cartridge->luac();
  if (!luac) {
    wxLogError("No Lua bytecode in cartridge");
    return false;
  }

  m_luaState = luaL_newstate();
  if (!m_luaState) {
    wxLogError("Failed to create Lua state");
    return false;
  }

  luaL_openlibs(m_luaState);

  // Reset media counter before loading cartridge
  wherigo::resetMediaCounter();

  // Register Wherigo module
  wherigo::luaopen_Wherigo(m_luaState);
  lua_pop(m_luaState, 1);

  const auto &bytecode = luac->getData();
  const int result = luaL_loadbuffer(m_luaState,
                               reinterpret_cast<const char *>(bytecode.data()),
                               bytecode.size(),
                               "cartridge");

  if (result != 0) {
    const char *err = lua_tostring(m_luaState, -1);
    wxLogError("Lua load error: %s", err);
    lua_close(m_luaState);
    m_luaState = nullptr;
    return false;
  }

  // Execute the loaded chunk
  if (lua_pcall(m_luaState, 0, 0, 0) != 0) {
    const char *err = lua_tostring(m_luaState, -1);
    wxLogError("Lua exec error: %s", err);
    lua_close(m_luaState);
    m_luaState = nullptr;
    return false;
  }

  // Find the cartridge object by searching for ClassName = "ZCartridge"
  m_cartridgeRef = LUA_NOREF;
  lua_getglobal(m_luaState, "_G");
  lua_pushnil(m_luaState);
  while (lua_next(m_luaState, -2) != 0) {
    if (lua_istable(m_luaState, -1)) {
      lua_getfield(m_luaState, -1, "ClassName");
      if (lua_isstring(m_luaState, -1)) {
        if (const char *className = lua_tostring(m_luaState, -1);
            strcmp(className, "ZCartridge") == 0) {
          lua_pop(m_luaState, 1); // pop ClassName
          m_cartridgeRef = luaL_ref(m_luaState, LUA_REGISTRYINDEX); // store reference, pops table
          lua_pop(m_luaState, 1); // pop key
          break;
        }
      }
      lua_pop(m_luaState, 1); // pop ClassName
    }
    lua_pop(m_luaState, 1); // pop value
  }
  lua_pop(m_luaState, 1); // pop _G

  // Initialize media manager
  wherigo::MediaManager::getInstance().init(m_luaState, m_cartridge.get());

  // Set Player.CompletionCode from the cartridge binary so Lua can access it
  lua_getglobal(m_luaState, "Player");
  if (lua_istable(m_luaState, -1)) {
    std::string cc = m_cartridge->completionCode();
    if (cc.length() > 15) cc = cc.substr(0, 15);
    lua_pushstring(m_luaState, cc.c_str());
    lua_setfield(m_luaState, -2, "CompletionCode");
  }
  lua_pop(m_luaState, 1);

  if (m_cartridgeRef == LUA_NOREF) {
    wxLogError("No ZCartridge object found!");
    return false;
  }

  // Initialize the game engine (but don't start yet)
  wherigo::GameEngine::getInstance().init(m_luaState, m_cartridgeRef);

  return true;
}

void cApp::startGame() const {
  if (!m_luaState || m_cartridgeRef == LUA_NOREF) {
    wxLogError("No cartridge loaded");
    return;
  }

  // Call OnStart callback
  lua_rawgeti(m_luaState, LUA_REGISTRYINDEX, m_cartridgeRef);
  lua_getfield(m_luaState, -1, "OnStart");
  if (lua_isfunction(m_luaState, -1)) {
    lua_pushvalue(m_luaState, -2); // push cartridge as self
    if (lua_pcall(m_luaState, 1, 0, 0) != 0) {
      const char *err = lua_tostring(m_luaState, -1);
      wxLogError("OnStart error: %s", err);
      lua_pop(m_luaState, 1);
    }
  } else {
    lua_pop(m_luaState, 1);
  }
  lua_pop(m_luaState, 1); // pop cartridge

  // Start the game engine
  wherigo::GameEngine::getInstance().start();
}

void cApp::unloadCartridge() {
  // Shutdown the game engine
  wherigo::GameEngine::getInstance().shutdown();

  if (m_cartridgeRef != LUA_NOREF && m_luaState) {
    luaL_unref(m_luaState, LUA_REGISTRYINDEX, m_cartridgeRef);
    m_cartridgeRef = LUA_NOREF;
  }

  if (m_luaState) {
    lua_close(m_luaState);
    m_luaState = nullptr;
  }

  m_cartridge.reset();
  m_cartridgePath.clear();
}

std::string cApp::getAutoSavePath() const {
  if (m_cartridge) {
    wxStandardPaths& stdPaths = wxStandardPaths::Get();
    wxString userDataDir = stdPaths.GetUserDataDir();

    // Create save directory if it doesn't exist
    if (!wxDir::Exists(userDataDir)) {
      wxFileName::Mkdir(userDataDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
    }

    // Generate save filename from cartridge name
    wxString cartName = wxString::FromUTF8(m_cartridge->cartridgeName());
    cartName.Replace(" ", "_");
    cartName.Replace("/", "_");

    wxString savePath = userDataDir + wxFileName::GetPathSeparator() + cartName + ".save";
    return savePath.ToStdString();
  }
  return "";
}

bool cApp::saveGameState(const std::string &saveFilePath) const {
  if (!m_luaState) {
    wxLogError("No Lua state to save");
    return false;
  }

  const std::string savePath = saveFilePath.empty() ? getAutoSavePath() : saveFilePath;
  if (savePath.empty()) {
    wxLogError("No save path available");
    return false;
  }

  const bool success = wherigo::LuaPersistence::saveState(m_luaState, savePath);
  if (success) {
    wxLogMessage("Game saved to: %s", savePath);
  }
  return success;
}

bool cApp::loadGameState(const std::string &saveFilePath) const {
  if (!m_luaState) {
    wxLogError("No Lua state to load into");
    return false;
  }

  const std::string loadPath = saveFilePath.empty() ? getAutoSavePath() : saveFilePath;
  if (loadPath.empty() || !wxFileExists(loadPath)) {
    wxLogError("Save file not found: %s", loadPath);
    return false;
  }

  const bool success = wherigo::LuaPersistence::loadState(m_luaState, loadPath);
  if (success) {
    wxLogMessage("Game loaded from: %s", loadPath);
    // Notify game engine of state change
    wherigo::GameEngine::getInstance().notifyStateChanged();
  }
  return success;
}

std::string cApp::getCompletionLogPath() const {
  if (m_cartridge && !m_cartridgePath.empty()) {
    // Use the same directory as the GWC file
    wxFileName gwcFile(m_cartridgePath);
    wxString directory = gwcFile.GetPath();

    // Generate completion log filename from cartridge name
    wxString cartName = wxString::FromUTF8(m_cartridge->cartridgeName());
    cartName.Replace(" ", "_");
    cartName.Replace("/", "_");

    wxString logPath = directory + wxFileName::GetPathSeparator() + cartName + ".gwl";
    return logPath.ToStdString();
  }
  return "";
}

bool cApp::generateCompletionLog(const std::string &logFilePath) const {
  if (!m_luaState) {
    wxLogError("No Lua state for completion log");
    return false;
  }

  const std::string logPath = logFilePath.empty() ? getCompletionLogPath() : logFilePath;
  if (logPath.empty()) {
    wxLogError("No completion log path available");
    return false;
  }

  const bool success = wherigo::WherigoCompletion::generateCompletionLog(m_luaState, logPath, "Player");
  if (success) {
    wxLogMessage("Completion log generated: %s", logPath);
  }
  return success;
}

int cApp::OnExit()
{
  unloadCartridge();
  return wxApp::OnExit();
}

