/**
 * Name        : main.c
 * Version     :
 * Description : main definition for FreeRTOS application
 */

/*
 * FreeRTOS includes
 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "LPC17xx.h"
#include "i2c.h"

#define USERTASK_STACK_SIZE configMINIMAL_STACK_SIZE

// macro for changing state. Correct operation requires that me is pointer to state machine instance (see state template)
#define TRAN(st) me->next_state = st

typedef struct event_ {
	uint32_t type; // event type (= what happened)
	uint32_t value; // parameter (for example key number)
} event;

typedef struct smi_ smi;

typedef void (*smf)(smi *, const event *);  // prototype of state handler function pointer

#define SEQ_LEN 5
struct smi_ {
	smf state;  // current state (function pointer)
	smf next_state; // next state (function pointer)
	int timer;  // used for time keeping
	int counter; // counter for key presses in programming mode
	char seq[SEQ_LEN]; // opening sequence
};

enum eventTypes { eEnter, eExit, eTick, eKey };

static const event evEnter = { eEnter, 0 };
static const event evExit = { eExit, 0 };

static xQueueHandle key_q;

void __error__(char *pcFilename, unsigned long ulLine) {
}

static void setupHardware(void) {
	// TODO: Put hardware configuration and initialisation in here
	LPC_GPIO0->FIODIR |= (1 << 22); // LPCXpresso user led
	LPC_GPIO0->FIODIR |= (1 << 15); // exp. board led 1
	LPC_GPIO0->FIODIR |= (1 << 16); // exp. board led 2
	LPC_GPIO0->FIODIR |= (1 << 23); // exp. board led 3
	LPC_GPIO0->FIODIR |= (1 << 24); // exp. board led 4
	LPC_GPIO0->FIODIR |= (1 << 29); // ETH LED
	LPC_GPIO0->FIODIR |= (1 << 30); // ETH LED

	LPC_GPIO0->FIODIR &= ~(1 << 6);
	LPC_GPIO0->FIODIR &= ~(1 << 7);
	LPC_GPIO0->FIODIR &= ~(1 << 8);
	LPC_GPIO0->FIODIR &= ~(1 << 9);
	
	// Warning: If you do not initialize the hardware clock, the timings will be inaccurate
}

void led_set(int led, int state)
{
	switch(led) {
	case 1:
		if(state) LPC_GPIO0->FIOSET = (1 << 15); // exp. board led 1
		else LPC_GPIO0->FIOCLR = (1 << 15); // exp. board led 1
		break;
	case 2:
		if(state) LPC_GPIO0->FIOSET = (1 << 16); // exp. board led 2
		else LPC_GPIO0->FIOCLR = (1 << 16); // exp. board led 2
		break;
	case 3:
		if(state) LPC_GPIO0->FIOSET = (1 << 23); // exp. board led 3
		else LPC_GPIO0->FIOCLR = (1 << 23); // exp. board led 3
		break;
	case 4:
		if(state) LPC_GPIO0->FIOSET = (1 << 24); // exp. board led 4
		else LPC_GPIO0->FIOCLR = (1 << 24); // exp. board led 4
		break;
	case 5:
		if(state) LPC_GPIO0->FIOSET = (1 << 22); // LPCXpresso user led
		else LPC_GPIO0->FIOCLR = (1 << 22); // LPCXpresso user led
		break;
	case 6:
		if(state) LPC_GPIO0->FIOSET = (1 << 29); // ethernet led
		else LPC_GPIO0->FIOCLR = (1 << 29); // ethernet led
		break;
	case 7:
		if(state) LPC_GPIO0->FIOSET = (1 << 30); // ethernet led
		else LPC_GPIO0->FIOCLR = (1 << 30); // ethernet led
		break;
	}
}

int button_read(int button)
{
	int value = 0;

	switch(button) {
	case 1:
		value = !(LPC_GPIO0->FIOPIN & (1 << 9));
		break;
	case 2:
		value = !(LPC_GPIO0->FIOPIN & (1 << 8));
		break;
	case 3:
		value = !(LPC_GPIO0->FIOPIN & (1 << 7));
		break;
	case 4:
		value = !(LPC_GPIO0->FIOPIN & (1 << 6));
		break;
	}
	return value;
}

// this requires that configUSE_TICK_HOOK is set to 1 in FreeRTOSConfig.h
void vApplicationTickHook( void )
{
	static int ticks;

	ticks++;
	if(ticks >= configTICK_RATE_HZ) {
		event e;
		portBASE_TYPE x; // not really used for anything  but required by ISR queue send call
		e.type = eTick;
		e.value = ticks;
		xQueueSendToBackFromISR(key_q, &e, &x); // Tick hook runs within ISR context so we need to use ISR send call
		ticks = 0;
	}

}
/**
 * Read and debounce buttons and send key press events
 */
void vUserTask1(void *pvParameters) {
	int i = 0;
	int laskuri = 0;
	struct {
		int count;
		int state;
	} napit[4] = { { 0 } };
	event nappi;

	while (1) {
		for(i = 0; i < 4; i++) {
			if(button_read(i+1)) napit[i].count++;
			else napit[i].count--;
			if(napit[i].count<0) napit[i].count=0;
			if(napit[i].count>3) napit[i].count=3;

			if(napit[i].count==3 && napit[i].state == 0) {
				napit[i].state = 1;
				nappi.type = eKey;
				nappi.value = i + 1;
				// lähetä jonoon
				xQueueSendToBack((xQueueHandle)pvParameters, &nappi, 0);
			}
			if(napit[i].count==0) napit[i].state = 0;

		}
		if(napit[0].state==1 && napit[3].state==1) {
			laskuri++;
			if(laskuri == 100) { // pressed for one second, send two events
				nappi.type = eKey;
				nappi.value = 5;
				// lähetä jonoon
				xQueueSendToBack((xQueueHandle)pvParameters, &nappi, 0);
			}
		}
		else {
			laskuri = 0;
		}
		vTaskDelay(10);
	}
}

#if 0
/* this is state template */
void stStateTemplate(smi *me, const event *e)
{
	switch(e->type) {
	case eEnter:
		break;
	case eExit:
		break;
	case eKey:
		break;
	case eTick:
		break;
	}
}
#endif

// forward declarations of states
void stLukossa(smi *me, const event *e);
void stOikein1(smi *me, const event *e);
void stOikein2(smi *me, const event *e);
void stOikein3(smi *me, const event *e);
void stOikein4(smi *me, const event *e);
void stAuki(smi *me, const event *e);
void stAseta(smi *me, const event *e);

void led_bar(int length)
{
	int i;
	for(i = 1; i <= length && i <= 4; i++) {
		led_set(i, 1);
	}
	for(; i <= 4; i++) {
		led_set(i, 0);
	}
}

void stLukossa(smi *me, const event *e)
{
	switch(e->type) {
	case eEnter:
		i2c_read(0xA0, 0, SEQ_LEN, me->seq);
		break;
	case eExit:
		break;
	case eKey:
		if(e->value == 5) TRAN(stAseta);
		else if(e->value == me->seq[0]) TRAN(stOikein1);
		break;
	case eTick:
		break;
	}
}

void stOikein1(smi *me, const event *e)
{
	switch(e->type) {
	case eEnter:
		me->timer = 0;
		//led_bar(4);
		break;
	case eExit:
		break;
	case eKey:
		if(e->value == 5) TRAN(stAseta);
		else if(e->value == me->seq[1]) TRAN(stOikein2);
		else TRAN(stLukossa);
		break;
	case eTick:
		me->timer++;
		//led_bar(5 - me->timer);
		if(me->timer >= 5) TRAN(stLukossa);
		break;
	}
}


void stOikein2(smi *me, const event *e)
{
	switch(e->type) {
	case eEnter:
		me->timer = 0;
		break;
	case eExit:
		break;
	case eKey:
		if(e->value == 5) TRAN(stAseta);
		else if(e->value == me->seq[2]) TRAN(stOikein3);
		else TRAN(stLukossa);
		break;
	case eTick:
		me->timer++;
		if(me->timer >= 5) TRAN(stLukossa);
		break;
	}
}


void stOikein3(smi *me, const event *e)
{
	switch(e->type) {
	case eEnter:
		me->timer = 0;
		break;
	case eExit:
		break;
	case eKey:
		if(e->value == 5) TRAN(stAseta);
		else if(e->value == me->seq[3]) TRAN(stOikein4);
		else TRAN(stLukossa);
		break;
	case eTick:
		me->timer++;
		if(me->timer >= 5) TRAN(stLukossa);
		break;
	}
}


void stOikein4(smi *me, const event *e)
{
	switch(e->type) {
	case eEnter:
		me->timer = 0;
		break;
	case eExit:
		break;
	case eKey:
		if(e->value == 5) TRAN(stAseta);
		else if(e->value == me->seq[4]) TRAN(stAuki);
		else TRAN(stLukossa);
		break;
	case eTick:
		me->timer++;
		if(me->timer >= 5) TRAN(stLukossa);
		break;
	}
}



void stAuki(smi *me, const event *e)
{
	switch(e->type) {
	case eEnter:
		me->timer = 0;
		led_set(1,1);
		break;
	case eExit:
		led_set(1,0);
		break;
	case eKey:
		break;
	case eTick:
		me->timer++;
		if(me->timer >= 5) TRAN(stLukossa);
		break;
	}
}

void stAseta(smi *me, const event *e)
{
	switch(e->type) {
	case eEnter:
		me->counter = 0;
		led_set(4,1);
		led_set(3,1);
		break;
	case eExit:
		i2c_write(0xA0, 0, SEQ_LEN, me->seq);
		led_set(4,0);
		led_set(3,0);
		break;
	case eKey:
		if(e->value != 5) { // do not accept setup key into key sequence
			me->seq[me->counter] = e->value;
			me->counter++;
			if(me->counter >= 5) TRAN(stLukossa);
		}
		break;
	case eTick:
		break;
	}
}



/**
 * Receive events from queue and dispatch them to state machine
 */
void vUserTask2(void *pvParameters) {
	event e;
	smi me = { stLukossa, stLukossa, 0, 0, { 1,2,3,4,1 }}; // initialize state machine instance

	i2c_init(); // initialize i2c interface (eeprom in on i2c bus)

	me.state(&me, &evEnter); // enter initial state

	while (1) {
		// read queue
		xQueueReceive((xQueueHandle)pvParameters, &e, portMAX_DELAY);
		me.state(&me, &e); // dispatch event to current state
		if(me.state != me.next_state) { // check if state was changed
			me.state(&me, &evExit); // exit old state (cleanup)
			me.state = me.next_state; // change state
			me.state(&me, &evEnter); // enter new state
		}
	}
}

/**
 * Program entry point 
 */
int main(void) {
	setupHardware();

	// create queue to hold
	key_q = xQueueCreate(20, sizeof(event));
	/* 
	 * Start the tasks defined within this file/specific to this demo. 
	 */
	xTaskCreate( vUserTask1, ( signed portCHAR * ) "Task1", USERTASK_STACK_SIZE, key_q, tskIDLE_PRIORITY, NULL );
	xTaskCreate( vUserTask2, ( signed portCHAR * ) "Task2", USERTASK_STACK_SIZE, key_q, tskIDLE_PRIORITY, NULL );

	/* 
	 * Start the scheduler. 
	 */
	vTaskStartScheduler();

	/* 
	 * Will only get here if there was insufficient memory to create the idle task. 
	 */
	return 1;
}
