/*
 * Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#pragma once

#include <ox/fs/fs.hpp>
#include <ox/std/buffer.hpp>

#include "assetmanager.hpp"
#include "event.hpp"
#include "input.hpp"

namespace nostalgia::geo {
class Size;
}

namespace nostalgia::core {

class BaseClipboardObject {
	public:
		virtual ~BaseClipboardObject() = default;

		[[nodiscard]]
		virtual ox::String typeId() const noexcept = 0;

		[[nodiscard]]
		constexpr auto typeMatch(auto name, auto version) const noexcept {
			auto inId = ox::sfmt("{};{}", name, version);
			return typeId() == inId;
		}
};

template<typename T>
class ClipboardObject: public BaseClipboardObject {
	[[nodiscard]]
	ox::String typeId() const noexcept final {
		return ox::sfmt("{};{}", T::TypeName, T::TypeVersion);
	}
};

class Drawer;

struct BgCbbData {
	unsigned bpp = 4;
};

// User Input Output
class Context {
	friend constexpr void setApplicationData(Context *ctx, void *applicationData) noexcept;
	template<typename T>
	friend constexpr T *applicationData(Context *ctx) noexcept;
	friend constexpr void setConstantRefresh(Context *ctx, bool) noexcept;
	friend bool bgStatus(Context *ctx, unsigned bg) noexcept;
	friend bool buttonDown(Context *ctx, Key) noexcept;
	friend geo::Size getScreenSize(Context *ctx) noexcept;
	friend int getScreenHeight(Context *ctx) noexcept;
	friend int getScreenWidth(Context *ctx) noexcept;
	friend ox::Error initGfx(Context *ctx) noexcept;
	friend ox::Error loadBgTileSheet(Context *ctx,
	                                 unsigned cbb,
	                                 const ox::FileAddress &tilesheetPath,
	                                 const ox::FileAddress &palettePath) noexcept;
	friend ox::Error run(Context *ctx) noexcept;
	friend void shutdown(Context *ctx) noexcept;
	friend ox::Result<ox::UniquePtr<Context>> init(ox::UniquePtr<ox::FileSystem> fs, const char *appName) noexcept;
	friend ox::String getClipboardText(Context *ctx) noexcept;
	friend uint64_t ticksMs(Context *ctx) noexcept;
	friend uint8_t bgStatus(Context *ctx) noexcept;
	friend void clearTileLayer(Context *ctx, unsigned bgIdx) noexcept;
	friend void draw(Context *ctx) noexcept;
	friend void focusWindow(Context *ctx) noexcept;
	friend void setBgCbb(Context *ctx, unsigned bgIdx, unsigned cbb) noexcept;
	friend void setBgStatus(Context *ctx, uint32_t status) noexcept;
	friend void setBgStatus(Context *ctx, unsigned bg, bool status) noexcept;
	friend void setClipboardText(Context *ctx, const ox::String &text) noexcept;
	friend void setUpdateHandler(Context *ctx, UpdateHandler h) noexcept;
	friend constexpr void setKeyEventHandler(Context *ctx, KeyEventHandler h) noexcept;
	friend constexpr KeyEventHandler keyEventHandler(Context *ctx) noexcept;
	friend void setTile(Context *ctx, unsigned bgIdx, int column, int row, uint8_t tile) noexcept;
	friend void setWindowTitle(Context *ctx, const char *title) noexcept;

	public:
		ox::UniquePtr<ox::FileSystem> rom;
		ox::Vector<Drawer*, 5> drawers;
		const char *appName = "Nostalgia";

#ifndef OX_BARE_METAL
		AssetManager assetManager;
		int uninterruptedRefreshes = 3;
		ox::UniquePtr<BaseClipboardObject> clipboard;
#else
		bool running = true;
#endif
	protected:
#ifndef OX_BARE_METAL
		// sets screen refresh to constant instead of only on event
		bool constantRefresh = true;
#endif
		KeyEventHandler m_keyEventHandler = nullptr;
		void *m_customData = nullptr;

	private:
		void *m_windowerData = nullptr;
		void *m_rendererData = nullptr;

	public:
		Context() noexcept = default;

		Context(Context &other) noexcept = delete;
		Context(const Context &other) noexcept = delete;
		Context(const Context &&other) noexcept = delete;

		template<typename T>
		[[nodiscard]]
		constexpr T *windowerData() noexcept {
			return static_cast<T*>(m_windowerData);
		}

	protected:
		constexpr void setWindowerData(void *windowerData) noexcept {
			m_windowerData = windowerData;
		}

		constexpr void setRendererData(void *rendererData) noexcept {
			m_rendererData = rendererData;
		}

		template<typename T>
		[[nodiscard]]
		constexpr T *rendererData() noexcept {
			return static_cast<T*>(m_rendererData);
		}

};

constexpr void setApplicationData(Context *ctx, void *applicationData) noexcept {
	ctx->m_customData = applicationData;
}

template<typename T>
[[nodiscard]]
constexpr T *applicationData(Context *ctx) noexcept {
	return static_cast<T*>(ctx->m_customData);
}

constexpr void setKeyEventHandler(Context *ctx, KeyEventHandler h) noexcept {
	ctx->m_keyEventHandler = h;
}

constexpr KeyEventHandler keyEventHandler(Context *ctx) noexcept {
	return ctx->m_keyEventHandler;
}

constexpr void setConstantRefresh([[maybe_unused]] Context *ctx, [[maybe_unused]] bool r) noexcept {
#ifndef OX_BARE_METAL
	ctx->constantRefresh = r;
#endif
}

}

