/*******************************************************************************
********************************************************************************
**
**  Copyright(c) 2022, Alliance for Automotive Innovation
**  Used only under license from the Alliance for Automotive Innovation. All Rights Reserved.
**
**  Project:  J1699-3
**  FileName: j1699.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.
**
**  Description:
**
**  Modifications:  12/07/2022  Initial Version
**
********************************************************************************
**
**  The design parameters/rationale used during the development of
**  this source code:
**  1 - Keep it as simple as possible
**  2 - Make response data look like ISO15765/ISO15031 to simplify
**      processing wherever possible
**  3 - Log all screen text, user prompts and diagnostic message traffic
**  4 - Use globals for response information storage to simplify usage
**      across functions
**  5 - Make all code capable of being compiled with the
**      free Microsoft Visual Studio C-compiler
**  6 - Use only native C-language, ANSI-C runtime library and
**      SAE J2534 API functions
**
********************************************************************************
**
**  How to build:
**      Go to https://visualstudio.microsoft.com/downloads/ and
**      download and install the Microsoft Visual Studio 2022 environment.
**
**      Run Visual Studio and from the prompt "Open a Project or Solution"
**      select the j1699.sln solution located in the directory that
**      contains the j1699 source code files.
**
**      In the "Solution Configuration" section of the tool bar,
*       select "Release" (default) or "Debug" (if examination of running code is needed).
**
**      In the "Build" menu, select "Rebuild j1699" or "Rebuild Solution."
**      The executable will be in the "Release" or "Debug" (depending on your
**      selection of "Solution Configuration") sub-directory of the j1699 source code.
**
**  How to run:
**      Install the J2534 software for your PassThru vehicle interface hardware.
**
**      From a MS-DOS Cosole window, in the directory with the j1699.exe file,
**      type "J1699" to run the program.
**
**      User prompts will lead you through the program.
**
**      The flow of the program follows the SAE J1699-3 document as much as possible.
**      Please refer to the document for more information.
**
**  Log file:
**      The log filename created will be a concatenation of the vehicle
**      information that the user enters (model year, manufacturer and make)
**      along with a numeric value and a ".log" extension.  The numeric value
**      will automatically be incremented by the program to the next available
**      number.
**
********************************************************************************
*******************************************************************************/

#include <stdio.h>    // C Library input and output declarations
#include <stdlib.h>   // C Library general function declarations
#include <string.h>   // C Library character array 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


/*******************************************************************************
**  j1699 revision
*******************************************************************************/
const char szJ1699_VER[] = "J1699-3";

const char gszAPP_REVISION[] = "17.03.00";

const char szBUILD_DATE[] = __DATE__;


/*******************************************************************************
**  j2534 interface pointers
*******************************************************************************/
PTCONNECT               PassThruConnect = 0;
PTDISCONNECT            PassThruDisconnect = 0;
PTREADMSGS              PassThruReadMsgs = 0;
PTWRITEMSGS             PassThruWriteMsgs = 0;
PTSTARTPERIODICMSG      PassThruStartPeriodicMsg = 0;
PTSTOPPERIODICMSG       PassThruStopPeriodicMsg = 0;
PTSTARTMSGFILTER        PassThruStartMsgFilter = 0;
PTSTOPMSGFILTER         PassThruStopMsgFilter = 0;
PTSETPROGRAMMINGVOLTAGE PassThruSetProgrammingVoltage = 0;
PTREADVERSION           PassThruReadVersion = 0;
PTGETLASTERROR          PassThruGetLastError = 0;
PTIOCTL                 PassThruIoctl = 0;
PTOPEN                  PassThruOpen = 0;
PTCLOSE                 PassThruClose = 0;


/**********************
**  Application Banner
**********************/
char *gBanner =
"\n\n"
"  JJJJJJJJJJJ     1      6666666666   9999999999   9999999999    3333333333\n"
"       J         11      6        6   9        9   9        9             3\n"
"       J        1 1      6            9        9   9        9             3\n"
"       J          1      6            9        9   9        9             3\n"
"       J          1      6            9        9   9        9             3\n"
"       J          1      6            9        9   9        9             3\n"
"       J          1      6666666666   9999999999   9999999999 --   33333333\n"
"       J          1      6        6            9            9             3\n"
"       J          1      6        6            9            9             3\n"
"       J          1      6        6            9            9             3\n"
"       J          1      6        6            9            9             3\n"
"  J    J          1      6        6   9        9   9        9             3\n"
"  JJJJJJ       1111111   6666666666   9999999999   9999999999    3333333333\n"
"\n"
"  Copyright (C) 2022\n"
"\n\n";

// Compliance Type definitions
char *gComplianceTestOptionsStrings[] =  // string of compliance test options
{
	"    1 US OBD-II\n"
	"    2 Heavy Duty US OBD (vehicle > 14,000 lb GVWR US, >= 2610 kg RW Europe)\n"
	"    3 European OBD with IUMPR\n"
	"    4 European OBD without IUMPR\n"
	"    5 Heavy Duty European OBD (vehicle > 14,000lb GVWR US, >= 2610kg RW Europe)\n"
	"    6 India OBD without IUMPR\n"
	"    7 Heavy Duty India OBD without IUMPR (vehicle > 3.5T GVW)\n"
	"    8 Brazil OBD without IUMPR\n"
	"    9 China OBD with IUMPR\n"
};
#define COMPLIANCE_TEST_LIST_COUNT 9

// ISO15765 workaround options
char *gISO15765OptionsStrings[] =        // string of ISO15765 workaround options
{
	"    1 ISO15765 @ 500k and 250k\n"
	"    2 ISO15765 @ 500k only, BUS_OFF work-around)\n"
	"    3 ISO15765 @ 250k only, BUS_OFF work-around)\n"
};
#define ISO15765OPTIONS 3

char *gComplianceTestTypeStrings[10] =   // strings that represent the user selected compliance test
{
	"UNKNOWN",
	"US OBD II",
	"European OBD with IUMPR",
	"European OBD without IUMPR",
	"HD OBD",
	"HD EOBD",
	"India OBD without IUMPR",
	"HD IOBD without IUMPR",
	"Brazil OBD without IUMPR",
	"China OBD with IUMPR"
};

// Engine Type definitions
char *gIgnitionTypeOptionsStrings[] =    // string of engine type options
{
	"    1 Spark Ignition\n"
	"    2 Compression Ignition (i.e. Diesel)\n"
};
#define ENGINE_LIST_COUNT 2

char *gEngineTypeStrings[] =             // strings that represent the user selected engine type
{
	"UNKNOWN",
	"GASOLINE",
	"DIESEL"
};

// Powertrain Type definitions
char *gPwrtrnTypeOptionsStrings[] =      // string of powertrain type options
{
	"    1 Conventional\n"
	"    2 Stop/Start Vehicle\n"
	"    3 Hybrid Electric Vehicle\n"
	"    4 Plug-In Hybrid Electric Vehicle\n"
};
#define PWRTRN_LIST_COUNT 4

char *gPwrTrnTypeStrings[] =             // strings that represent the user selected powertrain type
{
	"UNKNOWN",
	"CONVENTIONAL",
	"STOP/START",
	"HEV",
	"PHEV"
};

// Vehicle Type definitions
char *gVehicleTypeOptionsStrings[] =     // string of vehicle type options
{
	"    1 Light/Medium Duty Vehicle Chassis Certified\n"
	"    2 Medium Duty Truck Engine Dyno Certified\n"
};
#define VEHICLE_LIST_COUNT 2

char *gVehicleTypeStrings[] =            // strings that represent the user selected vehicle type
{
	"UNKNOWN",
	"LIGHT/MEDIUM DUTY VEHICLE",
	"MEDIUM DUTY TRUCK ENGINE DYNO CERTIFIED",
	"HEAVY DUTY TRUCK"
};

// String Definitions
char *gDisplayStrings[] =                    // strings used for display/logging, kept here to insure consistent text
{
	"Model Year of this vehicle? " ,                               // DSPSTR_PRMPT_MODEL_YEAR
	"Engine Certification Year of this vehicle?  ",                // DSPSTR_PRMPT_ENG_CERT_YEAR
	"How many OBD-II ECUs are on this vehicle?  ",                 // DSPSTR_PRMPT_OBD_ECU
	"How many reprogrammable OBD-II ECUs are on this vehicle?  ",  // DSPSTR_PRMPT_RPGM_ECU
	"What type of engine is in this vehicle?  ",                   // DSPSTR_PRMPT_ENG_TYPE
	"What type of powertrain is in this vehicle?  ",               // DSPSTR_PRMPT_PWRTRN_TYPE
	"What type of compliance test is to be performed?  ",          // DSPSTR_STMT_COMPLIANCE_TYPE
	"What type of vehicle is being tested?  ",                     // DSPSTR_PRMPT_VEH_TYPE
	"What type of ISO15765 is to be used?  ",                      // DSPSTR_PRMPT_ISO15765_TYPE
	"Total number of USER WARNINGS = ",                            // DSPSTR_RSLT_TOT_USR_ERROR
	"Total number of J2534 FAILURES = ",                           // DSPSTR_RSLT_TOT_J2534_FAIL
	"Total number of WARNINGS = ",                                 // DSPSTR_RSLT_TOT_WARN
	"Total number of FAILURES = ",                                 // DSPSTR_RSLT_TOT_FAIL
	"Total number of COMMENTS = ",                                 // DSPSTR_RSLT_TOT_COMMENTS
};


/*******************************************************************************
**  Defines
*******************************************************************************/
#define LINE_LEN 80
#define MY_LEN   5  // 4 year characters and NULL terminator

// Earliest allowable model year
#define MIN_MODEL_YEAR           1981

// Latest allowable model year
#define MAX_MODEL_YEAR           2025


/*******************************************************************************
**  Global variables
*******************************************************************************/
// User Input
USER_INPUT     gstUserInput;                    // structure for user selected compliance test information
char gUserModelYearString[MY_LEN] = {0};
char gUserMakeString[LINE_LEN] = {0};
char gUserModelString[LINE_LEN] = {0};
BYTE gUserNumOfECUs = 0;
BYTE gUserNumOfECUsReprgm = 0;

BOOL             gbTestAborted = FALSE;          // set (in ABORT_RETURN only) if any test was aborted,
                                                 // here is the only place this should be set to FALSE

BOOL             gbTestSectionAborted = FALSE;   // set (in ABORT_RETURN only) if a test section (Static/Dynamic) was aborted,
                                                 // clear only before Static test and before Dynamic tests

BOOL             gbTestFailed = FALSE;           // set if any test failed,
                                                 // here is the only place this should be set to FALSE

BOOL             gbTestSectionFailed = FALSE;    // set if a test (static/dynamic) failed,
                                                 // clear only before Static test and before Dynamic test

BOOL             gbTestSubsectionFailed = FALSE; // set if a test subsection failed,
                                                 // clear before each subsection (ie test 5.10, test 7.4, etc)

int              gModelYear = 0;                      // the integer version of the Model Year to test the vehicle as
int              gVINModelYear = 0;                   // the integer version of the Model Year returned in VIN
BOOL             gbProtocolDetermined = FALSE;        // set if an OBD protocol was found
unsigned short   gProtocolIdx = 0;                    // index of the OBD protocol being used
PROTOCOL_LIST    gstProtocolList[OBD_MAX_PROTOCOLS];
BYTE             gNumOfECUs = 0;
BYTE             gNumOfECUsResp = 0;                  // number of responding ECUs
BYTE             gNumOfECUsForAlloc = 0;              // number of ECUs used for memory allocation
BOOL             gbEngineRunning = FALSE;             // set if engine should be running
BOOL             gbEngineWarm = FALSE;                // set if engine should be warm
BOOL             gbDiesel = FALSE;                    // set if the user chose a compression ignition (diesel) vehicle
BOOL             gbHybrid = FALSE;                    // set if the user chose a hybrid vehicle
BOOL             gbPlugIn = FALSE;
BOOL             gbDynoCert = FALSE;                  // set if the user chose a Dyno Certified vehicle

BOOL             gbDTCPending = FALSE;                // set if a DTC should be pending
BOOL             gbDTCStored = FALSE;                 // set if a DTC should be stored
BOOL             gbDTCHistorical = FALSE;
BOOL             gbDTCPermanent = FALSE;              // set if a DTC should be permanent
DTC_LIST        *gstDTCList;

ECU_TIMING_DATA *gstEcuTimingData;
ECU_SID_DATA    *gstEcuSIDData;
unsigned long    gRequestDelayTimeMsecs = 100;
unsigned long    gLastLogTimestampMsecs = 0;
BOOL             gbIgnoreUnsupported = FALSE;
BOOL             gbSuspendScreenOutput = FALSE;
BOOL             gbSuspendLogOutput = FALSE;
BOOL             gbPeriodicMsgEnabled = TRUE;         // Option to turn off Tester Present Message
OBD_DATA        *gstResponse;
OBD_DATA        *gstCompareResponse;
char             gVINString[VIN_BUFFER_SIZE] = {0};

FILE            *gLogFileHandle = NULL;
char             gLogFileNameString[MAX_LOGFILENAME] = {0};
FILE            *gTempLogFileHandle = NULL;
char             gTempLogFilenameString[MAX_PATH] = {0};

unsigned long    gDeviceID = 0;                       // global storage for J2534-1 Device ID

TEST_PHASE       geTestPhase = eTestNone;
BYTE             gTestSubsection;      // test subsection to be used in conjunction with geTestPhase (ie test phase 5, subsection 14 - 5.14)


/*******************************************************************************
**  Local Function Prototypes
*******************************************************************************/
STATUS RunStaticTests ( void );
STATUS RunDynamicTests ( void );
void   LogSoftwareVersion ( SCREENOUTPUT bDisplay,
                            LOGOUTPUT bLog );
void   LogVersionInformation ( SCREENOUTPUT bDisplay,
                               LOGOUTPUT    bLog );

STATUS OpenTempLogFile ( void );
STATUS AppendLogFile ( void );

BOOL WINAPI HandlerRoutine ( DWORD dwCtrlType );


/*******************************************************************************
**  External Function Prototypes
*******************************************************************************/
extern STATUS FindJ2534Interface ( void );
extern void   ReadIni ( void );
extern STATUS VehicleReport ( void );
extern void   LogStats ( void );
extern void   ClearTransactionBuffer ( void );      // clears transaction ring buffer
extern void   AllocateMemoryForTest10_InUseCounters ( void );
extern void   FreeMemoryForTest10_InUseCounters ( void );
extern void   AllocateMemoryForTest11_PerformanceCounters ( void );
extern void   FreeMemoryForTest11_PerformanceCounters ( void );


/*******************************************************************************
**
**  Main function
**
*******************************************************************************/
int main ( int    argc,
           char **argv )
{
	BYTE          EcuIdx;
	STATUS        eRetCode = FAIL;

	unsigned long LogFileIdx;
	char          Buffer[256];
	char          characterset[] = " !#$%&' ( )+,-.0123456789;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_`abcdefghijklmnopqrstuvwxyz{}~^";

	int           nTestChoice;
	int           nOBDType;
	int           nISOType;
	int           nEngType;
	int           nPwrTrnType;
	int           nVehType;
	char          key;


	// setup ctrl-C handler
	SetConsoleCtrlHandler ( HandlerRoutine, TRUE );

	// use an unbuffered stdout
	setvbuf ( stdout, NULL, _IONBF, 0 );

	ClearTransactionBuffer ( );  // initialize log file ring buffer for Mfg. Spec. Drive Cycle

	gLastLogTimestampMsecs = GetTickCount ( );  // Get the start time for the log file

	// Send out the banner
	printf ( gBanner );

	// open temp log file
	if ( OpenTempLogFile ( ) != PASS )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT,
		      "Cannot open temp log file\n" );
		exit (FAIL);
	}

	// Find the J2534 interface and attach to the DLL
	if ( FindJ2534Interface ( ) != PASS )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "J2534 interface not found\n" );
		exit (FAIL);
	}

	// Open J2534 device
	{
		unsigned long eRetVal = PassThruOpen ( NULL, &gDeviceID );
		if ( eRetVal != STATUS_NOERROR )
		{
			Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s returned %ld",
			      "PassThruOpen",
			      eRetVal );
			exit (FAIL);
		}
	}


	// Allow multiple consecutive tests
	do
	{
		key           = 0;
		nTestChoice   = 0;
		nOBDType      = 0;
		nISOType      = 0;
		nEngType      = 0;
		nPwrTrnType   = 0;
		nVehType      = 0;
		gVINModelYear = 0;
		gModelYear    = 0;

		// Get user input to determine which test to run
		while ( (key < '1' || key > '3') && key != 'e' && key != 'E' )
		{
			ClearKeyboardBuffer ( );
			Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
			      "(Q02) Select the sections of test to be run from following list:\n"
			      "    1. Run Static  Tests contained in Sections 5  through 9.\n"
			      "    2. Run Dynamic Tests contained in Sections 10 through 11.\n"
			      "    3. Generate Vehicle Report.\n"
			      "    E. Exit.\n"
			      "Enter choice (1, 2 or 3) (E to exit): " );
			scanf_s ( "%1c", &key, 1 );
			Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );
		}

		if ( key == 'e' || key == 'E' )
		{
			exit (PASS) ;
		}
		else
		{
			nTestChoice = key - '0';
		}


		// Get the user input to create the log filename


		// Collect user input for vehicle model year
		while ( gVINModelYear < MIN_MODEL_YEAR || gVINModelYear > MAX_MODEL_YEAR )
		{
			ClearKeyboardBuffer ( );
			Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
			      "(Q03) Enter VIN Model Year of vehicle (model year as indicated by\n"
			      "year character in VIN) (%d to %d): ",
			      MIN_MODEL_YEAR,
			      MAX_MODEL_YEAR );
			scanf_s ( "%4s", &gUserModelYearString, sizeof ( gUserModelYearString ) );
			Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );
			gVINModelYear = atoi ( gUserModelYearString );
			gModelYear = gVINModelYear;
		}


		// Collect user input for static tests
		if ( nTestChoice == 1 )
		{
			ClearKeyboardBuffer ( );
			do
			{
				Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
				      "(Q04) Enter make of vehicle (e.g. Dodge, Ford, GMC, Honda, Toyota...): " );
				scanf_s ( "%s", Buffer, sizeof ( Buffer ) );
				Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );
			} while ( strspn ( Buffer, characterset ) < strlen ( Buffer ) || // contains nonalphanumeric characters
			          (strlen ( Buffer ) + 1) > MAX_LOGFILENAME );        // longer than max filename
			strcpy_s ( gUserMakeString, LINE_LEN, Buffer );


			ClearKeyboardBuffer ();
			do
			{
				Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
				      "(Q05) Enter vehicle model: " );
				scanf_s ( "%s", Buffer, sizeof(Buffer) );
				Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );
			} while ( strspn ( Buffer, characterset ) < strlen ( Buffer ) || // contains nonalphanumeric characters
			          (strlen ( Buffer ) + 1) > MAX_LOGFILENAME );        // longer than max filename
			strcpy_s ( gUserModelString, LINE_LEN, Buffer );
		}


		// Ask the user for number of ECUs on the vehicle
		while ( gUserNumOfECUs < 1 || gUserNumOfECUs > MAX_ECUS )
		{
			ClearKeyboardBuffer ( );
			Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
			      "(Q06) %s\n"
			      "(1 to 8 (239 for 29-Bit ID)):  ",
			      gDisplayStrings[DSPSTR_PRMPT_OBD_ECU] );
			scanf_s ( "%hhd", &gUserNumOfECUs );
			Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );
		}
		gNumOfECUs = gUserNumOfECUs;


		// Ask the user for number of reprogamable ECUs on the vehicle
		while ( gUserNumOfECUsReprgm < 1 || gUserNumOfECUsReprgm > MAX_ECUS )
		{
			ClearKeyboardBuffer ( );
			Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
			      "(Q07) %s\n"
			      "(1 to 8 (239 for 29-Bit ID)):  ",
			      gDisplayStrings[DSPSTR_PRMPT_RPGM_ECU] );
			scanf_s ( "%hhd", &gUserNumOfECUsReprgm );
			Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );
		}


		// Ask the user for the type of engine in the vehicle
		while ( nEngType < 1 || nEngType > ENGINE_LIST_COUNT )
		{
			ClearKeyboardBuffer ( );
			Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
			      "(Q08) %s\n%s"
			      "Enter selection number (1 to %d)? ",
			      gDisplayStrings[DSPSTR_PRMPT_ENG_TYPE],
			      gIgnitionTypeOptionsStrings[0],
			      ENGINE_LIST_COUNT );
			scanf_s ( "%1d", &nEngType );
			Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );

			gstUserInput.eEngineType = nEngType;
		}
		switch ( nEngType )
		{
			case GASOLINE:  // Spark Ignition (Gasoline)
				gbDiesel = FALSE;
			break;

			case DIESEL:     // Compression Ignition (Diesel)
				gbDiesel = TRUE;
			break;
		}


		// Ask the user for the type of powertrain in the vehicle
		while ( nPwrTrnType < 1 || nPwrTrnType > PWRTRN_LIST_COUNT )
		{
			ClearKeyboardBuffer ( );
			Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
			      "(Q09) %s\n"
			      "%s"
			      "Enter selection number (1 to %d)? ",
			      gDisplayStrings[DSPSTR_PRMPT_PWRTRN_TYPE],
			      gPwrtrnTypeOptionsStrings[0],
			      PWRTRN_LIST_COUNT );
			scanf_s ( "%1d", &nPwrTrnType );
			Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );

			gstUserInput.ePwrTrnType = nPwrTrnType;

			if ( nPwrTrnType == PHEV && nTestChoice == 1 )
			{
				Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTON, ENTER_PROMPT,
				      "NOTE: Do NOT plug-in charge the vehicle during static test.\n" );
			}
		}
		switch ( nPwrTrnType )
		{
			case CONV:  // Conventional
				gbHybrid = FALSE;
				gbPlugIn = FALSE;
			break;

			case SS:    // Stop/Start
				gbHybrid = TRUE;
				gbPlugIn = FALSE;
			break;

			case HEV:   // Hybrid Electric Vehicle
				gbHybrid = TRUE;
				gbPlugIn = FALSE;
			break;

			case PHEV:  // Plug-In Hybrid Electric Vehicle
				gbHybrid = TRUE;
				gbPlugIn = TRUE;
			break;
		}


		// Ask the user for the type of compliance test to be run
		while ( nOBDType < 1 || nOBDType > COMPLIANCE_TEST_LIST_COUNT )
		{
			ClearKeyboardBuffer ( );
			Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
			      "(Q10) %s\n%s"
			      "Enter selection number (1 to %d)? ",
			      gDisplayStrings[DSPSTR_STMT_COMPLIANCE_TYPE],
			      gComplianceTestOptionsStrings[0],
			      COMPLIANCE_TEST_LIST_COUNT );
			scanf_s ( "%1d", &nOBDType );
		}
		// build the complaince information
		switch ( nOBDType )
		{
			case 1: // Legacy + ISO 15765 @ 500k only
				gstUserInput.eComplianceType = US_OBDII;
				gstUserInput.eScanTable = USOBD;
				gstUserInput.NumDCToSetMIL = 2;
			break;

			case 2: // Heavy Duty OBD
				gstUserInput.eComplianceType = HD_OBD;
				gstUserInput.eScanTable = USOBD;
				gstUserInput.NumDCToSetMIL = 2;
			break;

			case 3: // Legacy + ISO 15765
				gstUserInput.eComplianceType = EOBD_WITH_IUMPR;
				gstUserInput.eScanTable = EOBD;
				gstUserInput.NumDCToSetMIL = 3;
			break;

			case 4: // Legacy + ISO 15765
				gstUserInput.eComplianceType = EOBD_NO_IUMPR;
				gstUserInput.eScanTable = EOBD;
				gstUserInput.NumDCToSetMIL = 3;
			break;

			case 5: // Heavy Duty EOBD
				gstUserInput.eComplianceType = HD_EOBD;
				gstUserInput.eScanTable = EOBD;
				gstUserInput.NumDCToSetMIL = 3;
			break;

			case 6: // Light Duty IOBD
				gstUserInput.eComplianceType = IOBD_NO_IUMPR;
				gstUserInput.eScanTable = EOBD;
				gstUserInput.NumDCToSetMIL = 3;
			break;

			case 7: // Heavy Duty IOBD
				gstUserInput.eComplianceType = HD_IOBD_NO_IUMPR;
				gstUserInput.eScanTable = EOBD;
				gstUserInput.NumDCToSetMIL = 3;
			break;

			case 8: // Light Duty OBDBr
				gstUserInput.eComplianceType = OBDBr_NO_IUMPR;
				gstUserInput.eScanTable = EOBD;
				gstUserInput.NumDCToSetMIL = 3;
			break;

			case 9: // China OBD
				gstUserInput.eComplianceType = CNOBD_NO_IUMPR;
				gstUserInput.eScanTable = USOBD;
				gstUserInput.NumDCToSetMIL = 2;
			break;

			default:
				gstUserInput.eComplianceType = UNKNOWN;
				gstUserInput.eScanTable = USOBD;
				gstUserInput.NumDCToSetMIL = 2;
			break;
		}


		// if the OBD Type is one that has BUS OFF problems, select the type of ISO15765 to use
		if ( nOBDType >= 3 && nOBDType <= 8 )
		{
			// Ask the user for the type of ISO15765 to be used
			while ( nISOType < 1 || nISOType > ISO15765OPTIONS )
			{
				ClearKeyboardBuffer ( );
				Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
				      "(Q14) %s\n%s"
				      "Enter selection number (1 to %d)? ",
				      gDisplayStrings[DSPSTR_PRMPT_ISO15765_TYPE],
				      gISO15765OptionsStrings[0],
				      ISO15765OPTIONS );
				scanf_s ( "%1d", &nISOType );
			}

			// set the scantable for the selected type of ISO15765
			switch ( nISOType )
			{
				case 1: // Legacy + ISO 15765 @ 500k and 250k
					gstUserInput.eScanTable = EOBD;
				break;

				case 2: // Legacy + ISO 15765 @ 500k only, BUS_OFF work-around
					gstUserInput.eScanTable = USOBD;
				break;

				case 3: // Legacy + ISO 15765 @ 250k only, BUS_OFF work-around
					gstUserInput.eScanTable = EOBD_250K;
				break;
			}
		}


		// If the chosen OBD Type is for Heavy Duty, set Vehicle Type to Heavy Duty
		if ( nOBDType == 2 || nOBDType == 5 || nOBDType == 7 )
		{
			nVehType = VEHICLE_LIST_COUNT+1;
		}
		else
		{
			// Ask the user for the type of vehicle
			while ( nVehType < 1 || nVehType > VEHICLE_LIST_COUNT )
			{
				ClearKeyboardBuffer ( );
				Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
				      "(Q11) %s\n%s"
				      "Enter selection number (1 to %d)? ",
				      gDisplayStrings[DSPSTR_PRMPT_VEH_TYPE],
				      gVehicleTypeOptionsStrings[0],
				      VEHICLE_LIST_COUNT );
				scanf_s ( "%1d", &nVehType );
				Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );
			}
		}
		switch ( nVehType )
		{
			case 1:       // Light Duty Vehicle
				gstUserInput.eVehicleType = LD;
				gbDynoCert = FALSE;
			break;

			case 2:       // Medium Duty Truck Engine Dyno Certified
				gstUserInput.eVehicleType = MD;
				gbDynoCert = TRUE;
			break;

			case ( VEHICLE_LIST_COUNT+1 ):       // Heavy Duty Truck (Always Engine Dyno Certified)
				gstUserInput.eVehicleType = HD;
				gbDynoCert = TRUE;
			break;
		}


		// if vehicle is Medium Duty Dyno Certified or Heavy Duty, prompt for an engine certification year
		if ( gstUserInput.eVehicleType == MD || gstUserInput.eVehicleType == HD )
		{
			do
			{
				ClearKeyboardBuffer ( );
				Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
				      "(Q15) Enter Engine Certification Model Year (model year as indicated by\n"
				      "engine certification label) (%d to %d): ",
				      MIN_MODEL_YEAR,
				      MAX_MODEL_YEAR );
				scanf_s ( "%4s", &gUserModelYearString, sizeof ( gUserModelYearString ) );
				Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );
				gModelYear = atoi ( gUserModelYearString );
			} while ( gModelYear < MIN_MODEL_YEAR || gModelYear > MAX_MODEL_YEAR );


			if ( gModelYear != gVINModelYear )
			{
				Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTON, ENTER_PROMPT,
				      "NOTE: The Engine Certification Year does not match the VIN Year\n"
				      "entered previously.\n"
				      "It is assumed that the user is engaged in development work and the\n"
				      "engine certification year will be used to for year-based criteria\n"
				      "(beside VIN) during this test. " );
			}
		}


		// Option to turn off Tester Present Message
		if ( Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_PROMPT,
		           "(Q13) Disable Tester Present Messages for ISO15765 vehicles?\n"
		           "(All other protocols will send Tester Present Messages regardless\n"
		           "of the response to this prompt)  " ) == 'Y' )
		{
			gbPeriodicMsgEnabled = FALSE;
		}


		// Echo user responses to the log file
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s%d\n",
		      gDisplayStrings[DSPSTR_PRMPT_MODEL_YEAR],
		      gVINModelYear );

		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s%d\n",
		      gDisplayStrings[DSPSTR_PRMPT_ENG_CERT_YEAR],
		      gModelYear );

		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s%d\n",
		      gDisplayStrings[DSPSTR_PRMPT_OBD_ECU],
		      gUserNumOfECUs );

		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s%d\n",
		      gDisplayStrings[DSPSTR_PRMPT_RPGM_ECU],
		      gUserNumOfECUsReprgm );

		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s%s\n",
		      gDisplayStrings[DSPSTR_PRMPT_ENG_TYPE],
		      gEngineTypeStrings[gstUserInput.eEngineType] );

		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s%s\n",
		      gDisplayStrings[DSPSTR_PRMPT_PWRTRN_TYPE],
		      gPwrTrnTypeStrings[gstUserInput.ePwrTrnType] );

		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s%s\n",
		      gDisplayStrings[DSPSTR_PRMPT_VEH_TYPE],
		      gVehicleTypeStrings[gstUserInput.eVehicleType] );

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

		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Tester Present Messages %sabled\n\n",
		      gbPeriodicMsgEnabled ? "En" : "Dis" );


		// Allocate memory and initialize Static And Dynamic Test ECU arrays
		gNumOfECUsForAlloc = (gUserNumOfECUs+5 < MAX_ECUS) ? gUserNumOfECUs+5 : MAX_ECUS;

		if ( (gstResponse = (OBD_DATA *) malloc ( sizeof ( OBD_DATA ) * gNumOfECUsForAlloc )) == NULL )
		{
			Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_PROMPT,
			      "Unable to allocate memory (%d bytes) for gstResponse.",
			      sizeof ( OBD_DATA ) * gNumOfECUsForAlloc );
			exit ( FAIL );
		}
		memset ( &gstResponse[0],
		         0x00,
		         sizeof ( OBD_DATA ) * gNumOfECUsForAlloc );

		if ( (gstCompareResponse = (OBD_DATA *) malloc ( sizeof ( OBD_DATA ) * gNumOfECUsForAlloc )) == NULL )
		{
			Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_PROMPT,
			      "Unable to allocate memory (%d bytes) for gstCompareResponse.",
			      sizeof ( OBD_DATA ) * gNumOfECUsForAlloc );
			exit ( FAIL );
		}
		memset ( &gstCompareResponse[0],
		         0x00,
		         sizeof ( OBD_DATA ) * gNumOfECUsForAlloc );

		if ( (gstEcuTimingData = (ECU_TIMING_DATA *) malloc ( sizeof ( ECU_TIMING_DATA ) * gNumOfECUsForAlloc )) == NULL )
		{
			Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_PROMPT,
			      "Unable to allocate memory (%d bytes) for gstEcuTimingData.",
			      sizeof ( ECU_TIMING_DATA ) * gNumOfECUsForAlloc );
			exit ( FAIL );
		}
		memset ( &gstEcuTimingData[0],
		         0x00,
		         sizeof ( ECU_TIMING_DATA ) * gNumOfECUsForAlloc );

		if ( (gstEcuSIDData = (ECU_SID_DATA *) malloc ( sizeof ( ECU_SID_DATA ) * gNumOfECUsForAlloc )) == NULL )
		{
			Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_PROMPT,
			      "Unable to allocate memory (%d bytes) for gstEcuSIDData.",
			      sizeof ( ECU_SID_DATA ) * gNumOfECUsForAlloc );
			exit ( FAIL );
		}
		memset ( &gstEcuSIDData[0],
		         0x00,
		         sizeof ( ECU_SID_DATA ) * gNumOfECUsForAlloc );


		// initialize OBD Protocol List
		memset ( &gstProtocolList[0],
		         0x00,
		         (sizeof ( PROTOCOL_LIST )) * OBD_MAX_PROTOCOLS );


		// initialize reverse order NOT requested
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			gReverseOrderStateArray[EcuIdx] = NOT_REVERSE_ORDER;
		}

		// Run Vehicle Report, if requested
		if ( nTestChoice == 3 )
		{
			VehicleReport ( );
		}
	}
	while ( nTestChoice == 3 );




	/*******************************************************
	**  Run Static test
	*******************************************************/
	if ( nTestChoice == 1 )
	{
		// Allocate memory and initialize Static Test only ECU arrays
		if ( (gstDTCList = (DTC_LIST *) malloc ( sizeof ( DTC_LIST ) * gNumOfECUsForAlloc )) == NULL )
		{
			Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_PROMPT,
			      "Unable to allocate memory (%d) for gstDTCList.",
			      sizeof ( DTC_LIST ) * gNumOfECUsForAlloc );
			exit ( FAIL );
		}
		memset ( &gstDTCList[0],
		         0x00,
		         sizeof ( DTC_LIST ) * gNumOfECUsForAlloc );

		// Create the log file name from the user input
		gLogFileHandle = NULL;
		for ( LogFileIdx = 1;
		      LogFileIdx < 1000000;
		      LogFileIdx++ )
		{
			sprintf_s ( gLogFileNameString, MAX_LOGFILENAME,
			            "%d-%s-%s-%ld.log",
			            gModelYear,
			            gUserMakeString,
			            gUserModelString,
			            LogFileIdx );

			// Check if log file already exists
			if ( fopen_s ( &gLogFileHandle, gLogFileNameString, "r" ) == 0 )
			{
				fclose ( gLogFileHandle );
			}
			else
			{
				break;  // leave logfile index for loop
			}
		}

		// Open the log file
		if ( fopen_s ( &gLogFileHandle, gLogFileNameString, "w+" ) != 0 )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT,
			      "Cannot open log file %s\n",
			      gLogFileNameString );
			StopTest ( FAIL, geTestPhase );
			exit (FAIL);
		}

		// Log Application version, build date, OS, etc
		LogVersionInformation ( SCREENOUTPUTON, LOGOUTPUTON );

		// Copy temp log file to actual log file
		if ( AppendLogFile ( ) != PASS )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT,
			      "Unable to copy temp log file to %s\n",
			      gLogFileNameString );
			StopTest ( FAIL, geTestPhase );
			exit (FAIL);
		}


		// Read INI file, if it exists, and log it
		ReadIni ( );


		// Ask for optional user information
		Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTON, ENTER_PROMPT,
		      "(Q12) Enter your name and/or contact information (optional) " );


		// Run Static test
		eRetCode = RunStaticTests ( );
		if ( eRetCode == EXIT )
		{
			Log ( SECTION_INCOMPLETE_RESULT, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Static" );
		}
		else if ( eRetCode == FAIL )
		{
			Log ( SECTION_FAILED_RESULT, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Static" );
		}
		else
		{
			Log ( SECTION_PASSED_RESULT, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Static" );
		}

		// cannot continue to dynamic tests if DetermineOBDProtocol failed
		if ( gbProtocolDetermined == FALSE )
		{
			Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT, "\n\n" );
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Protocol determination unsuccessful. Unable to continue to Dynamic Tests.\n\n" );
		}
		// Prompt user to run dynamic test
		else if ( Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_PROMPT,
		                "Would you like to run dynamic tests?" ) == 'Y' )
		{
			nTestChoice = 2;
			LogStats ( );
			fclose ( gLogFileHandle );
			gLogFileHandle = gTempLogFileHandle;
			strcpy_s ( gLogFileNameString, MAX_LOGFILENAME, gTempLogFilenameString );
		}

		// free memory for Static Test only ECU arrays
		free( gstDTCList );
	}


	/*******************************************************
	**  Run Dynamic test
	*******************************************************/
	if ( nTestChoice == 2 )
	{
		// Read INI file, if it exists
		ReadIni ( );


		// run dynamic tests
		eRetCode = RunDynamicTests ( );
		if ( eRetCode == FAIL )
		{
			Log ( SECTION_FAILED_RESULT, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Dynamic" );
		}
		else if ( eRetCode == ABORT )
		{
			Log ( SECTION_INCOMPLETE_RESULT, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Dynamic" );
		}
		else if ( eRetCode == EXIT )
		{
			// do nothing
		}
		else
		{
			Log ( SECTION_PASSED_RESULT, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Dynamic" );
		}
	}

	// Done
	LogStats ( );
	StopTest ( eRetCode, geTestPhase );

	free( gstResponse );
	free( gstCompareResponse );
	free( gstEcuTimingData );
	free( gstEcuSIDData );

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  RunStaticTests
**
**  Purpose:
**
*******************************************************************************/
STATUS RunStaticTests ( void )
{
	// Clear Test Failure Flag
	gbTestSectionFailed = FALSE;

	// Clear Test Aborted Flag
	gbTestSectionAborted = FALSE;

	// Reset vehicle state
	gbEngineRunning = FALSE;
	gbDTCPending    = FALSE;
	gbDTCStored     = FALSE;
	gbDTCHistorical = FALSE;

	// Run tests 5.XX
	geTestPhase = eTestNoDTC;
	if ( Test5_NoDtc ( ) != PASS )
	{
		return FAIL;
	}

	// Sleep 5 seconds between each test to "drop out" of diagnostic session
	// so we can see if a different OBD protocol is found on the next search
	Sleep ( 5000 );

	// Run tests 6.XX
	geTestPhase = eTestPendingDTC;
	if ( Test6_PendingDtc ( ) != PASS )
	{
		return FAIL;
	}

	// Sleep 5 seconds between each test to "drop out" of diagnostic session
	// so we can see if a different OBD protocol is found on the next search
	Sleep ( 5000 );

	// Run tests 7.XX
	geTestPhase = eTestConfirmedDTC;
	if ( Test7_ConfirmedDtc ( ) != PASS )
	{
		return FAIL;
	}

	// Sleep 5 seconds between each test to "drop out" of diagnostic session
	// so we can see if a different OBD protocol is found on the next search
	Sleep ( 5000 );

	// Run tests 8.XX
	geTestPhase = eTestFaultRepaired;
	if ( Test8_FaultRepaired ( ) != PASS )
	{
		return FAIL;
	}

	// Sleep 5 seconds between each test to "drop out" of diagnostic session
	// so we can see if a different OBD protocol is found on the next search
	Sleep ( 5000 );

	// Run tests 9.XX
	geTestPhase = eTestNoFault3DriveCycle;
	if ( Test9_NoFaultsAfter3DriveCycles ( ) != PASS )
	{
		return FAIL;
	}

	if ( gbTestSectionFailed == TRUE )
	{
		return FAIL;
	}

	return PASS;
}


/*******************************************************************************
**
**  Function:  RunDynamicTests
**
**  Purpose:
**
*******************************************************************************/
STATUS RunDynamicTests ( void )
{
	STATUS eRetCode;

	BOOL   gbTestSectionFailed_Copy;   // copy of gbTestSectionFailed to determine if either dynamic test failed
	BOOL   gbTestSectionAborted_Copy;  // copy of gbTestSectionAborted to determine if either dynamic test aborted
	BOOL   bReEnterTest = FALSE;       // set to TRUE if test in being re-entered


	// Clear Test Failure Flag
	gbTestSectionFailed = FALSE;

	// Clear Test Aborted Flag
	gbTestSectionAborted = FALSE;


	// Run tests 10.XX
	geTestPhase = eTestInUseCounters;
	AllocateMemoryForTest10_InUseCounters ( );
	eRetCode = Test10_InUseCounters ( &bReEnterTest );
	FreeMemoryForTest10_InUseCounters ( );
	if ( eRetCode == FAIL || eRetCode == EXIT )
	{
		return eRetCode;
	}


	// Save copy of Test Failure Flag
	gbTestSectionFailed_Copy = gbTestSectionFailed;
	// Clear Test Failure Flag
	gbTestSectionFailed = FALSE;

	// Save copy of Test Aborted Flag
	gbTestSectionAborted_Copy = gbTestSectionAborted;
	// Clear Test Aborted Flag
	gbTestSectionAborted = FALSE;


	// Proceed to tests 11.XX
	geTestPhase = eTestPerformanceCounters;
	AllocateMemoryForTest11_PerformanceCounters ( );
	eRetCode = Test11_PerformanceCounters ( &bReEnterTest );
	FreeMemoryForTest11_PerformanceCounters ( );
	if ( eRetCode == FAIL || gbTestSectionFailed == TRUE || gbTestSectionFailed_Copy == TRUE )
	{
		return FAIL;
	}
	else if ( eRetCode == ABORT || gbTestSectionAborted == TRUE || gbTestSectionAborted_Copy == TRUE )
	{
		return ABORT;
	}

	return PASS;
}


/*******************************************************************************
**
**  Function:  OpenTempLogFile
**
**  Purpose:
**
*******************************************************************************/
STATUS OpenTempLogFile ( void )
{
	// Get temp log filename
	if ( GetTempFileName ( ".", "j1699", 0, gTempLogFilenameString ) == 0 )
	{
		return FAIL;
	}

	// Open the log file
	if ( fopen_s ( &gTempLogFileHandle, gTempLogFilenameString, "w+" ) != 0 )
	{
		return FAIL;
	}
	gLogFileHandle = gTempLogFileHandle;

	return PASS;
}


/*******************************************************************************
**
**  Function:  AppendLogFile
**
**  Purpose:
**
*******************************************************************************/
STATUS AppendLogFile ( void )
{
	char Buffer[1024];


	// move to beginning of temp log file
	fflush ( gTempLogFileHandle );
	fseek ( gTempLogFileHandle, 0, SEEK_SET );

	// move to end of log file
	fseek ( gLogFileHandle, 0, SEEK_END );

	// append temp log file to official log file
	while ( fgets ( Buffer, sizeof ( Buffer ), gTempLogFileHandle ) != NULL )
	{
		fputs ( Buffer, gLogFileHandle );
	}

	return PASS;
}


/*******************************************************************************
**
**  Function:  HandlerRoutine
**
**  Purpose:
**
*******************************************************************************/
BOOL WINAPI HandlerRoutine ( DWORD dwCtrlType )
{
	// ctrl-C or other system events which terminate the test
	Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
	      "Ctrl-C :: terminating application\n\n" );

	gbTestAborted = TRUE;
	LogStats ( );
	StopTest ( CTRL_C, geTestPhase );

	exit (CTRL_C);
}


/*******************************************************************************
**
**  Function:  LogSoftwareVersion
**
**  Purpose:   Log the J1699-3 software version information
**
*******************************************************************************/
void LogSoftwareVersion ( SCREENOUTPUT bDisplay,
                          LOGOUTPUT bLog )
{
#ifdef _DEBUG
	Log ( INFORMATION, bDisplay, bLog, NO_PROMPT,
	      "**** SAE %s DEBUG Build Revision %s  (Build Date: %s) ****\n\n",
	      szJ1699_VER,
	      gszAPP_REVISION,
	      szBUILD_DATE );
#else
	Log ( INFORMATION, bDisplay, bLog, NO_PROMPT,
	      "**** SAE %s Build Revision %s  (Build Date: %s) ****\n\n",
	      szJ1699_VER,
	      gszAPP_REVISION,
	      szBUILD_DATE);
#endif
}


/*******************************************************************************
**
**  Function:  LogVersionInformation
**
**  Purpose:
**
*******************************************************************************/
void LogVersionInformation ( SCREENOUTPUT bDisplay,
                             LOGOUTPUT    bLog )
{
	time_t          current_time;
	struct tm       current_tm;
	char            timestr[26];
	OSVERSIONINFOEX osvi;
	DWORDLONG       dwlConditionMask = 0;
	int             op = VER_GREATER_EQUAL;


	// Put the date / time in the log file
	time ( &current_time );
	localtime_s ( &current_tm, &current_time );
	asctime_s ( timestr, sizeof ( timestr ), &current_tm );
	Log ( INFORMATION, bDisplay, LOGOUTPUTON, NO_PROMPT,
	      "%s\n\n",
	      timestr );

	// Let the user know what version is being run and the log file name
	Log ( INFORMATION, bDisplay, bLog, NO_PROMPT,
	      "**** LOG FILENAME %s ****\n",
	      gLogFileNameString );

	LogSoftwareVersion ( bDisplay, bLog );

	Log ( INFORMATION, bDisplay, bLog, NO_PROMPT,
	      "**** NOTE: Timestamp on left is from the PC ****\n" );
	Log ( INFORMATION, bDisplay, bLog, NO_PROMPT,
	      "**** NOTE: Timestamp with messages is from the J2534 interface ****\n\n" );

	// Log Microsoft Windows OS
	osvi.dwOSVersionInfoSize = sizeof ( OSVERSIONINFO );


	// Initialize the OSVERSIONINFOEX structure.

	ZeroMemory ( &osvi, sizeof ( OSVERSIONINFOEX ) );
	osvi.dwOSVersionInfoSize = sizeof ( OSVERSIONINFOEX );
	osvi.dwMajorVersion = 5;
	osvi.dwMinorVersion = 0;

	// Initialize the condition mask.

	VER_SET_CONDITION ( dwlConditionMask, VER_MAJORVERSION, op );
	VER_SET_CONDITION ( dwlConditionMask, VER_MINORVERSION, op );

	if ( VerifyVersionInfo ( &osvi, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask ) )
	{
		Log ( INFORMATION, bDisplay, bLog, NO_PROMPT,
		      "Windows NT/2K/XP/7/8/10\n" );
	}
	else
	{
		Log ( WARNING, bDisplay, bLog, NO_PROMPT,
		      "Windows 9X/ME\n" );
	}
}


/*******************************************************************************
**
**  Function:  ClearKeyboardBuffer
**
**  Purpose:  Remove all pending data from input buffer
**
*******************************************************************************/
void ClearKeyboardBuffer ( void )
{
	char key;


	while ( _kbhit ( ) && (key = _getch ( )) != '\n' && key != '\r' && key != EOF );

	fseek ( stdin, 0, SEEK_END );
	fflush ( stdin );
}
