/*
 * uart_echo.c
 *
 * Created: 16.08.2018 17:39:49
 * Author : jens
 */ 

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>

#include "machine.h"
#include "fifo.h"
#include "message.h"
#include "adc.h"
#include "port.h"
#include "timer.h"
#include "uart.h"
#include "i2c.h"
#include "mcp42x6.h"

#define PRINT(a,...)	printf(a, ##__VA_ARGS__)
#define PRINT_DEBUG(a,...)				\
		if (g_debug)					\
		{								\
			printf(a, ##__VA_ARGS__);	\
		}								

#define PRINT_CRLF				putchar(0x0D);putchar(0x0A);
#define PRINT_ANSWER(a,...)		PRINT_CRLF; printf(a, ##__VA_ARGS__);
#define PRINT_PROMPT			PRINT_CRLF; putchar(':');
#define USART_BAUDRATE			(115200UL)
#define TIMER_HW_CLOCK			(F_CPU/8)
#define TIMER_IRQ_CLOCK			1000UL
#define TIMER_HW_RELOAD			(0xFFFF-(TIMER_HW_CLOCK/TIMER_IRQ_CLOCK))

#define TIMER_SW_DELAY_MS(dly)	((dly*TIMER_IRQ_CLOCK)/1000UL)

FILE uart_file = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

size_t cmdStrSize = 0;
Fifo messageFifo;
int g_debug = 0;

// Switch IN
int16_t g_switch_in_digits = 0;
int g_switch_in = 0xFFFF;
int g_switch_in_last = 0xFFFF;

// Switch OUT
int g_switch_out = 0;

// Poti IN
uint16_t g_poti_in_digits = 0;

// Poti OUT
uint16_t g_poti_out_digits = 0;

typedef enum _eState_t
{
	StateNormal = 0,
	StateRemote = 1,
	StateSuspend = 2,
	StateError = 3,
	State_NumStates
} State_t;

static const char SW_IDENTIFIER[] = "Hendi-Control"; 
static const char SW_VERSION[] = "v1.12"; 
static const char *state_str[State_NumStates] = {"Normal", "Remote", "Suspend", "Error"};
static const uint16_t POTI_HSYTERSE = 1;

typedef enum _eSwitch
{
	SwitchOff = 0,
	SwitchOn = 1	
} Switch;

void setSwitch(int on)
{
	g_switch_out = on;
	const char *sw_str[2] = {"OFF", "ON"};
	PRINT_DEBUG("Set switch to %s\n", sw_str[on != 0]);
	portSet(PwrSwitch_out, on != 0);
}

typedef enum _ePower
{
	PowerLow = 4095,
	PowerMax = 0	
} Power;

void setPower(uint16_t digits)
{
	g_poti_out_digits = digits;
	PRINT_DEBUG("Set Power to %u digits\n", digits);
	uint8_t dac_cmd[] = {0x00, 0x00};					// 6.2 Write Volatile Memory (C2:C0 = ‘010’)
	size_t res = MCP47x6_write_volatile_dac(digits, dac_cmd, sizeof(dac_cmd));
	i2c_send(0x60, I2C_WRITE, dac_cmd, res);
}

int main(void)
{
	int hb_state = 0;
	cli();
    wdt_reset();
    wdt_disable();
		
	State_t state = StateSuspend;
	State_t state_next = StateSuspend;
	
	fifo_init(&messageFifo, 8, sizeof(Msg_t), "Fifo");
	
    /* Replace with your application code */
	uart_init(&messageFifo, USART_BAUDRATE);
	timer_init(&messageFifo, TIMER_HW_RELOAD, TimerClockSel_PS_8);
	adc_init(&messageFifo);
	i2c_init();
	port_init();
	
	stdout = &uart_file;
	printf("%s %s\n", SW_IDENTIFIER, SW_VERSION);

	uint8_t dac_cmd[] = {0x00, 0x00, 0x00};		// 6.1 Write Volatile DAC Register (C2:C0 = ‘00x’)
	size_t res = MCP47x6_write_volatile_mem(0x0000, dac_cmd, sizeof(dac_cmd));
	i2c_send(0x60, I2C_WRITE, dac_cmd, res);

	char cmdStr[16];
	PRINT_PROMPT;
	
	// Start Timer
	timer_start(TIMER_GENERAL, 0);
	timer_start(TIMER_ADC, 0);

	sei();
	while (1) 
    {
		state_next = state;
		Msg_t msg;
		if (!fifo_isEmpty(&messageFifo))
		{
			fifo_pop(&messageFifo, &msg);
			switch(msg.code)
			{
				case Uart:
				{
					// Process Uart message
					char c = (char)msg.m.uart.data;
					cmdStr[cmdStrSize] = 0;
					
					// Command finished with CR/LF
					if (c == 0x0D)
					{
						char *pArgs = cmdStr;
						cmdStrSize = 0;
						while (*pArgs)
						{
							// Power Control
							if (toupper(*pArgs) == 'P')
							{
								pArgs++;
								if ((*pArgs) == '?')
								{
									pArgs++;
									PRINT_ANSWER("OK:%04u", g_poti_out_digits);
								}
								else
								{
									if (!isdigit(*pArgs))
									{
										break;
									}
									uint16_t arg = (uint16_t)strtol(pArgs, &pArgs, 10);
									if (state == StateRemote)
									{
										setPower(arg);
										PRINT_ANSWER("OK:%04u", g_poti_out_digits);
									}
									else
									{
										PRINT_ANSWER("ERR:Invalid state");
									}
								}
							}

							// Switch Control
							else if (toupper(*pArgs) == 'S')
							{
								pArgs++;
								if ((*pArgs) == '?')
								{
									pArgs++;
									PRINT_ANSWER("OK:%u", g_switch_out);
								}
								else
								{
									if (!isdigit(*pArgs))
									{
										break;
									}
									uint16_t arg = (uint16_t)strtol(pArgs, &pArgs, 10);
								
									if (state == StateRemote)
									{
										setSwitch(arg);
										PRINT_ANSWER("OK:%u", g_switch_out);
									}
									else
									{
										PRINT_ANSWER("ERR:Invalid state");
									}
								}
							}

							// Enter/exit remote
							else if (toupper(*pArgs) == 'R')
							{
								pArgs++;
								if ((*pArgs) == '?')
								{
									pArgs++;
									PRINT_ANSWER("OK:%d", (int)(state_next == StateRemote));
								}
								else
								{
									if (!isdigit(*pArgs))
									{
										break;
									}
									uint16_t arg = (uint16_t)strtol(pArgs, &pArgs, 10);
									if (arg == 1)
									{
										// Enter remote
										if (state == StateNormal || state == StateSuspend)
										{
											setPower(PowerLow);
											setSwitch(SwitchOff);
											state_next = StateRemote;
											PRINT_ANSWER("OK:%d", arg);
										}
										else
										{
											PRINT_ANSWER("ERR:Invalid state");
										}
									}
									else if (arg == 0)
									{
										// Exit remote
										if (state == StateRemote)
										{
											setPower(PowerLow);
											setSwitch(SwitchOff);
											state_next = StateSuspend;
											PRINT_ANSWER("OK:%d", arg);
										}
										else
										{
											PRINT_ANSWER("ERR:Invalid state");
										}
									}
								}
							}

							// Debug Control
							else if (toupper(*pArgs) == 'D')
							{
								pArgs++;
								if ((*pArgs) == '?')
								{
									pArgs++;
									PRINT_ANSWER("OK:%d", (int)(state_next == StateRemote));
								}
								else
								{
									if (!isdigit(*pArgs))
									{
										break;
									}
									g_debug = (uint16_t)strtol(pArgs, &pArgs, 10);
									PRINT_ANSWER("OK:%d", g_debug);
								}
							}
							
							// SW identifier
							else if (toupper(*pArgs) == 'I')
							{
								pArgs++;
								if ((*pArgs) == '?')
								{
									pArgs++;
									PRINT_ANSWER("OK:%s", SW_IDENTIFIER);
								}
							}

							// Version
							else if (toupper(*pArgs) == 'V')
							{
								pArgs++;
								if ((*pArgs) == '?')
								{
									pArgs++;
									PRINT_ANSWER("OK:%s", SW_VERSION);
								}
							}
							
							// State
							else if (toupper(*pArgs) == 'T')
							{
								pArgs++;
								if ((*pArgs) == '?')
								{
									pArgs++;
									const char *pState = state_str[state];
									PRINT_ANSWER("OK:%s", pState);
								}
							}
							
							// no match
							else
							{
								pArgs++;
								PRINT_ANSWER("ERR:Unknown command");
							}
							
						} // while (*pArgs)
						timer_start(TIMER_TIMEOUT, TIMER_SW_DELAY_MS(10000));
						PRINT_PROMPT;
					}
					else
					{
						if ((c != 0x0A) && (c != 0x0D))
						{
							if (cmdStrSize < (sizeof(cmdStr)-1))
							{
								cmdStr[cmdStrSize++] = c;
							}
							putchar(c);
						}
					} // if (c == 0x0D)
				} // case Uart:
				break;

				case AdcComplete:
				{
					uint8_t adc_ch = msg.m.adc.ch;
					int16_t adc_curr = (int16_t)msg.m.adc.data;
					if (adc_ch == 0)
					{
						if (((adc_curr - (int16_t)g_poti_in_digits) <= -(int16_t)POTI_HSYTERSE) || ((adc_curr - (int16_t)g_poti_in_digits) >= (int16_t)POTI_HSYTERSE))
						{
							if (state == StateNormal)
							{
								setPower(adc_curr << 2);
							}
							g_poti_in_digits = (uint16_t)adc_curr;
						}
					}
					if (adc_ch == 2)
					{
						g_switch_in_digits = adc_curr;
						g_switch_in = (int)(g_switch_in_digits > 512);
					}
				}
				break;

				case Timer:
				{
					int timer_id = msg.m.timer.ch;
					switch(timer_id)
					{
						case TIMER_GENERAL:
						{
							uint16_t interval = 1000;
							uint16_t pulse = 50;
							if (state == StateRemote)
							{
								interval = 500;
								pulse = 50;
							}

							hb_state = !hb_state;
							if (hb_state)
							{
								timer_start(timer_id, TIMER_SW_DELAY_MS(pulse));
							}
							else
							{
								timer_start(timer_id, TIMER_SW_DELAY_MS((interval-pulse)));
							}
							portSet(Led_HB, hb_state);
						}
						break;
						
						case TIMER_TIMEOUT:
						{
							if (state == StateRemote)
							{
								PRINT_DEBUG("Remote timeout!\n");
								state_next = StateError;
							}
						}
						break;
						
						case TIMER_ADC:
						{
							timer_start(timer_id, TIMER_SW_DELAY_MS(50));
							if (g_switch_in != g_switch_in_last)
							{
								PRINT_DEBUG ("PwrSwitch_in = %d\n", g_switch_in);
								if (g_switch_in == 0)
								{
									if (state == StateError || state == StateRemote || state == StateSuspend)
									{
										setSwitch(SwitchOn);
										state_next = StateNormal;
									}
								}
							}
							g_switch_in_last = g_switch_in;
							adc_enqueue_conversion(0);
							adc_enqueue_conversion(2);
						}
						break;
					}
				}
				break;

				case NOP:
				break;
			}
		}
		if (state != state_next)
		{
			PRINT_DEBUG("State change \"%s\" => \"%s\"\n", state_str[state], state_str[state_next]);
			if (state_next == StateError)
			{
				setPower(PowerLow);
				setSwitch(SwitchOff);
				portSet(Led_ERR, 1);
			}
			if (state_next == StateNormal)
			{
				portSet(Led_ERR, 0);
			}
		}
		state = state_next;

    }
}

