// fbgl.h - v0.1.0 - public domain Levent Kaya 2024
// FBGL - Framebuffer Graphics Library
//
// This file provides both the interface and the implementation.
// To instantiate the implementation,
//      #define FBGL_IMPLEMENTATION
// in *ONE* source file, before #including this file.
//
//
// History:
//  - 0.1.0 First public release
//
// Status:
// 24/11/2024	texture rendering implemented
//
// Contributors:
//  @lvntky
//  @dario-loi
//
// LICENSE
//
//   See end of file for license information.

#ifndef __FBGL_H__
#define __FBGL_H__

//#define USE_16_BIT 1
#define FBGL_VALIDATE_PUT_PIXEL 1

#define VERSION "0.1.0"
#define NAME "FBGL"
#define DEFAULT_FB "/dev/fb1" // gt changed from fb0

#include <fcntl.h>
#include <linux/fb.h>
#include <math.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
/**
 * Structs
 */
#ifdef USE_16_BIT
#define PIXEL uint16_t
#define TO_PIXEL(c) (/*R:*/((((c>>16)&0xFF) * 32 / 256)<<11) | /*G:*/((((c>>8)&0xFF) * 64 / 256)<<5)  | /*B:*/ (((c)&0xFF)) * 32 / 256 )
#else
#define PIXEL uint32_t
#define TO_PIXEL(c) (c)
#endif

typedef struct fbgl {
	int32_t width;
	int32_t height;
	int32_t fd;
	uint32_t screen_size;
	PIXEL *pixels;
	struct fb_var_screeninfo vinfo; // Variable screen information
	struct fb_fix_screeninfo finfo; // Fixed screen information
} fbgl_t;

typedef struct fbgl_window {
	int32_t x; // Top-left x-coordinate of the window
	int32_t y; // Top-left y-coordinate of the window
	uint32_t width; // Width of the window
	uint32_t height; // Height of the window
	fbgl_t *fb; // Pointer to the framebuffer context
} fbgl_window_t;

typedef struct fbgl_point {
	int32_t x;
	int32_t y;
} fbgl_point_t;

typedef struct fbgl_tga_texture {
	uint16_t width;
	uint16_t height;
	PIXEL *data;
} fbgl_tga_texture_t;

typedef struct fbgl_psf1_font {
	uint8_t magic[2]; // Magic number (0x36, 0x04 for PSF1)
	uint8_t mode; // Mode (0 = 256 glyphs, 1 = 512 glyphs)
	uint8_t char_height; // Character height in pixels
	uint8_t *glyphs; // Pointer to glyph data
	uint16_t glyph_count; // Number of glyphs (calculated from mode)
	uint16_t char_width; // Character width in pixels (always 8 for PSF1)
} fbgl_psf1_font_t;

typedef enum fbgl_key {
	FBGL_KEY_NONE = 0,
	FBGL_KEY_UP,
	FBGL_KEY_DOWN,
	FBGL_KEY_LEFT,
	FBGL_KEY_RIGHT,
	FBGL_KEY_ESCAPE,
	FBGL_KEY_ENTER,
	FBGL_KEY_SPACE,
} fbgl_key_t;

typedef struct fbgl_keyboard_state {
	bool is_key_down;
	fbgl_key_t current_key;
	bool special_key_pressed;
} fbgl_keyboard_state_t;

/**
 * Key state function and variables
 *
 */
static struct termios orig_termios;
static fbgl_keyboard_state_t g_keyboard_state = { 0 };
static double HZ = 60.0;
#ifdef __cplusplus
extern "C" {
#endif

/**
 * General purpose methods
 */
char const *fbgl_name_info(void);
char const *fbgl_version_info(void);
float fbgl_get_fps(void);
void fbgl_set_fps(float hz);
void fbgl_wait_for_next_frame(void);

/*Create and destroy methods*/
int fbgl_init(const char *device, fbgl_t *fb);
void fbgl_destroy(fbgl_t *fb);

/**
 * Drawing functions
 */
void fbgl_clear(fbgl_t *fb);
void fbgl_put_pixel(int x, int y, uint32_t color, fbgl_t *fb);
void fbgl_draw_line(fbgl_point_t x, fbgl_point_t y, uint32_t color, fbgl_t *fb);

/**
 * Access framebuffer data methods
 */
PIXEL *fb_get_data(fbgl_t const *fb);
uint32_t fb_get_width(fbgl_t const *fb);
uint32_t fb_get_height(fbgl_t const *fb);

/**
 * Shapes
 */
void fbgl_draw_rectangle_outline(fbgl_point_t top_left,
				 fbgl_point_t bottom_right, uint32_t color,
				 fbgl_t *fb);
void fbgl_draw_rectangle_filled(fbgl_point_t top_left,
				fbgl_point_t bottom_right, uint32_t color,
				fbgl_t *fb);
void fbgl_draw_circle_outline(int x, int y, int radius, uint32_t color,
			      fbgl_t *fb);
void fbgl_draw_circle_filled(int x, int y, int radius, uint32_t color,
			     fbgl_t *fb);

/**
 * texture
 */
fbgl_tga_texture_t *fbgl_load_tga_texture(const char *path);
void fbgl_destroy_texture(fbgl_tga_texture_t *texture);
void fbgl_draw_texture(fbgl_t *fb, fbgl_tga_texture_t const *texture, int32_t x,
		       int32_t y);

/**
* Text
*/
fbgl_psf1_font_t *fbgl_load_psf1_font(const char *path);
void fbgl_destroy_psf1_font(fbgl_psf1_font_t *font);
void fbgl_render_psf1_text(fbgl_t *fb, fbgl_psf1_font_t *font, const char *text,
			   int x, int y, uint32_t color);
/**
 * Keyboard
 */
int fbgl_keyboard_init(void);
void fbgl_destroy_keyboard(void);
fbgl_key_t fbgl_get_key(void);
fbgl_key_t fbgl_poll_key(void);
bool fbgl_is_key_pressed(fbgl_key_t key);

/**
 * Color Utilities
 *
 */
#define FBGL_RGB(r, g, b) ((uint32_t)(((r) << 16) | ((g) << 8) | (b)))
#define FBGL_RGBA(r, g, b, a) \
	((uint32_t)(((a) << 24) | ((r) << 16) | ((g) << 8) | (b)))

// Surely these '* 255' should be '* 256'???  Shouldn't values be 0.0 <= rgb < 1.0 ?
  
#define FBGL_F32RGB_TO_U32(r, g, b)                                     \
	((uint32_t)(((uint8_t)(r * 255) << 16) | ((uint8_t)(g * 255) << 8) | \
		    (uint8_t)(b * 255)))
#define FBGL_F32RGBA_TO_U32(r, g, b, a) ((uint32_t)(((uint8_t)(a * 255) << 24) | ((uint8_t)(r * 255) << 16) | ((uint8_t)(g * 255) << 8) | (uint8_t)(b * 255))

// Inside functions
static void i_fbgl_die(const char *s);
static void i_fbgl_disable_raw_mode(void);
static void i_fbgl_enable_raw_mode(void);

static void i_fbgl_die(const char *s)
{
	perror(s);
	exit(1);
}

static void i_fbgl_enable_raw_mode(void)
{
	if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) {
		i_fbgl_die("tcgetattr");
	}
	atexit(i_fbgl_disable_raw_mode);
	struct termios raw = orig_termios;
	raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
	raw.c_oflag &= ~(OPOST);
	raw.c_cflag |= (CS8);
	raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
	raw.c_cc[VMIN] = 0;
	raw.c_cc[VTIME] = 1;
	if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
		i_fbgl_die("tcsetattr");
	}
}

static void i_fbgl_disable_raw_mode(void)
{
	if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
		i_fbgl_die("tcesetattr");
	}
}

#ifdef FBGL_IMPLEMENTATION

char const *fbgl_name_info(void)
{
	return NAME;
}

char const *fbgl_version_info(void)
{
	return VERSION;
}

int fbgl_init(const char *device, fbgl_t *fb)
{
	if (!fb) {
		fprintf(stderr, "Error: fbgl_t pointer is NULL.");
		return -1;
	}

	fb->fd = device == NULL ? open(DEFAULT_FB, O_RDWR) :
				  open(device, O_RDWR);
	if (fb->fd == -1) {
		perror("Error openning framebuffer device");
		return -1;
	}

	if (ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->finfo) == -1) {
		perror("Error: Reading fixed information.");
		close(fb->fd);
		return -1;
	}
	if (ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vinfo) == -1) {
		perror("Error reading variable information");
		close(fb->fd);
		return -1;
	}

	fb->width = fb->vinfo.xres;
	fb->height = fb->vinfo.yres;
	fb->screen_size = fb->finfo.smem_len;

	// Map framebuffer to memory
	fb->pixels = (PIXEL *)mmap(NULL, fb->screen_size,
				      PROT_READ | PROT_WRITE, MAP_SHARED,
				      fb->fd, 0);
	if (fb->pixels == MAP_FAILED) {
		perror("Error mapping framebuffer device to memory");
		close(fb->fd);
		return -1;
	}

        if ((8 * fb->screen_size)/(fb_get_width(fb)*fb_get_height(fb)) != 8*sizeof(PIXEL)) {
          fprintf(stderr, "This is not a %d-bit per pixel frame buffer.", 8*sizeof(PIXEL));
#ifdef USE_16_BIT
                fprintf(stderr, "  Comment out the line with #define USE_16_BIT 1 in fbgl.h or remove -DUSE_16_BIT from the cc command.\n");
#else
                fprintf(stderr, "  Insert a line with #define USE_16_BIT 1 in fbgl.h or add -DUSE_16_BIT to the cc command.\n");
#endif
		close(fb->fd);
                exit(1);
		return -1;
        }
        
	return 0;
}

void fbgl_destroy(fbgl_t *fb)
{
	if (!fb || fb->fd == -1) {
		fprintf(stderr,
			"Error: framebuffer not initialized or already destroyed.\n");
		return;
	}

	if (fb->pixels && fb->pixels != MAP_FAILED) {
		munmap(fb->pixels, fb->screen_size);
	}

	close(fb->fd);
	fb->fd = -1;
}

void fbgl_set_bg(fbgl_t *fb, uint32_t color)
{
#ifdef DEBUG
	if (!fb || fb->fd == -1) {
		fprintf(stderr, "Error: framebuffer not initialized.\n");
		return;
	}
#endif // DEBUG

	// Fill the entire framebuffer with the specified color. TOO SLOW!
        uint32_t px = TO_PIXEL(color);
	for (int32_t i = 0; i < fb->width * fb->height; i++) {
          fb->pixels[i] = px;
	}
}

void fbgl_clear(fbgl_t *fb)
{
#ifdef DEBUG
	if (!fb || fb->fd == -1) {
		fprintf(stderr, "Error: framebuffer not initialized.\n");
		return;
	}
#endif // DEBUG

	// Fill the entire framebuffer with the specified color. TOO SLOW!
        memset(fb->pixels, 0, fb->screen_size);
}

void fbgl_put_pixel(int x, int y, uint32_t color, fbgl_t *fb)
{
#ifdef FBGL_VALIDATE_PUT_PIXEL
	if (!fb || !fb->pixels) {
		fprintf(stderr, "Error: framebuffer not initialized.\n");
		return;
	}

	if (x < 0 || x >= fb->width || y < 0 || y >= fb->height) {
		return; // Ignore out-of-bound coordinates
	}
#endif // FBGL_VALIDATE_PUT_PIXEL

	const size_t index = y * fb->width + x;
	fb->pixels[index] = TO_PIXEL(color);
}

void fbgl_draw_line(fbgl_point_t x, fbgl_point_t y, uint32_t color,
		    fbgl_t *buffer)
{

  // testing to see if direction is relevant:
  fbgl_point_t tmp;
  if (x.x > y.x) { // try y next
    tmp = x; x = y; y = tmp;
  } else if (x.y > y.y) { // try y next
    tmp = x; x = y; y = tmp;
  }


  
	const int32_t dx = abs(y.x - x.x);
	const int32_t dy = abs(y.y - x.y);

	const int32_t sx = (x.x < y.x) ? 1 : -1;
	const int32_t sy = (x.y < y.y) ? 1 : -1;

	int32_t err = dx - dy;

	while (1) {
		// Set the pixel at the current position
		fbgl_put_pixel(x.x, x.y, color, buffer);

		// If we've reached the end point, break
		if (x.x >= y.x && x.y >= y.y)
			break;

		const int32_t e2 = 2 * err;

		if (e2 > -dy) {
			err -= dy;
			x.x += sx;
		}

		if (e2 < dx) {
			err += dx;
			x.y += sy;
		}
	}
}
void fbgl_draw_rectangle_outline(fbgl_point_t top_left,
				 fbgl_point_t bottom_right, uint32_t color,
				 fbgl_t *fb)
{
	// Top horizontal line
	for (int x = top_left.x; x < bottom_right.x; x++) {
		fbgl_put_pixel(x, top_left.y, color, fb);
	}

	// Bottom horizontal line
	for (int x = top_left.x; x < bottom_right.x; x++) {
		fbgl_put_pixel(x, bottom_right.y - 1, color, fb);
	}

	// Left vertical line
	for (int y = top_left.y; y < bottom_right.y; y++) {
		fbgl_put_pixel(top_left.x, y, color, fb);
	}

	// Right vertical line
	for (int y = top_left.y; y < bottom_right.y; y++) {
		fbgl_put_pixel(bottom_right.x - 1, y, color, fb);
	}
}

void fbgl_draw_rectangle_filled(fbgl_point_t top_left,
				fbgl_point_t bottom_right, uint32_t color,
				fbgl_t *fb)
{
	for (int32_t y = top_left.y; y < bottom_right.y; y++) {
		// Manually set each pixel in the row
		for (int32_t x = top_left.x; x < bottom_right.x; x++) {
			fbgl_put_pixel(x, y, color, fb);
		}
	}
}

void fbgl_draw_circle_outline(int x, int y, int radius, uint32_t color,
			      fbgl_t *fb)
{
	int f = 1 - radius;
	int ddF_x = 1;
	int ddF_y = -2 * radius;
	int xx = 0;
	int yy = radius;

	fbgl_put_pixel(x, y + radius, color, fb);
	fbgl_put_pixel(x, y - radius, color, fb);
	fbgl_put_pixel(x + radius, y, color, fb);
	fbgl_put_pixel(x - radius, y, color, fb);

	while (xx < yy) {
		if (f >= 0) {
			yy--;
			ddF_y += 2;
			f += ddF_y;
		}
		xx++;
		ddF_x += 2;
		f += ddF_x;

		fbgl_put_pixel(x + xx, y + yy, color, fb);
		fbgl_put_pixel(x - xx, y + yy, color, fb);
		fbgl_put_pixel(x + xx, y - yy, color, fb);
		fbgl_put_pixel(x - xx, y - yy, color, fb);
		fbgl_put_pixel(x + yy, y + xx, color, fb);
		fbgl_put_pixel(x - yy, y + xx, color, fb);
		fbgl_put_pixel(x + yy, y - xx, color, fb);
		fbgl_put_pixel(x - yy, y - xx, color, fb);
	}
}

void fbgl_draw_circle_filled(int x, int y, int radius, uint32_t color,
			     fbgl_t *fb)
{
	for (int yy = -radius; yy <= radius; ++yy) {
		int half_width = (int)sqrt(radius * radius - yy * yy);

		int row_start = x - half_width;
		int row_end = x + half_width;

		if (y + yy < 0 || y + yy >= fb->height)
			continue;
		if (row_start < 0)
			row_start = 0;
		if (row_end >= fb->width)
			row_end = fb->width - 1;

		int pixel_offset = (y + yy) * fb->width + row_start;
		int num_pixels = row_end - row_start + 1;

		PIXEL *row_start_ptr = fb->pixels + pixel_offset;
		for (int i = 0; i < num_pixels; ++i) {
			row_start_ptr[i] = color;
		}
	}
}

fbgl_tga_texture_t *fbgl_load_tga_texture(const char *path)
{
	FILE *file = fopen(path, "rb");
	if (!file) {
		perror("Unable to open texture file");
		return NULL;
	}

	// TGA header structure
	uint8_t header[18];
	if (fread(header, 1, sizeof(header), file) != sizeof(header)) {
		perror("Error reading TGA header");
		fclose(file);
		return NULL;
	}

	// Allocate texture structure
	fbgl_tga_texture_t *texture =
		(fbgl_tga_texture_t *)malloc(sizeof(fbgl_tga_texture_t));
	if (!texture) {
		perror("Failed to allocate texture structure");
		fclose(file);
		return NULL;
	}

	// Extract dimensions from header
	texture->width = header[12] | (header[13] << 8);
	texture->height = header[14] | (header[15] << 8);
	uint8_t bits_per_pixel = header[16];
	uint8_t image_descriptor = header[17];

	// Verify format support
	if (bits_per_pixel != 24 && bits_per_pixel != 32) {
		fprintf(stderr,
			"Unsupported TGA bit depth: %d (only 24 and 32-bit supported)\n",
			bits_per_pixel);
		free(texture);
		fclose(file);
		return NULL;
	}

	// Skip image ID field
	if (header[0]) {
		fseek(file, header[0], SEEK_CUR);
	}

	// Allocate pixel data
	size_t pixel_count = texture->width * texture->height;
	texture->data = (PIXEL *)malloc(pixel_count * sizeof(PIXEL));
	if (!texture->data) {
		perror("Failed to allocate pixel data");
		free(texture);
		fclose(file);
		return NULL;
	}

	// Read pixel data
	uint8_t *pixel_buffer = (uint8_t *)malloc(bits_per_pixel / 8);
	if (!pixel_buffer) {
		perror("Failed to allocate pixel buffer");
		free(texture->data);
		free(texture);
		fclose(file);
		return NULL;
	}

	// Determine if image is flipped (based on image descriptor)
	bool bottom_up = !(image_descriptor & 0x20);

	for (size_t i = 0; i < pixel_count; i++) {
		size_t pixel_index =
			bottom_up ?
				(texture->height - 1 - (i / texture->width)) *
						texture->width +
					(i % texture->width) :
				i;

		if (fread(pixel_buffer, 1, bits_per_pixel / 8, file) !=
		    bits_per_pixel / 8) {
			perror("Error reading pixel data");
			free(pixel_buffer);
			free(texture->data);
			free(texture);
			fclose(file);
			return NULL;
		}

		// Convert BGR(A) to RGBA
		uint32_t pixel = 0xFF000000; // Default alpha to 255
		pixel |= pixel_buffer[2] << 16; // R
		pixel |= pixel_buffer[1] << 8; // G
		pixel |= pixel_buffer[0]; // B
		if (bits_per_pixel == 32) {
			pixel = (pixel & 0x00FFFFFF) |
				(pixel_buffer[3] << 24); // A
		}

		texture->data[pixel_index] = pixel;
	}

	free(pixel_buffer);
	fclose(file);
	return texture;
}

void fbgl_destroy_texture(fbgl_tga_texture_t *texture)
{
	if (texture) {
		free(texture->data);
		free(texture);
	}
}

void fbgl_draw_texture(fbgl_t *fb, fbgl_tga_texture_t const *texture, int32_t x,
		       int32_t y)
{
	if (!fb || !texture || !texture->data) {
		return;
	}

	for (int ty = 0; ty < texture->height; ty++) {
		for (int tx = 0; tx < texture->width; tx++) {
			int screen_x = x + tx;
			int screen_y = y + ty;

			// Skip if outside screen bounds
			if (screen_x < 0 || screen_x >= fb->width ||
			    screen_y < 0 || screen_y >= fb->height) {
				continue;
			}

			PIXEL pixel =
				texture->data[ty * texture->width + tx];
			// Only draw if pixel is not fully transparent
			if ((pixel & 0xFF000000) != 0) {
				fbgl_put_pixel(screen_x, screen_y, pixel, fb);
			}
		}
	}
}

uint32_t fb_get_width(fbgl_t const *fb)
{
	return fb->width;
}

uint32_t fb_get_height(fbgl_t const *fb)
{
	return fb->height;
}

PIXEL *fb_get_data(fbgl_t const *fb)
{
	return fb->pixels;
}

static struct timespec previous_frame_time_fps = { 0, 0L };
float fbgl_get_fps(void)
{
	struct timespec current_time_fps;
	clock_gettime(CLOCK_MONOTONIC, &current_time_fps);

	if (previous_frame_time_fps.tv_sec == 0 &&
	    previous_frame_time_fps.tv_nsec == 0) {
		previous_frame_time_fps = current_time_fps;
		return 0.0f;
	}

	double time_diff =
		(current_time_fps.tv_sec - previous_frame_time_fps.tv_sec) +
		(current_time_fps.tv_nsec - previous_frame_time_fps.tv_nsec) / 1.0e9;

	previous_frame_time_fps = current_time_fps;

	if (time_diff > 0.0) {
		return 1.0 / time_diff;
	} else {
		return 0.0f; // Avoid division by zero
	}
}

void fbgl_set_fps(float hz) {
  HZ = (double)hz;
}

static struct timespec previous_frame_time_vsync = { 0, 0L };
void fbgl_wait_for_next_frame(void)
{
	struct timespec current_time_vsync;
	clock_gettime(/*CLOCK_MONOTONIC*/ CLOCK_REALTIME, &current_time_vsync);

	if (previous_frame_time_vsync.tv_sec == 0 &&
	    previous_frame_time_vsync.tv_nsec == 0) {
                // Initialise
		previous_frame_time_vsync = current_time_vsync;
		return;
	}

        for (;;) {
	  double time_diff =
             (double)(current_time_vsync.tv_sec - previous_frame_time_vsync.tv_sec) +
            ((double)(current_time_vsync.tv_nsec - previous_frame_time_vsync.tv_nsec) / (double)1.0e9);
          //fprintf(stderr, "%2.9f > %2.9f ?\r\n", time_diff, ((double)1.0e9/HZ) / 1.0e9);
          if (time_diff >= ((double)1.0e9/HZ) / 1.0e9) { // Has 1/60th of a second passed?
            previous_frame_time_vsync = current_time_vsync;
            return;
          } else {
            // We can either busy-wait *or* nano-sleep...
            struct timespec remaining_time;
            struct timespec requested_time = {
              0, (long) (((((double)1.0e9/HZ) / 1.0e9) - time_diff) * 1.0e9)
            };
            if (nanosleep(&requested_time, &remaining_time) == -1) {
              if (errno == EINTR) {
                // Nanosleep interrupted by a signal.
              } else {
                // nanosleep failed - fall back on busy-waiting
              }
            } else {
              // Nanosleep completed successfully.
            }
            clock_gettime(/*CLOCK_MONOTONIC*/ CLOCK_REALTIME, &current_time_vsync);
          }
        }
}

fbgl_psf1_font_t *fbgl_load_psf1_font(const char *path)
{
	FILE *file = fopen(path, "rb");
	if (!file) {
		perror("Failed to open font file");
		return NULL;
	}

	// Allocate memory for the font structure
	fbgl_psf1_font_t *font = malloc(sizeof(fbgl_psf1_font_t));
	if (!font) {
		perror("Failed to allocate memory for font");
		fclose(file);
		return NULL;
	}

	// Read the header (4 bytes)
	uint8_t header[4];
	if (fread(header, 1, sizeof(header), file) != sizeof(header)) {
		perror("Failed to read font header");
		free(font);
		fclose(file);
		return NULL;
	}

	// Verify magic number
	if (header[0] != 0x36 || header[1] != 0x04) {
		fprintf(stderr, "Invalid PSF1 magic number\n");
		free(font);
		fclose(file);
		return NULL;
	}

	// Populate the font structure
	font->magic[0] = header[0];
	font->magic[1] = header[1];
	font->mode = header[2];
	font->char_height = header[3];
	font->glyph_count = (font->mode & 0x01) ? 512 :
						  256; // Determine glyph count
	font->char_width = 8; // PSF1 glyphs are always 8 pixels wide

	// Allocate memory for glyphs
	size_t glyph_data_size = font->glyph_count * font->char_height;
	font->glyphs = malloc(glyph_data_size);
	if (!font->glyphs) {
		perror("Failed to allocate memory for glyphs");
		free(font);
		fclose(file);
		return NULL;
	}

	// Read glyph data
	if (fread(font->glyphs, 1, glyph_data_size, file) != glyph_data_size) {
		perror("Failed to read glyph data");
		free(font->glyphs);
		free(font);
		fclose(file);
		return NULL;
	}

	fclose(file);
	return font;
}

void fbgl_destroy_psf1_font(fbgl_psf1_font_t *font)
{
	if (font) {
		free(font->glyphs);
		free(font);
	}
}

void fbgl_render_psf1_text(fbgl_t *fb, fbgl_psf1_font_t *font, const char *text,
			   int x, int y, uint32_t color)
{
	if (!fb || !font || !text)
		return;

	int cursor_x = x;
	int cursor_y = y;

	for (const char *c = text; *c; c++) {
		uint8_t glyph_index = (uint8_t)*c;

		// Ensure glyph index is within range
		if (glyph_index >= font->glyph_count)
			glyph_index = 0; // Default to space or undefined glyph

		// Locate the glyph in the glyph table
		uint8_t *glyph = font->glyphs + glyph_index * font->char_height;

		// Render the glyph
		for (int row = 0; row < font->char_height; row++) {
			for (int col = 0; col < font->char_width; col++) {
				// Check if the bit is set in the glyph
				if (glyph[row] & (0x80 >> col)) {
					fbgl_put_pixel(cursor_x + col,
						       cursor_y + row, color,
						       fb);
				}
			}
		}

		// Move to the next character position
		cursor_x += font->char_width;
	}
}

int fbgl_keyboard_init(void)
{
	i_fbgl_enable_raw_mode();

	// Initialize keyboard state
	g_keyboard_state.is_key_down = false;
	g_keyboard_state.current_key = FBGL_KEY_NONE;
	g_keyboard_state.special_key_pressed = false;

	return 0;
}

void fbgl_destroy_keyboard(void)
{
	i_fbgl_disable_raw_mode();
}

fbgl_key_t fbgl_get_key(void)
{
	char c;
	ssize_t bytes_read = read(STDIN_FILENO, &c, 1);

	if (bytes_read <= 0) {
		return FBGL_KEY_NONE;
	}

	// Handle escape sequences for special keys
	if (c == 27) {
		char seq[3];
		if (read(STDIN_FILENO, &seq[0], 1) != 1)
			return FBGL_KEY_ESCAPE;
		if (read(STDIN_FILENO, &seq[1], 1) != 1)
			return FBGL_KEY_ESCAPE;

		if (seq[0] == '[') {
			switch (seq[1]) {
			case 'A':
				return FBGL_KEY_UP;
			case 'B':
				return FBGL_KEY_DOWN;
			case 'C':
				return FBGL_KEY_RIGHT;
			case 'D':
				return FBGL_KEY_LEFT;
			}
		}

		return FBGL_KEY_NONE;
	}

	// Handle direct key presses
	switch (c) {
	case 10: // Enter key
		return FBGL_KEY_ENTER;
	case 32: // Space key
		return FBGL_KEY_SPACE;
	case 27: // Escape key
		return FBGL_KEY_ESCAPE;
	}

        if ('a' <= c && c <= 'z') c = c - 'a' + 'A';
        if ('A' <= c && c <= 'Z') return c;

	return FBGL_KEY_NONE;
}

fbgl_key_t fbgl_poll_key(void)
{
	// Use select() for non-blocking input check
	fd_set read_fds;
	struct timeval timeout = {0, 0L};	// Set a very short timeout

	FD_ZERO(&read_fds);
	FD_SET(STDIN_FILENO, &read_fds);

	// Check if there's input available
	if (select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout) > 0) {
		return fbgl_get_key();
	}

	return FBGL_KEY_NONE;
}

bool fbgl_is_key_pressed(fbgl_key_t key)
{
        return fbgl_poll_key() == key;
}

#endif // FBGL_IMPLEMENTATION

#ifdef __cplusplus
} // extern "C"
#endif
#endif // __FBGL_H__

/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2024 Levent Kaya
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
