/*
*  C Implementation: devicePart
*
* Description: 
*
*
* Author: Juergen Sachs <jsachs@mobacomp.de>, (C) 2007
*
* Copyright: See COPYING file that comes with this distribution
*
*/

#include <avr/io.h>
#include <string.h>
#include <stdlib.h>
#include <avr/pgmspace.h>

#include "buslogic/busAPI.h"
#include "softuart.h"
#include "lcd_hd44780.h"
#include "setup_pages.h"
#include "avr_sk1.h"
#include "lcdBuffer.h"
#include "devstatus.h"

// Pinconfigurations
// Backlight
#define BACKLIGHT_PORT	PORTA
#define BACKLIGHT_PIN		PINA
#define BACKLIGHT_DDR		DDRA
#define BACKLIGHT_PORTPIN	PA1
// Keyport
#define KEY_PORT	PORTB
#define KEY_PIN		PINB
#define KEY_DDR		DDRB
// BUS LED
#define BUSLED_PORT	PORTD
#define BUSLED_PIN	PIND
#define BUSLED_DDR	DDRD
#define BUSLED_PORTPIN PD3

// Bitmaske der Tasten die ins Setup Menü führen
#define SETUP_KEYS 0xaf

prog_char DEVICE_VERSION[] = "v0.97 SK1";
// The firmware ID should be uniqu for all devices
#define FW_ID 0x0010

volatile uint8_t timerFlag;
struct _sChannel chan;
struct _sLevel level[9];
struct _sString str;
struct _sString cmd;

struct _sBacklight Backlight;
struct _sDevStatus  devStatus;
struct _sLcdBuffer lcdBuffer;

prog_char HOMEPAGE_URL[] = "http://www.mobacomp.de/";
prog_char TEXT_UNKNOWN[] = "Unknown:    ";
prog_char PRODUCT_NAME[] = "MobaComp Softkey SK1";

prog_char MASTER_OFFLINE[] = "Waiting for Master";
prog_char MASTER_ONLINE[] = "Master present";

char TEXT[] = "SLEEP";
uint8_t TEXT_LEN = 5;
char CMD[] = "Cmd";
uint8_t CMD_LEN = 3;

uint8_t lev = 1;

// Struct for all supported commands
struct _sCmd
{
	prog_char *cmd;
	const uint8_t len;
};

// Unterstützte Kommandos des Device
prog_char cmd_clear[] 	= "CLS";
prog_char cmd_pos[]		= "POS-";
prog_char cmd_version[] = "?VER";
prog_char cmd_text[] = "TEXT";
prog_char cmd_set_baud[] = "SET BAUD ";
prog_char cmd_rxon[] = "RXON";
prog_char cmd_rxoff[] = "RXOFF";
prog_char cmd_backlight[] = "BACKLIGHT=";
prog_char cmd_timeout[] = "TIMEOUT=";
prog_char cmd_levon[] = "LEVON";
prog_char cmd_levoff[] = "LEVOFF";
prog_char cmd_loadchar[] = "LOADCHAR=";
prog_char cmd_reloadchar[] = "RELOADCHAR";
prog_char cmd_setup[] = "SETUP";

#define CMD_CLS 0
#define CMD_POS 1
#define CMD_VERSION 2
#define CMD_TEXT 3
#define CMD_SET_BAUD 4
#define CMD_RXON 5
#define CMD_RXOFF 6
#define CMD_BACKLIGHT 7
#define CMD_TIMEOUT 8
#define CMD_LEVON 9
#define CMD_LEVOFF 10
#define CMD_LOADCHAR 11
#define CMD_RELOADCHAR 12
#define CMD_SETUP 13

struct _sCmd cmds[] =
{
	{ cmd_clear, 3 },
	{ cmd_pos, 4 },
	{ cmd_version, 4 },
	{ cmd_text, 4 },
	{ cmd_set_baud, 9 },
	{ cmd_rxon, 4 },
	{ cmd_rxoff, 5 },
	{ cmd_backlight, 10 },
	{ cmd_timeout, 8 },
	{ cmd_levon, 5 },
	{ cmd_levoff, 6 },
	{ cmd_loadchar, 9 },
	{ cmd_reloadchar, 10 },
	{ cmd_setup, 5 }
};

void _sBacklight::setBacklight(bool status)
{
	if (status) 
	{
		BACKLIGHT_PORT &= ~_BV(BACKLIGHT_PORTPIN);
	}
	else 
	{
		BACKLIGHT_PORT |= _BV(BACKLIGHT_PORTPIN);
	}
	this->status = status;
	timeoutcnt  = 0;
}

// Report our device version to the api
prog_char *getDeviceVersion(void)
{
	return DEVICE_VERSION;
}

// The API "poll" if we have changes to report, be quick here
uint8_t getChanges(void)
{
	uint8_t res = CH_NONE;
	if (chan.channel != 0) 	res |= chan.type;
	if (level[lev].level != 0) 		res |= CH_LEVEL;
	if (str.string != 0) 		res |= CH_STRING;
	if (cmd.string != 0) 		res |= CH_COMMAND;
	return res;
}

// The api request our channel change
struct _sChannel *getChannel(void)
{
	static struct _sChannel rep;	// Für Rückgabe
	rep.channel		= chan.channel;
	rep.val 			= chan.val;
	rep.type 			= chan.type;
	chan.channel	= 0;	// Mark channel as reported
	return &rep;
}

// The api request our level change
struct _sLevel *getLevel(void)
{
	static struct _sLevel rep;	// Für Rückgabe
	rep.level = level[lev].level;
	rep.val = level[lev].val;
	level[lev].level = 0;	// Mark channel as reported
	return &rep;
}

// The API request our String
struct _sString *getString(void)
{
	static struct _sString st;
	st.string = str.string;
	st.len = str.len;
	str.string = 0;	// Mark string as reported
	return &st;
}

// The API request our Command
struct _sString *getCommand(void)
{
	static struct _sString st;
	st.string = cmd.string;
	st.len = cmd.len;
	cmd.string = 0;	// Mark command as reported
	return &st;
}

void Delay(void)
{
	for (uint16_t cnt2 = 0 ; cnt2 < 0xFFFF ; cnt2++) nop();
}

// The API calls this when we have to init our device
// This is only called once after reset !
void initDevice(void)
{
	uint16_t cnt;
	uint16_t cnt2;
	
	BUSLED_DDR |= _BV(BUSLED_PORTPIN);	// Bus LED
	
	// Siganel wir booten
	for (cnt = 0 ; cnt <=30 ; cnt++)
	{
		setBusLED(true);
		for (cnt2 = 0 ; cnt2 < 0xFFFF ; cnt2++) nop();
		setBusLED(false);
		for (cnt2 = 0 ; cnt2 < 0xFFFF ; cnt2++) nop();
	};

	// Alle Tastenpins auf Eingang und pullup an
	KEY_DDR = 0x00;		// Eingang
	KEY_PORT = 0xFF;	// Pullup an
	
	// Wir machen als kurz Pause was anderes zwischendurch
	uart_init(38400);	// Init Softuart with baudrate
	
	lcdInit();
		
	loadDisplayChar(MAX_CHARS);	// Custom Font laden	
	
	// Nun Informatives aufs LCD
	displayVersion();
	lcdBuffer.doRefresh();
	
	// BACKLIGHT Vorbereiten
	BACKLIGHT_DDR |= _BV(BACKLIGHT_PORTPIN);	// Ausgang
	Backlight.setBacklight(true);
	
	// Signal kurz vor Fertig
	for (cnt = 0 ; cnt <=30 ; cnt++)
	{
		setBusLED(false);
		for (cnt2 = 0 ; cnt2 < 0xFFFF ; cnt2++) nop();
		setBusLED(true);
		for (cnt2 = 0 ; cnt2 < 0xFFFF ; cnt2++) nop();
	};
	
	// Zum Schluss schreiben wir nur noch den Produktnamen
	lcdBuffer.clear();
	lcdBuffer.write_p(11, 1, PRODUCT_NAME);
	lcdBuffer.write(11,2, "\x00");
	lcdBuffer.write(11,3,"\x01");
}

// Switch our busled on or off
void setBusLED(bool val)
{
	if (val == 0) 	BUSLED_PORT |= _BV(BUSLED_PORTPIN);
	else 						BUSLED_PORT &= ~_BV(BUSLED_PORTPIN);
}

// This is called periodic.
// Do not make LONG loops here, be quick
void processMain(void)
{
	static uint16_t blinker = 0;
	static uint8_t toggle = 0;
	
	// In Zyklischen Abständen wird "timerFlag" gesetzt
	// unser Zeichen wieder was zu tun
	if (timerFlag)
	{
		timerFlag = 0;
		// gab es einen Tastendruck der zwei mal identisch war (zur Entprellung)
		if ((KEY_PIN & devStatus.lastKey) != devStatus.reportedKey)
		{
			Backlight.setBacklight( true );	// Licht an
			devStatus.lastKey = KEY_PIN;
			if (chan.channel == 0)	// Nur wenn wir nicht schon reporten
			{
				// erste Veränderung suchen
				for (uint8_t key = 0 ; key < 8 ; key++)
				{
					register uint8_t bvkey = _BV(key);
					if (((devStatus.lastKey ^ devStatus.reportedKey) & bvkey) == bvkey)	// Taste "key" verändert
					{
						// nur gemeldete Taste merken
						if (devStatus.lastKey & bvkey) 	devStatus.reportedKey |= bvkey;
						else 				devStatus.reportedKey &= ~bvkey;
						
						processKeys(key, ((devStatus.lastKey & bvkey) == 0));
						break;	// Wir können immer nur eine Änderung gleichzeitig melden
					}
				}	// for
			}
		}
		else
		{
			devStatus.lastKey = KEY_PIN;
		}
		
		// Backlight timeout
		if (Backlight.status)
		{
			if (Backlight.timeout > 0)	// nur wenn aktiv
			{
				if (Backlight.timeout > Backlight.timeoutcnt) Backlight.timeoutcnt++;
				else Backlight.setBacklight( false );	// Zeit abgelaufen
			}
		}
		
		
		toggle = !toggle;
	}	// if (timerFlag)
	
	// Some action, so we see, we are alive
	blinker++;
	if (blinker >= 0xFF)
	{
		blinker = 0;
	}

	// Send data to the softuart, if there is any
	DoUart(&pcbuffer);
	
	lcdBuffer.doRefresh();
}
 
// Set an output to a given <level> to <value> in the range 0-255
void processLevel(uint8_t level, uint8_t val)
{

}

// Switch an Output
void processChannel(uint8_t channel, bool val)
{
	if (val)
	{
		switch(channel)
		{
			case 255: { Backlight.setBacklight(true); break; }
		}
	}
	else
	{
		switch(channel)
		{
			case 255: { Backlight.setBacklight(false); break; }
		}
	}
}

// STring to put out to the softuart
void processString(char *data, uint8_t len)
{
	pcbuffer.add(data); //, len);	// TODO Busdata is not alway Zero temrinated
}

// We should perform the given textual command
void processCommand(char *data, uint8_t len)
{
	data[len-1] = 0;	// This is not the default by the busprotokoll
	// "'CLS'"
	if (0 == strncmp_P(data, cmds[CMD_CLS].cmd, cmds[CMD_CLS].len))
	{
		clearLCDandKeys();
	}
	// "'POS'"
	else if (0 == strncmp_P(data, cmds[CMD_POS].cmd, cmds[CMD_POS].len))
	{
		// TODO POS
		lcdBuffer.write(1,1,data);
	}
	// "'?VER'"
	else if (0 == strncmp_P(data, cmds[CMD_VERSION].cmd, cmds[CMD_VERSION].len))
	{
		displayVersion();
	}
	// "'TEXT17-Juhu'"
	else if (0 == strncmp_P(data, cmds[CMD_TEXT].cmd, cmds[CMD_TEXT].len))
	{
		register char *d = data + cmds[CMD_TEXT].len;	// Auf erste Zahl
		register uint8_t ch = atoi(d);
		d = strchr(d, '-');
		if (d && ch)	// gültiger channel und gültiger Syntax
		{
			d++;	// überspringen
			writeTextfeld( ch, d );
		}
	}
	// "'SET BAUD 9600,N,8,1 485 DISABLE'"
	else if (0 == strncmp_P(data, cmds[CMD_POS].cmd, cmds[CMD_POS].len))
	{
		static prog_char x[] = "Baudrate set";
		lcdBuffer.write_p(1,1,x);
	}
	// "'BACKLIGHT=1'"
	else if (0 == strncmp_P(data, cmds[CMD_BACKLIGHT].cmd, cmds[CMD_BACKLIGHT].len))
	{
		Backlight.setBacklight( (data[10] > '0') );	// Wenn wert ungleich 0 einschalten
	}
	// "'TIMEOUT=200'"
	else if (0 == strncmp_P(data, cmds[CMD_TIMEOUT].cmd, cmds[CMD_TIMEOUT].len))
	{
		Backlight.timeout = atoi(data + 9);	// Ab Platz 10 steht die Zahl
	}
	// Lädt die Fonts neu aus dem EEPROM
	// "''RELOADCHAR'"
	else if (0 == strncmp_P(data, cmds[CMD_RELOADCHAR].cmd, cmds[CMD_RELOADCHAR].len))
	{
		loadDisplayChar(MAX_CHARS);
	}
	// "'LOADCHAR=', $2, $12, $12, $12, $5, $2, $13, $12, $12"
	else if (0 == strncmp_P(data, cmds[CMD_LOADCHAR].cmd, cmds[CMD_LOADCHAR].len))
	{
		// genug Zeichen bekommen ???
		if (len >= (cmds[CMD_LOADCHAR].len + 9))	// char nummer + 8 Byte Char data
		{
			// Bus ist 1 Bassierend, wir aber NULL !
			saveDisplayChar((data[(cmds[CMD_LOADCHAR].len)] - 1), (uint8_t*)(data + (cmds[CMD_LOADCHAR].len) + 1), MAX_CHARS );
		}
	}
	// "'RXON'"
	else if (0 == strncmp_P(data, cmds[CMD_RXON].cmd, cmds[CMD_RXON].len))
	{
		devStatus.mode.rxon = true;
	}
	// "'RXOFF'"
	else if (0 == strncmp_P(data, cmds[CMD_RXOFF].cmd, cmds[CMD_RXOFF].len))
	{
		devStatus.mode.rxon = false;
	}
	// "'SETUP'"
	else if (0 == strncmp_P(data, cmds[CMD_SETUP].cmd, cmds[CMD_SETUP].len))
	{
		showSetupMenu(0);	// Ab auf erste Setup Seite
	}
	// "'LEVON'"
	else if (0 == strncmp_P(data, cmds[CMD_LEVON].cmd, cmds[CMD_LEVON].len))
	{
		devStatus.mode.levon = true;
	}
	// "'LEVOFF'"
	else if (0 == strncmp_P(data, cmds[CMD_LEVOFF].cmd, cmds[CMD_LEVOFF].len))
	{
		devStatus.mode.levon = false;
	}
	// UNKNOWN
	else
	{
		lcdBuffer.clear();
		lcdBuffer.write_p(1,1,TEXT_UNKNOWN);
		data[len] = 0;
		lcdBuffer.write(10,1,data, (len - 1));	// Das abschliesende NULL Byte wollen wir nicht haben
	}
}

// Displays version information on the build in display
void displayVersion(void)
{
	lcdBuffer.clear();
	static prog_char DVER[] = "Dev Ver:";
	static prog_char APIVER[] = "Bus Ver:";
	lcdBuffer.write_p( 1, 1, DVER);
	lcdBuffer.write_p(10, 1, DEVICE_VERSION);
	lcdBuffer.write_p(21, 1, APIVER);
	lcdBuffer.write_p(31, 1, getBUSApiVersion());
	lcdBuffer.write_p(8, 2, HOMEPAGE_URL);
}

// returns our firmware id to the bus api
uint16_t getFwId(void)
{
	return FW_ID;
}

// handle lokal key presses and store them for later (polled) report to the api
void processKeys(uint8_t key, uint8_t state)
{
	uint8_t channel = devStatus.chanMap[key];	// welcher Channel steckt dahinter
	// Keypad bearbeiten
	if (devStatus.mode.Keypad == true)
	{
	
	}
	// Setup Menü
	else if (devStatus.mode.Setup == true)
	{
		processSetupPages(channel, state);
	}
	// Sonstiges
	else
	{
		chan.channel 	= channel;
		chan.type 		= CH_CHANNEL_IN;
		chan.val 			= state;
		
		// Zum Schluss prüfen wir ob es vielleicht ins Setup Menü soll
		if (devStatus.reportedKey == SETUP_KEYS)
		{
			showSetupMenu(0);	// Show Entry of Setup
		}
	}
}

// Writes text from flash to a given position of the LCD
void writeTextfeld_p(uint8_t nr, prog_char *text)
{
	// TODO optimize this point. do not copy from flash to ram !
	const uint8_t len = 42;
	char t[len];
	strlcpy_P(t, text, len);
	writeTextfeld( nr, t);
}

// Writes text a spezifik position of the LCD and reprogram the keys assigned to the area
void writeTextfeld(uint8_t nr, char *text)
{
	uint8_t len = strlen(text);
	uint8_t x = 0;
	uint8_t y = 0;
	uint8_t lineLen = 0;
	switch(nr)
	{
		case 1:
		{
			lineLen = 40;
			if (len > lineLen) len = lineLen;
			x = 1;
			y = 1;
			break;
		}
		case 2:
		{
			lineLen = 40;
			if (len > lineLen) len = lineLen;
			x = 1;
			y = 2;
			for (uint8_t i = 0 ; i < 8 ; i++) devStatus.chanMap[i] = i + 1;
			break;
		}
		case 11:
		{
			lineLen = 40;
			if (len > lineLen) len = lineLen;
			x = 1;
			y = 2;
			for (uint8_t i = 0 ; i < 8 ; i++) devStatus.chanMap[i] = 11;
			break;
		}
		case 21:
		{
			lineLen = 20;
			if (len > lineLen) len = lineLen;
			x = 1;
			y = 2;
			devStatus.chanMap[0] = 21;
			devStatus.chanMap[1] = 21;
			devStatus.chanMap[2] = 21;
			devStatus.chanMap[3] = 21;
			break;
		}
		case 22:
		{
			lineLen = 20;
			if (len > lineLen) len = lineLen;
			x = 21;
			y = 2;
			devStatus.chanMap[4] = 22;
			devStatus.chanMap[5] = 22;
			devStatus.chanMap[6] = 22;
			devStatus.chanMap[7] = 22;
			break;
		}
		case 41:
		{
			lineLen = 10;
			if (len > lineLen) len = lineLen;
			x = 1;
			y = 2;
			devStatus.chanMap[0] = 41;
			devStatus.chanMap[1] = 41;
			break;
		}
		case 42:
		{
			lineLen = 10;
			if (len > lineLen) len = lineLen;
			x = 11;
			y = 2;
			devStatus.chanMap[2] = 42;
			devStatus.chanMap[3] = 42;
			break;
		}
		case 43:
		{
			lineLen = 10;
			if (len > lineLen) len = lineLen;
			x = 21;
			y = 2;
			devStatus.chanMap[4] = 43;
			devStatus.chanMap[5] = 43;
			break;
		}
		case 44:
		{
			lineLen = 10;
			if (len > lineLen) len = lineLen;
			x = 31;
			y = 2;
			devStatus.chanMap[6] = 44;
			devStatus.chanMap[7] = 44;
			break;
		}
		case 81:
		{
			lineLen = 5;
			if (len > lineLen) len = lineLen;
			x = 1;
			y = 2;
			devStatus.chanMap[0] = 81;
			break;
		}
		case 82:
		{
			lineLen = 10;
			if (len > lineLen) len = lineLen;
			x = 6;
			y = 2;
			devStatus.chanMap[1] = 82;
			break;
		}
		case 83:
		{
			lineLen = 10;
			if (len > lineLen) len = lineLen;
			x = 11;
			y = 2;
			devStatus.chanMap[2] = 83;
			break;
		}
		case 84:
		{
			lineLen = 10;
			if (len > lineLen) len = lineLen;
			x = 16;
			y = 2;
			devStatus.chanMap[3] = 84;
			break;
		}
		case 85:
		{
			lineLen = 10;
			if (len > lineLen) len = lineLen;
			x = 21;
			y = 2;
			devStatus.chanMap[4] = 85;
			break;
		}
		case 86:
		{
			lineLen = 10;
			if (len > lineLen) len = lineLen;
			x = 26;
			y = 2;
			devStatus.chanMap[5] = 86;
			break;
		}
		case 87:
		{
			lineLen = 10;
			if (len > lineLen) len = lineLen;
			x = 31;
			y = 2;
			devStatus.chanMap[6] = 87;
			break;
		}
		case 88:
		{
			lineLen = 10;
			if (len > lineLen) len = lineLen;
			x = 36;
			y = 2;
			devStatus.chanMap[7] = 88;
			break;
		}
	}	// switch
	if (x && y)
	{
		lcdBuffer.clearPos( x, y, lineLen);	// Position löschen
		//if (nr > 10) lcdBuffer.writeCenter(x, y, text, len, lineLen);		// Text schreiben
		//else lcdBuffer.write(x, y, text, len);		// Text schreiben
		lcdBuffer.write(x, y, text, len);		// Text schreiben
	}
}

// Clear LCD and reset Keys to default bindings
void clearLCDandKeys(void)
{
	lcdBuffer.clear();
	devStatus.initKeys();
}

// Master status has changed
void processMasterStatus(uint8_t status)
{
	if (status)
	{
		writeTextfeld_p(1, MASTER_ONLINE);
	}
	else
	{
		clearLCDandKeys();
		writeTextfeld_p(1, MASTER_OFFLINE);
		devStatus.reportedKey = 0;	// wir haben nichts gemeldet !
	}
}
