/*! \file isptest.cpp

  \brief This file demonstrates the usage of the ATMEL's In-System Programming API.

*/


#include <iostream>
#include <iomanip>
using namespace std;

// For Unicode.
#include <TChar.h>

#include "atisp.h"
#include "isptest.h"
#include "utility.h"

// Selected communication interface.
int intrfce = RS232_PORT;

// ISP done through CAN bootloader?
bool canItfce;

// CAN node number.
char *nnb = "FF";

DECLARE_FUNCTION_POINTER (ispInit);         // PF_ispInit lpispInit = NULL;  
DECLARE_FUNCTION_POINTER (ispGetLastError);
DECLARE_FUNCTION_POINTER (ispSelectDevice);
DECLARE_FUNCTION_POINTER (ispSelectCommHardware);
DECLARE_FUNCTION_POINTER (ispOpenRs232Port);
DECLARE_FUNCTION_POINTER (ispOpenUsbPort);
DECLARE_FUNCTION_POINTER (ispOpenCanPort);
DECLARE_FUNCTION_POINTER (ispSyncTarget);
DECLARE_FUNCTION_POINTER (ispSelectCanNode);
DECLARE_FUNCTION_POINTER (ispReadSpecialByte);
DECLARE_FUNCTION_POINTER (ispWriteSpecialByte);
DECLARE_FUNCTION_POINTER (ispGetLastResult);
DECLARE_FUNCTION_POINTER (ispEraseBlock);
DECLARE_FUNCTION_POINTER (ispEraseDevice);
DECLARE_FUNCTION_POINTER (ispBlankCheckDevice);
DECLARE_FUNCTION_POINTER (ispCancel);
DECLARE_FUNCTION_POINTER (ispFillBuffer);
DECLARE_FUNCTION_POINTER (ispParseHexFile);
DECLARE_FUNCTION_POINTER (ispReadSingleByteFromBuffer);
DECLARE_FUNCTION_POINTER (ispProgramDevice);
DECLARE_FUNCTION_POINTER (ispVerifyDevice);
DECLARE_FUNCTION_POINTER (ispReadAddrRange);
DECLARE_FUNCTION_POINTER (ispSelectMemory);
DECLARE_FUNCTION_POINTER (ispEnterAppliMode);
DECLARE_FUNCTION_POINTER (ispEnterIspMode);

/*!
******************************************************************************
\fn         void writeTitle()
\brief	    Displays the application name in the console window title area.
******************************************************************************
*/
void writeTitle()
{
    SetConsoleTitle( "  ATMEL ISP Demo Program" );
    cout << endl << endl;
}

/*!
******************************************************************************
\fn         void setOperationTitle(string str)
\brief	    Displays the on going operation name.
\param      str     The text to be displayed
******************************************************************************
*/
void setOperationTitle(string str)
{
	cout << _TEXT(str) << setfill('.') <<
        setw(TABDOTWIDTH - str.length()) << flush;
}


/*!
******************************************************************************
\fn         void display(int status)
\brief	    Display the ISP command result; PASS or FAIL.
\param      pass		FAIL or PASS
\param		newLine		if true, insert a New Line after displaying PASS
******************************************************************************
*/
void display(int status, bool newLine)
{
    if ( status == PASS )
    {
		if ( newLine )
	        cout << _TEXT("PASS") << endl;
		else
			cout << _TEXT("PASS") << TAB << flush;
    }
    else
    {
		if ( newLine )
	        cout << _TEXT("FAIL") << endl;
		else
			cout << _TEXT("FAIL") << TAB << flush;
    }
}


/*!
******************************************************************************
\fn         void loadLibFuncs(HINSTANCE h)
\brief	    Get the addresses of the atisp library functions.
\param      HINSTANCE h     handle of the dynamically loaded library
******************************************************************************
*/
void loadLibFuncs(HINSTANCE h)
{
    // lpispInit = (PF_ispInit)GetProcAddress(h, ispInit);
    LOAD_FUNCTION_POINTER( h, ispInit );        
    LOAD_FUNCTION_POINTER( h, ispGetLastError );
    LOAD_FUNCTION_POINTER( h, ispSelectDevice );
    LOAD_FUNCTION_POINTER( h, ispSelectCommHardware );
    LOAD_FUNCTION_POINTER( h, ispOpenRs232Port );
    LOAD_FUNCTION_POINTER( h, ispOpenUsbPort );
    LOAD_FUNCTION_POINTER( h, ispOpenCanPort );
    LOAD_FUNCTION_POINTER( h, ispSyncTarget );
    LOAD_FUNCTION_POINTER( h, ispSelectCanNode );
    LOAD_FUNCTION_POINTER( h, ispReadSpecialByte );
    LOAD_FUNCTION_POINTER( h, ispWriteSpecialByte );
    LOAD_FUNCTION_POINTER( h, ispGetLastResult );
    LOAD_FUNCTION_POINTER( h, ispEraseBlock );
    LOAD_FUNCTION_POINTER( h, ispEraseDevice );
    LOAD_FUNCTION_POINTER( h, ispBlankCheckDevice );
    LOAD_FUNCTION_POINTER( h, ispCancel );
    LOAD_FUNCTION_POINTER( h, ispFillBuffer );
    LOAD_FUNCTION_POINTER( h, ispParseHexFile );
    LOAD_FUNCTION_POINTER( h, ispReadSingleByteFromBuffer );
    LOAD_FUNCTION_POINTER( h, ispProgramDevice );
    LOAD_FUNCTION_POINTER( h, ispVerifyDevice );
    LOAD_FUNCTION_POINTER( h, ispReadAddrRange );
    LOAD_FUNCTION_POINTER( h, ispSelectMemory );
    LOAD_FUNCTION_POINTER( h, ispEnterAppliMode );
    LOAD_FUNCTION_POINTER( h, ispEnterIspMode );
}


/*!
******************************************************************************
\fn         int main()
\brief	    ISP test main function.
******************************************************************************
*/
int main()
{
	int status = 0;

    writeTitle();

	// Handle to the explicitly loaded DLL.
	HINSTANCE hinstDll = NULL;

    if ( ( hinstDll = LoadLibrary("atisp.dll") ) == NULL )
    {
	    return STATUS_CANNOT_LOAD_DLL;
    }
    else
    {
        loadLibFuncs( hinstDll );
    }

	// **********************************************************
	// ************** ISP software initialization ***************
	// **********************************************************

    setOperationTitle("Initializing");
    
    if ( DYNCALL( ispInit() ) )     // lpispInit;
		display(PASS, NEWLINE);
	else
	{
        display(FAIL, NO_NEWLINE);
        return DYNCALL( ispGetLastError() );
	}

	// ********************************************************
	// ************** Microcontroller selection ***************
	// ********************************************************

    setOperationTitle("Device selection");
    if ( DYNCALL( ispSelectDevice( AT89C51RD2 ) ) )
		display(PASS, NEWLINE);
	else
	{
        display(FAIL, NO_NEWLINE);
        return DYNCALL( ispGetLastError() );
	}

	// *************************************************
	// ************** Hardware selection ***************
	// *************************************************

    setOperationTitle("Hardware selection");
    if ( DYNCALL( ispSelectCommHardware)( intrfce ) )
		display(PASS, NEWLINE);
	else
	{
        display(FAIL, NO_NEWLINE);
        return DYNCALL( ispGetLastError() );
	}

	// *************************************************
	// ************** Open communication ***************
	// *************************************************

    setOperationTitle("Opening port");
    bool pass;

    switch (intrfce)
    {
		case RS232_PORT:
			pass = DYNCALL( ispOpenRs232Port( "COM1", 38400 ) );
			canItfce = false;
			break;
		case USB_PORT:
			pass = DYNCALL( ispOpenUsbPort() );
			canItfce = false;
			break;
		case VECTOR:
		case IXXAT:
		case PEAK:
		case SYSTEC_USB_CAN:
			pass = DYNCALL( ispOpenCanPort( 500, 0, 1, 1, 0, 115200 ) );
			canItfce = true;
			break;
		default:
			cout << "Invalid interface selection" << endl;
    }
    if ( pass )
		display(PASS, NEWLINE);
	else
	{
        display(FAIL, NO_NEWLINE);
        return DYNCALL( ispGetLastError() );
	}

	// ***********************************************************
	// ************** Synchronize the RS232 target ***************
	// ***********************************************************

	if ( SYNCHRO )
	{
		if ( intrfce == RS232_PORT )
		{
			setOperationTitle("Synchronizing target");
			if ( DYNCALL( ispSyncTarget() ) )
				display(PASS, NEWLINE);
			else
			{
				display(FAIL, NO_NEWLINE);
				status = DYNCALL( ispGetLastError() );
				DYNCALL( ispCancel() );
				return status;
			}
		}
	}

	// ************************************************************
	// ************** Activate the CAN node for ISP ***************
	// ************************************************************

    // For CAN devices only.
    // In ISP mode, the ATMEL's bootloader accepts 0xFF as node number
    // for the 'select CAN node' command, whatever the actual node number is.

    if ( canItfce )
    {
        setOperationTitle("Selecting CAN node");
	    if ( DYNCALL( ispSelectCanNode( 0xFF ) ) )
		    display(PASS, NEWLINE);
	    else
	    {
            display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
		    DYNCALL( ispCancel() );
            return status;
	    }

        // We need to get the actual device node number to be able
        // to go back to ISP mode from appli mode.

        if ( DYNCALL( ispReadSpecialByte(NNB) ) )
            nnb = DYNCALL( ispGetLastResult() );

        setOperationTitle("Setting the CRIS");
	    if ( DYNCALL( ispWriteSpecialByte( CRIS, 0x00 ) ) )
		    display(PASS, NEWLINE);
	    else
	    {
            display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
		    DYNCALL( ispCancel() );
            return status;
	    }
    }

	// ******************************************************
	// ************ Read the bootloader version *************
	// ******************************************************

	// Demonstrates how to read a device special byte.
	// The bootloader version byte always exists.
	// See ispdll.h for the enumeration of special bytes.

	if ( READ_BLV )
	{
		setOperationTitle("Reading the bootloader version");
		if ( DYNCALL( ispReadSpecialByte( BOOTLOADER_VERSION ) ) )
		{
			display(PASS, NO_NEWLINE);
			string s = DYNCALL( ispGetLastResult() );
			cout << "1." << s.substr(0, 1) << "." << s.substr(1, 1) << endl;
		}
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	// ******************************************************
	// *************** Erase the FLASH memory ***************
	// ******************************************************

	// Show block erasing for demo purpose.
	// Block erasing is not supported by all devices.
	
	if ( ERASE_BLOCK_0 )
	{
		setOperationTitle("Erasing block 0");
		if ( DYNCALL( ispEraseBlock( 0 ) ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	if ( FULL_ERASE )
    {
		setOperationTitle("Erasing FLASH");
		if ( DYNCALL( ispEraseDevice() ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	// *****************************************************
	// *********** Blank check the FLASH memory ************
	// *****************************************************

	if ( BLANK_CHECK )
	{
		setOperationTitle("Blank checking FLASH memory");
		if ( DYNCALL( ispBlankCheckDevice() ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			if ( DYNCALL( ispGetLastError() ) == STATUS_BLANK_CHECK_ERROR )
			{
				string s = DYNCALL( ispGetLastResult() );
				cout << "at 0x" << s << endl;
			}
			DYNCALL( ispCancel() );
			return status;
		}
	}

	// **************************************************************
	// ************** Fill internal buffer with zeros ***************
	// **************************************************************

	// This is for demo about the way to fill the ISP internal buffer.

	if ( FILL_ZEROS )
	{
		setOperationTitle("Filling buffer with 00");
		if ( DYNCALL( ispFillBuffer( "00" ) ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	// *************************************************
	// ************** Parse the HEX file ***************
	// *************************************************

	if ( PARSE_HEX )
	{
		setOperationTitle("Parsing HEX file");
		if ( DYNCALL( ispParseHexFile(".\\test.hex") ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	// *************************************************
	// ******** Read single byte from buffer ***********
	// *************************************************

	if ( READ_SINGLE_BYTE_BUFFER )
	{
		setOperationTitle("Reading buffer");
		string s;
		DYNCALL( ispReadSingleByteFromBuffer( 0 ) );
		s = DYNCALL( ispGetLastResult() );
		cout << s.c_str() << endl;
	}

	// *************************************************
	// ************** Program the FLASH ****************
	// *************************************************

	if ( PROGRAM_FLASH )
	{
		setOperationTitle("Programming FLASH");
		if ( DYNCALL( ispProgramDevice() ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	// ************************************************
	// ************** Verify the FLASH ****************
	// ************************************************

	if ( VERIFY_FLASH )
	{
		setOperationTitle("Verifying FLASH");
		if ( DYNCALL( ispVerifyDevice() ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	// ************************************************
	// *************** Read the FLASH *****************
	// ************************************************

	// We read the FLASH memory and produce a HEX file from its content.
	// You may call the ispReadAddrRange function without specifying an
	// output file.
	// Call the ispReadDevice function to read the whole memory.

	if ( READ_FLASH )
	{
		setOperationTitle("Reading FLASH");
		if ( DYNCALL( ispReadAddrRange( 0x00, 0xFF, ".\\flash_read.hex") ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	// ************************************************
	// **************** Select EEPROM *****************
	// ************************************************

	if ( SELECT_EEPROM )
	{
		setOperationTitle("Selecting EEPROM");
		if ( DYNCALL( ispSelectMemory( EEPROM ) ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	// **************************************************************
	// **************** Fill EEPROM buffer with 0x55 ****************
	// **************************************************************

	// This is for demo about the way to fill the ISP internal buffer.
	if ( FILL_EEPROM_BUFFER )
	{
		setOperationTitle("Filling EEPROM buffer with 55");
		if ( DYNCALL( ispFillBuffer( "55" ) ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	// ************************************************
	// ***************** Program EEPROM ***************
	// ************************************************

	if ( PROGRAM_EEPROM )
	{
		setOperationTitle("Programming EEPROM");
		if ( DYNCALL( ispProgramDevice() ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	// ************************************************
	// ************** Verify the EEPROM ***************
	// ************************************************

	if ( VERIFY_EEPROM )
	{
		setOperationTitle("Verifying EEPROM");
		if ( DYNCALL( ispVerifyDevice() ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	// ***************************************************
	// ************** Switch to appli mode ***************
	// ***************************************************
	/*
		The purpose of the following code is to show how to switch
		between ISP and application modes. If the embedded bootloader
		supports switching to application mode, you may uncomment this
		code to try it.
	*/
	if ( SWITCH_TO_APPLI )
	{
		setOperationTitle("Switching to appli mode");
		if ( DYNCALL( ispEnterAppliMode( false, 0 ) ) )
			display(PASS, NEWLINE);
		else
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
	}

	if ( VERIFY_IN_APPLI_MODE )
	{
		// Try a verify device which should fail since we are
		// in application mode.
		setOperationTitle("Verifying device");
		if ( DYNCALL( ispVerifyDevice() ) )
		{
			display(FAIL, NO_NEWLINE);
			status = DYNCALL( ispGetLastError() );
			DYNCALL( ispCancel() );
			return status;
		}
		else
			display(PASS, NEWLINE);
	}

    if ( canItfce )
    {
        // Try to enter back ISP mode. Will probably fail until
        // we implement the proper mechanism in the firmware.

		if ( GOBACKTO_ISP )
		{
			setOperationTitle("Switching to ISP mode");
			BYTE nnbByte = strtoul( nnb, NULL, 16 );

			if ( DYNCALL( ispEnterIspMode( nnbByte, 0xFF00 ) ) )
				display(PASS, NEWLINE);
			else
			{
				display(FAIL, NO_NEWLINE);
				status = DYNCALL( ispGetLastError() );
				DYNCALL( ispCancel() );
				return status;
			}
		}


        if ( VERIFY_POST_BACKTO_ISP )
		{
			// Try a verify device which should pass since we are
			// back in ISP mode.
			setOperationTitle("Verifying device");
			if ( DYNCALL( ispVerifyDevice() ) )
				display(PASS, NEWLINE);
			else
			{
				display(FAIL, NO_NEWLINE);
				status = DYNCALL( ispGetLastError() );
				DYNCALL( ispCancel() );
				return status;
			}
		}
    }

	// ********************************************
	// ************** Terminate ISP ***************
	// ********************************************

	// Always call the ispCancel() function before exiting; it closes
	// any open port and frees dynamically allocated memory for internal
	// data buffer and objects.

	DYNCALL( ispCancel() );

    return 0;
}
