#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>

#include <stdint.h>

#include "i2c_master.h"

#define EEPROM_SIZE		256
#define EEPROM_IDX_SIZE 4

#define BATTERY_REG_MODE		0x00
#define BATTERY_REG_CTRL		0x01
#define BATTERY_READ_ADDR		0x02
#define BATTERY_READ_ADDR_END	0x0B
#define BATTERY_BUFF_SIZE		(BATTERY_READ_ADDR_END - BATTERY_READ_ADDR + 1)

#define BATTERY_CHARGE			0
#define BATTERY_VOLT			6
#define BATTERY_TEMP			8

#define BATTERY_MILLIOHM		30

#define BATTERY_CHARGE_MULT		67
#define BATTERY_CHARGE_DIV		(10 * BATTERY_MILLIOHM)
#define BATTERY_VOLT_MULT		244
#define BATTERY_VOLT_DIV		100
#define BATTERY_TEMP_MULT		125
#define BATTERY_TEMP_DIV		100

#define CHAR_SIZE		5
#define BUFF_SIZE		16

#define OLED_ADDRESS	(0x3C << 1)
// #define OLED_ADDRESS	(0x3D << 1)
#define EEPROM_ADDRESS	(0x50 << 1)
#define BATTERY_ADDRESS	(0x70 << 1)

#define OLED_X_SIZE		128
#define OLED_Y_SIZE		64

#define OLED_BATTERY_ICON_X1		(OLED_X_SIZE - (CHAR_SIZE + 1) * 2 * 2)
#define OLED_BATTERY_ICON_X2		(OLED_X_SIZE - 1)
#define OLED_BATTERY_ICON_Y1		2
#define OLED_BATTERY_ICON_Y2		((OLED_Y_SIZE / 8) - 1)
#define OLED_BATTERY_ICON_SIZE		2
#define OLED_BATTERY_ICON_PERCENT	5

// Time(ms) to keep the display on before sleep
#define DISPLAY_DELAY	10000

// Time(ms) to assume long button press
#define LONG_PRESS		500

// Rotary Encoder Parameters
#define ROT_PULSE_COUNT	12
#define ROT_DETENTS		24
#define ROT_WHEEL_RAD	13.875
//#define ROT_REVERSE

// Event queue macros
#define MAX_EVENT_COUNT 64
#define PTR_INC(x) ((x) = events + ((((x) - events) + 1) % MAX_EVENT_COUNT))

#define MENU_COUNT		3
#define MENU_STRLEN		8

enum event_e
{
	EVENT_NONE,
	EVENT_ROT_CW,
	EVENT_ROT_CCW,
	EVENT_SEL_UP,
	EVENT_SEL_DOWN,
	EVENT_BTN_UP,
	EVENT_BTN_DOWN
};

enum btn_state_e
{
	BTN_NONE = 0,
	BTN_PRESS,
	BTN_UP5,
	BTN_UP14,
	BTN_DOWN5,
	BTN_DOWN14
};

enum main_state_e
{
	STATE_SLEEP,
	STATE_IDLE,
	STATE_MENU,
	STATE_ADJUST,
	STATE_BATTERY
};

// These are set on hardware init
uint8_t old_btn_pin_state;
uint8_t old_rot_pin_state;

uint8_t btn_state_stable = BTN_NONE;
uint8_t btn_state_current = BTN_NONE;

uint8_t events[MAX_EVENT_COUNT];
uint8_t event_count  = 0;
uint8_t *event_read  = events;
uint8_t *event_write = events;

uint32_t ms = 0;

#ifdef ROT_REVERSE
const uint8_t PROGMEM rot_table[16] =
{
	EVENT_NONE, EVENT_ROT_CCW, EVENT_ROT_CW, EVENT_NONE,
	EVENT_ROT_CW, EVENT_NONE, EVENT_NONE, EVENT_ROT_CCW,
	EVENT_ROT_CCW, EVENT_NONE, EVENT_NONE, EVENT_ROT_CW,
	EVENT_NONE, EVENT_ROT_CW, EVENT_ROT_CCW, EVENT_NONE
};
#else
const uint8_t PROGMEM rot_table[16] =
{
	EVENT_NONE, EVENT_ROT_CW, EVENT_ROT_CCW, EVENT_NONE,
	EVENT_ROT_CCW, EVENT_NONE, EVENT_NONE, EVENT_ROT_CW,
	EVENT_ROT_CW, EVENT_NONE, EVENT_NONE, EVENT_ROT_CCW,
	EVENT_NONE, EVENT_ROT_CCW, EVENT_ROT_CW, EVENT_NONE
};
#endif

const uint8_t PROGMEM btn_table[36] =
{
	// Old BTN_NONE
	EVENT_NONE, EVENT_BTN_DOWN, EVENT_NONE, EVENT_SEL_UP, EVENT_NONE, EVENT_SEL_DOWN,
	// Old BTN_PRESS
	EVENT_BTN_UP, EVENT_NONE, EVENT_BTN_UP, EVENT_SEL_UP, EVENT_BTN_UP, EVENT_SEL_DOWN,
	// Old BTN_UP5
	EVENT_NONE, EVENT_BTN_DOWN, EVENT_NONE, 0xFF, EVENT_NONE, EVENT_SEL_DOWN,
	// Old BTN_UP14
	EVENT_NONE, EVENT_BTN_DOWN, EVENT_NONE, EVENT_NONE, EVENT_NONE, EVENT_SEL_DOWN,
	// Old BTN_DOWN5
	EVENT_NONE, EVENT_BTN_DOWN, EVENT_NONE, EVENT_SEL_UP, EVENT_NONE, 0xFF,
	// Old BTN_DOWN14
	EVENT_NONE, EVENT_BTN_DOWN, EVENT_NONE, EVENT_SEL_UP, EVENT_NONE, EVENT_NONE
};

const uint8_t PROGMEM unfold_table[16] =
{
	0x00,
	0x02,
	0x08,
	0x0A,
	0x20,
	0x22,
	0x28,
	0x2A,
	0x80,
	0x82,
	0x88,
	0x8A,
	0xA0,
	0xA2,
	0xA8,
	0xAA
};

#define CHAR_0		0
#define CHAR_A		10
#define CHAR_B		11
#define CHAR_C		12
#define CHAR_D		13
#define CHAR_V		14
#define CHAR_a		15
#define CHAR_c		16
#define CHAR_d		17
#define CHAR_e		18
#define CHAR_h		19
#define CHAR_j		20
#define CHAR_m		21
#define CHAR_r		22
#define CHAR_s		23
#define CHAR_t		24
#define CHAR_u		25
#define CHAR_y		26
#define CHAR_dot	27
#define CHAR_lt		28
#define CHAR_gt		29
#define CHAR_deg	30

// Bottom -> Top (In Byte); Left -> Right (In Row)
const uint8_t PROGMEM symbols[31 * CHAR_SIZE] =
{
	0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
	0x00, 0x42, 0x7F, 0x40, 0x00, // 1
	0x42, 0x61, 0x51, 0x49, 0x46, // 2
	0x21, 0x41, 0x45, 0x4B, 0x31, // 3
	0x18, 0x14, 0x12, 0x7F, 0x10, // 4
	0x27, 0x45, 0x45, 0x45, 0x39, // 5
	0x3C, 0x4A, 0x49, 0x49, 0x30, // 6
	0x03, 0x71, 0x09, 0x05, 0x03, // 7
	0x36, 0x49, 0x49, 0x49, 0x36, // 8
	0x06, 0x49, 0x49, 0x29, 0x1E, // 9
	0x7E, 0x11, 0x11, 0x11, 0x7E, // A
	0x7F, 0x49, 0x49, 0x49, 0x36, // B
	0x3E, 0x41, 0x41, 0x41, 0x22, // C
	0x7F, 0x41, 0x41, 0x41, 0x3E, // D
	0x1F, 0x20, 0x40, 0x20, 0x1F, // V
	0x20, 0x54, 0x54, 0x54, 0x78, // a
	0x38, 0x44, 0x44, 0x44, 0x20, // c
	0x38, 0x44, 0x44, 0x48, 0x7F, // d
	0x38, 0x54, 0x54, 0x54, 0x18, // e
	0x7F, 0x08, 0x04, 0x04, 0x78, // h
	0x20, 0x40, 0x44, 0x3D, 0x00, // j
	0x7C, 0x04, 0x78, 0x04, 0x78, // m
	0x7C, 0x08, 0x04, 0x04, 0x08, // r
	0x48, 0x54, 0x54, 0x54, 0x40, // s
	0x04, 0x3F, 0x44, 0x40, 0x20, // t
	0x3C, 0x40, 0x40, 0x20, 0x7C, // u
	0x0C, 0x50, 0x50, 0x50, 0x3C, // y
	0x00, 0x60, 0x60, 0x00, 0x00, // .
	0x00, 0x08, 0x14, 0x22, 0x41, // <
	0x41, 0x22, 0x14, 0x08, 0x00, // >
	0x0E, 0x11, 0x11, 0x0E, 0x00  // deg
};

const char PROGMEM menu_strings[(MENU_COUNT + 1) * MENU_STRLEN] =
{
	0xFF, CHAR_A, CHAR_t, CHAR_t, CHAR_a, CHAR_c, CHAR_h, 0xFF,
	0xFF, CHAR_A, CHAR_d, CHAR_j, CHAR_u, CHAR_s, CHAR_t, 0xFF,
	0xFF, CHAR_B, CHAR_a, CHAR_t, CHAR_t, CHAR_e, CHAR_r, CHAR_y,
	0xFF, CHAR_D, CHAR_e, CHAR_t, CHAR_a, CHAR_c, CHAR_h, 0xFF
};

// 1 / (4 * ROT_PULSE_COUNT) * (2 * pi * ROT_WHEEL_RAD)
const float rot_coeff = 1.57f * ROT_WHEEL_RAD / ROT_PULSE_COUNT;
float count_value_fine = 0;
uint32_t count_value = 0;
int8_t rot_value = 0;
uint8_t rot_dir_is_A = 1;

int16_t eeprom_idx = 0;
uint8_t spool_attached = 0;
uint8_t spool_counting = 0;

uint32_t sleep_when_ms = DISPLAY_DELAY;
uint32_t long_press_when_ms = 0xFFFFFFFF;

uint8_t count_highlight = 0xFF;
uint8_t dir_highlight = 0xFF;

uint8_t needs_update = 0;

uint8_t state;

uint8_t menu_option = 0;

int16_t battery_mAh = 0;
int16_t battery_mV = 0;
int16_t battery_temp = 0;

void (*menu[MENU_COUNT])();

// Init direct hardware
void simple_init();

// OLED send cmd helper
void display_send_cmd(uint8_t cmd);

// OLED send data helper
void display_send_data(const uint8_t *data, uint16_t buflen);

// OLED print area helper
void display_set_area(uint8_t start_x, uint8_t end_x, uint8_t start_y, uint8_t end_y);

// OLED init
void display_init();

// OLED On/Off
void display_enable(uint8_t en);

// Symbol printing helper
void get_symbol16(uint8_t index, uint8_t *out);

// Print symbol on screen
// Symbol idx in table,
// Start X pixel - 0 -- 117
// Y row - 0 -- 3
// Char size - 10x16
void print_symbol(uint8_t symbol_idx, uint8_t x, uint8_t y, uint8_t invert);

// Print the length left with mm and highlight the required digit
// uses count_value for the value
// count_highlight - for highlit digit
// 0 - no highlight
// 1 -- 6 (rigth to left digit)
// 0xFF - clear all text
void print_mm();

// Print the direction of the filament based on
// rot_dir_is_A - direction
// dir_highlight - 0 - no, 1 - yes, 0xFF - clear
void print_direction();

// Clears the menu/data area
void print_clear_menu_area();

// Print the menu
void print_menu(uint8_t force_draw);

// Prints the option in the correct place
void print_option(uint8_t option);

// Prints the battery icon depending on battery percentage
void print_battery_icon();

// Prints battery statistics in menu area
void print_battery_stat(uint8_t force_draw);

// Prints single battery stat on row using symbols
void print_battery_single_stat(uint8_t y, int16_t bat_stat, uint8_t *symbols, uint8_t decimal_idx);

// Find the current eeprom data idx of the attached spool
int16_t find_eeprom_idx();

// Read value and direction of current spool
int8_t read_eeprom_val(int8_t idx, uint32_t *value, uint8_t *direction);

// Write value and direction of current spool
int8_t write_eeprom_val(int8_t idx, uint32_t value, uint8_t direction);

// Reset battery counter stats
int8_t reset_battery();

// Read battery stats
// temp is in celsius
int8_t read_battery(int16_t *mAh, int16_t *mV, int16_t *temp);

// I2C write helper
int8_t i2c_write_buff(uint8_t i2c_addr, uint8_t data_addr, uint8_t *buf, uint8_t len);

// I2C read helper
int8_t i2c_read_buff(uint8_t i2c_addr, uint8_t data_addr, uint8_t *buf, uint8_t len);

// Do the big sleep
void do_sleep();

// Extracts digit from value
// 1 - ones, 2 - tens, 3 - hundreds ...
uint8_t extract_digit(uint32_t value, uint8_t digit_num);

// Returns an event from the queue or EVENT_NONE
// EVENT_NONE could be from the queue
uint8_t consume_event();

// Adjusts count value based on event
void spool_count(uint8_t event);

void process_sleep();
void process_idle();
void process_menu();
void process_adjust();
void process_battery();

// Attaches or detaches spool (Menu 0)
void attach_detach_spool();

// Sets the state to adjust spool (Menu 1)
void set_adjust_spool();

// Sets the state to battery info (Menu 3)
void set_battery_state();

// Updates the ms counter
ISR(TIM1_CAPT_vect)
{
	cli();
	ms += 10;
	sei();
}

// Changes on button
ISR(PCINT0_vect)
{
	// Check button inputs
	uint8_t btn_pin_state = PINA & 0x2F;
	uint8_t btn_event = EVENT_NONE;
	uint8_t btn_state = btn_state_current;
	uint8_t btn_idx;

	cli();

	// Check if button has changed
	if ((btn_pin_state ^ old_btn_pin_state) && (event_count != MAX_EVENT_COUNT))
	{
		// Pins to state
		switch (btn_pin_state)
		{
		// None
		case 0x2F:
			btn_state = BTN_NONE;
			break;
		
		// Press
		case 0x0F:
			btn_state = BTN_PRESS;
			break;

		// Up 5
		case 0x2D:
			btn_state = BTN_UP5;
			break;
		
		// Up 14
		case 0x2C:
			btn_state = BTN_UP14;
			break;
		
		// Down 5
		case 0x27:
			btn_state = BTN_DOWN5;
			break;
		
		// Down 14
		case 0x23:
			btn_state = BTN_DOWN14;
			break;
		}

		btn_idx = btn_state_current * 6 + btn_state;
		btn_event = pgm_read_byte(&(btn_table[btn_idx]));
		if (0xFF == btn_event)
		{
			switch (btn_state)
			{
			case BTN_UP14:
				if (BTN_UP14 != btn_state_stable)
				{
					btn_event = EVENT_SEL_UP;
				}
				else
				{
					btn_event = EVENT_NONE;
				}
				break;
			
			case BTN_DOWN14:
				if (BTN_DOWN14 != btn_state_stable)
				{
					btn_event = EVENT_SEL_DOWN;
				}
				else
				{
					btn_event = EVENT_NONE;
				}
				break;
			
			default:
				btn_event = EVENT_NONE;
				break;
			}
		}
	}

	// Actually put the event in the queue
	if (btn_event != EVENT_NONE)
	{
		*event_write = btn_event;
		++event_count;
		PTR_INC(event_write);
	}

	old_btn_pin_state = btn_pin_state;
	btn_state_current = btn_state;
	if ((btn_state != BTN_UP5) && (btn_state != BTN_DOWN5))
	{
		btn_state_stable = btn_state;
	}

	sei();
}

// Changes on rotary encoder
ISR(PCINT1_vect)
{
	uint8_t rot_pin_state = PINB & 0x03;
	uint8_t rot_event = EVENT_NONE;
	uint8_t rot_idx;

	cli();
	if ((rot_pin_state ^ old_rot_pin_state) && (event_count != MAX_EVENT_COUNT))
	{
		rot_idx = (old_rot_pin_state << 2) | rot_pin_state;
		rot_event = pgm_read_byte(&(rot_table[rot_idx]));
		if (EVENT_NONE != rot_event)
		{
			*event_write = rot_event;
			++event_count;
			PTR_INC(event_write);
		}
	}
	old_rot_pin_state = rot_pin_state;

	sei();
}

int main()
{
	// Init Direct Hardware
	simple_init();

	i2c_init();
	display_init();
	reset_battery();
	read_battery(&battery_mAh, &battery_mV, &battery_temp);

	// Attempt to attach spool on initial startup
	attach_detach_spool();

	display_enable(1);
	print_mm();
	print_direction();
	print_battery_icon();
	state = STATE_IDLE;

	sei();
	while(1)
	{
		switch (state)
		{
		case STATE_SLEEP:
			process_sleep();
			break;

		case STATE_IDLE:
			process_idle();
			break;
		
		case STATE_MENU:
			process_menu();
			break;
		
		case STATE_ADJUST:
			process_adjust();
			break;
		
		case STATE_BATTERY:
			process_battery();
			break;
		}

		if (STATE_SLEEP != state)
		{
			print_mm();
			print_direction();
			print_battery_icon();
		}
	}
}

void simple_init()
{
	// Pull-ups on button
	PORTA = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 5);

	// Pull-ups on rotary encoder
	PORTB = (1 << 0) | (1 << 1);

	// No Prescaler - 1 MHz
	// Set Mode to CTC with ICR1
	TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);

	// 1 000 000 / 10 000 = 100 (Hz)
	// Interrupt every 10ms
	ICR1 = 10000;

	// Enable Interrupt on capture
	TIMSK1 = (1 << ICIE1);

	// Interrupt on Up/Down/Click pins
	PCMSK0 = (1 << PCINT0) | (1 << PCINT1) | (1 << PCINT2) | (1 << PCINT3) | (1 << PCINT5);

	// Interrupt on rotary encoder pins
	PCMSK1 = (1 << PCINT8) | (1 << PCINT9);

	// Enable Interrupt on pin-changes (only button)
	GIMSK = (1 << PCIE0);

	// Sleep mode - power down
	MCUCR = (1 << SM1);

	// Power reduction - Disable timer 0 and ADC
	PRR = (1 << PRTIM0) | (1 << PRADC);

	old_btn_pin_state = 0x2F;
	old_rot_pin_state = (PINB & 0x03);
	menu[0] = attach_detach_spool;
	menu[1] = set_adjust_spool;
	menu[2] = set_battery_state;
}

void display_send_cmd(uint8_t cmd)
{
	i2c_start(OLED_ADDRESS | I2C_WRITE);
	i2c_write(0x00); // Command Indicator
	i2c_write(cmd);
	i2c_stop();
}

void display_send_data(const uint8_t *data, uint16_t buflen)
{
	uint16_t i;

	for (i = 0; i < buflen; ++i)
	{
		if (0 == (i % 0x0F))
		{
			i2c_start(OLED_ADDRESS | I2C_WRITE);
			i2c_write(0x40); // Data Indicator
		}

		i2c_write(data[i]);

		if (0x0F == (i % 0x0F))
		{
			i2c_stop();
		}
	}

	if (0 != (i % 0x0F))
	{
		i2c_stop();
	}
}

void display_set_area(uint8_t start_x, uint8_t end_x, uint8_t start_y, uint8_t end_y)
{
	uint8_t cmd_list[6] =
	{
		0x21, // Set Column Address
		0x00, // Start Address
		0x00, // End Address
		0x22, // Set Page Address
		0x00, // Start Page
		0x00  // End Page
	};
	uint8_t i;

#if 0
	if (start_x >= OLED_X_SIZE)
	{
		start_x = OLED_X_SIZE - 1;
	}

	if (end_x >= OLED_X_SIZE)
	{
		end_x = OLED_X_SIZE - 1;
	}

	if (end_x < start_x)
	{
		end_x = start_x;
	}

	if (start_y >= (OLED_Y_SIZE / 8))
	{
		start_y = (OLED_Y_SIZE / 8) - 1;
	}

	if (end_y >= (OLED_Y_SIZE / 8))
	{
		end_y = (OLED_Y_SIZE / 8) - 1;
	}

	if (end_y < start_y)
	{
		end_y = start_y;
	}
#endif
	cmd_list[1] = start_x;
	cmd_list[2] = end_x;
	cmd_list[4] = start_y;
	cmd_list[5] = end_y;

	for (i = 0; i < sizeof(cmd_list); ++i)
	{
		display_send_cmd(cmd_list[i]);
	}
}

void display_init()
{
	uint16_t i;
	uint8_t buf[BUFF_SIZE];

	const uint8_t cmd_list[] =
	{
		0xAE,        // Display OFF
		0xD5,        // Set Clock Divider / Oscillator Frequency
		0x80,        // Default OSC / No Div
		0xA8,        // Set Multiplex Ratio
		OLED_Y_SIZE - 1,
		0xD3,        // Set Display Offset
		0x00,        // 0 Offset
		0x40 | 0x00, // Set Start Line To 0
		0x8D,        // Set Charge Pump
		0x14,        // Enable Charge Pump When Display Is On
		0x20,        // Set Memory Mode
		0x00,        // Horizontal Mode
		0xA0 | 0x01, // Set Segment Remap - column 127 is seg 0
		0xC8,        // Set COM Scan Direction To Decrement
		0xDA,        // Set COM Pin Configuration
		0x02 | 0x10, // Alternating COM Pin Configuration (COM63 = 0, COM31 = 1, ...)
		0x81,        // Set Contrast
		0xFF,        // Contrast Value
		0xD9,        // Set Precharge Period
		0x01 | 0xF0, // Phase 1 - 1 CLK, Phase 2 - 15 CLK
		0xDB,        // Set VCOM Deselect Level
#if 1
		0x30,        // 0.83 x Vcc
#else
		0x40,        // UNKNOWN (0.90 x Vcc Assumed)
#endif
		0xA4,        // Display RAM Contents
		0xA6,        // Set Normal Display (1 is white)
		0x2E,        // Deactivate Scroll
		0xAF,        // Display ON

		// Commands to send to RAM
		0x21,        // Set Column Address
		0x00,
		OLED_X_SIZE - 1,
		0x22,        // Set Page Address
		0x00,
		(OLED_Y_SIZE / 8) - 1
	};

	// Send Commands
	for (i = 0; i < sizeof(cmd_list); ++i)
	{
		display_send_cmd(cmd_list[i]);
	}

	// Fill Buffer With Black
	for (i = 0; i < sizeof(buf); ++i)
	{
		buf[i] = 0;
	}

	// Black Entire Screen
	for (i = 0; i < OLED_X_SIZE * OLED_Y_SIZE / 8 / sizeof(buf); ++i)
	{
		display_send_data(buf, sizeof(buf));
	}
}

void display_enable(uint8_t en)
{
	static uint8_t old_state = 1;

	if (en != 0)
	{
		en = 1;
	}

	if (en != old_state)
	{
		display_send_cmd(0xAE | (en & 0x01));
	}

	old_state = en;
}

void get_symbol16(uint8_t index, uint8_t *out)
{
	uint8_t i;
	uint8_t data_byte;

	for (i = 0; i < CHAR_SIZE * 4; ++i)
	{
		out[i] = 0;
	}

	if (index > sizeof(symbols) / 5)
	{
		return;
	}

	for (i = 0; i < CHAR_SIZE; ++i)
	{
		data_byte = pgm_read_byte(&(symbols[index * CHAR_SIZE + i]));
		out[i * 2 + 1] = pgm_read_byte(&(unfold_table[data_byte & 0x0F]));
		out[CHAR_SIZE * 2 + i * 2 + 1] = pgm_read_byte(&(unfold_table[(data_byte >> 4) & 0x0F]));
	}
}

// Symbol idx in table,
// Start x pixel - 0 -- 117
// Y row - 0 -- 3
// Char Size - 10x16
void print_symbol(uint8_t symbol_idx, uint8_t x, uint8_t y, uint8_t invert)
{
	uint8_t unfolded_symbol[CHAR_SIZE * 4];
	uint8_t i;

	get_symbol16(symbol_idx, unfolded_symbol);
	if (invert)
	{
		for (i = 0; i < sizeof(unfolded_symbol); ++i)
		{
			unfolded_symbol[i] = ~(unfolded_symbol[i]);
		}
	}

	x = x % (OLED_X_SIZE);
	y = y % (OLED_Y_SIZE / (8 * 2));

	if (x > (OLED_X_SIZE - CHAR_SIZE * 2))
	{
		x = OLED_X_SIZE - CHAR_SIZE * 2;
	}

	display_set_area(x, x + CHAR_SIZE * 2 - 1, y * 2, y * 2 + 1);
	display_send_data(unfolded_symbol, sizeof(unfolded_symbol));
}

void print_mm()
{
	static uint8_t old_symbols[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
	static uint8_t old_highlight = 0xFF;
	uint8_t x;
	uint8_t y;
	uint8_t symbol;
	uint8_t invert;
	uint8_t is_first;
	uint8_t i;

	x = 0;
	y = 0;
	is_first = 1;

	// Print 6 digits (only needed ones)
	for (i = 6; i > 0; --i)
	{
		symbol = extract_digit(count_value, i);
		if (count_highlight == i)
		{
			invert = 1;
		}
		else
		{
			invert = 0;
		}

		// Leading zero removal
		// There's no highlight, this is the first digit to write, it's 0
		// and it's not the last digit
		// or everything is removed
		if ((is_first && (0 == count_highlight) && (0 == symbol) && (1 != i)) ||
			(0xFF == count_highlight))
		{
			symbol = 0xFF;
		}
		else
		{
			is_first = 0;
		}

		// Update only necessary digits
		// On highlight change or on symbol change
		if ((old_highlight != count_highlight) || (symbol != old_symbols[6 - i]))
		{
			print_symbol(symbol, x, y, invert);
			old_symbols[6 - i] = symbol;
		}
		x += (CHAR_SIZE + 1) * 2;
	}

	// Print 'mm' (only after clear)
	if ((0xFF == old_highlight) && (0xFF != count_highlight))
	{
		print_symbol(CHAR_m, x, y, 0);
		x += (CHAR_SIZE + 1) * 2;
		print_symbol(CHAR_m, x, y, 0);
	}

	// Clear 'mm'
	if (0xFF == count_highlight)
	{
		print_symbol(0xFF, x, y, 0);
		x += (CHAR_SIZE + 1) * 2;
		print_symbol(0xFF, x, y, 0);
	}
	old_highlight = count_highlight;
}

void print_direction()
{
	uint8_t symbol;
	static uint8_t old_is_A = 0;
	static uint8_t old_highlight = 0xFF;

	if (rot_dir_is_A)
	{
		symbol = CHAR_A;
	}
	else
	{
		symbol = CHAR_B;
	}

	// Clear it all
	if ((0xFF == dir_highlight) && (old_highlight != dir_highlight))
	{
		print_symbol(0xFF, OLED_X_SIZE - (CHAR_SIZE + 1) * 2 * 2, 0, 0);
		print_symbol(0xFF, OLED_X_SIZE - (CHAR_SIZE + 1) * 2, 0, 0);
	}

	if (0xFF != dir_highlight)
	{
		// Print '>' only when display is comming from clear
		if (0xFF == old_highlight)
		{
			print_symbol(CHAR_gt, OLED_X_SIZE - (CHAR_SIZE + 1) * 2 * 2, 0, 0);
		}

		// Print symbol on change or highlight change
		if ((old_is_A != rot_dir_is_A) || (old_highlight != dir_highlight))
		{
			print_symbol(symbol, OLED_X_SIZE - (CHAR_SIZE + 1) * 2, 0, dir_highlight);
		}
	}
	old_is_A = rot_dir_is_A;
	old_highlight = dir_highlight;
}

void print_clear_menu_area()
{
	uint8_t x;
	uint8_t y;

	for (y = 1; y < (OLED_Y_SIZE / 8) / 2; ++y)
	{
		for (x = 0; x < 8; ++x)
		{
			print_symbol(0xFF, x * (CHAR_SIZE + 1) * 2, y, 0);
		}
	}
}

void print_menu(uint8_t force_draw)
{
	static uint8_t old_option = 0xFF;
	static uint8_t old_spool = 0;
	uint8_t i;

	if (force_draw || (old_option != menu_option) || (old_option == 0 && (old_spool != spool_attached)))
	{
		for (i = 0; i < MENU_COUNT; ++i)
		{
			if (force_draw || (i == old_option) || (i == menu_option))
			{
				print_option(i);
			}
		}
	}

	old_option = menu_option;
	old_spool = spool_attached;
}

void print_option(uint8_t option)
{
	uint8_t select = 0xFF;
	uint8_t x = (CHAR_SIZE + 1) * 2;
	uint8_t y = 1 + option;
	uint8_t i;

	// Whether to invert or not
	if (option == menu_option)
	{
		select = CHAR_gt;
	}

	// Whether to display "Attach" or "Detach"
	if ((0 == option) && spool_attached)
	{
		option += MENU_COUNT;
	}

	print_symbol(select, 0, y ,0);

	for (i = 1; i < MENU_STRLEN; ++i)
	{
		// Get the symbol index from the menu_strings
		print_symbol(pgm_read_byte(&(menu_strings[option * MENU_STRLEN + i])), x, y, 0);
		x += (CHAR_SIZE + 1) * 2;
	}
}

void print_battery_icon()
{
#if 1
	uint32_t idx = (uint32_t) eeprom_idx;

	if (eeprom_idx >= 0)
	{
		print_symbol(extract_digit(idx, 2), OLED_X_SIZE - (CHAR_SIZE + 1) * 2, 1, 0);
		print_symbol(extract_digit(idx, 1), OLED_X_SIZE - (CHAR_SIZE + 1) * 2, 2, 0);
	}
#endif

#if 0
	static uint8_t old_percent = 0xFF;
	uint8_t buf[BUFF_SIZE];
	uint8_t i;
	uint8_t y;
	uint8_t percent;
	uint8_t threshold;
	uint8_t bits;
	uint8_t mask;

	for (i = 0; i < sizeof(buf); ++i)
	{
		buf[i] = 0xFF;
	}

// Buffer safety
#if ((OLED_BATTERY_ICON_Y2 - OLED_BATTERY_ICON_Y1 + 1) * OLED_BATTERY_ICON_SIZE > BUFF_SIZE)
#error "THIS Y OVERFLOWS"
#endif

	// Set bitmask
	bits = 0x00;
	for (i = 0; i < OLED_BATTERY_ICON_SIZE; ++i)
	{
		bits >>= 1;
		bits |= 0x80;
	}

	// Draw left/right borders only once
	if (0xFF == old_percent)
	{
		// Draw left border
		display_set_area(OLED_BATTERY_ICON_X1, OLED_BATTERY_ICON_X1 + OLED_BATTERY_ICON_SIZE - 1,
			OLED_BATTERY_ICON_Y1, OLED_BATTERY_ICON_Y2);
		display_send_data(buf, (OLED_BATTERY_ICON_Y2 - OLED_BATTERY_ICON_Y1 + 1) * OLED_BATTERY_ICON_SIZE);

		// Draw right border
		display_set_area(OLED_BATTERY_ICON_X2 - OLED_BATTERY_ICON_SIZE + 1, OLED_BATTERY_ICON_X2,
			OLED_BATTERY_ICON_Y1, OLED_BATTERY_ICON_Y2);
		display_send_data(buf, (OLED_BATTERY_ICON_Y2 - OLED_BATTERY_ICON_Y1 + 1) * OLED_BATTERY_ICON_SIZE);

		for (i = 0; i < sizeof(buf); ++i)
		{
			buf[i] = (bits >> (8 - OLED_BATTERY_ICON_SIZE));
		}

		// Draw top extra border
		display_set_area(OLED_BATTERY_ICON_X1 + OLED_BATTERY_ICON_SIZE,
			OLED_BATTERY_ICON_X1 + OLED_BATTERY_ICON_SIZE * 2 - 1,
			OLED_BATTERY_ICON_Y1, OLED_BATTERY_ICON_Y1);
		display_send_data(buf, OLED_BATTERY_ICON_SIZE);
		display_set_area(OLED_BATTERY_ICON_X2 - OLED_BATTERY_ICON_SIZE * 2 + 1,
			OLED_BATTERY_ICON_X2 - OLED_BATTERY_ICON_SIZE,
			OLED_BATTERY_ICON_Y1, OLED_BATTERY_ICON_Y1);
		display_send_data(buf, OLED_BATTERY_ICON_SIZE);

		for (i = 0; i < sizeof(buf); ++i)
		{
			buf[i] = bits;
		}

		// Draw bot extra border
		display_set_area(OLED_BATTERY_ICON_X1 + OLED_BATTERY_ICON_SIZE,
			OLED_BATTERY_ICON_X1 + OLED_BATTERY_ICON_SIZE * 2 - 1,
			OLED_BATTERY_ICON_Y2, OLED_BATTERY_ICON_Y2);
		display_send_data(buf, OLED_BATTERY_ICON_SIZE);
		display_set_area(OLED_BATTERY_ICON_X2 - OLED_BATTERY_ICON_SIZE * 2 + 1,
			OLED_BATTERY_ICON_X2 - OLED_BATTERY_ICON_SIZE,
			OLED_BATTERY_ICON_Y2, OLED_BATTERY_ICON_Y2);
		display_send_data(buf, OLED_BATTERY_ICON_SIZE);
	}

	// TODO: Calc battery percent
	percent = 50;

	// Do not draw if not needed
	if ((old_percent / OLED_BATTERY_ICON_PERCENT) == (percent / OLED_BATTERY_ICON_PERCENT))
	{
		old_percent = percent;
		return;
	}

	// Threshold is artificially bigger to not draw over border
	// On lower border it underflows
	threshold = 100 - ((OLED_BATTERY_ICON_PERCENT + 1) / 2) + OLED_BATTERY_ICON_PERCENT * 2;

	for (y = OLED_BATTERY_ICON_Y1; y <= OLED_BATTERY_ICON_Y2; ++y)
	{
		mask = 0x00;

		for (i = 0; i < 8 / OLED_BATTERY_ICON_SIZE; ++i)
		{
			mask >>= OLED_BATTERY_ICON_SIZE;
			if (percent > threshold)
			{
				mask |= bits;
			}

			threshold -= OLED_BATTERY_ICON_PERCENT;
		}

		// Add top border
		if (OLED_BATTERY_ICON_Y1 == y)
		{
			mask |= (bits >> (8 - OLED_BATTERY_ICON_SIZE));
		}

		// Add bottom border
		if (OLED_BATTERY_ICON_Y2 == y)
		{
			mask |= bits;
		}

		// Fill buffer
		for (i = 0; i < sizeof(buf); ++i)
		{
			buf[i] = mask;
		}

		// Diplay percent
		display_set_area(OLED_BATTERY_ICON_X1 + OLED_BATTERY_ICON_SIZE * 2,
			OLED_BATTERY_ICON_X2 - OLED_BATTERY_ICON_SIZE * 2,
			y, y);
		display_send_data(buf, OLED_BATTERY_ICON_X2 - OLED_BATTERY_ICON_X1 -
			OLED_BATTERY_ICON_SIZE * 4 + 1);
	}

// Buffer safety
#if ((OLED_BATTERY_ICON_X2 - OLED_BATTERY_ICON_X1 - OLED_BATTERY_ICON_SIZE * 4 + 1) > BUFF_SIZE)
#error "THIS X OVERFLOWS"
#endif
#endif
}

void print_battery_stat(uint8_t force_draw)
{
	uint8_t symbols[MENU_STRLEN] = {0xFF, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF};
	int16_t old_mAh = 0xFFFF;
	int16_t old_mV = 0xFFFF;
	int16_t old_temp = 0xFFFF;

	if ((old_mAh != battery_mAh) || force_draw)
	{
		symbols[5] = CHAR_m;
		symbols[6] = CHAR_A;
		symbols[7] = CHAR_h;

		print_battery_single_stat(1, battery_mAh, symbols, 5);
		old_mAh = battery_mAh;
	}

	if ((old_mV != battery_mV) || force_draw)
	{
		symbols[5] = CHAR_m;
		symbols[6] = CHAR_V;
		symbols[7] = 0xFF;

		print_battery_single_stat(2, battery_mV, symbols, 5);
		old_mV = battery_mV;
	}

	if ((old_temp != battery_temp) || force_draw)
	{
		symbols[5] = CHAR_deg;
		symbols[6] = CHAR_C;
		symbols[7] = 0xFF;

		print_battery_single_stat(3, battery_temp, symbols, 3);
		old_temp = battery_temp;
	}
}

void print_battery_single_stat(uint8_t y, int16_t bat_stat, uint8_t *symbols, uint8_t decimal_idx)
{
	uint8_t x = 0;
	uint8_t i;
	uint16_t stat = bat_stat;

	// Show +/-
	if (bat_stat < 0)
	{
		symbols[0] = CHAR_lt;
		stat = -stat;
	}
	else
	{
		symbols[0] = CHAR_gt;
	}

	// Extract 4 digits
	for (i = 0; i < 4; ++i)
	{
		if ((4 - i) == decimal_idx)
		{
			symbols[decimal_idx] = CHAR_dot;
			continue;
		}
		symbols[4 - i] = stat % 10;
		stat /= 10;
	}

	// Remove leading zeroes
	for (i = 1; i < 5; ++i)
	{
		if (0 == symbols[i] && (decimal_idx != (i + 1)))
		{
			symbols[i] = 0xFF;
		}
		else
		{
			break;
		}
	}

	// Do the actual print
	for (i = 0; i < MENU_STRLEN; ++i)
	{
		print_symbol(symbols[i], x, y, 0);
		x += (CHAR_SIZE + 1) * 2;
	}
}

int16_t find_eeprom_idx()
{
	uint8_t err;
	int16_t idx = 0;
	uint8_t found = 0;
	uint8_t dir = 0xFF;
	uint32_t value = 0x00FFFFFF;

	// FF is the erased byte value
	for (idx = 0; idx < EEPROM_SIZE / EEPROM_IDX_SIZE; ++idx)
	{
		err = read_eeprom_val(idx, &value, &dir);
		if (0 != err)
		{
			return -1;
		}

		// if ((value != 0x00FFFFFF) && (dir != 0xFF))
		if (value != 0x00FFFFFF)
		{
			found = 1;
			break;
		}
	}

	// Not found = new EEPROM
	if (!found)
	{
		return 0;
	}

	return idx;
}

int8_t read_eeprom_val(int8_t idx, uint32_t *value, uint8_t *direction)
{
	int8_t err;
	uint8_t buf[4];

	if (idx < 0)
	{
		return -1;
	}
	idx %= (EEPROM_SIZE / EEPROM_IDX_SIZE);

	err = i2c_read_buff(EEPROM_ADDRESS, idx * EEPROM_IDX_SIZE, buf, 4);
	if (4 != err)
	{
		return -1;
	}

	(*direction) = buf[0];
	buf[0] = 0;
	(*value) = 0;

	for (uint8_t i = 0; i < 4; ++i)
	{
		(*value) <<= 8;
		(*value) |= buf[i];
	}

	return 0;
}

int8_t write_eeprom_val(int8_t idx, uint32_t value, uint8_t direction)
{
	int8_t err;
	uint8_t buf[4];

	if (idx < 0)
	{
		return -1;
	}
	idx %= (EEPROM_SIZE / EEPROM_IDX_SIZE);

	for (uint8_t i = 0; i < 4; ++i)
	{
		buf[i] = ((value >> ((3 - i) * 8)) & 0xFF);
	}

	buf[0] = direction;

	err = i2c_write_buff(EEPROM_ADDRESS, idx * EEPROM_IDX_SIZE, buf, 4);
	if (4 != err)
	{
		return -1;
	}

	return 0;
}

int8_t reset_battery()
{
	int8_t err;
	uint8_t val;

	// Reset All Battery Counters
	val = 0x02;
	err = i2c_write_buff(BATTERY_ADDRESS, BATTERY_REG_CTRL, &val, 1);
	if (1 != err)
	{
		return -1;
	}

	// Set Running Mode
	val = 0x10;
	err = i2c_write_buff(BATTERY_ADDRESS, BATTERY_REG_MODE, &val, 1);
	if (1 != err)
	{
		return -1;
	}

	return 0;
}

int8_t read_battery(int16_t *mAh, int16_t *mV, int16_t *temp)
{
	// Reading from 0x02 to 0x0B inclusive
	uint8_t buf[BATTERY_BUFF_SIZE];
	int8_t err;
	int32_t val;

	err = i2c_read_buff(BATTERY_ADDRESS, BATTERY_READ_ADDR, buf, BATTERY_BUFF_SIZE);
	if (err != BATTERY_BUFF_SIZE)
	{
		return -1;
	}

	// Read mAh bits
	val = buf[BATTERY_CHARGE + 1];
	val <<= 8;
	val |= buf[BATTERY_CHARGE];

	// If bit 15 is set
	if (val & 0x00008000)
	{
		val -= 65536;
	}

	// Transform into mAh
	val *= BATTERY_CHARGE_MULT;
	val /= BATTERY_CHARGE_DIV;
	(*mAh) = val;

	// Read mV bits
	val = buf[BATTERY_VOLT + 1];
	val <<= 8;
	val |= buf[BATTERY_VOLT];

	// If bit 11 is set
	if (val & 0x00000800)
	{
		val -= 2048;
	}

	// Transform into mV
	val *= BATTERY_VOLT_MULT;
	val /= BATTERY_VOLT_DIV;
	(*mV) = val;

	// Read temp bits
	val = buf[BATTERY_TEMP + 1];
	val <<= 8;
	val |= buf[BATTERY_TEMP];

	// If bit 11 is set
	if (val & 0x00000800)
	{
		val -= 2048;
	}

	// Transform into temp
	val *= BATTERY_TEMP_MULT;
	val /= BATTERY_TEMP_DIV;
	(*temp) = val;

	return 0;
}

int8_t i2c_write_buff(uint8_t i2c_addr, uint8_t data_addr, uint8_t *buf, uint8_t len)
{
	uint8_t err;
	uint8_t i;

	err = i2c_start(i2c_addr | I2C_WRITE);
	if (0 != err)
	{
		i2c_stop();
		return -1;
	}

	err = i2c_write(data_addr);
	if (0 != err)
	{
		i2c_stop();
		return -1;
	}

	for (i = 0; i < len; ++i)
	{
		err = i2c_write(buf[i]);
		if (0 != err)
		{
			i2c_stop();
			return i;
		}
	}

	i2c_stop();
	return i;
}

int8_t i2c_read_buff(uint8_t i2c_addr, uint8_t data_addr, uint8_t *buf, uint8_t len)
{
	uint8_t err;
	uint8_t i;

	err = i2c_start(i2c_addr | I2C_WRITE);
	if (0 != err)
	{
		i2c_stop();
		return -1;
	}

	err = i2c_write(data_addr);
	if (0 != err)
	{
		i2c_stop();
		return -1;
	}

	err = i2c_start(i2c_addr | I2C_READ);
	if (0 != err)
	{
		i2c_stop();
		return -1;
	}

	for (i = 0; i < len; ++i)
	{
		buf[i] = i2c_read(i == len - 1);
	}

	i2c_stop();
	return i;
}

void do_sleep()
{
	// Disable clock to USI
	PRR |= (1 << PRUSI);

	// Enable sleep
	MCUCR |= (1 << SE);

	// Sleep
	sleep_cpu();

	// Reset ms counter
	cli();
	ms = 0;
	sei();

	// Disable sleep
	MCUCR &= ~(1 << SE);

	// Enable clock to USI
	PRR &= ~(1 << PRUSI);
}

uint8_t extract_digit(uint32_t value, uint8_t digit_num)
{
	while (1 != digit_num)
	{
		value /= 10;
		--digit_num;
	}
	return value % 10;
}

uint8_t consume_event()
{
	uint8_t curr_event;

	if (event_count > 0)
	{
		cli();
		curr_event = *event_read;
		PTR_INC(event_read);
		--event_count;
		sei();
	}
	else
	{
		curr_event = EVENT_NONE;
	}

	return curr_event;
}

void spool_count(uint8_t event)
{
	if ((!spool_attached) || (!spool_counting))
	{
		return;
	}

	// A / CW  = +
	// B / CCW = +

	// B / CW  = -
	// A / CCW = -
	if ((rot_dir_is_A && (event == EVENT_ROT_CW)) ||
		(!rot_dir_is_A && (event == EVENT_ROT_CCW)))
	{
		++rot_value;
	}
	else
	{
		--rot_value;
	}

	if (rot_value / (ROT_PULSE_COUNT * 4 / ROT_DETENTS) != 0)
	{
		count_value_fine += rot_coeff * rot_value;
		rot_value = 0;

		if (count_value_fine <= 0)
		{
			// Update EEPROM with 0

			// Clear old cell & write to next one
			write_eeprom_val(eeprom_idx, 0xFFFFFFFF, 0xFF);
			++eeprom_idx;
			eeprom_idx %= EEPROM_SIZE / EEPROM_IDX_SIZE;
			write_eeprom_val(eeprom_idx, 0, rot_dir_is_A);

			count_value_fine = 0;
			count_value = 0;
			spool_counting = 0;

			// Turn Display ON to show value
			display_enable(1);
			read_battery(&battery_mAh, &battery_mV, &battery_temp);
			sleep_when_ms = ms + DISPLAY_DELAY;
		}
		else
		{
			// Update EEPROM every 100 mm
			if ((uint32_t) count_value_fine / 100 != count_value / 100)
			{
				// Clear old cell & write to next one
				write_eeprom_val(eeprom_idx, 0xFFFFFFFF, 0xFF);
				++eeprom_idx;
				eeprom_idx %= EEPROM_SIZE;
				write_eeprom_val(eeprom_idx, (uint32_t) count_value_fine, rot_dir_is_A);
			}
			count_value = (uint32_t) count_value_fine;
		}
	}
}

void process_sleep()
{
	uint8_t curr_event;

	while (event_count > 0)
	{
		curr_event = consume_event();
		switch (curr_event)
		{
		case EVENT_ROT_CW: // Fall-through
		case EVENT_ROT_CCW:
			spool_count(curr_event);
			break;

		case EVENT_BTN_DOWN:
			display_enable(1);
			read_battery(&battery_mAh, &battery_mV, &battery_temp);
			sleep_when_ms = ms + DISPLAY_DELAY;
			state = STATE_IDLE;
			break;
		
		case EVENT_BTN_UP:
			long_press_when_ms = 0xFFFFFFFF;
			break;
		}
	}

	if (STATE_SLEEP == state)
	{
		display_enable(0);
		do_sleep();
	}
}

void process_idle()
{
	uint8_t curr_event;

	while (event_count > 0)
	{
		curr_event = consume_event();
		switch (curr_event)
		{
		case EVENT_ROT_CW: // Fall-through
		case EVENT_ROT_CCW:
			spool_count(curr_event);
			break;

		case EVENT_BTN_DOWN:
			long_press_when_ms = ms + LONG_PRESS;
			sleep_when_ms = ms + DISPLAY_DELAY;
			break;
		
		case EVENT_BTN_UP:
			long_press_when_ms = 0xFFFFFFFF;
			break;
		}
	}

	// Hold button to enter menu
	if (ms >= long_press_when_ms)
	{
		state = STATE_MENU;
		menu_option = 0;
		long_press_when_ms = 0xFFFFFFFF;
		print_menu(1);
	}

	// Go to sleep
	if (ms >= sleep_when_ms)
	{
		state = STATE_SLEEP;
		sleep_when_ms = 0xFFFFFFFF;
	}
}

void process_menu()
{
	uint8_t curr_event;

	while (event_count > 0)
	{
		curr_event = consume_event();
		switch (curr_event)
		{
		case EVENT_ROT_CW: // Fall-through
		case EVENT_ROT_CCW:
			spool_count(curr_event);
			break;

		case EVENT_BTN_DOWN:
			long_press_when_ms = ms + LONG_PRESS;
			sleep_when_ms = ms + DISPLAY_DELAY;
			break;

		case EVENT_BTN_UP:
			if (0xFFFFFFFF != long_press_when_ms)
			{
				// Button Clicked - Do whatever is selected
				menu[menu_option]();
			}
			long_press_when_ms = 0xFFFFFFFF;
			break;

		case EVENT_SEL_UP:
			sleep_when_ms = ms + DISPLAY_DELAY;
			menu_option += MENU_COUNT - 1;
			menu_option %= MENU_COUNT;
			break;
		
		case EVENT_SEL_DOWN:
			sleep_when_ms = ms + DISPLAY_DELAY;
			++menu_option;
			menu_option %= MENU_COUNT;
			break;
		}

		if (state != STATE_MENU)
		{
			break;
		}
	}

	// Hold button to exit menu
	if (ms >= long_press_when_ms)
	{
		print_clear_menu_area();
		state = STATE_IDLE;
		long_press_when_ms = 0xFFFFFFFF;
	}
	else
	{
		// Print Menu in auto mode
		print_menu(0);
	}

	if (ms >= sleep_when_ms)
	{
		print_clear_menu_area();
		state = STATE_SLEEP;
		sleep_when_ms = 0xFFFFFFFF;
	}
}

void process_adjust()
{
	uint8_t curr_event;
	uint32_t digit_val = 1;

	for (uint8_t i = 1; i < count_highlight; ++i)
	{
		digit_val *= 10;
	}

	// NO SLEEP
	sleep_when_ms = 0xFFFFFFFF;
	while (event_count > 0)
	{
		curr_event = consume_event();
		switch (curr_event)
		{
		case EVENT_BTN_DOWN:
			long_press_when_ms = ms + LONG_PRESS;
			break;

		case EVENT_BTN_UP:
			if (0xFFFFFFFF != long_press_when_ms)
			{
				// Button Clicked - Go to next digit or direction
				if (count_highlight)
				{
					--count_highlight;
					if (0 == count_highlight)
					{
						dir_highlight = 1;
					}
				}
				else
				{
					// Wrap around
					dir_highlight = 0;
					count_highlight = 6;
				}
			}
			long_press_when_ms = 0xFFFFFFFF;
			break;

		case EVENT_SEL_UP:
			// Digit ++
			if (0 != count_highlight)
			{
				if (9 == extract_digit(count_value, count_highlight))
				{
					count_value -= digit_val * 10;
				}
				count_value += digit_val;
			}
			else
			{
				rot_dir_is_A = !rot_dir_is_A;
			}
			break;
		
		case EVENT_SEL_DOWN:
			// Digit --
			if (0 != count_highlight)
			{
				if (0 == extract_digit(count_value, count_highlight))
				{
					count_value += digit_val * 10;
				}
				count_value -= digit_val;
			}
			else
			{
				rot_dir_is_A = !rot_dir_is_A;
			}
			break;
		}
	}

	// Hold button to stop
	if (ms >= long_press_when_ms)
	{
		count_value_fine = count_value;

		// Write to EEPROM
		// Clear old cell & write to next one
		write_eeprom_val(eeprom_idx, 0xFFFFFFFF, 0xFF);
		++eeprom_idx;
		eeprom_idx %= EEPROM_SIZE / EEPROM_IDX_SIZE;
		write_eeprom_val(eeprom_idx, count_value, rot_dir_is_A);

		// Go back to idle
		state = STATE_IDLE;
		long_press_when_ms = 0xFFFFFFFF;
		sleep_when_ms = ms + DISPLAY_DELAY;
		spool_counting = 1;

		count_highlight = 0;
		dir_highlight = 0;
	}
}

void process_battery()
{
	uint8_t curr_event;

	while (event_count > 0)
	{
		curr_event = consume_event();
		switch (curr_event)
		{
		case EVENT_ROT_CW: // Fall-through
		case EVENT_ROT_CCW:
			spool_count(curr_event);
			break;

		case EVENT_BTN_DOWN:
			sleep_when_ms = ms + DISPLAY_DELAY;
			long_press_when_ms = ms + LONG_PRESS;
			break;

		case EVENT_BTN_UP:
			if (0xFFFFFFFF != long_press_when_ms)
			{
				// Button Clicked - Update Battery Info
				read_battery(&battery_mAh, &battery_mV, &battery_temp);
				print_battery_icon();
				print_battery_stat(0);
			}
			long_press_when_ms = 0xFFFFFFFF;
			break;
		}
	}

	// Hold button to return to idle
	if (ms >= long_press_when_ms)
	{
		print_clear_menu_area();
		state = STATE_IDLE;
		long_press_when_ms = 0xFFFFFFFF;
	}

	if (ms >= sleep_when_ms)
	{
		print_clear_menu_area();
		state = STATE_SLEEP;
		sleep_when_ms = 0xFFFFFFFF;
	}
}

void attach_detach_spool()
{
	if (spool_attached)
	{
		// Detach
		// Clear old cell & write to next one
		write_eeprom_val(eeprom_idx, 0xFFFFFFFF, 0xFF);
		++eeprom_idx;
		eeprom_idx %= EEPROM_SIZE;
		write_eeprom_val(eeprom_idx, (uint32_t) count_value_fine, rot_dir_is_A);

		spool_counting = 0;
		spool_attached = 0;
		count_highlight = 0xFF;
		dir_highlight = 0xFF;

		// Disable rotary encoder interrupts
		GIMSK &= (~(1 << PCIE1));
	}
	else
	{
		// Attach
		// Detect EEPROM
		eeprom_idx = find_eeprom_idx();
		if (-1 == eeprom_idx)
		{
			return;
		}
		
		// Read Value
		if (0 != read_eeprom_val(eeprom_idx, &count_value, &rot_dir_is_A))
		{
			return;
		}
		
		// New EEPROM
		if (0x00FFFFFF == count_value)
		{
			spool_counting = 0;
			count_value = 0;
			count_value_fine = 0;
			rot_dir_is_A = 1;
		}
		else
		{
			spool_counting = 1;
			count_value_fine = count_value;
		}
		spool_attached = 1;
		count_highlight = 0;
		dir_highlight = 0;

		// Enable rotary encoder interrupts
		GIMSK |= (1 << PCIE1);
	}
}

void set_adjust_spool()
{
	if (!spool_attached)
	{
		return;
	}

	print_clear_menu_area();
	count_highlight = 6;
	spool_counting = 0;
	state = STATE_ADJUST;
}

void set_battery_state()
{
	print_clear_menu_area();
	state = STATE_BATTERY;
	read_battery(&battery_mAh, &battery_mV, &battery_temp);
	print_battery_stat(1);
}
