//************************************************************************
//*	Arduino Test Suite
//*		(C) 2010 by Mark Sproul
//*		Open source as per standard Arduino code
//*	
//*	  This library is free software; you can redistribute it and/or
//*	  modify it under the terms of the GNU Lesser General Public
//*	  License as published by the Free Software Foundation; either
//*	  version 2.1 of the License, or (at your option) any later version.
//*	
//*	  This library 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
//*	  Lesser General Public License for more details.
//************************************************************************
//*	Aug 31,	2010	<MLS> Started on TestArduino
//*	Oct 18,	2010	<MLS> Added memory testing
//************************************************************************

#include	<avr/pgmspace.h>
#include	<avr/io.h>
#include	<avr/eeprom.h>



#include	"ArduinoTestSuite.h"


#include	"Arduino.h"
#include	"HardwareSerial.h"
#include	"pins_arduino.h"


#include	"avr_cpunames.h"

#if defined(USART3_RX_vect)
	#define	SERIAL_PORT_COUNT		4
#elif  defined(USART1_RX_vect)
	#define	SERIAL_PORT_COUNT		2
#else
	#define	SERIAL_PORT_COUNT		1
#endif




//************************************************************************
enum 
{
	ATS_Manufacturer	=	1,
	ATS_CPU,
	ATS_GCC_version,
	ATS_LIBC_version,
	ATS_CompiledDate,
	ATS_TestSuiteName,
	ATS_FreeMemory,
	
	
};
unsigned long	gTestStartTime;
short			gTagIndent;
int				gYotalErrors;
int				gTestCount;



prog_char	gTextMsg_Manufacturer[]			PROGMEM	=	"MANUFACTURER";
prog_char	gTextMsg_CPUname[]				PROGMEM	=	"CPU-NAME";
prog_char	gTextMsg_GCC_VERSION[]			PROGMEM	=	"GCC-Version";
prog_char	gTextMsg_AVR_LIBC[]				PROGMEM	=	"AVR-LibC-Ver";
prog_char	gTextMsg_COMPILED_DATE[]		PROGMEM	=	"Compiled-date";
prog_char	gTextMsg_TEST_SUITE_NAME[]		PROGMEM	=	"Test-Suite-Name";
prog_char	gTextMsg_memoryUsage[]			PROGMEM	=	"Free-memory";
prog_char	gTextMsg_dotdotdot[]			PROGMEM	=	"... ";
prog_char	gTextMsg_ok[]					PROGMEM	=	"ok";
prog_char	gTextMsg_FAIL[]					PROGMEM	=	"FAIL";
prog_char	gTextMsg_spaceEqual[]			PROGMEM	=	" = ";
prog_char	gTextMsg_info[]					PROGMEM	=	"info.";
prog_char	gTextMsg_dashLine[]				PROGMEM	=	"--------------------------";
prog_char	gTextMsg_DigitalRW[]			PROGMEM	=	"DigitalReadWrite_";
prog_char	gTextMsg_PWMoutput[]			PROGMEM	=	"PWMoutput_";
prog_char	gTextMsg_AnalogInput[]			PROGMEM	=	"AnalogInput_";

//************************************************************************
void Serial_print_P(prog_char *flashMemStr)
{
char	theChar;
int		ii;

	ii		=	0;
#if (FLASHEND > 0x10000)
	while (theChar	=	pgm_read_byte_far(flashMemStr + ii++))
#else
	while (theChar	=	pgm_read_byte_near(flashMemStr + ii++))
#endif
	{
		Serial.print(theChar);
	}
}

//************************************************************************
void Serial_println_P(prog_char *flashMemStr)
{
	Serial_print_P(flashMemStr);
	Serial.println();
}

//************************************************************************
//*	this is for internal use only, not made pubic to the API
static void	ATS_PrintProperty(	int		propertyTagNum,
								char	*propertyName,
								char	*propertyValue)
{
char	lineBuffer[64];

	strcpy_P(lineBuffer, gTextMsg_info);
	switch(propertyTagNum)
	{
		case 0:
			strcat(lineBuffer, propertyName);
			break;
			
		case ATS_Manufacturer:
			strcat_P(lineBuffer, gTextMsg_Manufacturer);
			break;

		case ATS_CPU:
			strcat_P(lineBuffer, gTextMsg_CPUname);
			break;

		case ATS_GCC_version:
			strcat_P(lineBuffer, gTextMsg_GCC_VERSION);
			break;

		case ATS_LIBC_version:
			strcat_P(lineBuffer, gTextMsg_AVR_LIBC);
			break;

		case ATS_CompiledDate:
			strcat_P(lineBuffer, gTextMsg_COMPILED_DATE);
			break;

		case ATS_TestSuiteName:
			strcat_P(lineBuffer, gTextMsg_TEST_SUITE_NAME);
			break;

		case ATS_FreeMemory:
			strcat_P(lineBuffer, gTextMsg_memoryUsage);
			break;
	}

	while (strlen(lineBuffer) < 20)
	{
		strcat(lineBuffer, " ");
	}
	
	strcat_P(lineBuffer, gTextMsg_spaceEqual);
	if (propertyValue != 0)
	{
		strcat(lineBuffer, propertyValue);
	}
	Serial.println(lineBuffer);

}




//************************************************************************
void	ATS_begin(char *manufName, char *testSuiteName)
{
int		freeMemory;
char	memoryMsg[48];

	gYotalErrors	=	0;
	gTestCount		=	0;

	Serial.begin(9600);
	delay(1000);
	
	gTestStartTime	=	millis();

	Serial.println();
	Serial.println();
	Serial.println();

	ATS_PrintProperty(ATS_Manufacturer,		0,	manufName);
	ATS_PrintProperty(ATS_CPU,				0,	_AVR_CPU_NAME_);
	ATS_PrintProperty(ATS_GCC_version,		0,	__VERSION__);
	ATS_PrintProperty(ATS_LIBC_version,		0,	__AVR_LIBC_VERSION_STRING__);
	ATS_PrintProperty(ATS_CompiledDate,		0,	__DATE__);
	ATS_PrintProperty(ATS_TestSuiteName,	0,	testSuiteName);

	freeMemory	=	ATS_GetFreeMemory();
	sprintf(memoryMsg, "%d bytes", freeMemory);
	ATS_PrintProperty(ATS_FreeMemory,	0,	memoryMsg);

	randomSeed(analogRead(0));

}

//************************************************************************
void	ATS_end()
{
long	seconds;
long	milliSecs;


	Serial_println_P(gTextMsg_dashLine);
	
	//	Ran 4 tests in 0.000s
	Serial.print("Ran ");
	Serial.print(gTestCount);
	Serial.print(" tests in ");
	
	seconds		=	millis() / 1000;
	milliSecs	=	millis() % 1000;
	Serial.print(seconds);
	Serial.print('.');
	Serial.print(milliSecs);
	Serial.print('s');
	Serial.println();
	Serial.println();

	if (gYotalErrors == 0)
	{
		Serial.print("OK");
	}
	else
	{
		Serial.print("FAILED (failures=");
		Serial.print(gYotalErrors);
		Serial.print(")");
	}
	Serial.println();
	
	//*	send control D to terminate (End Of File)
	Serial.write(0x04);
}



//************************************************************************
void ATS_PrintTestStatus(char *testString, boolean passed)
{
int	sLen;

	Serial.print(testString);
	sLen	=	strlen(testString);
	while (sLen < 60)
	{
		Serial.print(' ');
		sLen++;
	}
	Serial_print_P(gTextMsg_dotdotdot);
	if (passed)
	{
		Serial_print_P(gTextMsg_ok);
	}
	else
	{
		Serial_print_P(gTextMsg_FAIL);
		gYotalErrors++;
	}
	Serial.println();
	
	gTestCount++;
}



//************************************************************************
//*	returns true if no errors, false if there is an error
int	ATS_Test_DigitalPinWithHelper(uint8_t digitalPinToTest, uint8_t helperpin)
{
boolean	passedOK;
int		pinValue;
char	testName[64];
char	numString[32];

	strcpy_P(testName, gTextMsg_DigitalRW);
	sprintf(numString, "%02d", digitalPinToTest);
	strcat(testName, numString);

	passedOK	=	true;
	
	//*	test senario 1
	pinMode(digitalPinToTest, OUTPUT);
	pinMode(helperpin, INPUT);

	digitalWrite(digitalPinToTest, HIGH);
	pinValue	=	digitalRead(helperpin);
	if (pinValue != HIGH)
	{
		passedOK	=	false;
	}

	digitalWrite(digitalPinToTest, LOW);
	pinValue	=	digitalRead(helperpin);
	if (pinValue != LOW)
	{
		passedOK	=	false;
	}

	
	//*	now reverse the input/output
	pinMode(digitalPinToTest, INPUT);
	pinMode(helperpin, OUTPUT);
	
	digitalWrite(helperpin, HIGH);
	pinValue	=	digitalRead(digitalPinToTest);
	if (pinValue != HIGH)
	{
		passedOK	=	false;
	}

	digitalWrite(helperpin, LOW);
	pinValue	=	digitalRead(digitalPinToTest);
	if (pinValue != LOW)
	{
		passedOK	=	false;
	}


	if (! passedOK)
	{
		sprintf(numString, " (helper pin=%02d)", helperpin);
		strcat(testName, numString);
	}
	ATS_PrintTestStatus(testName, passedOK);
	return(passedOK);
}

//************************************************************************
boolean	ATS_Test_DigitalPin(uint8_t digitalPinToTest)
{
boolean	passedOK;
uint8_t helperpin;

	if ((digitalPinToTest % 2) == 0)
	{
		//*	if its EVEN, add 1
		helperpin	=	digitalPinToTest + 1;
	}
	else
	{
		//*	if its ODD
		helperpin	=	digitalPinToTest - 1;
	}
	passedOK	=	ATS_Test_DigitalPinWithHelper(digitalPinToTest, helperpin);
	return(passedOK);
}



//************************************************************************
//*	returns true if no errors, false if there is an error
int	ATS_TestTimer(	uint8_t	timerPinNumber,
					uint8_t	inputPin,
					char *statusString,
					char *errorString)
{
boolean			passedOK;
unsigned long	loopCounter;
unsigned long	lowCount;
unsigned long	highCount;
unsigned long	startTime;
int				percentLow;
int				percentHigh;
int				pinValue;
char			numString[48];
int				pwmValue;

	pwmValue	=	128;
	loopCounter	=	0;
	lowCount	=	0;
	highCount	=	0;
	passedOK	=	true;
	
	startTime	=	millis();
	pinMode(inputPin, INPUT);
	analogWrite(timerPinNumber, pwmValue);
	while ((millis() - startTime) < 500)
	{
		pinValue	=	digitalRead(inputPin);
		if (pinValue == HIGH)
		{
			highCount++;
		}
		else
		{
			lowCount++;
		}
	}
	analogWrite(timerPinNumber, 0);

	//*	the difference should be about 50%
	percentLow	=	lowCount / ((lowCount + highCount) / 100);
	percentHigh	=	highCount / ((lowCount + highCount) / 100);
	if ((percentLow > 45) && (percentLow < 55))
	{
		passedOK	=	true;
	}
	else
	{
		passedOK	=	false;
		strcat(errorString, " PWM ERROR");
	}
	sprintf(numString, "  (PWM=%02d %d%% LOW %d%% HIGH)", pwmValue, percentLow, percentHigh);
	strcat(statusString, numString);

	return(passedOK);
}


//************************************************************************
//*	returns true if no errors, false if there is an error
boolean	ATS_Test_PWMPinWithHelper(uint8_t pwmPinToTest, uint8_t helperpin)
{
boolean	passedOK;
char	testName[64];
char	errorString[48];
char	numString[8];
uint8_t	timerNumber;



	strcpy_P(testName, gTextMsg_PWMoutput);
	sprintf(numString, "%02d", pwmPinToTest);
	strcat(testName, numString);

	passedOK		=	true;
	errorString[0]	=	0;
	

	//*	is pin1 a timer?
	timerNumber	=	digitalPinToTimer(pwmPinToTest);
	if (timerNumber != NOT_ON_TIMER)
	{
		passedOK	=	ATS_TestTimer(pwmPinToTest, helperpin, testName, errorString);
	}
	else
	{
		//*	we should not get here
		passedOK	=	false;
	}

	ATS_PrintTestStatus(testName, passedOK);

	
	return(passedOK);
}

//************************************************************************
boolean	ATS_Test_PWM_Pin(uint8_t pwmPinToTest)
{
boolean	passedOK;
uint8_t helperpin;

	if ((pwmPinToTest % 2) == 0)
	{
		//*	if its EVEN, add 1
		helperpin	=	pwmPinToTest + 1;
	}
	else
	{
		//*	if its ODD
		helperpin	=	pwmPinToTest - 1;
	}
	passedOK	=	ATS_Test_PWMPinWithHelper(pwmPinToTest, helperpin);
	return(passedOK);
}


#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
	#define	kAnalogPinOffset	54
#else
	#define	kAnalogPinOffset	14
#endif


//************************************************************************
boolean	ATS_Test_AnalogInputWithHelper(uint8_t analogPintoTest, uint8_t helperPin)
{
boolean	passedOK;
char	testName[64];
char	infoString[48];
int		analogValueHigh;
int		analogValueLow;


	//*	first we have to set the ANALOG pin to INPUT
	pinMode(analogPintoTest + kAnalogPinOffset, INPUT);
	
	passedOK	=	true;
	
	strcpy_P(testName, gTextMsg_AnalogInput);
	sprintf(infoString, "%02d", analogPintoTest);
	strcat(testName, infoString);


	pinMode(helperPin, OUTPUT);

	digitalWrite(helperPin, LOW);
	analogValueLow	=	analogRead(analogPintoTest);
	if (analogValueLow > 100)
	{
		passedOK	=	false;
	}


	digitalWrite(helperPin, HIGH);
	analogValueHigh	=	analogRead(analogPintoTest);
	if (analogValueHigh < 1000)
	{
		passedOK	=	false;
	}


	sprintf(infoString, " (Low=%4d High=%4d helper pin=%d)", analogValueLow, analogValueHigh, helperPin);
	strcat(testName, infoString);
	
	ATS_PrintTestStatus(testName, passedOK);

	return(passedOK);
}


//************************************************************************
boolean	ATS_Test_AnalogInput(uint8_t analogPinToTest)
{
boolean	passedOK;
uint8_t helperpin;

	if ((analogPinToTest % 2) == 0)
	{
		//*	if its EVEN, add 1
		helperpin	=	kAnalogPinOffset + analogPinToTest + 1;
	}
	else
	{
		//*	if its ODD
		helperpin	=	kAnalogPinOffset + analogPinToTest - 1;
	}
	passedOK	=	ATS_Test_AnalogInputWithHelper(analogPinToTest, helperpin);
	return(passedOK);
}


#define	kSerialTestBaudRate	9600
#define	kSerialTestDelay	3


#if (SERIAL_PORT_COUNT > 1) && !defined(__AVR_ATmega32U4__)
//************************************************************************
//*	retunrs 0 if no errors, 1 if an error occured
short	ATS_TestSerialLoopback(HardwareSerial *theSerialPort, char *serialPortName)
{
char	xmitChar;
char	rcvChar;
short	ii;
short	serialErrCt;
short	timeOutLoopCtr;


	serialErrCt	=	1;
	if (theSerialPort != 0)
	{
		serialErrCt	=	0;
		theSerialPort->begin(kSerialTestBaudRate);
		
		for (ii=0; ii<150; ii++)
		{
			xmitChar	=	ii;
			theSerialPort->print(xmitChar);
			
			timeOutLoopCtr	=	0;
			//*	wait for data to come back or timeout
			while (!theSerialPort->available() && (timeOutLoopCtr < kSerialTestDelay))
			{
				delay(1);
				timeOutLoopCtr++;
			}
			
			if (theSerialPort->available())
			{
				//*	get the char
				rcvChar	=	theSerialPort->read();
				if (rcvChar != xmitChar)
				{
					serialErrCt	=	1;
				}
			}
			else
			{
				serialErrCt	=	1;
			}
		}
		theSerialPort->end();

		if (serialErrCt == 0)
		{
			ATS_PrintTestStatus(serialPortName, PASSED);
		}
		else
		{
			ATS_PrintTestStatus(serialPortName, FAILED);
		}
	}
	
	return(serialErrCt);
}
#endif


//************************************************************************
boolean	ATS_Test_EEPROM(void)
{
boolean		passedOK;
uint8_t 	dataByte;
uint8_t		dataByteRead;
uint16_t	dataWord;
uint16_t	dataWordRead;
uint32_t	dataLongWord;
uint32_t	dataLongWordRead;
int			addressPtr;
char		reportString[48];

	passedOK		=	true;
	//*	test BYTE read/write
	addressPtr		=	random(E2END);
	dataByte		=	0x5A;
	eeprom_write_byte((uint8_t *)addressPtr, dataByte);
	dataByteRead	=	eeprom_read_byte((uint8_t *)addressPtr);

	sprintf(reportString, "EEPROM_byte_rw  (addr= 0x%04X)", addressPtr);
	if (dataByteRead == dataByte)
	{
		ATS_PrintTestStatus(reportString, PASSED);
	}
	else
	{
		ATS_PrintTestStatus(reportString, FAILED);
		passedOK		=	false;
	}


	//*	test WORD read/write
	addressPtr		=	random(E2END);
	dataWord		=	0xA55A;
	eeprom_write_word((uint16_t *)addressPtr, dataWord);
	dataWordRead	=	eeprom_read_word((uint16_t *)addressPtr);

	sprintf(reportString, "EEPROM_word_rw  (addr= 0x%04X)", addressPtr);
	if (dataWordRead == dataWord)
	{
		ATS_PrintTestStatus(reportString, PASSED);
	}
	else
	{
		ATS_PrintTestStatus(reportString, FAILED);
		passedOK		=	false;
	}


	//*	test Long WORD read/write
	addressPtr		=	random(E2END);
	dataLongWord	=	0x5AA5A55A;
	eeprom_write_dword((uint32_t *)addressPtr, dataLongWord);
	dataLongWordRead	=	eeprom_read_dword((uint32_t *)addressPtr);

	sprintf(reportString, "EEPROM_dword_rw (addr= 0x%04X)", addressPtr);
	if (dataLongWordRead == dataLongWord)
	{
		ATS_PrintTestStatus(reportString, PASSED);
	}
	else
	{
		ATS_PrintTestStatus(reportString, FAILED);
		passedOK		=	false;
	}


	return(passedOK);
}



//************************************************************************
extern unsigned int __data_start;
extern unsigned int __data_end;
extern unsigned int __bss_start;
extern unsigned int __bss_end;
extern unsigned int __heap_start;
extern void *__brkval;



//************************************************************************
int	ATS_GetFreeMemory()
{
int free_memory;

	if((int)__brkval == 0)
	{
		free_memory = ((int)&free_memory) - ((int)&__bss_end);
	}
	else
	{
		free_memory = ((int)&free_memory) - ((int)__brkval);
	}
	return free_memory;
}