/*******************************************************************************
********************************************************************************
**
**  Copyright(c) 2022, Alliance for Automotive Innovation
**  Used only under license from the Alliance for Automotive Innovation. All Rights Reserved.
**
**  Project:  J1699-3
**  FileName: FilenameXYZ.c
**  Author:   EnGenius
**  Date:     2/25/2022
**  Email:    <support@autosinnovate.org>
**
**  Purpose:  SAE J1699-3 Vehicle OBD II Compliance Test Cases Source Code.
**            This source code is intended to run the tests described in
**            the SAE J1699-3 document in an automated manner, when compiled
**            and used with an SAE J2534-compatible pass-thru device.
**
**            File j1699.c contains information on building and running this test.
**
**  Description:
**
**  Modifications:  12/07/2022  Initial Version
**
********************************************************************************
*******************************************************************************/

#include <stdio.h>    // C Library input and output declarations
#include <stdlib.h>   // C Library general function declarations
#include <stdarg.h>   // C Library variable argument list declarations
#include <time.h>     // C Library time and date declarations
#include <conio.h>    // MS-DOS console input and output declarations
#include <windows.h>  // Windows API declarations
#include "j2534.h"    // j1699 project j2534 declarations
#include "j1699.h"    // j1699 project general declarations


#define MAX_USERINPUT          255
#define DESIRED_DUMP_SIZE      8      // desired number of transactions to be dumped from ring buffer
#define MAX_TRANSACTION_COUNT  (DESIRED_DUMP_SIZE + 1)  // max. number of transactions in ring buffer
#define MAX_RING_BUFFER_SIZE   16384  // max size of transaction ring buffer


// Function Prototypes
void ClearTransactionBuffer ( void );
void WriteToLog ( char *pLogString, LOGTYPE eLogType );
void AddToTransactionBuffer ( char *pszStringToAdd );  // adds a string to the transaction ring buffer


// Variables
// OBD message response data structures
typedef struct
{
	unsigned long StartIndex;
	unsigned long Length;
} TRANSACTIONENTRY;


// Global variables that should only be accessed by functions in this file
unsigned long gCommentCount = 0;          // the total count of "COMMENT:"
unsigned long gUserErrorCount = 0;        // the total count of "USER WARNING:"
unsigned long gFailureCount = 0;          // the total count of "FAILURE:"
unsigned long gWarningCount = 0;          // the total count of "WARNING:"
unsigned long gJ2534FailureCount = 0;     // the total count of "J2534 FAILURE:"
unsigned long gTransactionBufferEnd = 0;  // array index for end of transaction ring buffer
unsigned long gTransactionCount = 0;      // count of transactions in ring buffer
char gszTransactionBuffer[MAX_RING_BUFFER_SIZE]; // transaction ring buffer
TRANSACTIONENTRY grgsTransactionList[MAX_TRANSACTION_COUNT]; // list of start indexes for transactions in ring buffer


extern const char      gszAPP_REVISION[];
extern unsigned long   gLastLogTimestampMsecs;


/*******************************************************************************
**
**  Function:  Log
**
**  Purpose:   Print information to both the console and the log file
**
*******************************************************************************/
char Log ( LOGTYPE       eLogType,
           SCREENOUTPUT  eScreenOutput,
           LOGOUTPUT     eLogOutput,
           PROMPTTYPE    ePromptType,
           const char   *pLogString, ... )
{
	char PrintString[MAX_LOG_STRING_SIZE];
	char ErrorString[MAX_LOG_STRING_SIZE];
	char PrintBuffer[MAX_LOG_STRING_SIZE];
	char CommentString[MAX_MESSAGE_LOG_SIZE];
	char UserResponse[MAX_USERINPUT];
	BOOL bAddComment = FALSE;
	static BOOL bContinueAll = FALSE;


	// Get the full input string
	va_list Args;

	va_start ( Args, pLogString );

	vsprintf_s ( PrintString, MAX_LOG_STRING_SIZE, pLogString, Args );

	va_end ( Args );


	UserResponse[0] = 0;


	// Format the string depending on the log type
	switch ( eLogType )
	{
		case ERROR_FAILURE:
		case FAILURE:
			// Failure in the test
			gbTestFailed = TRUE;

			// Failure in the test Section (Static or Dynamic Test)
			gbTestSectionFailed = TRUE;
			gbTestSubsectionFailed = TRUE;
			gFailureCount++;

			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "%s %s",
			            "FAILURE:",
			            PrintString );
		break;

		case J2534_FAILURE:
			// Failure in the test
			gbTestFailed = TRUE;

			// Failure in the test Section (Static or Dynamic Test)
			gbTestSectionFailed = TRUE;

			// Failure in the test subsection (Test X.XX)
			gbTestSubsectionFailed = TRUE;

			PassThruGetLastError ( ErrorString );
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "%s  %s  PassThruGetLastError=%s\n",
			            "J2534 FAILURE:",
			            PrintString,
			            ErrorString );
			gJ2534FailureCount++;
		break;

		case USER_ERROR:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "%s %s",
			            "USER WARNING:",
			            PrintString );
			gUserErrorCount++;
		break;

		case WARNING:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "%s %s",
			            "WARNING:",
			            PrintString );
			gWarningCount++;
		break;

		case INFORMATION:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "%s %s",
			            "INFORMATION:",
			            PrintString );
		break;

		case RESULTS:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "%s %s",
			            "RESULTS:",
			            PrintString );
		break;

		case NETWORK:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "%s %s",
			            "NETWORK:",
			            PrintString );
		break;

		case BLANK:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "%s",
			            PrintString );
			break;

		case SUBSECTION_BEGIN:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "%s %d.%d %s %s\n",
			            "TEST: **** Test",
			            geTestPhase,
			            gTestSubsection,
			            PrintString, "****" );
		break;

		case SUBSECTION_PASSED_RESULT:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "%s %d.%d %s %s\n\n",
			            "RESULTS: **** Test",
			            geTestPhase,
			            gTestSubsection,
			            "PASSED",
			            "****" );
		break;

		case SUBSECTION_FAILED_RESULT:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "%s %d.%d %s %s\n\n",
			            "RESULTS: **** Test",
			            geTestPhase,
			            gTestSubsection,
			            "FAILED",
			            "****" );
		break;

		case SUBSECTION_INCOMPLETE_RESULT:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "%s %d.%d %s %s\n\n",
			            "RESULTS: **** Test",
			            geTestPhase,
			            gTestSubsection,
			            "INCOMPLETE",
			            "****" );
		break;

		case SECTION_PASSED_RESULT:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "\n\n%s %s %s %s\n\n",
			            "RESULTS: **** All J1699-3",
			            PrintString,
			            "Tests PASSED",
			            "****" );
		break;

		case SECTION_FAILED_RESULT:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "\n\n%s %s %s %s\n\n",
			            "RESULTS: **** J1699-3",
			            PrintString,
			            "Test FAILED",
			            "****" );
		break;

		case SECTION_INCOMPLETE_RESULT:
			sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
			            "\n\n%s %s %s %s\n\n",
			            "RESULTS: **** J1699-3",
			            PrintString,
			            "Test INCOMPLETE",
			            "****" );
		break;

		case COMMENT:
		case PROMPT:
		default:
			// include for consistency, but do nothing
		break;
	}

	if ( eLogType != PROMPT &&
	     eLogType != COMMENT )
	{
		// If Enabled, Print to the Screen
		if ( eScreenOutput == SCREENOUTPUTON &&
		     gbSuspendScreenOutput == FALSE )
		{
			printf ( "%s", PrintBuffer );
		}

		// If Enabled, Print to the Log File
		if ( eLogOutput == LOGOUTPUTON )
		{
			WriteToLog ( PrintBuffer, eLogType );
		}
	}


	// If a Prompt is requested
	if ( ePromptType != NO_PROMPT )
	{
		if ( eLogType == SUBSECTION_FAILED_RESULT )
		{
			sprintf_s ( &PrintString[strlen ( PrintString )], MAX_LOG_STRING_SIZE - strlen ( PrintString ),
			            "Failure(s) detected.  Do you wish to continue?  " );
		}

		else if ( eLogType == FAILURE ||
		          eLogType == SECTION_FAILED_RESULT )
		{
			if ( ePromptType == ENTER_PROMPT )
			{
				sprintf_s ( PrintString, MAX_LOG_STRING_SIZE,
				            "Failure(s) detected.  " );
			}
			else
			{
				sprintf_s ( PrintString, MAX_LOG_STRING_SIZE,
				            "Failure(s) detected.  Do you wish to continue?  " );
			}
		}

		else if ( eLogType != PROMPT &&
		          eLogType != COMMENT )
		{
			if ( ePromptType == ENTER_PROMPT )
			{
				sprintf_s ( PrintString, MAX_LOG_STRING_SIZE, " " );
			}
			else
			{
				sprintf_s ( PrintString, MAX_LOG_STRING_SIZE,
				            "Do you wish to continue?  " );
			}
		}


		switch ( ePromptType )
		{
			case QUIT_CONTINUE_PROMPT:
			{
				do
				{
					ClearKeyboardBuffer ( );
					sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
					            "PROMPT: %s (Enter Quit or Continue):  ",
					            PrintString );

					// If Enabled, Print to the Screen
					if ( eScreenOutput == SCREENOUTPUTON &&
					     gbSuspendScreenOutput == FALSE )
					{
						printf ( "\n%s", PrintBuffer );
					}
					fgets ( UserResponse, sizeof ( UserResponse ), stdin );

					// remove ASCII lowercase bit
					UserResponse[0] &= 0xDF;
				} while ( UserResponse[0] != 'C' &&
				          UserResponse[0] != 'Q' );

				sprintf_s ( &PrintBuffer[strlen ( PrintBuffer )], MAX_LOG_STRING_SIZE - strlen ( PrintBuffer ),
				            "%s\n\n",
				            UserResponse );
			}
			break;

			case YES_NO_PROMPT:
			{
				do
				{
					ClearKeyboardBuffer ( );
					sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
					            "PROMPT: %s (Enter Yes or No):  ",
					            PrintString );

					// If Enabled, Print to the Screen
					if ( eScreenOutput == SCREENOUTPUTON &&
					     gbSuspendScreenOutput == FALSE )
					{
						printf ( "\n%s", PrintBuffer );
					}
					fgets ( UserResponse, sizeof ( UserResponse ), stdin );

					// remove ASCII lowercase bit
					UserResponse[0] &= 0xDF;
				} while ( UserResponse[0] != 'Y' &&
				          UserResponse[0] != 'N' );

				sprintf_s ( &PrintBuffer[strlen ( PrintBuffer )], MAX_LOG_STRING_SIZE - strlen ( PrintBuffer ),
				            "%s\n\n",
				            UserResponse );
			}
			break;

			case YES_NO_ALL_PROMPT:
			{
				do
				{
					ClearKeyboardBuffer ( );
					sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
					            "PROMPT: %s (Enter Yes, No, All yes, or Comment):  ",
					            PrintString );

					// If Enabled, Print to the Screen
					if ( eScreenOutput == SCREENOUTPUTON &&
					     gbSuspendScreenOutput == FALSE )
					{
						// Get the user response and log it
						printf ( "\n%s", PrintBuffer );
					}

					if ( bContinueAll )
					{
						bContinueAll = TRUE;
						UserResponse[0] = 'Y';
						UserResponse[1] = 0x00;
						printf ( "%s\n", UserResponse );
					}
					else
					{
						fgets ( UserResponse, sizeof ( UserResponse ), stdin );
					}

					// remove ASCII lowercase bit
					UserResponse[0] &= 0xDF;
				} while ( UserResponse[0] != 'Y' &&
				          UserResponse[0] != 'N' &&
				          UserResponse[0] != 'A' &&
				          UserResponse[0] != 'C' );

				// If user selected 'C'omment, allow the user to enter a short comment
				if ( UserResponse[0] == 'C' )
				{
					bAddComment = TRUE;
				}
				// If user selected 'A'll Yes, set bContinueAll to respond 'Y'es to all future YES_NO_ALL_PROMPTs
				else if ( UserResponse[0] == 'A' )
				{
					bContinueAll = TRUE;
				}

				sprintf_s ( &PrintBuffer[strlen ( PrintBuffer )], MAX_LOG_STRING_SIZE - strlen ( PrintBuffer ),
				            "%s\n\n",
				            UserResponse );
			}
			break;

			case CUSTOM_PROMPT:
			{
				sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
				            "PROMPT: %s",
				            PrintString );

				// If Enabled, Print to the Screen
				if ( eScreenOutput == SCREENOUTPUTON &&
				     gbSuspendScreenOutput == FALSE )
				{
					printf ( "\n%s", PrintBuffer );
				}

				UserResponse[0] = 'Y';
				UserResponse[1] = 0x00;
			}
			break;

			case COMMENT_PROMPT:
			{
				if ( eScreenOutput == SCREENOUTPUTON &&
				     gbSuspendScreenOutput == FALSE )
				{
					ClearKeyboardBuffer ( );
					printf ( "\nPROMPT: %s  Type a comment (%d chars max) (Press Enter to continue):  ",
					         PrintString,
					         MAX_MESSAGE_LOG_SIZE-1 );

					fgets ( CommentString, MAX_MESSAGE_LOG_SIZE, stdin );

					// If the comment isn't empty (New Line), print it
					if ( CommentString[0] != 0x0A )
					{
						sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
						            "COMMENT: %s\n",
						            CommentString );
						gCommentCount++;
					}
					else
					{
						sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE, "\n" );
						eLogType = BLANK;
					}
				}
				// if not able to prompt for a comment, don't write anything to the log
				else
				{
					eLogOutput = LOGOUTPUTOFF;
				}
			}
			break;

			case ENTER_PROMPT:
			default:
			{
				ClearKeyboardBuffer  ( );
				sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
				            "PROMPT: %s (Press Enter to continue):  ",
				            PrintString );

				// If Enabled, Print to the Screen
				if ( eScreenOutput == SCREENOUTPUTON &&
				     gbSuspendScreenOutput == FALSE )
				{
					printf ( "\n%s", PrintBuffer );
				}
				fgets ( UserResponse, sizeof ( UserResponse ), stdin );

				sprintf_s ( &PrintBuffer[strlen ( PrintBuffer )], MAX_LOG_STRING_SIZE - strlen ( PrintBuffer ),
				            "%s\n\n",
				            UserResponse );

				UserResponse[0] = 'Y';
				UserResponse[1] = 0x00;
			}
		}


		// If the user chose to add a comment, log it
		if ( bAddComment == TRUE )
		{
			// If Enabled, Print to the Screen
			if ( eScreenOutput == SCREENOUTPUTON &&
			     gbSuspendScreenOutput == FALSE )
			{
				WriteToLog ( PrintBuffer, eLogType );

				ClearKeyboardBuffer ( );

				//Prompt for comment
				printf( "\nPROMPT: Type a comment (%d chars max) (Press Enter to continue):  ",
				        MAX_MESSAGE_LOG_SIZE-1 );

				fgets ( CommentString, MAX_MESSAGE_LOG_SIZE, stdin );

				// If the comment isn't empty (New Line), print it
				if ( CommentString[0] != 0x0A )
				{
					sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
					            "COMMENT: %s\n",
					            CommentString );
					WriteToLog ( PrintBuffer, eLogType );
					gCommentCount++;
				}

				do
				{
					ClearKeyboardBuffer ( );

					// Prompt to continue test
					sprintf_s ( PrintBuffer, MAX_LOG_STRING_SIZE,
					            "PROMPT: Do you wish to continue?  (Enter Yes or No):  " );
					printf( "\n%s", PrintBuffer );

					// Get the user response and log it
					fgets ( UserResponse, sizeof ( UserResponse ), stdin );

					// remove ASCII lowercase bit
					UserResponse[0] &= 0xDF;
				} while ( UserResponse[0] != 'Y' &&
				          UserResponse[0] != 'N' );

				sprintf_s ( &PrintBuffer[strlen( PrintBuffer )], MAX_LOG_STRING_SIZE - strlen ( PrintBuffer ),
				            "%s\n\n",
				            UserResponse );
			}
		}


		// If Enabled, Print to the Log File
		if ( eLogOutput == LOGOUTPUTON )
		{
			WriteToLog ( PrintBuffer, eLogType );
		}


		// Return first character of user response
		return  UserResponse[0] & 0xDF;
	}

	// By Default return 'Y'es - positive result
	return  'Y';
}


/*******************************************************************************
**
**  Function:  WriteToLog
**
**  Purpose:   Write the passed string to the log file
**
********************************************************************************/
void WriteToLog ( char    *pLogString,
                  LOGTYPE  eLogType )
{
	char          LogBuffer[MAX_LOG_STRING_SIZE];
	unsigned long StringIndex;


	if ( gLogFileHandle == NULL )
	{
		return;
	}

	if ( eLogType == BLANK )
	{
		sprintf_s ( LogBuffer, MAX_LOG_STRING_SIZE, pLogString );
	}
	else
	{
		// Get rid of any control characters or spaces at the beginning of the string before logging
		for ( StringIndex = 0;
		      StringIndex < sizeof ( pLogString ) &&
		      pLogString[StringIndex] <= ' ' &&
		      pLogString[StringIndex] != '\0';
		      StringIndex++ )  {}

		// Add the timestamp and put the string in the log file
		if ( eLogType == PROMPT )
		{
			sprintf_s ( LogBuffer, MAX_LOG_STRING_SIZE,
			            "\n+%06ldms %s",
			            (GetTickCount ( ) - gLastLogTimestampMsecs),
			            &pLogString[StringIndex] );
		}
		else
		{
			sprintf_s ( LogBuffer, MAX_LOG_STRING_SIZE,
			            "+%06ldms %s",
			            (GetTickCount ( ) - gLastLogTimestampMsecs),
			            &pLogString[StringIndex] );
		}
	}

	gLastLogTimestampMsecs = GetTickCount ( );

	if ( gbSuspendLogOutput == FALSE )
	{
		fputs ( LogBuffer, gLogFileHandle );
		fflush ( gLogFileHandle );
	}
	else
	{
		AddToTransactionBuffer ( LogBuffer );
	}
}


/*
 * During the Manufacturer Specific Drive Cycle in the Dynamic Test, writing to
 * the log file is suspended to avoid making the log files too large. Only state
 * changes and FAILUREs are logged. To help improve OEM debugging a ring buffer
 * was added so that network transactions prior to the failure could also be logged.
 * gszTransactionBuffer is used as temporary storage for strings that would
 * normally be written to the log file. The start of each transaction is marked by a
 * call to SaveTransactionStart, which records the offset in gszTransactionBuffer
 * where the strings will be written. The size of a transaction will vary and will
 * include multiple strings - size the buffers accordingly!
 */

/*******************************************************************************
**
**  Function:  SaveTransactionStart
**
**  Purpose:   Save the start index a of a new transaction
**
*******************************************************************************/
void SaveTransactionStart ( void )
{
	// add this transaction to the list
	grgsTransactionList[gTransactionCount % MAX_TRANSACTION_COUNT].StartIndex = gTransactionBufferEnd;

	gTransactionCount++;

	// account for roll over
	if ( gTransactionCount == (MAX_TRANSACTION_COUNT * 2) )
	{
		gTransactionCount = MAX_TRANSACTION_COUNT;
	}

	// zero out the next length
	grgsTransactionList[gTransactionCount % MAX_TRANSACTION_COUNT].Length = 0;
}


/*******************************************************************************
**
**  Function:  AddToTransactionBuffer
**
**  Purpose:   Add a string to the transaction ring buffer
**
*******************************************************************************/
void AddToTransactionBuffer ( char *pszStringToAdd )
{
	unsigned long StringLength;
	unsigned long Count;


	StringLength = strlen ( pszStringToAdd );

	// update the transaction size
	grgsTransactionList[(gTransactionCount - 1) % MAX_TRANSACTION_COUNT].Length += StringLength;

	if ( (gTransactionBufferEnd + StringLength) < sizeof ( gszTransactionBuffer ) )
	{
		strcpy_s ( &gszTransactionBuffer[gTransactionBufferEnd], MAX_RING_BUFFER_SIZE - gTransactionBufferEnd, pszStringToAdd );
		gTransactionBufferEnd += StringLength;
	}
	else
	{
		// account for buffer roll over
		Count = sizeof ( gszTransactionBuffer ) - (gTransactionBufferEnd+1);
		strncpy_s ( &gszTransactionBuffer[gTransactionBufferEnd], MAX_RING_BUFFER_SIZE - gTransactionBufferEnd, pszStringToAdd, Count );
		strcpy_s ( &gszTransactionBuffer[0], MAX_RING_BUFFER_SIZE, &pszStringToAdd[Count] );
		gTransactionBufferEnd = StringLength - Count;
	}
}


/*******************************************************************************
**
**  Function:  LogLastTransaction
**
**  Purpose:   Write transaction ring buffer to log file
**
*******************************************************************************/
void LogLastTransaction ( void )
{
	unsigned long TempBufferStart = 0;


	TempBufferStart = grgsTransactionList[(gTransactionCount - 1) % MAX_TRANSACTION_COUNT].StartIndex;


	// output the transaction buffer
	if ( TempBufferStart < gTransactionBufferEnd )
	{
		fwrite( &gszTransactionBuffer[TempBufferStart],
		        1,
		        (gTransactionBufferEnd - TempBufferStart),
		        gLogFileHandle );
	}
	else
	{
		// account for buffer roll over
		fwrite( &gszTransactionBuffer[TempBufferStart],
		        1,
		        (sizeof ( gszTransactionBuffer ) - TempBufferStart),
		        gLogFileHandle );

		fwrite ( &gszTransactionBuffer[0], 1, gTransactionBufferEnd, gLogFileHandle );
	}

	fflush ( gLogFileHandle );
}


/*******************************************************************************
**
**  Function:  ClearTransactionBuffer
**
**  Purpose:   Clear the transaction ring buffer
**
*******************************************************************************/
void ClearTransactionBuffer ( void )
{
	gTransactionBufferEnd = 0;
	gTransactionCount = 0;
	gszTransactionBuffer[0] = 0;
	memset ( &grgsTransactionList[0],
	         0x00,
	         sizeof ( grgsTransactionList ) );
}


/*******************************************************************************
**
**  Function:  DumpTransactionBuffer
**
**  Purpose:   Write transaction ring buffer to log file
**
*******************************************************************************/
void DumpTransactionBuffer ( void )
{
	unsigned long TempTransCount = 0;
	unsigned long TempEntryCount = 0;
	unsigned long TempSize = 0;
	unsigned long TempBufferStart = 0;
	BOOL          bDone = FALSE;
	char          szTempBuffer[MAX_LOG_STRING_SIZE];


	szTempBuffer[0] = 0;
	TempTransCount = gTransactionCount;

	fputs ( "\n******** Buffer dump start (Failure detected) ********\n", gLogFileHandle );

	if ( gTransactionCount > 0 )
	{
		// determine the starting point of the transaction buffer
		do
		{
			TempTransCount--;
			TempSize += grgsTransactionList[TempTransCount % MAX_TRANSACTION_COUNT].Length;

			if ( TempSize > sizeof ( gszTransactionBuffer ) )
			{
				// went too far back, this transaction was over-written
				bDone = TRUE;
				TempTransCount++;
			}
			else
			{
				TempEntryCount++;
			}

		} while ( bDone == FALSE &&
		          TempTransCount > 0 &&
		          TempEntryCount < DESIRED_DUMP_SIZE );

		TempBufferStart = grgsTransactionList[TempTransCount % MAX_TRANSACTION_COUNT].StartIndex;


		// output the transaction buffer
		if ( TempBufferStart < gTransactionBufferEnd )
		{
			fwrite( &gszTransactionBuffer[TempBufferStart],
			        1,
			        (gTransactionBufferEnd - TempBufferStart),
			        gLogFileHandle );
		}
		else
		{
			// account for buffer roll over
			fwrite( &gszTransactionBuffer[TempBufferStart],
			        1,
			        (sizeof ( gszTransactionBuffer ) - TempBufferStart),
			        gLogFileHandle );

#ifdef _DEBUG
			fputs ( "\n***  ROLLOVER ***\n", gLogFileHandle );
#endif

			fwrite ( &gszTransactionBuffer[0], 1, gTransactionBufferEnd, gLogFileHandle );
		}
	}

#ifdef _DEBUG
	sprintf_s ( szTempBuffer, MAX_LOG_STRING_SIZE, "TransCnt: %d, Start: %d, End: %d\n", gTransactionCount, TempBufferStart, gTransactionBufferEnd );
	fputs ( szTempBuffer, gLogFileHandle );
	for ( TempTransCount = 0;
	      TempTransCount < MAX_TRANSACTION_COUNT;
	      TempTransCount++ )
	{
		sprintf_s( szTempBuffer,
		           MAX_LOG_STRING_SIZE,
		           "Idx: %d, Start: %d, Size: %d\n",
		           TempTransCount,
		           grgsTransactionList[TempTransCount % MAX_TRANSACTION_COUNT].StartIndex,
		           grgsTransactionList[TempTransCount % MAX_TRANSACTION_COUNT].Length );
		fputs ( szTempBuffer, gLogFileHandle );
	}
#endif

	fputs ( "******** Buffer dump end (Failure detected) ********\n\n", gLogFileHandle );

	fflush ( gLogFileHandle );

	ClearTransactionBuffer ( );
}


/*******************************************************************************
**
**  Function:  VerifyLogFile
**
**  Returns:   PASS   - OK to continue
**             FAIL   - File not open / User data mis-match
**             ERRORS - Expected data not found
**             EXIT   - Dynamic Tests already completed
**
*******************************************************************************/
STATUS VerifyLogFile ( FILE *hFileHandle )
{
	unsigned long TempValue;
	unsigned long TempUserErrorCount;
	unsigned long TempJ2534FailureCount;
	unsigned long TempWarningCount;
	unsigned long TempFailureCount;
	unsigned long TempCommentCount;
	unsigned int  LoggedYear;
	char          Buffer[256];
	char         *pcBuf;
	BOOL          bRevisionFound = FALSE;
	BOOL          bUserInputFound = FALSE;
	BOOL          bTotalsFound = FALSE;
	BOOL          bTestsDone = FALSE;
	BOOL          bTestIncomplete = FALSE;
	STATUS        eRetCode = PASS;


	// log file should be open
	if ( hFileHandle == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTOFF, ENTER_PROMPT,
		      "Log File not open\n" );
		return FAIL;
	}

	// search from beginning of file
	fseek ( hFileHandle, 0, SEEK_SET );

	while ( fgets (Buffer, sizeof ( Buffer ), hFileHandle) != 0 )
	{
		if ( bRevisionFound == FALSE )
		{
			// check software version from log file
			if ( substring ( Buffer, "Revision " ) != 0 )
			{
				if ( substring ( Buffer, gszAPP_REVISION ) == 0 )
				{
					Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, ENTER_PROMPT,
					      "j1699-3 software version in log file (%s) does not match currently running version (%s)\n",
					      Buffer,
					      gszAPP_REVISION );
				}

				bRevisionFound = TRUE;
			}
		}

		if ( bTestsDone == FALSE )
		{
			if ( substring ( Buffer, "**** Test 11.2 INCOMPLETE" ) != 0 ||
			     substring ( Buffer, "Test 11 aborted by user" )    != 0 )
			{
				// Test 11.2 was aborted previously
				bTestIncomplete = TRUE;
			}
			else if ( substring ( Buffer, "**** Test 11.11 (" ) != 0 &&
			          bTestIncomplete == FALSE )
			{
				// all tests have been run
				fseek ( hFileHandle, 0, SEEK_END );
				Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, ENTER_PROMPT,
				      "Dynamic Tests have already been completed!\n" );
				return EXIT;
			}
		}

		if ( bUserInputFound == FALSE )
		{
			// check for consistency of User Input
			if ( (pcBuf = substring ( Buffer, gDisplayStrings[DSPSTR_PRMPT_MODEL_YEAR] )) != 0 )
			{
				LoggedYear = atoi ( &pcBuf[strlen ( gDisplayStrings[DSPSTR_PRMPT_MODEL_YEAR] )]);
				if ( gModelYear != LoggedYear )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, ENTER_PROMPT,
					      "Model Year in log file (%d) does not match user input (%d)\n",
					      LoggedYear,
					      gModelYear );
					eRetCode = FAIL;
				}
			}

			if ( (pcBuf = substring ( Buffer, gDisplayStrings[DSPSTR_PRMPT_OBD_ECU] )) != 0 )
			{
				TempValue = atol ( &pcBuf[strlen ( gDisplayStrings[DSPSTR_PRMPT_OBD_ECU] )] );
				if ( gUserNumOfECUs != TempValue )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, ENTER_PROMPT,
					      "Number of OBD-II ECUs in log file (%l) does not match user input (%d)\n",
					      TempValue,
					      gUserNumOfECUs );
					eRetCode = FAIL;
				}
			}

			if ( (pcBuf = substring ( Buffer, gDisplayStrings[DSPSTR_PRMPT_RPGM_ECU] )) != 0 )
			{
				TempValue = atol ( &pcBuf[strlen ( gDisplayStrings[DSPSTR_PRMPT_RPGM_ECU] )] );
				if ( gUserNumOfECUsReprgm != TempValue )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, ENTER_PROMPT,
					      "Number of reprogrammable OBD-II ECUs in log file (%l) does not match user input (%d)\n",
					      TempValue,
					      gUserNumOfECUsReprgm );
					eRetCode = FAIL;
				}
			}

			if ( substring ( Buffer, gDisplayStrings[DSPSTR_PRMPT_ENG_TYPE] ) != 0 )
			{
				if ( substring ( Buffer, gEngineTypeStrings[gstUserInput.eEngineType] ) == 0 )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, ENTER_PROMPT,
					      "Engine type in log file (%s) does not match user input (%s)\n",
					      Buffer,
					      gEngineTypeStrings[gstUserInput.eEngineType] );
					eRetCode = FAIL;
				}
			}

			if ( substring ( Buffer, gDisplayStrings[DSPSTR_PRMPT_PWRTRN_TYPE] ) != 0 )
			{
				if ( substring ( Buffer, gPwrTrnTypeStrings[gstUserInput.ePwrTrnType] ) == 0 )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, ENTER_PROMPT,
					      "Powertrain type in log file (%s) does not match user input (%s)\n",
					      Buffer,
					      gPwrTrnTypeStrings[gstUserInput.ePwrTrnType] );
					eRetCode = FAIL;
				}
			}

			if ( substring ( Buffer, gDisplayStrings[DSPSTR_PRMPT_VEH_TYPE] ) != 0 )
			{
				if ( substring ( Buffer, gVehicleTypeStrings[gstUserInput.eVehicleType] ) == 0 )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, ENTER_PROMPT,
					      "Vehicle type in log file (%s) does not match user input (%s)\n",
					      Buffer,
					      gVehicleTypeStrings[gstUserInput.eVehicleType] );
					eRetCode = FAIL;
				}
			}

			if ( substring ( Buffer, gDisplayStrings[DSPSTR_STMT_COMPLIANCE_TYPE] ) != 0 )
			{
				if ( substring ( Buffer, gComplianceTestTypeStrings[gstUserInput.eComplianceType] ) == 0 )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, ENTER_PROMPT,
					      "Compliance type in log file (%s) does not match user input (%s)\n",
					      Buffer,
					      gComplianceTestTypeStrings[gstUserInput.eComplianceType] );
					eRetCode = FAIL;
				}

				bUserInputFound = TRUE;
			}
		}

		// get error totals; if file contians multiple instances, the last set will be used
		if ( (pcBuf = substring ( Buffer, gDisplayStrings[DSPSTR_RSLT_TOT_USR_ERROR] )) != 0 )
		{
			TempUserErrorCount = atol ( &pcBuf[strlen ( gDisplayStrings[DSPSTR_RSLT_TOT_USR_ERROR] )] );
		}

		if ( (pcBuf = substring ( Buffer, gDisplayStrings[DSPSTR_RSLT_TOT_J2534_FAIL] )) != 0 )
		{
			TempJ2534FailureCount = atol ( &pcBuf[strlen ( gDisplayStrings[DSPSTR_RSLT_TOT_J2534_FAIL] )] );
			if ( TempJ2534FailureCount )
			{
				gbTestSectionFailed = TRUE;
			}
		}

		if ( (pcBuf = substring ( Buffer, gDisplayStrings[DSPSTR_RSLT_TOT_WARN] )) != 0 )
		{
			TempWarningCount = atol ( &pcBuf[strlen ( gDisplayStrings[DSPSTR_RSLT_TOT_WARN] )] );
		}

		if ( (pcBuf = substring ( Buffer, gDisplayStrings[DSPSTR_RSLT_TOT_FAIL] )) != 0 )
		{
			TempFailureCount = atol ( &pcBuf[strlen ( gDisplayStrings[DSPSTR_RSLT_TOT_FAIL] )] );
			if ( TempFailureCount )
			{
				gbTestSectionFailed = TRUE;
			}

			bTotalsFound = TRUE;
		}

		if ( (pcBuf = substring ( Buffer, gDisplayStrings[DSPSTR_RSLT_TOT_COMMENTS] )) != 0 )
		{
			TempCommentCount = atol ( &pcBuf[strlen ( gDisplayStrings[DSPSTR_RSLT_TOT_COMMENTS] )] );
		}
	}

	if ( bRevisionFound == FALSE )
	{
		Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Software version for log file not found\n" );
		eRetCode = ERRORS;
	}

	if ( bUserInputFound == FALSE )
	{
		Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "User Input could not be found in log file\n" );
		eRetCode = ERRORS;
	}

	if ( bTotalsFound == FALSE )
	{
		Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Results Totals could not be found in log file\n" );
		eRetCode = ERRORS;
	}
	else
	{
		if ( eRetCode != FAIL )
		{
			// if User Input matches then use the last values found,
			// which should be from the end of the file
			gUserErrorCount += TempUserErrorCount;
			gJ2534FailureCount += TempJ2534FailureCount;
			gWarningCount += TempWarningCount;
			gFailureCount += TempFailureCount;
			gCommentCount += TempCommentCount;
		}
	}

	// move to end of file
	fseek ( hFileHandle, 0, SEEK_END );
	return eRetCode;
}


/*******************************************************************************
**
**  Function:  LogStats
**
**  Purpose:
**
*******************************************************************************/
void LogStats ( void )
{
	BYTE  EcuIdx;
	BYTE  index;


	Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTON, COMMENT_PROMPT,
	      "The statistics for the test so far are about to be written\n"
	      "to the log file.\n" );

	// Print out the request / response statistics for each ECU
	for ( EcuIdx = 0;
	      EcuIdx < MAX_ECUS;
	      EcuIdx++ )
	{
		if ( gstEcuTimingData[EcuIdx].EcuId == 0x00 )
		{
			break;  // leave ECU for loop
		}

		// Print out timinig information
		Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT, "\n\n" );

		Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "ECU %X:\n",
		      gstEcuTimingData[EcuIdx].EcuId );
		Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Average Initial Response Time = %ldmsec (%ld requests)\n",
		      gstEcuTimingData[EcuIdx].AggregateResponseTimeMsecs / gstEcuTimingData[EcuIdx].AggregateResponses,
		      gstEcuTimingData[EcuIdx].AggregateResponses );
		Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Longest Initial Response Time = %ldmsec\n",
		      gstEcuTimingData[EcuIdx].LongestResponsesTimeMsecs );
		Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Responses Out Of Range        = %d\n",
		      gstEcuTimingData[EcuIdx].RespTimeOutofRange );
		Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Responses Sooner Than Allowed = %d\n",
		      gstEcuTimingData[EcuIdx].RespTimeTooSoon );
		Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Responses Later  Than Allowed = %d\n",
		      gstEcuTimingData[EcuIdx].RespTimeTooLate );

		// Print out VIN information
		if ( gstEcuSIDData[EcuIdx].bVinValid )
		{
			Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "VIN     = %s\n",
			      gstEcuSIDData[EcuIdx].VinString );
		}

		// Print out CVN information
		if ( gstEcuSIDData[EcuIdx].CvnCount != 0 )
		{
			// if there is only one CVN, don't number it
			if ( gstEcuSIDData[EcuIdx].CvnCount == 1 )
			{
				Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "CVN     = %s\n",
				      &gstEcuSIDData[EcuIdx].pCvnString[0] );
			}
			// else, there are more than one CVN, number them
			else
			{
				for ( index = 0;
				      index < gstEcuSIDData[EcuIdx].CalidCount;
				      index++ )
				{
					Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "CVN%02d   = %s\n",
					      index+1,
					      &gstEcuSIDData[EcuIdx].pCvnString[index*CVN_BUFFER_SIZE] );
				}
			}
			free ( gstEcuSIDData[EcuIdx].pCvnString );
		}

		// Print out CALID information
		if ( gstEcuSIDData[EcuIdx].CalidCount != 0 )
		{
			// if there is only one CALID, don't number it
			if ( gstEcuSIDData[EcuIdx].CalidCount == 1 )
			{
				Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "CALID   = %s\n",
				      &gstEcuSIDData[EcuIdx].pCalidString[0] );
			}
			// else, there are more than one CALID, number them
			else
			{
				for ( index = 0;
				      index < gstEcuSIDData[EcuIdx].CalidCount;
				      index++ )
				{
					Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "CALID%02d = %s\n",
					      index+1,
					      &gstEcuSIDData[EcuIdx].pCalidString[index*CALID_BUFFER_SIZE] );
				}
			}
			free ( gstEcuSIDData[EcuIdx].pCalidString );
		}

		// Reset the per-ECU stats
		gstEcuTimingData[EcuIdx].AggregateResponseTimeMsecs = 0;
		gstEcuTimingData[EcuIdx].AggregateResponses = 0;
		gstEcuTimingData[EcuIdx].LongestResponsesTimeMsecs = 0;
		gstEcuTimingData[EcuIdx].RespTimeOutofRange = 0;
		gstEcuTimingData[EcuIdx].RespTimeTooSoon = 0;
		gstEcuTimingData[EcuIdx].RespTimeTooLate = 0;
	}  // end for (EcuIdx)

	// Print out the Warning/Failure statistics for the overall test
	Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT, "\n\n" );

	Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
	      "%s%d.\n",
	      gDisplayStrings[DSPSTR_RSLT_TOT_USR_ERROR],
	      gUserErrorCount );

	Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
	      "%s%d.\n",
	      gDisplayStrings[DSPSTR_RSLT_TOT_J2534_FAIL],
	      gJ2534FailureCount );

	Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
	      "%s%d.\n",
	      gDisplayStrings[DSPSTR_RSLT_TOT_WARN],
	      gWarningCount );

	Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
	      "%s%d.\n",
	      gDisplayStrings[DSPSTR_RSLT_TOT_FAIL],
	      gFailureCount );

	Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
	      "%s%d.\n",
	      gDisplayStrings[DSPSTR_RSLT_TOT_COMMENTS],
	      gCommentCount );

	Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT, "\n\n" );

	Log ( RESULTS, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
	      "%s%s\n",
	      gDisplayStrings[DSPSTR_STMT_COMPLIANCE_TYPE],
	      gComplianceTestTypeStrings[gstUserInput.eComplianceType] );

	// Reset the stats
	gUserErrorCount = 0;
	gFailureCount = 0;
	gJ2534FailureCount = 0;
	gWarningCount = 0;
}


/*******************************************************************************
**
**  Function:  substring
**
**  Returns: a pointer to the location of 'substr' in 'str'
**
*******************************************************************************/
char * substring ( char       *str,
                   const char *substr )
{
	int tok_len = strlen ( substr );
	int n       = strlen ( str ) - tok_len;
	int i;


	for ( i = 0;
	      i <= n;
	      i++ )
	{
		if ( str[i] == substr[0] )
		{
			if ( strncmp ( &str[i], substr, tok_len ) == 0 )
			{
				return &str[i];
			}
		}
	}

	return 0;
}


/*******************************************************************************
**
**  Function:  GetFailureCount
**
**  Purpose:   Return the current count of FAILUREs
**
*******************************************************************************/
unsigned long GetFailureCount ( void )
{
	return gFailureCount;
}
