/*
 * DisplayBoardFirmware.c
 *
 * Created: 08.01.2019 19:46:33
 * Author : julian
 */ 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stddef.h> // for size_t

#define DISPLAY_PORT PORTA
#define DISPLAY_PIN PINA
#define DISPLAY_DDR DDRA
#define DISPLAY_SCL (1<<PA3)
#define DISPLAY_SDA (1<<PA2)

#define DISPLAY_SET(pin) do { DISPLAY_PORT &= ~(pin); DISPLAY_DDR |= (pin); } while(0)
#define DISPLAY_PIN_CLEAR(pin) do { DISPLAY_DDR &= ~(pin); DISPLAY_PORT |= (pin); } while(0)

#define DISPLAY_HALFCLK 1

// display commands
enum {
	DISPLAY_CMD_CLK_DIV     = 0xD5,
	DISPLAY_CMD_MULTIPLEX   = 0xA8,
	DISPLAY_CMD_CHARGEPUMP  = 0x8D,

	DISPLAY_CMD_COMM_VOLTAGE_DETECT = 0xDB,

	DISPLAY_CMD_LINE_OFFSET = 0xD3,
	DISPLAY_CMD_LINE_START  = 0x40,
	DISPLAY_CMD_ENABLE      = 0xAE,
	DISPLAY_CMD_INVERT      = 0xA6,
	DISPLAY_CMD_TEST        = 0xA4,
	DISPLAY_CMD_CONTRAST    = 0x81,

	DISPLAY_CMD_ADDR_MODE   = 0x20,
	DISPLAY_CMD_COLUMN_ADDR = 0x21,
	DISPLAY_CMD_PAGE_ADDR   = 0x22,
	DISPLAY_CMD_SCANMODE_POS = 0xC8,
	DISPLAY_CMD_SCANMODE_NEG = 0xC0,
};


/* Display driver */
typedef struct {
	volatile uint8_t current_buffer:1;
} display_t;

display_t display;

static inline void display_start() {
	DISPLAY_SET(DISPLAY_SDA);
	_delay_us(DISPLAY_HALFCLK);
	DISPLAY_SET(DISPLAY_SCL);
}

static inline void display_stop() {
	DISPLAY_PIN_CLEAR(DISPLAY_SCL);
	_delay_us(DISPLAY_HALFCLK);
	DISPLAY_PIN_CLEAR(DISPLAY_SDA);
}

const uint8_t display_addr = 0x78;
//const uint8_t display_addr = 0x1E;

// returns 1 on NACK
int display_send(uint8_t b) {
	int i = 0;
	while(i < 8)
	{
		if (b&(0x80>>i))
			DISPLAY_PIN_CLEAR(DISPLAY_SDA);
		else
			DISPLAY_SET(DISPLAY_SDA);

		_delay_us(1);

		DISPLAY_PIN_CLEAR(DISPLAY_SCL);
		_delay_us(DISPLAY_HALFCLK);
		DISPLAY_SET(DISPLAY_SCL);
		_delay_us(DISPLAY_HALFCLK-2);
		i++;
	}

	DISPLAY_PIN_CLEAR(DISPLAY_SDA);
	_delay_us(DISPLAY_HALFCLK);
	DISPLAY_PIN_CLEAR(DISPLAY_SCL);

	_delay_us(DISPLAY_HALFCLK);
	// look for ACK
	int result = DISPLAY_PIN & DISPLAY_SDA;
	DISPLAY_SET(DISPLAY_SCL);

	// ack means 0 during clk period
	return result != 0;

}


int display_ncmd(uint8_t *cmd, uint8_t len) {
	int result = 1;
	display_start();

	if (display_send(display_addr)) goto fail;

	uint8_t prefix = 0x80;
	while(len--)
	{
		if (display_send(prefix) || display_send(*cmd))
			goto fail;

		if (len-1) {
			prefix = 0x00;
		}
		++cmd;
	}

	result = 0;
fail:
	display_stop();
	return result;
}

int display_cmd(uint8_t cmd) {
	return display_ncmd(&cmd, 1);
}

int display_data_chunk(uint8_t ** data, size_t *len) {
	int result = 1;
	uint8_t count = 16;

	display_start();

	if (display_send(display_addr)) {
		goto fail;
	}

	if (display_send(0x40))
		goto fail;

	while(count && *len && !display_send(**data)) {
		count--;

		(*len)--;
		(*data)++;
	}

	result = 0;
fail:

	display_stop();
	return result;
}
int display_data(uint8_t * data, size_t len) {
	int result = 1;

	while(len && !(result = display_data_chunk(&data, &len))) {}

	return result;
}

void display_copy(int x, int y,  int w, int h, uint8_t *data, size_t len) {
	display_cmd(DISPLAY_CMD_COLUMN_ADDR);
	display_cmd(y);
	display_cmd(y + h);

	display_cmd(DISPLAY_CMD_PAGE_ADDR);
	display_cmd(x     + 4*display.current_buffer);
	display_cmd(x + w + 4*display.current_buffer -1);

	display_data(data, len);
}

void display_clear() {
	static uint8_t chunk[8] = {0xFF};
	size_t len = 0;
	size_t c = 8;

	display_cmd(DISPLAY_CMD_COLUMN_ADDR);
	display_cmd(0);
	display_cmd(127);

	display_cmd(DISPLAY_CMD_PAGE_ADDR);
	display_cmd(display.current_buffer*4);
	display_cmd(display.current_buffer*4+3);

	uint8_t * current = chunk;
	while(len < 128*4) {
		display_start();
		display_send(display_addr);
		display_send(0x40);
		for (uint8_t i = 0; i < 16; i++) {
			display_send(0x00);
		}

		display_stop();
		len += 16;
	}
}
#include "font.h"

static inline void pgm_memcpy(uint8_t * dest, const uint8_t * src, size_t s) {
	while(s--) {
		*dest = pgm_read_byte(src);

		++src;
		++dest;
	}
}

void display_font_copy(char c, int x, int y) {
	static uint8_t buffer[FONT_HEIGHT*2];

	if (c < FONT_START || c > FONT_START + FONT_END)
		return;

	int offset = c - FONT_START;

	uint16_t index = pgm_read_word(Fonttable + offset);
	uint8_t w = pgm_read_byte(Fonttable + offset +2);
	uint8_t h = pgm_read_byte(Fonttable + offset +3);

	const uint8_t * current = Fonttable + index + FONT_END;
	uint8_t linebuffer = pgm_read_byte(current++);
	uint8_t count = 8;

	for (int y = 0; y < h; y++) {
		for (int x = 0; x < w; x++) {
			count--;

			if (!count) {
				count = 8;
				linebuffer = pgm_read_byte(current++);
			}
		}
	}


//	pgm_memcpy((uint8_t*)&glyph_buffer, (const uint8_t*)Glyphtable, sizeof(glyph_buffer));
//	pgm_memcpy(buffer, Fonttable + glyph_buffer.index, glyph_buffer.w);

//	display_copy(x, y, 1, FONT_HEIGHT, buffer, FONT_HEIGHT);
}


// initializes display 
// setup parameters for normal operation
void display_init()
{
	display.current_buffer = 1;

	DISPLAY_PIN_CLEAR(DISPLAY_SCL | DISPLAY_SDA);

	display_cmd(DISPLAY_CMD_ENABLE);
	display_cmd(DISPLAY_CMD_CLK_DIV);
	display_cmd(0x80);
	display_cmd(DISPLAY_CMD_MULTIPLEX);
	display_cmd(63);

	display_cmd(DISPLAY_CMD_CHARGEPUMP);
	display_cmd(0x14);

	display_cmd(0xDA);
	display_cmd(0x02);

	display_cmd(DISPLAY_CMD_LINE_OFFSET);
	display_cmd(0);
	display_cmd(DISPLAY_CMD_LINE_START | 0);

	display_cmd(DISPLAY_CMD_COMM_VOLTAGE_DETECT);
	display_cmd(0x40);


	display_cmd(DISPLAY_CMD_ADDR_MODE);
	display_cmd(0x01);
	display_cmd(DISPLAY_CMD_SCANMODE_POS);

	display_cmd(DISPLAY_CMD_TEST); // no testing mode

	display_clear();
	display_cmd(DISPLAY_CMD_ENABLE|1);
}

void display_swap() {
	display.current_buffer = !display.current_buffer;
	display_cmd(DISPLAY_CMD_LINE_START | 32*display.current_buffer);

}

// TODO: find out what display is capable of
void display_write(const char * str) 
{
	
}

// should be called after every change to display content is made
// TODO: check if this is possible to implement
void display_present() {
	
}

/* encoder driver */
typedef enum {
	ENC_DIR_NONE,
	ENC_DIR_CW,
	ENC_DIR_CCW,
} encoder_dir_t;

struct encoder;
typedef void (*encoder_callback_t)(struct encoder * encoder);

typedef struct encoder {
	encoder_dir_t dir;
	uint16_t velo;
	int16_t pos;
	
	encoder_callback_t callback;
} encoder_t;

encoder_t encoder;

// encoder can only be at the INT0/INT1 lines
// maybe pushbutton on pinchange
void encoder_init(encoder_t * encoder) {
	*encoder = (encoder_t){};
		
	// TODO: configure PCINT0 interrupt for direction
	// TODO: configure PCINT1 interrupt for push button
}

volatile encoder_dir_t first;

#include <avr/interrupt.h>

// turning interrupt
ISR(PCINT0_vect) {
	// find edge type
	if (first == ENC_DIR_NONE) {
		first = ENC_DIR_CCW;
		return;
	}
	
	encoder.dir = first;
	encoder.pos -= 1;
	if (encoder.callback) 
		encoder.callback(&encoder);
	first = ENC_DIR_NONE;
}

// push interrupt
ISR(PCINT1_vect) {
	// TODO
}

/* main application */
typedef struct menuitem {
	struct menuitem * root;
	const char * text;
	
	struct menuitem * children;
	uint8_t children_count;
} menuitem;

#include <stddef.h>

menuitem root = {.root = NULL, 
	.text = NULL,
	
};

int main(void)
{
	//_delay_ms(100);
	display_init();

	uint8_t face[8] = {
		0b11111111,
		0b11111110,
		0b11111100,
		0b11111000,
		0b11110000,
		0b11100000,
		0b11000000,
		0b10000000,
	};

	for (int j = 0; j< 16; j++)
		for (int i = 0; i<4; i++)
		{
			display_copy(i,j*8, 1,8, face, 8);
			display_font_copy('a', i,j*8);
		}
	display_swap();

    /* Replace with your application code */
    while (1) 
    {
    }
}

