//
// ctrl.c : AVR uC code for ctrl buffer initialisation and put/get ops
//
// Copyright (c) 2010 flukso.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
// $Id$

#include <avr/eeprom.h>
#include <util/crc16.h>

#include "global.h"
#include "main.h"
#include "buffer.h"
#include "ctrl.h"
#include "encode.h"

cBuffer ctrlRxBuffer; // ctrl receive buffer
cBuffer ctrlTxBuffer; // ctrl transmit buffer

static char ctrlRxData[CTRL_RX_BUFFER_SIZE];
static char ctrlTxData[CTRL_TX_BUFFER_SIZE];

extern struct version_struct EEMEM EEPROM_version;
extern struct version_struct version;

extern struct event_struct EEMEM EEPROM_event;
extern struct event_struct event;

extern uint8_t EEMEM EEPROM_enabled;
extern uint8_t enabled;

extern uint8_t EEMEM EEPROM_phy_to_log[MAX_SENSORS];
extern uint8_t phy_to_log[MAX_SENSORS];

extern struct sensor_struct EEMEM EEPROM_sensor[MAX_SENSORS];
extern struct sensor_struct sensor[MAX_SENSORS];

extern struct state_struct state[MAX_SENSORS];

void ctrlInit(void)
{
	// initialize the CTRL receive buffer
	bufferInit(&ctrlRxBuffer, (u08*) ctrlRxData, CTRL_RX_BUFFER_SIZE);
	// initialize the CTRL transmit buffer
	bufferInit(&ctrlTxBuffer, (u08*) ctrlTxData, CTRL_TX_BUFFER_SIZE);
}

uint8_t ctrlTxBufferIsEmpty(void)
{
	if(ctrlTxBuffer.datalength == 0) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}

uint8_t ctrlAddToTxBuffer(uint8_t data)
{
	return bufferAddToEnd(&ctrlTxBuffer, data);
}

uint8_t ctrlGetFromTxBuffer(uint8_t* data) {
	// make sure we have data in the Tx buffer
	if(ctrlTxBuffer.datalength) {
		// get byte from beginning of buffer
		*data = bufferGetFromFront(&ctrlTxBuffer);
		return TRUE;
	}
	else {
		// no data
		return FALSE;
	}
}

uint8_t ctrlRxBufferIsEmpty(void)
{
	if(ctrlRxBuffer.datalength == 0) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}

uint8_t ctrlAddToRxBuffer(uint8_t data)
{
	return bufferAddToEnd(&ctrlRxBuffer, data);
}

uint8_t ctrlGetFromRxBuffer(uint8_t* pdata)
{
	// make sure we have data in the Rx buffer
	if(ctrlRxBuffer.datalength) {
		// get byte from beginning of buffer
		*pdata = bufferGetFromFront(&ctrlRxBuffer);
		return TRUE;
	}
	else {
		// no data
		return FALSE;
	}
}

void ctrlFlushRxBuffer(void)
{
	ctrlRxBuffer.datalength = 0;
}

void ctrlFlushTxBuffer(void)
{
	ctrlTxBuffer.datalength = 0;
}

uint8_t ctrlReadCharFromRxBuffer(uint8_t* pdata)
{
	uint8_t high_hex, low_hex;

	if (ctrlGetFromRxBuffer(&high_hex) && ctrlGetFromRxBuffer(&low_hex)) {
		htob(high_hex, low_hex, pdata);
		return TRUE;
	}
	else {
		return FALSE;
	}	

}

uint8_t ctrlReadShortFromRxBuffer(uint16_t* pdata) 
{
	uint8_t high_char, low_char;

	if(ctrlReadCharFromRxBuffer(&high_char) && ctrlReadCharFromRxBuffer(&low_char)) {
		*pdata = ((uint16_t)high_char << 8) + low_char;
		return TRUE;
	}
	else {
		return FALSE;
	}
}

uint8_t ctrlReadLongFromRxBuffer(uint32_t* pdata) 
{
	uint16_t high_short, low_short;

	if(ctrlReadShortFromRxBuffer(&high_short) && ctrlReadShortFromRxBuffer(&low_short)) {
		*pdata = ((uint32_t)high_short << 16) + low_short;
		return TRUE;
	}
	else {
		return FALSE;
	}
}

uint8_t ctrlWriteCharToTxBuffer(uint8_t data)
{
	uint8_t high_hex, low_hex;

	btoh(data, &high_hex, &low_hex);
	if (ctrlAddToTxBuffer(high_hex) && ctrlAddToTxBuffer(low_hex)) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}

uint8_t ctrlWriteShortToTxBuffer(uint16_t data)
{
	if (ctrlWriteCharToTxBuffer((uint8_t)(data >> 8)) && ctrlWriteCharToTxBuffer((uint8_t)data)) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}

uint8_t ctrlWriteLongToTxBuffer(uint32_t data)
{
	if (ctrlWriteShortToTxBuffer((uint16_t)(data >> 16)) && ctrlWriteShortToTxBuffer((uint16_t)data)) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}

void ctrlRxToTxLoop(void)
{
	uint8_t data;

	while (ctrlGetFromRxBuffer(&data)) {
		ctrlAddToTxBuffer(data);
	}
}

uint8_t ctrlCalcCrc8(cBuffer* buffer, uint8_t chop)
{
	uint8_t i, crc = 0;

	for (i = 0; i < buffer->datalength - chop; i++) {
		crc = _crc_ibutton_update(crc, bufferGetAtIndex(buffer, i));
	}
	return crc;
}

uint8_t ctrlExtractCrc8fromMessage(cBuffer* buffer)
{
	uint8_t crc, high_hex, low_hex;

	high_hex = bufferGetAtIndex(buffer, buffer->datalength - 2);
	low_hex  = bufferGetAtIndex(buffer, buffer->datalength - 1);

	htob(high_hex, low_hex, &crc);
	return crc;
}

void ctrlDecode(void)
{
	uint8_t cmd[2], crc;

	ctrlFlushTxBuffer();

	crc = ctrlExtractCrc8fromMessage(&ctrlRxBuffer);
	if (ctrlCalcCrc8(&ctrlRxBuffer, 2) != crc) {
		ctrlAddToTxBuffer('z');
		ctrlAddToTxBuffer('z');
	}
	else if (ctrlGetFromRxBuffer(cmd) && ctrlGetFromRxBuffer(cmd+1)) {
		ctrlAddToTxBuffer(cmd[0]);
		ctrlAddToTxBuffer(cmd[1]);

		switch (cmd[0]) {
		case 'g':	/* get */
			ctrlCmdGet(cmd[1]);
			break;
 
		case 's':	/* set */
			ctrlCmdSet(cmd[1]);
			break;

		case 'c':	/* commit */
			if (cmd[1] == 't') ctrlCmdCommit();
			break;
		}
	}
	else {
		ctrlAddToTxBuffer('z');
		ctrlAddToTxBuffer('y');
	}

	crc = ctrlCalcCrc8(&ctrlTxBuffer, 0);
	ctrlWriteCharToTxBuffer(crc);

	ctrlAddToTxBuffer('.');

	ctrlFlushRxBuffer();
}

void ctrlCmdGet(uint8_t cmd)
{
	uint8_t i = 0;
	uint32_t tmp32, tmp32_bis;

	switch (cmd) {
	case 'h':		/* hardware {major,minor} version */
		ctrlWriteShortToTxBuffer(version.hw_major);
		ctrlWriteCharToTxBuffer(version.hw_minor);
		break;

	case 's':		/* software {major,minor} version */
		ctrlWriteCharToTxBuffer(version.sw_major);
		ctrlWriteCharToTxBuffer(version.sw_minor);
		break;

	case 'e':		/* sensor enabled | disabled */
		ctrlReadCharFromRxBuffer(&i);

		if (i < MAX_SENSORS) {
			ctrlWriteCharToTxBuffer(i);
			ctrlWriteCharToTxBuffer((enabled >> i) & 0x01);
		}
		break;

	case 'p':		/* phy-to-logical mapping */
		for (i = 0 ; i < MAX_SENSORS; i++) {
			ctrlWriteCharToTxBuffer(phy_to_log[i]);
		}
		break;

	case 'c':		/* sensor counter value */
		ctrlReadCharFromRxBuffer(&i);

		if (i < MAX_SENSORS) {
			cli();
			tmp32 = sensor[i].counter;
			sei();

			ctrlWriteCharToTxBuffer(i);
			ctrlWriteLongToTxBuffer(tmp32);
		}
		break;

	case 'm':		/* sensor meterconstant */
		ctrlReadCharFromRxBuffer(&i);

		if (i < MAX_SENSORS) {
			ctrlWriteCharToTxBuffer(i);
			ctrlWriteShortToTxBuffer(sensor[i].meterconst);
		}
		break;

	case 'w':		/* watchdog counter */
		ctrlWriteShortToTxBuffer(event.wdt);
		break;

	case 'b':		/* brown-out counter */
		ctrlWriteShortToTxBuffer(event.brown_out);
		break;

	case 'd':		/* delta: all changes since last gd */
		for (i = 0 ; i < MAX_SENSORS; i++) {
			if (state[i].flags & (STATE_PULSE | STATE_POWER)) {
				ctrlWriteCharToTxBuffer(i);

				cli();
				tmp32 = sensor[i].counter;
				tmp32_bis = (i < MAX_ANALOG_SENSORS) ? state[i].power : state[i].timestamp;
				state[i].flags &= ~(STATE_PULSE | STATE_POWER);
				sei();

				ctrlWriteLongToTxBuffer(tmp32);
				ctrlWriteLongToTxBuffer(tmp32_bis);
			}
		}
		break;
	}
}

void ctrlCmdSet(uint8_t cmd)
{
	uint8_t i = 0, tmp8 = 0;
	uint16_t tmp16 = 0;
	uint32_t tmp32 = 0;

	switch (cmd) {
	case 'h':		/* hardware {major,minor} version */
		ctrlReadShortFromRxBuffer(&version.hw_major);
		ctrlReadCharFromRxBuffer(&version.hw_minor);

		ctrlWriteShortToTxBuffer(version.hw_major);
		ctrlWriteCharToTxBuffer(version.hw_minor);
		break;

	case 's':		/* software {major,minor} version */
		ctrlReadCharFromRxBuffer(&version.sw_major);
		ctrlReadCharFromRxBuffer(&version.sw_minor);

		ctrlWriteCharToTxBuffer(version.sw_major);
		ctrlWriteCharToTxBuffer(version.sw_minor);
		break;

	case 'e':		/* sensor enabled | disabled */
		ctrlReadCharFromRxBuffer(&i);

		if (i < MAX_SENSORS) {
			ctrlReadCharFromRxBuffer(&tmp8);

			if (tmp8) {
				enabled |= (1 << i);
			}
			else {
				enabled &= ~(1 << i);
			}

			ctrlWriteCharToTxBuffer(i);
			ctrlWriteCharToTxBuffer((enabled >> i) & 0x01);
		}
		break;

	case 'p':		/* phy-to-logical mapping */
		for (i = 0 ; i < MAX_SENSORS; i++) {
			ctrlReadCharFromRxBuffer(&tmp8);

			cli();
			phy_to_log[i] = tmp8;
			sei();

			ctrlWriteCharToTxBuffer(phy_to_log[i]);
		}
		break;

	case 'c':		/* sensor counter value */
		ctrlReadCharFromRxBuffer(&i);

		if (i < MAX_SENSORS) {
			ctrlReadLongFromRxBuffer(&tmp32);

			cli();
			sensor[i].counter = tmp32;
			sei();

			ctrlWriteCharToTxBuffer(i);
			ctrlWriteLongToTxBuffer(tmp32);
		}
		break;

	case 'm':		/* sensor meterconstant */
		ctrlReadCharFromRxBuffer(&i);

		if (i < MAX_SENSORS) {
			ctrlReadShortFromRxBuffer(&tmp16);

			cli();
			sensor[i].meterconst = tmp16;	
			sei();

			ctrlWriteCharToTxBuffer(i);
			ctrlWriteShortToTxBuffer(sensor[i].meterconst);
		}
		break;

	case 'w':		/* watchdog counter */
		ctrlReadShortFromRxBuffer(&tmp16);

		cli();
		event.wdt = tmp16;
		sei();

		ctrlWriteShortToTxBuffer(event.wdt);
		break;

	case 'b':		/* brown-out counter */
		ctrlReadShortFromRxBuffer(&tmp16);

		cli();
		event.brown_out = tmp16;
		sei();

		ctrlWriteShortToTxBuffer(event.brown_out);
		break;
	}
}

void ctrlCmdCommit(void)
{
	cli();
	eeprom_update_block((const void*)&version, (void*)&EEPROM_version, sizeof(version));
	eeprom_update_block((const void*)&event, (void*)&EEPROM_event, sizeof(event));
	eeprom_update_block((const void*)&enabled, (void*)&EEPROM_enabled, sizeof(enabled));
	eeprom_update_block((const void*)&phy_to_log, (void*)&EEPROM_phy_to_log, sizeof(phy_to_log));
	eeprom_update_block((const void*)&sensor, (void*)&EEPROM_sensor, sizeof(sensor));
	sei();
}