/*
    OmniaFramework - A collection of useful functionality
    Copyright (C) 2026  OmniaX-Dev

    This file is part of OmniaFramework.

    OmniaFramework is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    OmniaFramework is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with OmniaFramework.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "PixelRenderer.hpp"
#include "../gui/Window.hpp"
#include "../../ostd/io/Memory.hpp"
#include <SDL3/SDL_render.h>

namespace ogfx
{
	void PixelRenderer::TextRenderer::initialize(void)
	{
		for (char c = ' '; c <= '~'; c++)
			characterMap[c] = getCharacterIndex(c);
	}

	bool PixelRenderer::TextRenderer::drawString(const String& str, u32 column, u32 row, u32* screenPixels, i32 screenWidth, i32 screenHeight, u32* fontPixels, const Color& color, const Color& background)
	{
		String se(str);
		if (se == "") return false;
		if (row >= CONSOLE_CHARS_V) return false;
		if (column >= CONSOLE_CHARS_H) return false;
		if (column + str.len() > CONSOLE_CHARS_H) return false;
		i32 x = column * FONT_CHAR_W;
		i32 y = row * FONT_CHAR_H;
		for (auto& c : str)
		{
			drawCharacter((u8*)screenPixels, screenWidth, screenHeight, (u8*)fontPixels, x, y, c, color, background);
			x += FONT_CHAR_W;
		}
		s_cursor_pos_x = x;
		if (s_cursor_pos_x >= CONSOLE_CHARS_H)
			s_cursor_pos_x = 0;
		return true;
	}

	i32 PixelRenderer::TextRenderer::getCharacterIndex(char c)
	{
		using namespace ostd;

		i32 charIndex = (i32)c - 32;
		IPoint charCoords = CONVERT_1D_2D(charIndex, FONT_H_CHARS);
		charCoords.x *= FONT_CHAR_W * 4;
		charCoords.y *= FONT_CHAR_H;
		charIndex = CONVERT_2D_1D(charCoords.x, charCoords.y, (FONT_H_CHARS * FONT_CHAR_W * 4));

		return charIndex;
	}

	Color PixelRenderer::TextRenderer::applyTint(const Color& baseColor, const Color& tintColor)
	{
		auto nBase = baseColor.getNormalizedColor();
		auto nTint = tintColor.getNormalizedColor();

		f32 r = nBase.r * nTint.r;
		f32 g = nBase.r * nTint.g;
		f32 b = nBase.r * nTint.b;

		Color::FloatCol nTinted(r, g, b, 1.0f);

		return Color(nTinted);
	}

	void PixelRenderer::TextRenderer::drawCharacter(u8* screenPixels, i32 screenWidth, i32 screenHeight, u8* fontPixels, i32 x, i32 y, char c, const Color& color, const Color& background)
	{
		using namespace ostd;
		i32 charIndex = characterMap[c];
		IPoint charCoords = CONVERT_1D_2D(charIndex, (FONT_CHAR_W * FONT_H_CHARS * 4));

		i32 screenx = x * 4, screeny = y;

		Color tintedColor;

		bool applyBackground = false;
		for (i32 y = charCoords.y; y < charCoords.y + (FONT_CHAR_H); y += 1)
		{
			for (i32 x = charCoords.x; x < charCoords.x + (FONT_CHAR_W * 4); x += 4)
			{
				i32 index = CONVERT_2D_1D(x, y, (FONT_CHAR_W * FONT_H_CHARS * 4));
				i32 screenIndex = CONVERT_2D_1D(screenx, screeny, (screenWidth * 4));
				screenx += 4;
				if (fontPixels[index] == 0x00 && fontPixels[index + 1] == 0x00 && fontPixels[index + 2] == 0x00)
				{
					if (background.a == 0)
						continue;
					applyBackground = true;
				}
				if (applyBackground)
				{
					screenPixels[screenIndex + 0] = background.b;
					screenPixels[screenIndex + 1] = background.g;
					screenPixels[screenIndex + 2] = background.r;
					screenPixels[screenIndex + 3] = 255;
					applyBackground = false;
					continue;
				}
				tintedColor = applyTint({ fontPixels[index], fontPixels[index + 1], fontPixels[index + 2], 255 }, color);
				screenPixels[screenIndex + 0] = tintedColor.b;
				screenPixels[screenIndex + 1] = tintedColor.g;
				screenPixels[screenIndex + 2] = tintedColor.r;
				screenPixels[screenIndex + 3] = fontPixels[index + 3];
			}
			screeny += 1;
			screenx = x * 4;
		}
	}




	PixelRenderer::~PixelRenderer(void)
	{
		if (isInvalid()) return;
		ostd::Memory::destroyArray(m_pixels);
		SDL_DestroyTexture(m_texture);
	}

	void PixelRenderer::initialize(WindowCore& parent)
	{
		if (isValid()) return; //TODO: Error
		if (!parent.isValid() || !parent.isInitialized())
			return; //TODO: Error
		m_parent = &parent;
		m_pixels = ostd::Memory::createArray<u32>(parent.getWindowWidth() * parent.getWindowHeight());
		m_texture = SDL_CreateTexture(parent.getSDLRenderer(), SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, parent.getWindowWidth(), parent.getWindowHeight());
		m_windowWidth = parent.getWindowWidth();
		m_windowHeight = parent.getWindowHeight();
		setTypeName("ogfx::PixelRenderer");
		enableSignals();
		connectSignal(ostd::BuiltinSignals::WindowResized);
		validate();
	}

	void PixelRenderer::handleSignal(ostd::Signal& signal)
	{
		if (isInvalid()) return;
		if (signal.ID == ostd::BuiltinSignals::WindowResized)
		{
			m_pixels = ostd::Memory::resizeArray<u32>(m_pixels, m_parent->getWindowWidth() * m_parent->getWindowHeight());
			SDL_DestroyTexture(m_texture);
			m_texture = SDL_CreateTexture(m_parent->getSDLRenderer(), SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, m_parent->getWindowWidth(), m_parent->getWindowHeight());
			m_windowWidth = m_parent->getWindowWidth();
			m_windowHeight = m_parent->getWindowHeight();
			updateBuffer();
			displayBuffer();
		}
	}

	void PixelRenderer::updateBuffer(void)
	{
		if (isInvalid()) return;
		SDL_UpdateTexture(m_texture, NULL, m_pixels, m_windowWidth * 4);
	}

	void PixelRenderer::displayBuffer(void)
	{
		if (isInvalid()) return;
		SDL_FRect rect { 0, 0, cast<f32>(m_windowWidth), cast<f32>(m_windowHeight) };
		SDL_RenderTexture(m_parent->getSDLRenderer(), m_texture, NULL, &rect);
	}

	void PixelRenderer::clear(const Color& color)
	{
		if (isInvalid()) return;
		for (i32 y = 0; y < m_windowHeight; y++)
		{
			for (u32 x = 0; x < m_windowWidth; x++)
			{
				i32 index = CONVERT_2D_1D(x, y, m_windowWidth);
				m_pixels[index] = color.asInteger(Color::eColorFormat::ARGB);
			}
		}
	}
}
