#include "main.h"
#include "lcd.h"
#include "generic_macros.h"

static char display_framebuffer[16*2*DISPLAY_FRAMES_AVAILABLE];
size_t display_current_frame;
static size_t display_framebuffer_mode;

static struct Display_emu_state des;

void display_init(void)
{
	// prepare virtual framebuffer
	display_current_frame = 0;
	display_framebuffer_mode = 0;
	des.cursor_offset = 0;
	des.next = 0;
	memset(display_framebuffer, 0x20, 16*2*DISPLAY_FRAMES_AVAILABLE);

	// switch to 4-bit 2-line mode
	display_write_instruction_byte(0x28);

	// clear display
	display_write_instruction_byte(0x01);

	// enable display
	display_write_instruction_byte(0x0C);

	// move cursor to first line
	display_write_instruction_byte(0x80);
}

void display_to_framebuffer(void)
{
	display_framebuffer_mode = 1;
}

void display_to_direct(void)
{
	display_framebuffer_mode = 0;
}

static uint8_t display_read_status(void)
{
	// make sure GPIOE is in correct mode
	GPIOE->MODER = 0x00504000;

	if (GPIOE->MODER != 0x00504000)
		PANIC(0x4000);

	uint8_t status = 0;

	GPIOE->ODR = 0x0;
	GPIOE->BSRR = DISPLAY_RW;

	GPIOE->BSRR = DISPLAY_ENA;
	status |= (GPIOE->IDR & 0xF000) >> 8;
	GPIOE->BSRR = (DISPLAY_ENA << 16);

	GPIOE->BSRR = DISPLAY_ENA;
	status |= (GPIOE->IDR & 0xF000) >> 12;
	GPIOE->BSRR = (DISPLAY_ENA << 16);

	GPIOE->ODR = 0x0;

	return status;
}

static void display_write_instruction_byte_direct(uint8_t code)
{
	DISPLAY_POLL_UNTIL_READY;

	// make sure GPIOE is in correct mode
	GPIOE->MODER = 0x55504000;

	if (GPIOE->MODER != 0x55504000)
		PANIC(0x4000);

	GPIOE->ODR = (code & 0xF0) << 8;
	GPIOE->BSRR = DISPLAY_ENA;
	GPIOE->BSRR = (DISPLAY_ENA << 16);

	GPIOE->ODR = (code & 0x0F) << 12;
	GPIOE->BSRR = DISPLAY_ENA;
	GPIOE->BSRR = (DISPLAY_ENA << 16);
}

static void display_write_instruction_byte_framebuffer(uint8_t code)
{
	// emulate physical display behavior on receiving instructions
	if (code & 0x80) {
		// decode new cursor offset
		size_t offset = ((code & 0x40) >> 2) | (code & 0xF);
		des.cursor_offset = offset;
	} else if (code == 0x01) {
		// reset screen
		memset(&(display_framebuffer[16*2*display_current_frame]), 0x20, 16*2);
		des.cursor_offset = 0;
		des.next = 0;
	} else if (code == 0x06) {
		// set increment mode
		des.next = 0;
	} else if (code == 0x04) {
		// set decrement mode
		des.next = 1;
	}
}

void display_write_instruction_byte(uint8_t code)
{
	if (display_framebuffer_mode) {
		display_write_instruction_byte_framebuffer(code);
	} else {
		display_write_instruction_byte_direct(code);
	}
}

static void display_write_data_byte_direct(uint8_t code)
{
	DISPLAY_POLL_UNTIL_READY;

	// make sure GPIOE is in correct mode
	GPIOE->MODER = 0x55504000;

	if (GPIOE->MODER != 0x55504000)
		PANIC(0x4000);

	GPIOE->ODR = ((code & 0xF0) << 8) | (DISPLAY_RS);
	GPIOE->BSRR = DISPLAY_ENA;
	GPIOE->BSRR = DISPLAY_ENA << 16;

	GPIOE->ODR = ((code & 0x0F) << 12) | (DISPLAY_RS);
	GPIOE->BSRR = DISPLAY_ENA;
	GPIOE->BSRR = DISPLAY_ENA << 16;
}

static void display_write_data_byte_framebuffer(uint8_t code)
{
	display_framebuffer[16*2*display_current_frame + des.cursor_offset] = (char) code;
	des.cursor_offset += des.next ? -1 : 1;
}

void display_write_data_byte(uint8_t code)
{
	if (display_framebuffer_mode) {
		display_write_data_byte_framebuffer(code);
	} else {
		display_write_data_byte_direct(code);
	}
}

void display_write_data_seq(char *codes)
{
	for (size_t i = 0; i < 16; i++) {
		if (codes[i])
			display_write_data_byte(codes[i]);
		else
			break;
	}
}

void display_load(uint32_t frame_no)
{
	if (display_framebuffer_mode)
		return;

	DISPLAY_CLEAR;

	for (uint32_t i = 0; i < 16; i++) {
		display_write_data_byte_direct(display_framebuffer[16*2*display_current_frame + i]);
	}

	DISPLAY_SET_CURSOR(1, 0);

	for (uint32_t i = 0; i < 16; i++) {
		display_write_data_byte_direct(display_framebuffer[16*2*display_current_frame + i + 16]);
	}
}
