/*
Copyright 2006, Freie Universitaet Berlin. All rights reserved.

These sources were developed at the Freie Universität Berlin, Computer
Systems and Telematics group.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

- Neither the name of Freie Universitaet Berlin (FUB) nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

This software is provided by FUB and the contributors on an "as is"
basis, without any representations or warranties of any kind, express
or implied including, but not limited to, representations or
warranties of non-infringement, merchantability or fitness for a
particular purpose. In no event shall FUB or contributors be liable
for any direct, indirect, incidental, special, exemplary, or
consequential damages (including, but not limited to, procurement of
substitute goods or services; loss of use, data, or profits; or
business interruption) however caused and on any theory of liability,
whether in contract, strict liability, or tort (including negligence
or otherwise) arising in any way out of the use of this software, even
if advised of the possibility of such damage.

This implementation was developed by the CST group at the FUB.

For documentation and questions please use the web site
http://scatterweb.mi.fu-berlin.de and the mailinglist
scatterweb@lists.spline.inf.fu-berlin.de (subscription via the Website).
Berlin, 2006
*/

/**
 * @file		ScatterWeb.Configuration.c
 * @brief		Node configurability
 * @ingroup		config
 * @version		$Rev: 509 $
 *
 * $Id: ScatterWeb.Configuration.c 509 2008-02-05 14:45:22Z baar@PCPOOL.MI.FU-BERLIN.DE $
 */

#include <stdio.h>
#include <string.h>
#include "ScatterWeb.System.h"

static bool _Configuration_setID(uint16_t node);
static void _Configuration_writeHeader(fp_putchar_t writer, appHeader_t* iD);
static void _Config_WriteLogLevel(char* buffer, uint8_t level);

config_t	Configuration;

bool Configuration_init() {
	Configuration_load();
	Debuglog_level = Configuration.boot_logLevel;
	return True;
}

void Configuration_reset() {
	memset(&Configuration, 0, sizeof(config_t));
	
	// random node id
	newid:
	Configuration.id = System_random() >> 8;
	if( Configuration.id == 0 || Configuration.id == 0xFF )
		goto newid;
	// Initial defaults (setting to zero ommitted)
	//Configuration.sht11_tempdiff = 0;
	//Configuration.radio_txpower = 0;
	Configuration.radio_channel = 1;
	Configuration.boot_logLevel = LOW;
	// firmware flags
	//Configuration.firmwareFlags = 0;
}

void Configuration_save() {
	uint8_t*					ppc = (uint8_t*)&Configuration;
	uint8_t					crc = 0;
	register int			i;

	// calculate checksum
	for( i = 0; i < sizeof(config_t); i++ ) {
		crc += *ppc;
		ppc++;
	}

	// write to flash
	Infomem_write(INFOMEM_CONFIG, 2, &Configuration, sizeof(config_t), &crc, 1);
}

bool Configuration_load() {
	uint8_t					crc1, crc2 = 0;					// checksums
	register int			i;								// loop counter
	register uint8_t*			ppc = (uint8_t*)&Configuration;	// persistant configuration pointer

	Infomem_read(&Configuration, INFOMEM_CONFIG, sizeof(config_t));
	Infomem_read(&crc1, INFOMEM_CONFIG + sizeof(config_t), 1);

	for( i = 0; i < sizeof(config_t); i++ ) {
		crc2 += *ppc;
		ppc++;
	}

	if( crc1 != crc2 ) {
		Configuration_reset();
		return false;
	}
	return true;
}

#if SYSTEM_CMDLEVEL >= MEDIUM
// app <br> Prints application name and version
/**
 * @brief Print application header: <code>app</code>
 * @ingroup commands
 *
 * Print application header information to stdout.
 */
COMMAND(app, 0, cmdargs) {
	String_writerInit(cmdargs->response_args);
	_Configuration_writeHeader(String_writer, (appHeader_t*)&__app_start);
}

/**
 * @brief Print complete configuration: <code>config</code>
 * @ingroup commands
 *
 * Prints the configuration. This is the same output as the startup Information.
 */
COMMAND(config, CMDFLAG_SERIAL, cmdargs) {
  Configuration_print();
}

/**
 * @brief			Read reset reason: <code>rrr</code>
 * @ingroup	commands
 * @return		Reason for last reset of node
 */
COMMAND(rrr, 0, cmdargs) {
    const char* reason = System_getStartupInfoString();
    snprintf(cmdargs->response_args, cmdargs->response_bufsize, "%#.2x [%s]",
    		system_startupInfo, reason);
}
#endif

/**
 * @brief			Node id property: <code>id [newid]</code>
 * @ingroup	commands
 * @param		newid	New node id (optional)
 * @return		Current id
 */
COMMAND(id, 0, cmdargs) {
	uint16_t newId = String_parseUint16(cmdargs->args, NULL);

	if( newId ) {
		bool result = _Configuration_setID(newId);
		if( result ) {}
		else {
			snprintf(cmdargs->response_args, cmdargs->response_bufsize,
					"ERR: OUT OF RANGE (%u-%u)", NETADDR_MIN, NETADDR_MAX);
		}
	}
	snprintf(cmdargs->response_args, cmdargs->response_bufsize,
			"%u", Configuration.id);
}

#if SYSTEM_CMDLEVEL >= LOW
/**
 * @name Radio
 * @{
 */

/**
 * @brief			Channel property: <code>channel [num]</code>
 * @ingroup	commands
 * @param		num New channel id (optional)
 * @return		Current channel
 */
COMMAND(channel, 0, cmdargs) {
	uint8_t newChannel = String_parseUint16(cmdargs->args, NULL);

	if( newChannel ) {
		if( newChannel > Radio.NumChannels ) {
			snprintf(cmdargs->response_args, cmdargs->response_bufsize,
					"ERR: INVALID (1-%u)", Radio.NumChannels);
			return;
		} else {
			Configuration.radio_channel = Radio.SelectChannel(newChannel);
			Configuration_save();			
		}
	}
	snprintf(cmdargs->response_args, cmdargs->response_bufsize,
			"%u", Configuration.radio_channel);
}

/**
 * @brief			Node id property: <code>txpwr [power]</code>
 * @ingroup	commands
 * @param		power	New transmit power (byte) (optional)
 * @return		Current transmit power
 */
COMMAND(txpwr, 0, cmdargs) {
	uint8_t newPwr = String_parseUint16(cmdargs->args, NULL);

	if( newPwr > 0 ) {
		newPwr = Radio.SetTxPower(newPwr);
		if( newPwr ) {
			Configuration.radio_txpower = newPwr;
			Configuration_save();
		}
	}
	snprintf(cmdargs->response_args, cmdargs->response_bufsize,
			"%#.2x", Configuration.radio_txpower);
}

/** @} */
#endif

/**
 * @brief			Loglevel property: <code>llv [newlevel]</code>
 * @ingroup	commands
 * @param		newlevel	New loglevel value (Debuglog_level)
 * @return		Current loglevel
 *
 * @see ScatterWeb.Debuglog.h
 */
COMMAND(llv, 0, cmdargs) {
	if( cmdargs->arg_size > 0 ) {
		uint16_t i = String_parseUint16(cmdargs->args, NULL);
		Debuglog_level = i;
	}
	_Config_WriteLogLevel(cmdargs->response_args, Debuglog_level);
}

/**
 * @brief			Boot Loglevel property: <code>bllv [newlevel]</code>
 * @ingroup	commands
 * @param		newlevel	New loglevel value (config_t:boot_logLevel)
 * @return		Current boot loglevel
 *
 * The boot loglevel is the initial loglevel used when the system boots. This value is stored
 * persistantly.
 *
 * @see Debuglog_level, ScatterWeb.Debuglog.h
 */
COMMAND(bllv, 0, cmdargs) {
	if( cmdargs->arg_size > 0 ) {
		uint16_t i = String_parseUint16(cmdargs->args, NULL);
		Configuration.boot_logLevel = i;
		Configuration_save();
	}
	_Config_WriteLogLevel(cmdargs->response_args, Configuration.boot_logLevel);
}

void _Config_WriteFirmwareFlags(char* buffer) {
	sprintf(buffer, "%.2X [%s%s]", Configuration.firmwareFlags,
                 ((Configuration.firmwareFlags & FF_PROGRAMMABLE) ? "PROG " : ""),
                 ((Configuration.firmwareFlags & FF_DCOCHECKER) ? "DCO" : "")
	);
}

/**
 * @brief			Read firmware flags: <code>sysconf [flags]</code>
 * @ingroup	commands
 * @return		Current firmware flags
 *
 * @see ScatterWeb.Configuration.h
 */
COMMAND(sysconf, 0, cmdargs) {
	if( cmdargs->arg_size > 0 ) {
		uint16_t i = String_parseUint16(cmdargs->args, NULL);
		Configuration.firmwareFlags = i;
		Configuration_save();
	}
	_Config_WriteFirmwareFlags(cmdargs->response_args);
}

void Configuration_print() {
	uint8_t	buffer[20];

	printf("\r\n#");
	String_writeChar(putchar, '=', 78);
	printf("{\r\n DCO:\t%#.2x\tBCS: %#.2x\t\tRESET:\t%u [%s]\r\n",
		DCOCTL, BCSCTL1, system_reset_count, System_getStartupInfoString() );
	printf(" Radio\t\tChannel: %u\t\tNode address:\t%u\r\n",
			Configuration.radio_channel, Configuration.id);
	_Config_WriteLogLevel(buffer, Debuglog_level);
	printf("\t\tTX Power: %#.2x\r\n"
	       " LogLevel:\tRuntime: %s",
	       Configuration.radio_txpower,
	       buffer
	);
	_Config_WriteLogLevel(buffer, Configuration.boot_logLevel);
	printf("\tBoot: %s\r\n", buffer);
	//_Config_WriteFirmwareFlags(buffer);
	//printf("# Firmware Fl:\t%s\r\n", buffer);
	printf(" Platform:\t%s rev %u\r\n"
		   " FLASH[BLD]:\t",
		   appHeader.platformName, appHeader.platformRev
	);
	if( LOADER_INSTALLED )
		_Configuration_writeHeader(&putchar, (appHeader_t*)&__bld_header);
	else
		printf("none");
	printf("\r\n FLASH[EOS]:\t"); _Configuration_writeHeader(putchar, (appHeader_t*)&__eos_header);
	printf("\r\n FLASH[APP]:\t"); _Configuration_writeHeader(putchar, (appHeader_t*)&__app_header);
	printf("\r\n");
	String_writeChar(putchar, '=', 78);
	printf("}\r\n");
}

static bool _Configuration_setID(uint16_t node) {

	// validate new node id
	if( (node < NETADDR_MIN) || (node > NETADDR_MAX) )
		return false;

	// store valid id persistantly
	Configuration.id = node;
	Configuration_save();

	return true;
}

static void _Configuration_writeHeader(fp_putchar_t writer, appHeader_t* iD)
{
	btime_t	btime;
	time_t	time;
	char	buffer[TIME_STRING_LENGTH];

	time.secs = iD->compileTime - UTC_OFFSET_SECONDS;
	time.millis = 0;
	Time_convertTime(&time, &btime);
	Time_write(buffer, &btime);

	uprintf(writer, "%s %u.%u\t%s|%.2X|%p",
		iD->appName, iD->appVer[0], iD->appVer[1], buffer,
		iD->type, (void*)iD->resetAdr);
}

static void _Config_WriteLogLevel(char* buffer, uint8_t level) {
	uint8_t l = level & DEBUGLOG_LEVEL_MASK;
	sprintf(buffer, "%#.2x [%s%s%s]", level,
				((level & DEBUG_RADIO) ? "RADIO " : ""),
                ((level & DEBUG_DCO) ? "DCO " : ""),
                String_stringtableGet( Debuglog_level_strings, l )
	);
}
