#include "utils/logger.h"
#include "filesystem/filemanager.h"
#include "render/texture2d.h"
#include "render/texturesmanager.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#include <glad/glad.h>

// STB Image loading code
void Texture2D::CreateFromFile(const char* filename)
{
	int width, height, channels;
	m_textureFileName = filename;

	File* file = g_fileManager->OpenFile(filename, FileAccess::Read);

	file->seek(SeekDir::End, 0);
	size_t imageSize = file->tell();
	file->seek(SeekDir::Begin, 0);

	uint8_t* fileData = (uint8_t*)malloc(imageSize);
	file->read(fileData, imageSize);

	g_fileManager->CloseFile(file);

	uint8_t* imageData = stbi_load_from_memory(fileData, int(imageSize), &width, &height, &channels, 0);

	if (imageData == NULL) {
		free(fileData);
		Msg("Texture loading error: %s (%s)", filename, stbi_failure_reason());
		assert(imageData);
	}

	CreateFromExistedData(imageData, width, height, channels);
	m_textureFileName = filename;

	free(fileData);

	stbi_image_free(imageData);
}

static const char* g_texFileExtensions[] = { ".png", ".jpeg", ".jpg", ".tga", ".bmp" };
const int kTexFileExtensionsSize = sizeof(g_texFileExtensions) / sizeof(g_texFileExtensions[0]);

TexturesManager* g_texturesManager = nullptr;

TexturesManager::TexturesManager()
{
	m_notex = nullptr;
}

TexturesManager::~TexturesManager()
{

}

void TexturesManager::Init()
{
	stbi_set_flip_vertically_on_load(true);

	m_notex = LoadTexture2D("content/textures/system/notex.png", true);
	if (!m_notex) {
		Logger::error("TexturesManager::Init: Failed to initialize system texture! 'system/notex.png' is not exist.");
	}
}

void TexturesManager::Shutdown()
{
	if (!m_textures.empty()) {
		Msg("--- unfreed textures ---");

		for (std::vector<Texture2D*>::iterator it = m_textures.begin(); it != m_textures.end(); ++it) {
			Msg("%s", (*it)->m_textureFileName.c_str());
			delete* it;
			*it = nullptr;
		}

		m_textures.clear();
	}
}

void TexturesManager::SetTexture(int slot, Texture2D* texture)
{
	glActiveTexture(GL_TEXTURE0 + slot);
	glBindTexture(GL_TEXTURE_2D, texture ? texture->GetHandle() : 0);
}

Texture2D* TexturesManager::CreateManual2D(const char* name, uint32_t width, uint32_t height, PixelFormat format, bool useAsRenderTarget)
{
	for (std::vector<Texture2D*>::iterator it = m_textures.begin(); it != m_textures.end(); ++it) {
		if ((*it)->m_textureFileName == name) {
			Logger::error("TexturesManager::CreateManual2D: texture %s is already created!", name);
		}
	}

	// allocate
	Texture2D* texture = Texture2D::Create();
	texture->CreateRaw(nullptr, width, height, format);
	texture->m_textureFileName = name;

	if (useAsRenderTarget)
		Msg("Created rt texture [%s]", name);

	return texture;
}

bool IsSupportedExtension(const char* filename)
{
	std::string ext = fs::getFileExtension(filename);

	for (int i = 0; i < kTexFileExtensionsSize; i++) {
		if (ext == g_texFileExtensions[i]) {
			return true;
		}
	}

	return false;
}

Texture2D* TexturesManager::LoadTexture2D(const char* texturename, bool useMipmaps /*= false*/)
{
	int texturesNbr = m_textures.size();
	for (int i = 0; i < texturesNbr; i++) {
		if (m_textures[i]->m_textureFileName == texturename)
			return m_textures[i];
	}

	if (strcmp(texturename, "$white$") == 0) {
		Texture2D* tex = Texture2D::Create();
		tex->m_textureFileName = "$white$";
		tex->CreateWhiteTexture(16, 16, 3);
		m_textures.push_back(tex);
		return tex;
	}
	if (strcmp(texturename, "$black$") == 0) {
		Texture2D* tex = Texture2D::Create();
		tex->m_textureFileName = "$black$";
		tex->CreateBlackTexture(16, 16, 3);
		m_textures.push_back(tex);
		return tex;
	}
	if (strcmp(texturename, "$gray$") == 0) {
		Texture2D* tex = Texture2D::Create();
		tex->m_textureFileName = "$gray$";
		tex->CreateGrayTexture(16, 16, 3);
		m_textures.push_back(tex);
		return tex;
	}
	if (strcmp(texturename, "$gray_console$") == 0) {
		Texture2D* tex = Texture2D::Create();
		tex->CreateTexture_Generator(16, 16, 3, 32);
		m_textures.push_back(tex);
		return tex;
	}

	if (strlen(texturename) <= 0) {
		return m_notex;
	}

	std::string texnamebuf;

	// find texture from disk
	for (int i = 0; i < kTexFileExtensionsSize; i++)
	{
		std::string textureFilename = fs::getFileNameWithoutExtension(texturename);
		textureFilename += g_texFileExtensions[i];

		if (g_fileManager->FileExist(textureFilename.c_str()))
		{
			texnamebuf = textureFilename;
			break;
		}
	}

	if (!texnamebuf.empty()) {
		Texture2D* texture = Texture2D::Create();
		texture->CreateFromFile(texnamebuf.c_str());

		if (useMipmaps)
			texture->GenerateMipmaps();

		Msg("loaded %s", fs::getFilenameWithoutPathAndExtension(texturename).c_str());
		m_textures.push_back(texture);
		return texture;
	}
	else if (texnamebuf.empty() && m_notex) {
		Msg("not found %s", fs::getFilenameWithoutPathAndExtension(texturename).c_str());
		return m_notex;
	}

	return nullptr;
}
