/* Copyright (C) 2021,2022 fef <owo@fef.moe>.  All rights reserved. */

#include <string.h>

#include <arch/interrupt.h>
#include <arch/multiboot.h>
#include <arch/port.h>
#include <arch/vmparam.h>

#include <gay/cdefs.h>
#include <gay/kprintf.h>
#include <gay/mm.h>
#include <gay/types.h>
#include <gay/util.h>

enum vga_color {
	VGA_COLOR_BLACK		= 0,
	VGA_COLOR_BLUE		= 1,
	VGA_COLOR_GREEN		= 2,
	VGA_COLOR_CYAN		= 3,
	VGA_COLOR_RED		= 4,
	VGA_COLOR_MAGENTA	= 5,
	VGA_COLOR_BROWN		= 6,
	VGA_COLOR_LIGHT_GREY	= 7,
	VGA_COLOR_DARK_GREY	= 8,
	VGA_COLOR_LIGHT_BLUE	= 9,
	VGA_COLOR_LIGHT_GREEN	= 10,
	VGA_COLOR_LIGHT_CYAN	= 11,
	VGA_COLOR_LIGHT_RED	= 12,
	VGA_COLOR_LIGHT_MAGENTA	= 13,
	VGA_COLOR_LIGHT_BROWN	= 14,
	VGA_COLOR_WHITE 	= 15,
};

/*
 * The character framebuffer sits at physical address 0x000b8000.
 * The entire low memory is always mapped to KERNBASE upwards.
 */
#define FB_ADDRESS (0x000b8000 + KERNBASE)
#define FB_LINES 24
#define FB_COLS 80

struct fb_cell {
	u8 c;
	enum vga_color fg:4;
	enum vga_color bg:4;
} __packed;

/** @brief BIOS provided character framebuffer */
static volatile struct fb_cell *const framebuffer = (volatile struct fb_cell *)FB_ADDRESS;
/** @brief current line in the framebuffer */
static unsigned int fb_line;
/** @brief current column in the framebuffer */
static unsigned int fb_col;
/** @brief current background color */
enum vga_color fb_background;
/** @brief current foreground color */
enum vga_color fb_foreground;

#define cell_at(line, col) ( &framebuffer[(line) * FB_COLS + (col)] )
#define current_cell (cell_at(fb_line, fb_col))

static isize fb_write(struct kprintf_printer *printer, const void *buf, usize size);
static isize fb_flush(struct kprintf_printer *printer);
static struct kprintf_printer fb_kprintf_printer = {
	.write = fb_write,
	.flush = fb_flush,
};
static void fb_newline(void);
static void fb_clear(void);
static void fb_init(enum vga_color fg, enum vga_color bg);

static void print_gay_propaganda(void);

static struct mb2_tag *next_tag(struct mb2_tag *tag);
static void handle_tag(struct mb2_tag *tag);

extern int main(int argc, char *argv[]);

__asmlink void _boot(void *address)
{
	volatile int x = 69420;
	while (x == 69420);
	kprintf_set_printer(&fb_kprintf_printer);
	fb_init(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK);

	x86_setup_interrupts();

	print_gay_propaganda();

	/* the +8 for the address has something to do with the tags
	 * being embedded in another struct iirc, but i have no idea what
	 * that was and quite honestly i'm just glad it works at all. */
	for (struct mb2_tag *tag = address + 8; tag != NULL; tag = next_tag(tag))
		handle_tag(tag);

	main(0, nil);
}

static inline void handle_tag(struct mb2_tag *tag)
{
	switch (tag->type) {
	case MB2_TAG_TYPE_END:
		break;
	case MB2_TAG_TYPE_CMDLINE:
		kprintf("Kernel command line: %s\n", ((struct mb2_tag_string *)tag)->string);
		break;
	case MB2_TAG_TYPE_MMAP:
		x86_paging_init((struct mb2_tag_mmap *)tag);
		break;
	default:
		//kprintf("Unknown tag %u\n", tag->type);
		break;
	}
}

static inline struct mb2_tag *next_tag(struct mb2_tag *tag)
{
	if (tag->type == MB2_TAG_TYPE_END)
		return NULL;
	else
		return (void *)tag + ( (tag->size + 7) & ~7 );
}

static void fb_newline(void)
{
	fb_col = 0;

	if (fb_line == FB_LINES - 1) {
		void *first_row = (void *)cell_at(0, 0);
		void *second_row = (void *)cell_at(1, 0);
		void *last_row_end = (void *)cell_at(FB_LINES, FB_COLS);
		memmove(first_row, second_row, last_row_end - second_row);
	} else {
		fb_line++;
	}
}

static isize fb_write(struct kprintf_printer *printer, const void *buf, usize size)
{
	isize ret = 0;
	const u8 *s = buf;

	while (size > s - (const u8 *)buf) {
		u8 c = *s++;
		ret++;

		if (fb_col == FB_COLS)
			fb_newline();

		if (c == '\n') {
			fb_newline();
			continue;
		}

		current_cell->c = c;
		current_cell->fg = fb_foreground;
		current_cell->bg = fb_background;

		fb_col++;
	}

	return ret;
}

static isize fb_flush(struct kprintf_printer *printer)
{
	return 0;
}

static void fb_clear(void)
{
	for (unsigned int l = 0; l < FB_LINES; l++) {
		for (unsigned int c = 0; c < FB_COLS; c++) {
			cell_at(l, c)->c = ' ';
			cell_at(l, c)->fg = fb_foreground;
			cell_at(l, c)->bg = fb_background;
		}
	}
}

static void fb_init(enum vga_color fg, enum vga_color bg)
{
	fb_line = 0;
	fb_col = 0;
	fb_foreground = fg;
	fb_background = bg;

	/* disable cursor */
	x86_outb(0x3D4, 0x0A);
	x86_outb(0x3D5, 0x20);

	fb_clear();
}

static void print_gay_propaganda(void)
{
	static const enum vga_color rainbow[] = {
		VGA_COLOR_RED,
		VGA_COLOR_LIGHT_RED,
		VGA_COLOR_LIGHT_BROWN,
		VGA_COLOR_GREEN,
		VGA_COLOR_BLUE,
		VGA_COLOR_MAGENTA,
	};
	static char line[FB_COLS];
	memset(line, ' ', FB_COLS);

	enum vga_color bg_before = fb_background;
	enum vga_color fg_before = fb_foreground;
	for (int i = 0; i < ARRAY_SIZE(rainbow); i++) {
		fb_background = rainbow[i];
		fb_write(NULL, line, FB_COLS);
	}
	fb_background = bg_before;

	kprintf("\nWelcome to ");
	const char *gaybsd = "GayBSD";
	for (const char *tmp = gaybsd; *tmp != '\0'; tmp++) {
		fb_foreground = rainbow[tmp - gaybsd];
		kprintf("%c", *tmp);
	}
	fb_foreground = fg_before;
	kprintf(", be gay do crime!\n\n");
}