#pragma once

#include <asset_parser/assets/text.hpp>
#include <asset_parser/assets/texture.hpp>
#include <filesystem>
#include <logger/logger.hpp>
#include <string_view>
#include <unordered_set>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

namespace lt {

class Loader
{
public:
	[[nodiscard]] virtual auto get_name() const -> std::string_view = 0;

	Loader() = default;

	Loader(Loader &&) = default;

	Loader(const Loader &) = delete;

	auto operator=(Loader &&) -> Loader & = default;

	auto operator=(const Loader &) -> Loader & = delete;

	virtual ~Loader() = default;

private:
};

class TextureLoader: public Loader
{
public:
	TextureLoader() = default;

	[[nodiscard]] virtual auto load(std::filesystem::path file_path) const
	    -> Assets::TextureAsset::PackageData
	    = 0;
};


class StbLoader: public TextureLoader
{
public:
	StbLoader() = default;

	void load(std::filesystem::path path);

	[[nodiscard]] static auto get_supported_extensions() -> std::unordered_set<std::string_view>
	{
		return { ".png" };
	}

	[[nodiscard]] auto get_name() const -> std::string_view override
	{
		return "StbLoader";
	}

	[[nodiscard]] auto load(std::filesystem::path file_path) const
	    -> Assets::TextureAsset::PackageData override
	{
		auto width = int {};
		auto height = int {};
		auto channels = int {};

		auto *pixels = stbi_load(file_path.string().c_str(), &width, &height, &channels, 4);
		if (!pixels)
		{
			throw std::runtime_error {
				std::format("Failed to load image file at: {} using stbi_load", file_path.string()),
			};
		}

		const auto metadata = Assets::Asset::Metadata {
			.type = Assets::Asset::Type::Texture,
		};

		const auto texture_metadata = Assets::TextureAsset::Metadata {
            .format = Assets::TextureAsset::Format::RGBA8,
            .num_components = static_cast<uint32_t>(channels),
            .pixel_size = {
                static_cast<uint32_t>(width),
                static_cast<uint32_t>(height),
                {},
            },
        };

		auto pixels_blob = Assets::Blob {};
		pixels_blob.resize(static_cast<size_t>(width) * height * channels);

		// TODO(Light): figure out if it's possible to directly populate a blob with stbi functions
		memcpy(pixels_blob.data(), pixels, pixels_blob.size());
		stbi_image_free(pixels);

		return Assets::TextureAsset::PackageData {
			.metadata = metadata,
			.texture_metadata = texture_metadata,
			.pixels = std::move(pixels_blob),
		};
	}
};

class TextureLoaderFactory
{
public:
	static auto create(std::string_view file_extension) -> std::unique_ptr<TextureLoader>
	{
		if (StbLoader::get_supported_extensions().contains(file_extension))
		{
			return std::make_unique<StbLoader>();
		}

		return {};
	}
};

class TextLoader: Loader
{
public:
	[[nodiscard]] static auto get_supported_extensions() -> std::unordered_set<std::string_view>
	{
		return { ".glsl", ".txt", ".hlsl" };
	}

	[[nodiscard]] auto get_name() const -> std::string_view override
	{
		return "TextLoader";
	}

	[[nodiscard]] auto load(const std::filesystem::path &file_path) const
	    -> Assets::TextAsset::PackageData
	{
		auto stream = std::ifstream { file_path, std::ios::binary };
		if (!stream.good())
		{
			throw std::runtime_error {
				std::format(
				    "Failed to open ifstream for text loading of file: {}",
				    file_path.string()
				),
			};
		}

		auto file_size = std::filesystem::file_size(file_path);

		auto text_blob = Assets::Blob(file_size);

		stream.read((char *)(text_blob.data()), static_cast<long>(file_size)); // NOLINT

		const auto metadata = Assets::Asset::Metadata {
			.type = Assets::Asset::Type::Text,
		};

		const auto text_metadata = Assets::TextAsset::Metadata {
			.lines = {},
		};

		return Assets::TextAsset::PackageData {
			.metadata = metadata,
			.text_metadata = {},
			.text_blob = std::move(text_blob),
		};
	}
};

class TextLoaderFactory
{
public:
	static auto create(std::string_view file_extension) -> std::unique_ptr<TextLoader>
	{
		if (TextLoader::get_supported_extensions().contains(file_extension))
		{
			return std::make_unique<TextLoader>();
		}

		return {};
	}
};

} // namespace lt
