#ifndef LIBRETRO
#include "types.h"
#include "emulator.h"
#include "hw/mem/addrspace.h"
#include "cfg/cfg.h"
#include "cfg/option.h"
#include "log/LogManager.h"
#include "ui/gui.h"
#include "oslib/oslib.h"
#include "oslib/directory.h"
#include "oslib/storage.h"
#include "debug/gdb_server.h"
#include "archive/rzip.h"
#include "ui/mainui.h"
#include "input/gamepad_device.h"
#include "lua/lua.h"
#include "stdclass.h"
#include "serialize.h"
#include "oslib/i18n.h"
#include "input/maplelink.h"
#include <time.h>
#ifdef TARGET_UWP
#include <winrt/Windows.System.h>
#include <winrt/Windows.Foundation.h>
#endif

static std::string lastStateFile;
static time_t lastStateTime;

#if defined(__ANDROID__)
    #include <android/api-level.h>
    // fmemopen was added in Marshmallow (API 23)
    #if __ANDROID_API__ >= 23
        #define HAS_FMEMOPEN
    #endif
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__GLIBC__)
    // Standard POSIX platforms usually have fmemopen
    #define HAS_FMEMOPEN
#endif

#ifdef HAS_FMEMOPEN
const u32 QUICKSAVE_DEFAULT_SIZE = 32 * 1024 * 1024; // 32 MB
static u8 quicksave_buf[QUICKSAVE_DEFAULT_SIZE] = {0};
#endif

struct SavestateHeader
{
	void init()
	{
		memcpy(magic, MAGIC, sizeof(magic));
		creationDate = time(nullptr);
		version = Deserializer::Current;
		pngSize = 0;
	}

	bool isValid() const {
		return !memcmp(magic, MAGIC, sizeof(magic));
	}

	char magic[8];
	u64 creationDate;
	u32 version;
	u32 pngSize;
	// png data

	static constexpr const char *MAGIC = "FLYSAVE1";
};

int flycast_init(int argc, char* argv[])
{
#if defined(TEST_AUTOMATION)
	setbuf(stdout, 0);
	setbuf(stderr, 0);
	settings.aica.muteAudio = true;
#endif
	try {
		if (!addrspace::reserve())
		{
			ERROR_LOG(VMEM, "Failed to alloc mem");
			return -1;
		}
		config::parseCommandLine(argc, argv);
		if (config::loadInt("naomi", "BoardId") != 0)
		{
			settings.naomi.multiboard = true;
			settings.naomi.slave = true;
		}
		settings.naomi.drivingSimSlave = config::loadInt("naomi", "DrivingSimSlave");

		config::Settings::instance().reset();
		LogManager::Shutdown();
		if (!config::open())
		{
			LogManager::Init();
			NOTICE_LOG(BOOT, "Config directory is not set. Starting onboarding");
			gui_open_onboarding();
		}
		else
		{
			LogManager::Init();
			config::Settings::instance().load(false);
		}
		i18n::reloadLanguage();
		gui_init();
		os_CreateWindow();
		os_SetupInput();

		if(config::GDB)
			debugger::init(config::GDBPort);
		lua::init();

		if(config::ProfilerEnabled)
			LogManager::GetInstance()->SetEnable(LogTypes::PROFILER, true);

		return 0;
	} catch (const std::exception& e) {
		ERROR_LOG(BOOT, "flycast_init failed: %s", e.what());
		return 1;
	} catch (...) {
		ERROR_LOG(BOOT, "flycast_init: unknown exception");
		return 1;
	}
}

#ifndef __ANDROID__
void dc_exit()
{
	try {
		emu.unloadGame();
	} catch (...) { }
	mainui_stop();

#ifdef TARGET_UWP
	extern std::string launchOnExitUri;
	if (!launchOnExitUri.empty())
	{
		INFO_LOG(BOOT, "Launching exit URI: %s", launchOnExitUri.c_str());
		try {
			using namespace winrt::Windows::System;
			using namespace winrt::Windows::Foundation;

			auto wUri = winrt::to_hstring(launchOnExitUri);
			Uri uri(wUri);
			auto asyncOp = Launcher::LaunchUriAsync(uri);
			asyncOp.get();
		} catch (...) {
			ERROR_LOG(BOOT, "Failed to launch exit URI");
		}
	}
#endif
}
#endif

void SaveSettings()
{
	config::Settings::instance().save();
	GamepadDevice::SaveMaplePorts();

#ifdef __ANDROID__
	void SaveAndroidSettings();
	SaveAndroidSettings();
#endif
	LogManager::GetInstance()->UpdateConfig();
}

void flycast_term()
{
	gui_cancel_load();
	lua::term();
	emu.term();
	os_DestroyWindow();
	gui_term();
	os_TermInput();
}

bool dc_savestateAllowed() {
	return !settings.content.path.empty() && !settings.network.online
			&& !settings.naomi.multiboard && !MapleLink::StorageEnabled();
}

void dc_savestate(int index, const u8 *pngData, u32 pngSize)
{
	if (!dc_savestateAllowed())
		return;

	lastStateFile.clear();

	Serializer ser;
	dc_serialize(ser);

	void *data = malloc(ser.size());
	if (data == nullptr)
	{
		WARN_LOG(SAVESTATE, "Failed to save state - could not malloc %d bytes", (int)ser.size());
		os_notify(i18n::T("Save state failed - memory full"), 5000);
    	return;
	}

	ser = Serializer(data, ser.size());
	dc_serialize(ser);

	hostfs::File *f = nullptr;
	std::string filename = "";
#ifdef HAS_FMEMOPEN
	if (index == -2)
	{
		// in-ram savestate
		filename = "RAM";
		f = new hostfs::StdFile(fmemopen(quicksave_buf, QUICKSAVE_DEFAULT_SIZE, "wb"));
	}
	else
#endif
	{
		// regular file savestate
		filename = hostfs::getSavestatePath(index, true);
		f = hostfs::storage().openFile(filename.c_str(), "wb");
	}
	
	if (f == nullptr)
	{
		WARN_LOG(SAVESTATE, "Failed to save state - could not open %s for writing", filename.c_str());
		os_notify(i18n::T("Cannot open save file"), 5000);
		free(data);
    	return;
	}

	RZipFile zipFile;
	SavestateHeader header;
	header.init();
	header.pngSize = pngSize;
	if (f->write(&header, sizeof(header), 1) != 1)
		goto fail;
	if (pngSize > 0 && f->write(pngData, 1, pngSize) != pngSize)
		goto fail;

#if 0
	// Uncompressed savestate
	f->write(data, 1, ser.size());
	delete f;
#else
	if (!zipFile.Open(f, true))
		goto fail;
	if (zipFile.Write(data, ser.size()) != ser.size())
		goto fail;
	zipFile.Close();
#endif

	free(data);
	NOTICE_LOG(SAVESTATE, "Saved state to %s size %d", filename.c_str(), (int)ser.size());
	os_notify(i18n::T("State saved"), 2000);
	return;

fail:
	WARN_LOG(SAVESTATE, "Failed to save state - error writing %s", filename.c_str());
	os_notify(i18n::T("Error saving state"), 5000);
	if (zipFile.rawFile() != nullptr)
		zipFile.Close();
	else
		delete f;
	free(data);
	// delete failed savestate?
}

void dc_loadstate(int index)
{
	if (!dc_savestateAllowed() || settings.raHardcoreMode)
		return;
	u32 total_size = 0;

	hostfs::File *f = nullptr;
	std::string filename = "";
#ifdef HAS_FMEMOPEN
	if (index == -2)
	{
		// in-ram savestate
		filename = "RAM";
		f = new hostfs::StdFile(fmemopen(quicksave_buf, QUICKSAVE_DEFAULT_SIZE, "rb"));
	}
	else
#endif
	{
		// regular file savestate
		filename = hostfs::getSavestatePath(index, false);
		f = hostfs::storage().openFile(filename, "rb");
	}
	
	if (f == nullptr)
	{
		WARN_LOG(SAVESTATE, "Failed to load state - could not open %s for reading", filename.c_str());
		os_notify(i18n::T("Save state not found"), 2000);
		return;
	}
	SavestateHeader header;
	if (f->read(&header, sizeof(header), 1) == 1)
	{
		if (!header.isValid())
			// seek to beginning of file if this isn't a valid header (legacy savestate)
			f->seek(0, SEEK_SET);
		else
			// skip png data
			f->seek(header.pngSize, SEEK_CUR);
	}
	else {
		// probably not a valid savestate but we'll fail later
		f->seek(0, SEEK_SET);
	}

	if (index == -1 && config::GGPOEnable)
	{
		long pos = f->tell();
		MD5Sum().add(f)
				.getDigest(settings.network.md5.savestate);
		f->seek(pos, SEEK_SET);
	}
	RZipFile zipFile;
	if (zipFile.Open(f, false)) {
		total_size = (u32)zipFile.Size();
	}
	else
	{
		long pos = f->tell();
		f->seek(0, SEEK_END);
		total_size = (u32)f->tell() - pos;
		f->seek(pos, SEEK_SET);
	}
	void *data = malloc(total_size);
	if (data == nullptr)
	{
		WARN_LOG(SAVESTATE, "Failed to load state - could not malloc %d bytes", total_size);
		os_notify(i18n::T("Failed to load state"), 5000, i18n::T("Not enough memory"));
		if (zipFile.rawFile() == nullptr)
			delete f;
		else
			zipFile.Close();
		return;
	}

	size_t read_size;
	if (zipFile.rawFile() != nullptr)
	{
		read_size = zipFile.Read(data, total_size);
		zipFile.Close();
	}
	else
	{
		read_size = f->read(data, 1, total_size);
		delete f;
	}
	if (read_size != total_size)
	{
		WARN_LOG(SAVESTATE, "Failed to load state - I/O error");
		os_notify(i18n::T("Failed to load state"), 5000, i18n::T("I/O error"));
		free(data);
		return;
	}

	try {
		Deserializer deser(data, total_size);
		emu.loadstate(deser);
	    NOTICE_LOG(SAVESTATE, "Loaded state ver %d from %s size %d", deser.version(), filename.c_str(), total_size);
		if (deser.size() != total_size)
			// Note: this isn't true for RA savestates
			WARN_LOG(SAVESTATE, "Savestate size %d but only %d bytes used", total_size, (int)deser.size());
	} catch (const Deserializer::Exception& e) {
		ERROR_LOG(SAVESTATE, "%s", e.what());
		os_notify(i18n::T("Failed to load state"), 5000, e.what());
	}

	free(data);
}

time_t dc_getStateCreationDate(int index)
{
	std::string filename = hostfs::getSavestatePath(index, false);
	if (filename != lastStateFile)
	{
		lastStateFile = filename;
		hostfs::File *f = hostfs::storage().openFile(filename, "rb");
		if (f == nullptr)
			lastStateTime = 0;
		else
		{
			SavestateHeader header;
			if (f->read(&header, sizeof(header), 1) != 1 || !header.isValid())
			{
				delete f;
				try {
					hostfs::FileInfo fileInfo = hostfs::storage().getFileInfo(filename);
					lastStateTime = fileInfo.updateTime;
				} catch (...) {
					lastStateTime = 0;
				}
			}
			else {
				delete f;
				lastStateTime = (time_t)header.creationDate;
			}
		}
	}
	return lastStateTime;
}

void dc_getStateScreenshot(int index, std::vector<u8>& pngData)
{
	pngData.clear();
	std::string filename = hostfs::getSavestatePath(index, false);
	hostfs::File *f = hostfs::storage().openFile(filename, "rb");
	if (f == nullptr)
		return;
	SavestateHeader header;
	if (f->read(&header, sizeof(header), 1) == 1 && header.isValid() && header.pngSize != 0)
	{
		pngData.resize(header.pngSize);
		if (f->read(pngData.data(), 1, pngData.size()) != pngData.size())
			pngData.clear();
	}
	delete f;
}

#endif
