/* Created by Emma Weaver on 2026-08-13. */
/* GPLv3+ */

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <math.h> /* sqrt */

#define FATAL(...)  { \
	perror("Fatal: " __VA_ARGS__); \
	exit(1); \
}

int w, h;
int bytes_per_pixel;

double
get_mandel(double x, double y)
{
	double x0 = x, y0 = y;
	double nx, ny;
	double mod;
	int iters;
	
	iters = 0;
	for (;;) {
		nx = (x*x - y*y) + x0;
		ny = (2*x*y) + y0;
		x = nx; y = ny;

		mod = x*x + y*y;
		if (mod >= 4) return 1.0;

		iters++;
		if (iters >= 10) return mod / 4.0;
	}
}

void
write_pixel(char *location, int x, int y)
{
	/* mandelbrot set */
	double a = 4.0 * (x - w/2) / h;
	double b = 4.0 * (y - h/2) / h;
	double mandel = get_mandel(a, b);

	int blue = 255;
	int green = 255 * mandel;
	int red = 127 + 128 * mandel;
	int alpha = 0;

	/* simple gradient */
	/*
	int blue = 255 - 255 * a / h;
	int green = 255 - (int) (255 * sqrt(a*a + b*b) / h);
	int red = 255 - 255 * b / h;
	int alpha = 0;
	*/
	
	if (bytes_per_pixel == 4) {
		/* 32 bit */
		*(location) = blue;
		*(location + 1) = green;
		*(location + 2) = red;
		*(location + 3) = alpha;
		return;
	}

	/* assume 16 bit otherwise */
	red >>= 4;
	green >>= 4;
	blue >>= 4;
	unsigned short int color = red << 11 | green << 5 | blue;
	*((unsigned short int*) location) = color;
}

int
main()
{
	int result;
	long int screen_bytes;
	char *fbp;
	int fbfd;
	struct fb_var_screeninfo vinfo;
	struct fb_fix_screeninfo finfo;

	fbfd = open("/dev/fb0", O_RDWR);
	if (fbfd == -1) FATAL("Failed to open framebuffer" "\n");

	/* read fixed info */
	result = ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
	if (result == -1) FATAL("ioctl fscreeninfo failed");

	/* read variable info */
	result = ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);
	if (result == -1) FATAL("ioctl vscreeninfo failed");

	w = vinfo.xres;
	h = vinfo.yres;
	bytes_per_pixel = vinfo.bits_per_pixel / 8;

	printf(
		"%d x %d offset" "\n"
		"%d x %d pixels" "\n"
		"%d bits per pixel" "\n"
		"%d bytes (?) per line vs %d expected" "\n",
		vinfo.xoffset, vinfo.yoffset,
		vinfo.xres, vinfo.yres,
		vinfo.bits_per_pixel,
		finfo.line_length, vinfo.bits_per_pixel * vinfo.xres / 8
	);

	screen_bytes = w * bytes_per_pixel * finfo.line_length;

	/* map the device to memory */
	fbp = (char *) mmap(0, screen_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
	if (fbp == (void *) -1)
		FATAL("failed to map framebuffer device to memory");

	printf("mmapped %p to %p" "\n", fbp, fbp + screen_bytes);

	printf("Press enter to render." "\n");
	getchar();

	/* write some colors into there */
	int x = 0, y = 0;
	long int location;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			// TODO fix x/yoffsets. I think they're broken...
			location = (x + vinfo.xoffset) * bytes_per_pixel +
				(y + vinfo.yoffset) * finfo.line_length;
			write_pixel(fbp + location, x, y);
		}
	}

	getchar();

	munmap(fbp, screen_bytes);
	close(fbfd);
	return 0;
}
