// command.c

#include <ctype.h>
#include <string.h>
#include "command.h"
#include "serial.h"
#include "xmodem.h"
#include "flash.h"

// storage for the program and erase routines
extern unsigned char ProgEraseArray[ 512 ];

// definition for the structure which stores the Flash programming and erasing code
extern struct rom_data {
	unsigned long start_address;
	unsigned long data_length;
	unsigned char data[1];
};

// extern constant structure containing the erasing function that
// is copied to RAM and executed from RAM
extern const struct rom_data erase;

// type definition for pointer to the Flash erasing function
typedef unsigned short (*pt2Function)(unsigned char );

//
// function to initialise the hardware interface used to communicate with the
// outside world
//
void InitCommandHandler (void)
{
	InitSci();
	InitDelayTimer();
}

//
// functionality of this handler will be dependent on the communication hardware interface
//
void RunCommandHandler (void)
{
	union union_c2s c2s;
	
	PurgeComms( 250 );	
	ShowMenu();
	 		
	while (1)
	{
		// timeout is not important as we know we have a byte waiting for us
		c2s.us = GetByte (100000);
		if ( c2s.uc[0] == OK )
		{
			switch ( c2s.uc[1] )
			{
				case '1':
					Command_1();
				break;
				
				case '2':
					Command_2();
				break;
				
				case '3':
					Command_3();
				break;
				
				default:
					ShowMenu();
			}
		}
	}
}

void ShowMenu (void)
{
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendString( "Bootloader Main Menu" );
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendString( "-----------------------------------" );
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendString( "1......Erase User Area" );
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendString( "2......Erase User Block" );
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendString( "3......Program Flash via XModem Download" );
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
}

void Command_1 (void)
{
// erase entire user MAT

	unsigned char uc;
	unsigned short Status;
	union union_c2s c2s;
	unsigned long *ptr;
	pt2Function fp;
	
	// load the erase function into RAM
	memcpy( (void *)ProgEraseArray, (void *)erase.data, erase.data_length );
	// load the function pointer
	ptr = ( (unsigned long *) &ProgEraseArray);
	fp = (pt2Function) *ptr;

	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	
	SendString( "Really erase ALL user blocks (Y/N)?" );
	c2s.us = GetByte( 10000 );
	SendByte( c2s.uc[1] );
	if ( c2s.uc[0] == TIMEOUT )
	{
		SendByte( LINEFEED );
		SendByte( CARRIAGE_RETURN );
		SendString( "Timed out waiting for response" );
		return;
	}
	else if ( c2s.uc[0] == ERROR )
	{
		SendByte( LINEFEED );
		SendByte( CARRIAGE_RETURN );
		SendString( "Comms error" );
		return;
	}
	else if ( !( ( c2s.uc[1] == 'y' ) || ( c2s.uc[1] == 'Y' ) ) )
	{
		SendByte( LINEFEED );
		SendByte( CARRIAGE_RETURN );
		SendString( "All blocks erase cancelled" );
		return;
	}

	for( uc=FIRST_USER_FLASH_BLK; uc<NO_OF_FLASH_BLOCKS; uc++ )
	{
		SendByte( LINEFEED );
		SendByte( CARRIAGE_RETURN );
		SendString( "Erasing block " );
		if( uc < 10 )
		{
			SendByte( uc + 48 );
		}
		else
		{
			SendByte( '1' );
			SendByte( ( uc - 10 ) + 48 );
		}
		SendString ( "..." );
		Status = fp ( uc );
		if ( Status != FLASH_NO_ERROR )
		{
			SendString( "Erasing of block " );
			if( uc < 10 )
			{
				SendByte( uc + 48 );
			}
			else
			{
				SendByte( '1' );
				SendByte( ( uc - 10 ) + 48 );
			}
			SendString( " FAILED" );
			SendByte( LINEFEED );
			SendByte( CARRIAGE_RETURN );
			return;
		}
	}

	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendString( "All user blocks erased" );
}

void Command_2 (void)
{
// single block erase
	
	union union_c2s c2s;
	unsigned char BlockNum, Count;
	unsigned short Status;
	unsigned long *ptr;
	pt2Function fp;
	
	BlockNum = 0;
	
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendString( "Enter block no to be erased as 2-digit number e.g. 08" );
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendString( ">D'" );
	
	for (Count=0; Count<2; Count++)
	{
		c2s.us = GetByte( 10000 );
		SendByte( c2s.uc[1] );
		if ( c2s.uc[0] == TIMEOUT )
		{
			SendByte( LINEFEED );
			SendByte( CARRIAGE_RETURN );
			SendString( "Timed out waiting for block number..." );
			return;
		}
		else if ( !isdigit( c2s.uc[1] ) )
		{
			SendByte( LINEFEED );
			SendByte( CARRIAGE_RETURN );
			SendString( "Invalid character" );
			return;
		}
		else
		{
			if ( Count == 0 )
			{
				BlockNum = 10 * ( c2s.uc[1] - 48 );
			}
			else
			{
				BlockNum += ( c2s.uc[1] - 48 );
			}
		}
	}
	
	// check that the block number is valid
	if ( !( ( BlockNum >= FIRST_USER_FLASH_BLK ) && ( BlockNum < NO_OF_FLASH_BLOCKS ) ) )
	{
		SendByte( LINEFEED );
		SendByte( CARRIAGE_RETURN );
		SendString( "Invalid block number" );
		return;
	}
	
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendString( "Erase block (Y/N)?" );
	c2s.us = GetByte( 10000 );
	SendByte( c2s.uc[1] );
	if ( c2s.uc[0] == TIMEOUT )
	{
		SendByte( LINEFEED );
		SendByte( CARRIAGE_RETURN );
		SendString( "Timed out waiting for confirmation" );
		return;
	}
	else if (  ( c2s.uc[1] == 'y' ) || ( c2s.uc[1] == 'Y' ) )
	{
		/// load the erase function into RAM
		memcpy( (void *)ProgEraseArray, (void *)erase.data, erase.data_length );
		// load the function pointer
		ptr = ( (unsigned long *) &ProgEraseArray);
		fp = (pt2Function) *ptr;		
	    
	 	Status = fp ( BlockNum );
 		SendByte( LINEFEED );
		SendByte( CARRIAGE_RETURN );
		SendString( "Erasing of block " );
		SendByte( BlockNum + 48 );
		if ( Status != FLASH_NO_ERROR )
		{
			SendString( " FAILED" );
		} 
		else
		{
			SendString( " PASSED" );
		}
		return;
	}
	else
	{
		SendByte( LINEFEED );
		SendByte( CARRIAGE_RETURN );
		SendString( "Block erase cancelled" );
		return;
	}
}

void Command_3 (void)
{
// Flash programming via xmodem download
	
	unsigned char Status, Shift;
	volatile unsigned long Address, ul;
	union union_c2s c2s;
	
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendString( "Enter Flash memory base address as a 32-bit hex value e.g. AABBCCDD" );
	
	Address = 0;
	Shift = 28;
	
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendString( ">H'" );
	for (Status=0; Status<8; Status++)
	{		
		c2s.us = GetByte( 10000 );
		if ( c2s.uc[0] == TIMEOUT )
		{
			SendByte( LINEFEED );
			SendByte( CARRIAGE_RETURN );
			SendString( "Timed out waiting for address..." );
			return;
		}
		else if ( c2s.uc[0] == ERROR )
		{
			SendByte( LINEFEED );
			SendByte( CARRIAGE_RETURN );
			SendString( "Comms error" );
			return;
		}
		else
		{
			if ( !isxdigit( c2s.uc[1] ) )
			{
				SendByte( LINEFEED );
				SendByte( CARRIAGE_RETURN );
				SendString( "Invalid character" );
				return;
			}
			SendByte( c2s.uc[1] );
			c2s.uc[1] = toupper( c2s.uc[1] );
			c2s.uc[1] =  isdigit( c2s.uc[1] ) ? c2s.uc[1] - 48 : c2s.uc[1] - 55;
			ul = (unsigned long) c2s.uc[1];
			ul = ul << Shift;
			Address |= ul;
			Shift -= 4;
		}		
	}
	
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	
	// check that address is valid
	if ( ( Address < FIRST_USER_FLASH_ADDR ) || ( Address > ( LAST_USER_FLASH_ADDR - FLASH_LINE_SIZE - 1 ) ) )
	{
		SendString( "Invalid start address - aborting" );
		return;
	}
	
	if ( ( Address % FLASH_LINE_SIZE ) != 0 )
	{
		SendString( "Start address not on a flash line boundary - aborting" );
		return;
	}
		
	SendString( "Program Flash (Y/N)?" );
	c2s.us = GetByte( 10000 );
	SendByte( c2s.uc[1] );
	if ( c2s.uc[0] == TIMEOUT )
	{
		SendByte( LINEFEED );
		SendByte( CARRIAGE_RETURN );
		SendString( "Timed out waiting for response" );
		return;
	}
	else if ( c2s.uc[0] == ERROR )
	{
		SendByte( LINEFEED );
		SendByte( CARRIAGE_RETURN );
		SendString( "Comms error" );
		return;
	}
	else if ( !( ( c2s.uc[1] == 'y' ) || ( c2s.uc[1] == 'Y' ) ) )
	{
		SendByte( LINEFEED );
		SendByte( CARRIAGE_RETURN );
		SendString( "Flash programming cancelled" );
		return;
	}
	
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	SendString( "Start XModem download..." );
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	
	Status = XmodemDownloadAndProgramFlash( Address );
	
	if ( Status == XM_TIMEOUT )
		SendString( "Timeout" );
	else if ( Status == XM_OK )
		SendString( "Download OK" );
	else if ( Status == XM_PROG_FAIL )
		SendString( "Flash program FAIL" );
	
	SendByte( LINEFEED );
	SendByte( CARRIAGE_RETURN );
	
	PurgeComms( 1000 );
}