/*---------------------------------------------------------------------------
	Project:	    WL33_NUCLEO_UART

	File Name:	    menu.c

	Author:		    MartinA

	Description:	Menu handler

					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, provided this copyright notice
					is included.

				    Copyright (c) Alberta Digital Radio Communications Society
				    All rights reserved.

	Revision History:

---------------------------------------------------------------------------*/
#include <string.h>

#include "types.h"
#include "usart.h"
#include "streambuffer.h"
#include "setup.h"
#include "tasks.h"
#include "utils.h"
#include "tod.h"
#include "config.h"

// menu state
uint8_t menuState;		// menu state
enum {
	MENU_OFF,			// off
	MENU_SHOWING,		// showing the menu
	MENU_SELECTING,		// getting a selection
	MENU_SELECTED,		// item has been selected
	MENU_PAUSED			// pausing a while...
};

// key entry state
uint8_t entryState;		// state of keyboard entry
enum	{
	NO_ENTRY=0,			// no entry yet..
	ENTERING,			// entering a value
	VALIDATING,			// validating the entry
};

// menu items
#define	NO_ITEM		-1			// no item selected

// DEC VT100 Escape sequences
#define ASCII_TAB		0x09
#define ASCII_RET		0x0D
#define	ASCII_ESC		0x1B			// escape char
#define	DEC_LEADIN		'['				// lead-in character
#define	MAX_SEQ			6				// max sequence length

// control codes..
enum {
	CONTROL_CLEAR=0,			// clear screen
	CONTROL_HOME,				// cursor home
	CONTROL_DOUBLE_TOP,			// double wide and height (top)
	CONTROL_DOUBLE_BOTTOM,		// double wide and height (bottom)
	CONTROL_SINGLE,				// single wide and height
	NCONTROL_CODES				// number of conrol codes
};

struct controlCodes_t	{
	char sequence[MAX_SEQ];		// sequence
	uint8_t	len;				// length
} controlCodes[NCONTROL_CODES] = {
		{{ASCII_ESC, DEC_LEADIN, '2', 'J'}, 4},
		{{ASCII_ESC, DEC_LEADIN, 'H'},      3},
		{{ASCII_ESC, '#', '3'},    		    3},
		{{ASCII_ESC, '#', '4'},    		    3},
		{{ASCII_ESC, '#', '5'},    		    3},
};

// strings common to all menu types
char *whatItem = 	"??Try again->";
char *menuSpacer =  "\r\n\n";
char *pauseString = "Hit enter to continue->";
char *selectItem = 	"Select an item->";

static char menu[100];			// Buffer for file items
int sel_item = 0;				// selected item
uint8_t	activeMenu;				// active menu

// forward refs in this module
void printMenu(void);		// print the menu
int getMenuItem(void);		// get a menu item
void sendControlCode(uint8_t code);
BOOL pause(void);
void Print_Frame_stats(FRAME_STATS *stats);
void Print_Radio_errors(uint32_t errs);
void Print_FSM_state(uint8_t state);
uint8_t getEntry(int activeMenu, int item);
uint8_t getKeyEntry(void);

// list of menus
enum	{
	MAIN_MENU=0,		// main menu
	RADIO_MENU,			// radio params menu
	STATION_MENU,		// Station params menu
	N_MENUS				// number of menus
};

// menu return functions
enum	{
	RET_MORE=0,			// more to do
	RET_DONE,			// done
	RET_PAUSE			// pause before leaving
};

/*
 * The first part of this code contains the menus and processing routines
 * handle the various menu options
 * return TRUE if done, else FALSE
 */
uint8_t printAllSetup(void)
{
	TIMEOFDAY tod;
	getTOD(&tod);

	USART_Print_string("Current firmware is at %d.%d\r\n",def_params.params.FirmwareVerMajor,
			def_params.params.FirmwareVerMinor);
	USART_Print_string("System time is %02d:%02d:%02d\r\n\n", tod.Hours, tod.Minutes, tod.Seconds);

	printStationSetup();
	printRadioSetup();
	return RET_PAUSE;
}
//
uint8_t printStnSetup(void)
{
	printStationSetup();
	return RET_PAUSE;
}
//
uint8_t printRadSetup(void)
{
	printRadioSetup();
	return RET_PAUSE;
}
// list mesh status
uint8_t listMesh(void)
{
	Mesh_ListStatus();
	return RET_PAUSE;
}

// enter chat mode
uint8_t chatMode(void)
{
	if(Chat_Task_exec())
		return RET_PAUSE;
	return RET_MORE;
}

// dump the rx stats
uint8_t showstats(void)
{
	Print_Frame_stats(GetFrameStats());
	Print_Radio_errors(GetRadioStatus());
	Print_FSM_state(GetFSMState());
	return RET_PAUSE;
}

// set the radio entry mode
uint8_t setRadio(void)
{
	activeMenu = RADIO_MENU;
	menuState = MENU_OFF;
	return RET_DONE;
}

// set the station entry mode
uint8_t setStation(void)
{
	activeMenu = STATION_MENU;
	menuState = MENU_OFF;
	return RET_DONE;
}
// enter chat mode
uint8_t ledTest(void)
{
	if(LedTest())
		return RET_PAUSE;
	return RET_MORE;
}
// write the flash memory
uint8_t writeSetup(void)
{
	if(!UpdateSetup())	{
		USART_Print_string("Error in writing flash: setup may be corrupt\r\n");
	} else {
		USART_Print_string("Flash written successfully\r\n");
	}
	menuState = MENU_OFF;
	return RET_PAUSE;
}
// exit the current menu
uint8_t exitMenu(void)
{
	activeMenu = MAIN_MENU;
	menuState = MENU_OFF;
	return RET_DONE;
}
// set a parameter value
uint8_t setParam(void)
{
	return(getEntry(activeMenu, sel_item));
}
#if __ENABLE_GPS
uint8_t gpsEcho(void)
{
	GPSEcho();
	return(getKeyEntry());
}
#endif

/*
 * main menu definition
 * 1) define the number of line items
 * 2) create menuitms struct
 * 3) add to menucontents struct
 */
struct menuItems_t {
		char	*menuLine;			// text of menu line
		char	selChar;			// character to select it
		uint8_t	(*func)(void);		// processing function
};

// main menu
#if __ENABLE_GPS
#define N_MAINMENU	11			// additional menu item for GPS
#else
#define N_MAINMENU	10			// number of lines in the menu
#endif

struct menuItems_t mainMenu[N_MAINMENU] = {
		{ "List setup parameters\r\n", 'A', printAllSetup },
		{ "Mesh Status\r\n", 'B', listMesh },
		{ "Chat Mode\r\n", 'C', chatMode },
		{ "Dump Frame stats\r\n", 'D', showstats },
#if __ENABLE_GPS
		{ "GPS Echo mode\r\n", 'G', gpsEcho },
#endif
		{ "LED test\r\n", 'L', ledTest },
		{ "Set Radio Parameters\r\n", 'R', setRadio },
		{ "Set Station Parameters\r\n", 'S', setStation },
		{ "Set clock (HH:MM)\r\n\n", 'T', setParam },
		{ "Write Setup Values\r\n", 'W', writeSetup },
		{ "Exit\r\n\n", 'X', exitMenu }
};

// radio menu
#define N_RADIOMENU	8
struct menuItems_t radioMenu[N_RADIOMENU] = {
		{ "RF Frequency\r\n", 'A', setParam },
		{ "Data Rate\r\n", 'B', setParam },
		{ "Peak Deviation\r\n", 'C', setParam },
		{ "Channel Filter BW\r\n", 'D', setParam },
		{ "Output Power (dBm)\r\n", 'E', setParam },
		{ "Rx Squelch (dBm)\r\n\n", 'F', setParam },
		{ "List Settings\r\n", 'L', printRadSetup },
		{ "Return to main menu\r\n\n", 'X', exitMenu }
};

// these need to correspond to the items above

// station menu
#define N_STATIONMENU	8
struct menuItems_t stationMenu[N_STATIONMENU] = {
		{ "Callsign\r\n", 'A', setParam },
		{ "Latitude\r\n", 'B', setParam },
		{ "Longitude\r\n", 'C', setParam },
		{ "Grid Square\r\n", 'D', setParam },
		{ "Repeat Default\r\n", 'E', setParam },
		{ "Beacon Interval\r\n\n", 'F', setParam },
		{ "List Settings\r\n", 'L', printStnSetup },
		{ "Return to main menu\r\n\n", 'X', exitMenu }
};

// menu contents
struct menuContents_t {
	char				*title;				// title of the menu
	int 				nMenuLines;			// number of lines
	struct menuItems_t	*menus;				// menu items
} menuContents[N_MENUS] = {
#if _BOARD_TYPE==NUCLEO_BOARD
		{ "      IP400 Nucleo Main menu\r\n", N_MAINMENU, mainMenu },
#else
		{ "      IP400 Pi menu\r\n", N_MAINMENU, mainMenu },
#endif
		{ "      Radio setup menu\r\n", N_RADIOMENU, radioMenu },
		{ "      Station Setup  menu\r\n", N_STATIONMENU, stationMenu }
};

// menu (main) task
void Menu_Task_Init(void)
{
	// start off with the menu showing
	activeMenu = MAIN_MENU;
	menuState = MENU_SHOWING;
	entryState = NO_ENTRY;
}

// send a control code
void sendControlCode(uint8_t code)
{
	USART_Send_String(controlCodes[code].sequence, controlCodes[code].len);
}

// send a control code
void sendTextString(char *string)
{
	strcpy(menu, string);
	USART_Send_String(menu, strlen(string));
}

// process our time slot
void Menu_Task_Exec(void)
{
	int nBytesinBuff = 0;
	char c;
	struct menuItems_t *m;

	switch(menuState)	{

	// if there is a return in the console buffer,
	// bring  up the menu
	case MENU_OFF:
		if((nBytesinBuff=databuffer_bytesInBuffer()) == 0)
			return;
		for(int i=0;i<nBytesinBuff;i++)
			if((c=databuffer_get(0)) == ASCII_RET)
				menuState = MENU_SHOWING;
		return;

	case MENU_SHOWING:
		printMenu();
		menuState = MENU_SELECTING;
		return;

	case MENU_SELECTING:
		if((sel_item=getMenuItem()) == NO_ITEM)
			return;
		menuState = MENU_SELECTED;
		break;

	case MENU_SELECTED:
		m=menuContents[activeMenu].menus;
		m += sel_item;
		switch((*m->func)()) {

		case RET_MORE:
			break;

		case RET_PAUSE:
			sendTextString(pauseString);
			menuState = MENU_PAUSED;
			break;

		case RET_DONE:
			menuState = MENU_SHOWING;
			break;
		}
		break;

	case MENU_PAUSED:
		if(pause())
			menuState = MENU_SHOWING;
		break;

	}
}

// print the main menu
void printMenu(void)
{
	struct menuItems_t *m;

	// title
	sendControlCode(CONTROL_CLEAR);
	sendControlCode(CONTROL_HOME);

#if _BOARD_TYPE==NUCLEO_BOARD
	// only use double wide mode with Nucleo
	sendControlCode(CONTROL_DOUBLE_TOP);
	sendTextString(menuContents[activeMenu].title);
	sendControlCode(CONTROL_DOUBLE_BOTTOM);
	sendTextString(menuContents[activeMenu].title);
	sendControlCode(CONTROL_SINGLE);
	sendTextString(menuSpacer);
#else
	sendTextString(menuContents[activeMenu].title);
	sendTextString(menuSpacer);
#endif

	// lines
	int nMenuLines = menuContents[activeMenu].nMenuLines;
	for(int i=0;i<nMenuLines;i++)	{
		m=menuContents[activeMenu].menus;
		m += i;
		menu[0] = m->selChar;
		strcpy(&menu[1], ") ");
		strcat(menu, m->menuLine);
		USART_Send_String(menu, strlen(menu));
	}

	// selection
	sendTextString(selectItem);
}

// get a menu item and dispatch the correct processing routine
int getMenuItem(void)
{
	int nBytesinBuff =0;
	struct menuItems_t *m;
	char c;

	if((nBytesinBuff=databuffer_bytesInBuffer()) == 0)
		return NO_ITEM;

	for(int i=0;i<nBytesinBuff;i++)		{
		if((c=databuffer_get(0)) != BUFFER_NO_DATA)	{

			// translate and echo
			c = islower(c) ? toupper(c) : c;
			USART_Send_Char(c);

			// find the correct processing routine
			int nMenuLines = menuContents[activeMenu].nMenuLines;
			for(int j=0;j<nMenuLines;j++)	{
				m=menuContents[activeMenu].menus;
				m += j;
				if(m->selChar == c)	{
					sendTextString(menuSpacer);
					return j;
				}
			}
		}
	}
	sendTextString(whatItem);
	return NO_ITEM;
}

BOOL pause(void)
{
	if(databuffer_bytesInBuffer() == 0)
		return FALSE;

	if(databuffer_get(0) == ASCII_RET)
		return TRUE;

	return FALSE;

}

/*
 * This part pertains to getting an entry and validating it..
 */

// keys in entry mode
#define	KEY_EOL			0x0D		// carriage return
#define	KEY_ESC			0x1B		// escape key
#define KEY_DEL			0x7F		// delete key
#define	KEY_BKSP		0x08		// backspace key

#define	MAX_ENTRY		40			// max entry chars

BOOL delMode = FALSE;
int pos = 0;
char keyBuffer[MAX_ENTRY];

// forward refs
uint8_t validateEntry(int activeMenu, int item, char *keyBuffer);

/*
 * Get a key entry: basically stolen from chat.c
 */
uint8_t getKeyEntry(void)
{

	char c;
	int nBytesinBuff;

	if((nBytesinBuff=databuffer_bytesInBuffer()) == 0)
		return RET_MORE;

	for(int i=0;i<nBytesinBuff;i++)		{

		c=databuffer_get(0);

		if(delMode)	{
			if((c != KEY_DEL) && (c != KEY_BKSP))	{
				USART_Print_string("\\%c", c);
				if(pos < MAX_ENTRY)
					keyBuffer[pos++] = c;
				delMode = FALSE;
			} else {
				if(pos > 0)	{
					USART_Print_string("%c", keyBuffer[--pos]);
				} else {
					USART_Print_string("\\\r\n");
					delMode = FALSE;
				}
			}
			continue;
		} else {
		// processing a key

			switch (c)	{

			// EOL key: sent the packet
			case KEY_EOL:
				USART_Print_string("\r\n");
				keyBuffer[pos++] = '\0';
				return RET_DONE;
				break;

			// escape key: abort the entry
			case KEY_ESC:
				return RET_PAUSE;
				break;

			case KEY_DEL:
			case KEY_BKSP:
				USART_Print_string("\\%c", keyBuffer[--pos]);
				delMode = TRUE;
				break;

			default:
				USART_Send_Char(c);
				if(pos < MAX_ENTRY)
					keyBuffer[pos++] = c;
				break;
			}
		}
	}
	return RET_MORE;
}

/*
 * Get an entry and validate it
 */
uint8_t getEntry(int activeMenu, int item)
{
	struct menuItems_t *m;

	switch(entryState)	{

	case NO_ENTRY:
		delMode = FALSE;
		pos = 0;
		entryState = ENTERING;
		m=menuContents[activeMenu].menus;
		m += item;
		USART_Print_string("%s->", m->menuLine);
		return RET_MORE;

	case ENTERING:
		int keyStat = getKeyEntry();
		if(keyStat == RET_DONE)	{
			entryState = VALIDATING;
			return RET_MORE;
		}
		return keyStat;

	case VALIDATING:
		entryState = NO_ENTRY;
		return validateEntry(activeMenu, item, keyBuffer);
	}

	return RET_MORE;
}

// data types we are updating
enum {
	uint8_type,		// uint8 field
	int16_type,		// int16 field
	uint32_type,	// uint32 field
	float_type,		// floating point
	char_type,		// character type
	field_type		// field type
};

// struct to hold validation values
typedef struct field_validator_t {
	int			MinVal;				// minimum value
	int			MaxVal;				// maximum value
	void    	*setupVal;			// pointer to setup value
	int			type;				// type of entry
	uint32_t	scalar;				// scalar to convert to decimal
} FIELD_VALIDATOR;

// validators for radio mmenu
FIELD_VALIDATOR radioValidators[] = {
		{ MIN_FREQ, 450000000, &setup_memory.params.radio_setup.lFrequencyBase, uint32_type, 1000000 },
		{ 9600, 600000, &setup_memory.params.radio_setup.lDatarate, uint32_type, 1000 },
		{ 12500, 150000, &setup_memory.params.radio_setup.lFreqDev, uint32_type, 1000 },
		{ 2600, 1600000, &setup_memory.params.radio_setup.lBandwidth, uint32_type, 1000 },
		{ 0, 20, &setup_memory.params.radio_setup.outputPower, uint8_type, 1 },
		{ -115, 0, &setup_memory.params.radio_setup.rxSquelch, int16_type, 1 },
};

FIELD_VALIDATOR stationValidators[] = {
		{ 4, 6, &setup_memory.params.setup_data.stnCall, char_type, 0 },
		{ 2, 14, &setup_memory.params.setup_data.latitude, char_type, 0 },
		{ 2, 14, &setup_memory.params.setup_data.longitude, char_type, 0 },
		{ 6, 6, &setup_memory.params.setup_data.gridSq, char_type, 0 },
		{ 0, 0x8, &setup_memory.params.setup_data.flags, field_type, 0 },
		{ 1, 100, &setup_memory.params.setup_data.beaconInt, int16_type, 0 }
};

uint8_t validateEntry(int activeMenu, int item, char *keyBuffer)
{

	int newValue, min, max;

	//NB: the cases here must jive with the menu items
	switch(activeMenu)	{

	case MAIN_MENU:			// the only menu item here is the clock
		setTOD(keyBuffer);
		break;

	case RADIO_MENU:
		// convert a floating point entry to the required decimal
		if(isfloat(keyBuffer))
				newValue = (int)(ascii2double(keyBuffer)*radioValidators[item].scalar);
		else 	newValue = ascii2Dec(keyBuffer);
		max = radioValidators[item].MaxVal;
		min = radioValidators[item].MinVal;
		if((newValue<min) || (newValue>max))	{
			USART_Print_string("Must be in the range of %d to %d\r\n", min, max);
				return RET_PAUSE;
		}
		switch(radioValidators[item].type){

		case uint8_type:
			uint8_t *v8 = (uint8_t *)radioValidators[item].setupVal;
			*v8 = (uint8_t)newValue;
			break;

		case int16_type:
			int16_t *v16 = (int16_t *)radioValidators[item].setupVal;
			*v16 = (int16_t)newValue;
			break;

		case uint32_type:
			uint32_t *v32 = (uint32_t *)radioValidators[item].setupVal;
			*v32 = (uint32_t)newValue;
			break;
		}

		break;

		case STATION_MENU:
			switch(stationValidators[item].type){

			case int16_type:
				newValue = ascii2Dec(keyBuffer);
				max = stationValidators[item].MaxVal;
				min = stationValidators[item].MinVal;
				if((newValue<min) || (newValue>max))	{
					USART_Print_string("Must be in the range of %d to %d\r\n", min, max);
						return RET_PAUSE;
				}
				uint16_t *v8 = (uint16_t *)stationValidators[item].setupVal;
				*v8 = (uint16_t)newValue;
				break;

			case char_type:
				size_t len = strlen(keyBuffer);
				max = stationValidators[item].MaxVal;
				min = stationValidators[item].MinVal;
				if((len<min) || (len>max))	{
					USART_Print_string("String must be %d to %d in length\r\n", min, max);
						return RET_PAUSE;
				}
				strcpy((char *)stationValidators[item].setupVal, keyBuffer);
				break;

			case field_type:
				uint8_t val = keyBuffer[0] == 'T' ? 1 : 0;
				uint8_t *f8= (uint8_t *)stationValidators[item].setupVal;
				if(val)
					*f8 |= (uint8_t)stationValidators[item].MinVal;
				else
					*f8 &= (uint8_t)stationValidators[item].MinVal;
				break;
			}
			break;

	}

	// falls to here when done...
	return RET_DONE;
}


/*
 * Print the frame stats
 */
void Print_Frame_stats(FRAME_STATS *stats)
{
	USART_Print_string("Frame Statistics\r\n\n");

	USART_Print_string("Transmitted frames->%d\r\n", stats->TxFrameCnt);
	USART_Print_string("CRC Errors->%d\r\n", stats->CRCErrors);
	USART_Print_string("Rx Timeouts->%d\r\n", stats->TimeOuts);
	USART_Print_string("Frames with good CRC->%d\r\n", stats->RxFrameCnt);
	USART_Print_string("Beacon frames->%d\r\n", stats->nBeacons);
	USART_Print_string("Repeated frames->%d\r\n", stats->nRepeated);

	USART_Print_string("Processed Frames->%d\r\n", stats->RxFrameCnt);
	USART_Print_string("Dropped frames->%d\r\n", stats->dropped);
	USART_Print_string("Duplicate frames->%d\r\n", stats->duplicates);
	USART_Print_string("Repeated frames->%d\r\n", stats->duplicates);


}
/*
 * Print the radio error stats
 */
struct radio_errs_t	{
	uint32_t	mask;
	char		*errmsg;
} radio_errs[N_RADIO_ERRS] = {
		{ SEQ_COMPLETE_ERR, "Sequencer Error" },
		{ SEQ_ACT_TIMEOUT, "Sequencer action timeout" },
		{ PLL_CALAMP_ERR, "VCO Amplitude calibration error" },
		{ PLL_CALFREQ_ERR, "VCO Frequency calibration error" },
		{ PLL_UNLOCK_ERR, "PLL Unlocked" },
		{ PLL_LOCK_FAIL, "PLL Lock failure" },
		{ DBM_FIFO_ERR, "Data buffer FIFO error" }
};
//
void Print_Radio_errors(uint32_t errs)
{
	if(errs == 0){
		USART_Print_string("No radio errors\r\n");
		return;
	}
	for(int i=0;i<N_RADIO_ERRS;i++)
		if(errs & radio_errs[i].mask)
			USART_Print_string("%s\r\n", radio_errs[i].errmsg);
}
/*
 * Print the FSM state
 */
char *fsm_states[FSM_N_FSM_STATES] = {
		"Idle",
		"Enable RF registers",
		"Wait for active 2",
		"Active 2",
		"Enable current",
		"Synth setup",
		"VCO calibration",
		"Lock Rx and Rx",
		"Llock on Rx",
		"Enable PA",
		"Transmit",
		"Analog power down",
		"End transmit",
		"lock on Rx",
		"Enable Rx",
		"Enable LNA",
		"Recieve",
		"End rx",
		"Synth power down"
};
void Print_FSM_state(uint8_t state)
{
	if(state > FSM_N_FSM_STATES)	{
		USART_Print_string("\r\n?Unknown FSM state\r\n");
		return;
	}
	USART_Print_string("\r\nRadio FSM State: %d: %s\r\n", state, fsm_states[state]);
}
