/*******************************************************************************
********************************************************************************
**
**  Copyright(c) 2022, Alliance for Automotive Innovation
**  Used only under license from the Alliance for Automotive Innovation. All Rights Reserved.
**
**  Project:  J1699-5
**  FileName: j1699.c
**  Author:   EnGenius
**  Date:     2/25/2022
**  Email:    <support@autosinnovate.org>
**
**  Purpose:  SAE J1699-5 Vehicle OBD II Compliance Test Cases Source Code.
**            This source code is intended to run the tests described in
**            the SAE J1699-5 document in an automated manner, when compiled
**            and used with an SAE J2534-compatible pass-thru device.
**
**  Description:
**
**  Modifications:  03/13/2023  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-5";

const char gszAPP_REVISION[] = "04.08.00";

const char szBUILD_DATE[] = __DATE__;


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

// Compliance Type definitions
char *gComplianceTestOptionsStrings[] =           // string of compliance test options
{
	"    1 US OBD\n"
	"    2 Heavy Duty US OBD (vehicle > 14,000 lb GVWR)\n"
	"      *** THE FOLLOWING TYPES ARE NOT CURRENTLY IMPLEMENTED ***\n"
	"    3 *** European OBD (EU7) ***\n"
	"    4 *** Heavy Duty European OBD (EU VII) (vehicle >= 2610kg RW) ***\n"
	"    5 *** China OBD (CN7) ***\n"
	"    6 *** India Bharat 6 Stage 3 OBD (BS6-St.3) ***\n"
	"    7 *** India Bharat 6 Stage 3 HD (BS6-St.3) (GVWR > 3.5T)***\n"
	"    8 *** Brazil OBD Phase 7 without Evap (OBDBr-P7) ***\n"
	"    9 *** Brazil OBD Phase 8 with Evap (OBDBr-P8) ***\n"
};
#define COMPLIANCE_TEST_LIST_COUNT 2

// 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[12] =         // strings that represent the user selected compliance test
{
	"UNKNOWN",
	"US OBD",
	"HD OBD",
	"EOBD",
	"EOBD_NO_IUMPR",
	"HD EOBD",
	"CNOBD",
	"IOBD",
	"HD IOBD",
	"BrOBD without Evap",
	"BrOBD with Evap",
	"US ZEV",
};

// Engine Type definitions
char *gEnergyTypeOptionsStrings[] =            // strings of engine energy type options
{
	"    1 Internal Combustion Engine (ICE)\n"
	"    2 Zero-Emission Vehicle (ZEV)\n"
};
#define ENERGY_LIST_COUNT 2

char *gEnergyTypeStrings[] =                     // strings that represent the user selected energy type
{
	"UNKNOWN",
	"ICE",
	"ZEV"
};

char *gFuelTypeOptionsStrings[] =            // string of fuel type options
{
	"    1 Gasonline (G)\n"
	"    2 Diesel (D)\n"
	"    3 *** Compressed Natural Gas/Hydrogen (CNG/H2) NOT CURRENTLY IMPLEMENTED ***\n"
	"    4 *** Other NOT CURRENTLY IMPLEMENTED ***\n"
};
#define FUEL_LIST_COUNT 4

char *gFuelTypeStrings[] =                     // strings that represent the user selected fuel type
{
	"UNKNOWN",
	"GASOLINE",
	"DIESEL"
	"COMPRESSED NATURAL GAS/HYDROGEN"
	"OTHER"
};

// 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"
};

// Battery Type definitions
char *gPowerTypeOptionsStrings[] =              // string of power type options
{
	"    1 Battery Electric (BEV)\n"
	"    2 Fuel Cell Electric Vehicle (FCEV)\n"
	"    3 Plug-In Hybrid Electric Vehicle Gasoline (PHEVG)\n"
	"    3 Plug-In Hybrid Electric Vehicle Diesel (PHEVD)\n"
};
#define POWER_LIST_COUNT 4

char *gPowerTypeStrings[] =                     // strings that represent the user selected power type
{
	"UNKNOWN",
	"BATTERY",
	"FUEL CELL",
	"PHEVG",
	"PHEVD"
};

// Vehicle Type definitions
char *gVehicleTypeOptionsStrings[] =             // string of vehicle type options
{
	"    1 Light Duty Vehicle Chassis Certified (LD)\n"
	"    2 Medium Duty Truck Chassis Certified (8,500 - 14,000 lbs GVWR) (MD)\n"
	"    3 Medium Duty Truck Engine Dyno Certified (< 14,000 lbs GVWR) (MDDC)\n"
};
#define VEHICLE_LIST_COUNT 3

char *gZEVVehicleTypeOptionsStrings[] =             // string of vehicle type options
{
	"    1 Light Duty Vehicle Chassis Certified (LD)\n"
	"    2 Medium Duty Truck Chassis Certified (8,500 - 14,000 lbs GVWR) (MD)\n"
	"    3 Medium Duty Truck Engine Dyno Certified (< 14,000 lbs GVWR) (MDDC)\n"
	"    4 Heavy Duty Truck (US > 14,000 pound GVWR or Europe > 2,610 kg RW) (HD)\n"
};
#define ZEV_VEHICLE_LIST_COUNT 4

char *gVehicleTypeStrings[] =                    // strings that represent the user selected vehicle type
{
	"UNKNOWN",
	"LIGHT DUTY VEHICLE",
	"MEDIUM DUTY TRUCK",
	"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 diagnostic or emission critical ECUs are on this vehicle?  ",  // DSPSTR_PRMPT_OBD_ECU
	"What type of energy is used by this vehicle?  ",              // DSPSTR_PRMPT_ENERGY_TYPE
	"What type of fuel is used by this vehicle?  ",                // DSPSTR_PRMPT_FUEL_TYPE
	"What type of power is used by this vehicle?  ",               // DSPSTR_PRMPT_POWER_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           2039


/*******************************************************************************
**  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           gbPSA = FALSE;                       // set if Propulsion System Active should be Active, ON, Ready
BOOL           gbEngineWarm = FALSE;                // set if engine should be warm
BOOL           gbHybrid = FALSE;                    // set if the user chose a hybrid vehicle
BOOL           gbPlugIn = FALSE;                    // set if the user chose a plug-in hybrid vehicle
BOOL           gbElectric = FALSE;                  // set if the user chose a fully electric vehicle
BOOL           gbDynoCert = FALSE;                  // set if the user chose a Dyno Certified vehicle
BOOL           gbPhysicalAddressing = FALSE;        // set if requests should be sent physically addressed
BOOL           gbSkipPA = FALSE;                    // set if skipping physically addressed (TESTING ONLY)
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

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
ECU_DATA      *gstResponse;
char           gVINString[VIN_BUFFER_SIZE] = {0};
char           gVIDString[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)

BOOL           gbDirectEthernet = FALSE;


/*******************************************************************************
**  Local Function Prototypes
*******************************************************************************/
STATUS RunStaticTests  ( void );
STATUS RunDynamicTests ( void );
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   GetDirectConnectList ( void );
extern void   ReadIni ( void );
extern STATUS VehicleReport ( void );
extern void   LogStats ( void );
extern void   ClearTransactionBuffer ( void );      // clears transaction ring buffer
extern void   AllocateMemoryForRunDynamicTest10 ( void );
extern void   FreeMemoryForRunDynamicTest10 ( 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 = 0;
	int           nISOType = 0;
	int           nEnergyType = 0;
	int           nFuelType = 0;
	int           nPwrTrnType = 0;
	int           nPowerType = 0;
	int           nVehType = 0;
	char          key = 0;


	// 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 );

	Sleep ( 1000 );  // delay to avoid Windows 11 display issue

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

	// Get user input to determine test connection
//	while ( (key < '1' || key > '2') &&
//	        key != 'e' && key != 'E' )
//	{
//		ClearKeyboardBuffer ( );
//		Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
//		      "Select the test connection from following list:\n"
//		      "    1. Direct Ethernet.\n"
//		      "    2. j2534 device.\n"
//		      "    E. Exit.\n"
//		      "Enter choice (1-2) (E to exit): " );
//		scanf_s ( "%1c", &key, 1 );
//		if ( key == 'e' || key == 'E' )
//		{
//			exit ( PASS );
//		}
//		// if direct ethernet connection selected
//		else if ( key == '1' )
//		{
//			gbDirectEthernet = TRUE;
//			GetDirectConnectList ( );
//		}
//		Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT, "\n" );
//	}
//
//
//	if ( gbDirectEthernet )
//	{
//		Log ( INFORMATION, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
//		      "Direct Ethernet Connection Selected\n\n" );
//	}
//	else
	{
		ClearKeyboardBuffer ( );

		// 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;
		nEnergyType    = 0;
		nFuelType      = 0;
		nPwrTrnType    = 0;
		nVehType       = 0;
		gVINModelYear  = 0;
		gModelYear     = 0;
		gUserNumOfECUs = 0;
		gstUserInput.eComplianceType = 0;

		// Get user input to determine which test to run
		while ( (key < '1' || key > '4') &&
		        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 ICE Static Tests  contained in Sections 5 through 9.\n"
			      "    2. Run ICE Dynamic Tests contained in Sections 10 and 11.\n"
			      "    3. Run ZEV/ACC II Tests  contained in Section  12.\n"
			      "    4. Vehicle Report.\n"
			      "    E. Exit.\n"
			      "Enter choice (1-4) (E to exit): " );
			scanf_s ( "%1c", &key, 1 );
			// toggle physical addressing (TESTING ONLY)
			if ( key == '!' )
			{
				gbSkipPA ^= TRUE;
				printf ( "Physical Addressing %s\n", gbSkipPA ? "OFF" : "ON" );
			}
			// toggle direct connection simulation (TESTING ONLY)
			else if ( key == '~' )
			{
				gbDirectEthernet ^= TRUE;
				printf ( "Direct Connect %s\n", gbDirectEthernet ? "ON" : "OFF" );
			}
			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 or ZEV tests
		if ( nTestChoice == STATIC_TESTS ||
		     nTestChoice == ZEV_TESTS )
		{
			ClearKeyboardBuffer ( );
			do
			{
				Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
				      "(Q04) Enter make of vehicle\n"
				      "(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\n"
				      "(e.g. F-150, A5, CR-V...): " );
				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"
			      "%s",
			      gDisplayStrings[DSPSTR_PRMPT_OBD_ECU],
			      nTestChoice == ZEV_TESTS ? "(1 to 239):  " : "(1 to 8 (239 for 29-Bit ID)):  " );
			scanf_s ( "%hhd", &gUserNumOfECUs );
			Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );
		}
		gNumOfECUs = gUserNumOfECUs;


		// if not ZEV test
		if ( nTestChoice != ZEV_TESTS )
		{
			// Ask the user for the type of energy used in the vehicle
			while ( nEnergyType < 1 || nEnergyType > ENERGY_LIST_COUNT )
			{
				ClearKeyboardBuffer ( );
				Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
				      "(Q07) %s\n%s"
				      "Enter selection number (1 to %d): ",
				      gDisplayStrings[DSPSTR_PRMPT_ENERGY_TYPE],
				      gEnergyTypeOptionsStrings[0],
				      ENERGY_LIST_COUNT );
				scanf_s ( "%1d", &nEnergyType );
				Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );

				gstUserInput.eEnergyType = nEnergyType;
			}
		}
		else
		{
			nEnergyType = ZEV;

			gstUserInput.eEnergyType = nEnergyType;
		}


		// if internal combustion engine (ICE) is selected
		if ( nEnergyType == ICE )
		{
			// Ask the user for the type of fuel used in the vehicle
			while ( nFuelType < 1 || nFuelType > 2 /*FUEL_LIST_COUNT*/ )
			{
				ClearKeyboardBuffer ( );
				Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
				      "(Q08a) %s\n%s"
				      "Enter selection number (1 to %d): ",
				      gDisplayStrings[DSPSTR_PRMPT_FUEL_TYPE],
				      gFuelTypeOptionsStrings[0],
//				      FUEL_LIST_COUNT );
				      2 );
				scanf_s ( "%1d", &nFuelType );
				Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );

				gstUserInput.eFuelType = 0x01 << (nFuelType - 1 );
			}


			// 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, LOGOUTPUTOFF, ENTER_PROMPT,
					      "NOTE: Do NOT plug-in charge the vehicle during static test." );
				}
			}
		}
		// otherwise Zero-Emmission Vehicle (ZEV) is selected
		else if ( nEnergyType == ZEV )
		{
			gstUserInput.eFuelType = OTHER;

			// Ask the user for the type of power used by the vehicle
			while ( nPowerType < 1 ||  nPowerType > PWRTRN_LIST_COUNT )
			{
				ClearKeyboardBuffer ( );
				Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
				      "(Q08b) %s\n"
				      "%s"
				      "Enter selection number (1 to %d): ",
				      gDisplayStrings[DSPSTR_PRMPT_POWER_TYPE],
				      gPowerTypeOptionsStrings[0],
				      POWER_LIST_COUNT );
				scanf_s ( "%1d", &nPowerType );
				Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT, "\n" );

				gstUserInput.ePowerType = nPowerType;

				if ( nPowerType == BATTERY ||
				     nPowerType == PHEVG   ||
				     nPowerType == PHEVD )
				{
					Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, ENTER_PROMPT,
					      "NOTE: Do NOT plug-in charge the vehicle during static test." );
				}

				if ( nPowerType == BATTERY ||
				     nPowerType == FUELCELL )
				{
					nPwrTrnType = ELEC;
				}
				else
				{
					nPwrTrnType = PHEV;

					if ( nPowerType == PHEVG )
					{
						gstUserInput.eFuelType = GASOLINE;
					}
					else
					{
						gstUserInput.eFuelType = DIESEL;
					}
				}
			}
			// If FCEV, select fuel type and with or without an auxiliary HV Battery
		}


		switch ( nPwrTrnType )
		{
			case CONV:  // Conventional
			{
				gbHybrid = FALSE;
				gbPlugIn = FALSE;
			}
			break;

			case SS:    // Stop/Start
			{
				gbHybrid = FALSE;
				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;

			case ELEC:  // Fully Electric Vehicle
			{
				// if BEV
				if ( nPowerType == 1 )
				{
					gbHybrid = FALSE;
					gbPlugIn = TRUE;
				}
				// if FCEV
				else
				{
					gbHybrid = FALSE;
					gbPlugIn = FALSE;
				}
			}
			break;
		}


		// if internal combustion engine (ICE) is selected
		if ( nEnergyType == ICE )
		{
			// 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,
				      "(Q11) %s\n%s"
				      "Enter selection number (1 to %d): ",
				      gDisplayStrings[DSPSTR_STMT_COMPLIANCE_TYPE],
				      gComplianceTestOptionsStrings[0],
				      COMPLIANCE_TEST_LIST_COUNT );
				scanf_s ( "%d", &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;
//					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_WITH_IUMPR;
//					gstUserInput.eScanTable = USOBD;
//					gstUserInput.NumDCToSetMIL = 2;
//				}
//				break;

//				case 10: // US ZEV
//				{
//					gstUserInput.eComplianceType = US_ZEV;
//					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: // ISO 15765 @ 500k and 250k
//					{
//						gstUserInput.eScanTable = EOBD;
//					}
//					break;
//		
//					case 2: // ISO 15765 @ 500k only, BUS_OFF work-around
//					{
//						gstUserInput.eScanTable = USOBD;
//					}
//					break;
//		
//					case 3: // ISO 15765 @ 250k only, BUS_OFF work-around
//					{
//						gstUserInput.eScanTable = EOBD_250K;
//					}
//					break;
//				}
//			}
		}  // end if ( nEnergyType == ICE )

		// If the chosen OBD Type is for Heavy Duty, set Vehicle Type to Heavy Duty
		if ( gstUserInput.eComplianceType == HD_OBD ||            // US Heavy Duty
		     gstUserInput.eComplianceType == HD_EOBD ||          // Europe Heavy Duty
		     gstUserInput.eComplianceType == HD_IOBD_NO_IUMPR )  // India Heavy Duty
		{
			nVehType = VEHICLE_LIST_COUNT+1;
		}
		else
		{
			// if internal combustion engine (ICE) is selected
			if ( nEnergyType == ICE )
			{
				// Ask the user for the type of vehicle
				while ( nVehType < 1 || nVehType > VEHICLE_LIST_COUNT )
				{
					ClearKeyboardBuffer ( );
					Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
					      "(Q12) %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" );
				}
			}
			// otherwise Zero-Emmission Vehicle (ZEV) is selected
			else
			{
				// Ask the user for the type of vehicle
				while ( nVehType < 1 || nVehType > ZEV_VEHICLE_LIST_COUNT )
				{
					ClearKeyboardBuffer ( );
					Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTOFF, CUSTOM_PROMPT,
					      "(Q12) %s\n%s"
					      "Enter selection number (1 to %d): ",
					      gDisplayStrings[DSPSTR_PRMPT_VEH_TYPE],
					      gZEVVehicleTypeOptionsStrings[0],
					      ZEV_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 Chassis Certified
			{
				gstUserInput.eVehicleType = MD;
				gbDynoCert = FALSE;
			}
			break;

			case 3:       // Medium Duty Truck Engine Dyno Certified
			{
				gstUserInput.eVehicleType = MDDC;
				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 == MDDC || 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." );
			}
		}


		// if zero-emission vehicle (ZEV) is selected
		if ( nEnergyType == ZEV)
		{
			gstUserInput.eComplianceType = US_ZEV;
			gstUserInput.eScanTable = USOBD;
			gstUserInput.NumDCToSetMIL = 2;
		}


		// Option to turn off Tester Present Message
//		if ( Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_PROMPT,
//		           "(Q13) Disable Tester Present Messages?  " ) == '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%s\n",
		      gDisplayStrings[DSPSTR_PRMPT_ENERGY_TYPE],
		      gEnergyTypeStrings[gstUserInput.eEnergyType] );

		// if internal combustion engine (ICE) is selected
		if ( nEnergyType == ICE )
		{
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s%s\n",
			      gDisplayStrings[DSPSTR_PRMPT_FUEL_TYPE],
			      gFuelTypeStrings[gstUserInput.eFuelType] );

			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s%s\n",
			      gDisplayStrings[DSPSTR_PRMPT_PWRTRN_TYPE],
			      gPwrTrnTypeStrings[gstUserInput.ePwrTrnType] );
		}
		else
		{
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s%s\n",
			      gDisplayStrings[DSPSTR_PRMPT_POWER_TYPE],
			      gPowerTypeStrings[gstUserInput.ePowerType] );
		}

		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 = (ECU_DATA *) malloc ( sizeof ( ECU_DATA ) * gNumOfECUsForAlloc )) == NULL )
		{
			Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_PROMPT,
			      "Unable to allocate memory (%d bytes) for gstResponse.",
			      sizeof ( ECU_DATA ) * gNumOfECUsForAlloc );
			exit ( FAIL );
		}
		memset ( &gstResponse[0],
		         0x00,
		         sizeof ( ECU_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 == VEHICLE_REPORT )
		{
			if ( nEnergyType != ZEV )
			{
				VehicleReport ( );
			}
			else
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "***  Vehicle Report is NOT CURRENTLY IMPLEMENTED for ZEV (DoIP) vehicles ***\n" );
			}
		}
	}
	while ( nTestChoice == VEHICLE_REPORT );


	/*******************************************************
	**  Run Static test
	*******************************************************/
	if ( nTestChoice == STATIC_TESTS ||
	     nTestChoice == ZEV_TESTS )
	{
		// Allocate memory and initialize Static-Test-only ECU arrays
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			if ( (gstResponse[EcuIdx].pDTCList = (DTC_LIST*) malloc ( sizeof ( DTC_LIST ) )) == NULL )
			{
				Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_PROMPT,
				      "Unable to allocate memory (%d) for DTCList.",
				      sizeof ( DTC_LIST ) );
				exit ( FAIL );
			}
			memset ( gstResponse[EcuIdx].pDTCList,
			         0x00,
			         sizeof ( DTC_LIST ) );

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

		}

		// 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)" );


		if ( nTestChoice == ZEV_TESTS )
		{
			// 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 12.XX
			geTestPhase = eTestZEV;
			eRetCode = Test12_ZEV ( );
			if ( eRetCode == EXIT )
			{
				Log ( SECTION_INCOMPLETE_RESULT, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ZEV/ACC-II" );
			}
			else if ( eRetCode == FAIL )
			{
				Log ( SECTION_FAILED_RESULT, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ZEV/ACC-II" );
			}
			else
			{
				Log ( SECTION_PASSED_RESULT, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ZEV/ACC-II" );
			}
		}
		else
		{
			// 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
			for ( EcuIdx = 0;
			      EcuIdx < gNumOfECUs;
			      EcuIdx++ )
			{
				free ( gstResponse[EcuIdx].pDTCList );
			}
		}
	}


	/*******************************************************
	**  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 - passed
	LogStats ( );
	StopTest ( eRetCode, geTestPhase );

	free ( gstResponse );

	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;
	eRetCode = Test10_InUseCounters ( &bReEnterTest );
	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 );
}
