/*******************************************************************************
********************************************************************************
**  SAE J1699-5 Vehicle OBD II Compliance Test Cases Source Code
**
**   Copyright (C) 2022
**
**  ****************************************************************************
**
**  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.
**
**  File j1699.c contains information on building and running this test.
**
********************************************************************************
*******************************************************************************/

#include <stdio.h>    // C Library input and output declarations
#include <stdlib.h>   // C Library general function declarations
#include <time.h>     // C Library time and date declarations
#include <windows.h>  // Windows API declarations
#include "j2534.h"    // j1699 project j2534 declarations
#include "j1699.h"    // j1699 project general declarations


#define MAX_KEY_LEN  300


/*******************************************************************************
**  function pointers for J2534 API
*******************************************************************************/
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;


// maximum number of J2534 devices allowed
#define MAX_J2534_DEVICES        50


/*******************************************************************************
**  Funtion Prototypes
*******************************************************************************/
STATUS        ReadJ2534List      ( char DeviceList[MAX_J2534_DEVICES][MAX_KEY_LEN], char LibraryList[MAX_J2534_DEVICES][MAX_KEY_LEN], unsigned long *pListIdx );
STATUS        LoadJ2534Api       ( char *szLibrary );
unsigned long SelectJ2534Device  ( char DeviceList[MAX_J2534_DEVICES][MAX_KEY_LEN], char LibraryList[MAX_J2534_DEVICES][MAX_KEY_LEN], unsigned long ListIdx );

extern void   LogSoftwareVersion ( SCREENOUTPUT bDisplay, LOGOUTPUT bLog );


/*******************************************************************************
**  Variables
*******************************************************************************/
HINSTANCE hDLL;  // j2534 device library handle
static char* szPROTOCOLS[] =
{
	"CAN",
	"ISO9141",
	"ISO14230",
	"ISO15765",
	"J1850VPW",
	"J1850PWM"
};
#define NUM_PROTOCOLS  sizeof ( szPROTOCOLS ) / sizeof ( szPROTOCOLS[0] )



/*******************************************************************************
**
**  Function:  FindJ2534Interface
**
**  Purpose:   Find appropriate J2534 interface and load the DLL
**
*******************************************************************************/
STATUS FindJ2534Interface ( void )
{
	unsigned long DeviceIdx;
	unsigned long ListIdx;
	char          DeviceList[MAX_J2534_DEVICES][MAX_KEY_LEN];
	char          LibraryList[MAX_J2534_DEVICES][MAX_KEY_LEN];


	// Acquire installed J2534 interface list.
	if ( ReadJ2534List ( DeviceList, LibraryList, &ListIdx ) != PASS )
	{
		return FAIL;
	}

	// Present list to user to choose appropriate tool (if more than one)
	if ( ListIdx == 0 )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "No interfaces found in registry\n" );
		return FAIL;
	}
	else
	{
		// Application version, build date, OS, etc
		LogSoftwareVersion ( SCREENOUTPUTON, LOGOUTPUTOFF );

		// Select J2534 interface
		DeviceIdx = SelectJ2534Device ( DeviceList, LibraryList, ListIdx );
	}

	// Load appropriate DLL and attach to API functions
	Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
	      "Loading %s library\n\n",
	      LibraryList[DeviceIdx] );

	// Attach & load vendor supplied J2534 interface.
	return  LoadJ2534Api ( LibraryList[DeviceIdx] );
}


/*******************************************************************************
**
**  Function:  LoadJ2534Api
**
**  Purpose:   Load user specified J2534 api & assign usage to
**             global data defined w/in this module.
**             When applied to an API (DLL) these references will
**             be contained w/in the DLL.
**
*******************************************************************************/
STATUS LoadJ2534Api ( char *szLibrary )
{
	/**********************************************************************/
	//
	// Once user selects the specifie API & hardware, load the vendor
	// supplied API calls.
	//
	/**********************************************************************/

	if ( (hDLL = LoadLibrary ( szLibrary )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot load %s\n",
		      szLibrary );
		return FAIL;
	}

	if ( (PassThruConnect = (PTCONNECT)GetProcAddress ( hDLL, "PassThruConnect" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruConnect function in %s\n",
		      szLibrary );
		return FAIL;
	}

	if ( (PassThruDisconnect = (PTDISCONNECT)GetProcAddress ( hDLL, "PassThruDisconnect" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruDisconnect function in %s\n",
		      szLibrary );
		return FAIL;
	}
	if ( (PassThruReadMsgs = (PTREADMSGS)GetProcAddress ( hDLL, "PassThruReadMsgs" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruReadMsgs function in %s\n",
		      szLibrary );
		return FAIL;
	}

	if ( (PassThruWriteMsgs = (PTWRITEMSGS)GetProcAddress (hDLL, "PassThruWriteMsgs" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruWriteMsgs function in %s\n",
		      szLibrary );
		return FAIL;
	}

	if ( (PassThruStartPeriodicMsg = (PTSTARTPERIODICMSG)GetProcAddress ( hDLL, "PassThruStartPeriodicMsg" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruStartPeriodicMsg function in %s\n",
		      szLibrary );
		return FAIL;
	}

	if ( (PassThruStopPeriodicMsg = (PTSTOPPERIODICMSG)GetProcAddress ( hDLL, "PassThruStopPeriodicMsg" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruStopPeriodicMsg function in %s\n",
		      szLibrary );
		return FAIL;
	}

	if ( (PassThruStartMsgFilter = (PTSTARTMSGFILTER)GetProcAddress ( hDLL, "PassThruStartMsgFilter" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruStartMsgFilter function in %s\n",
		      szLibrary );
		return FAIL;
	}

	if ( (PassThruStopMsgFilter = (PTSTOPMSGFILTER)GetProcAddress ( hDLL, "PassThruStopMsgFilter" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruStopMsgFilter function in %s\n",
		      szLibrary );
		return FAIL;
	}

	if ( (PassThruSetProgrammingVoltage = (PTSETPROGRAMMINGVOLTAGE)GetProcAddress ( hDLL, "PassThruSetProgrammingVoltage" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruSetProgrammingVoltage function in %s\n",
		      szLibrary );
		return FAIL;
	}
	if ( (PassThruReadVersion = (PTREADVERSION)GetProcAddress ( hDLL, "PassThruReadVersion" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruReadVersion function in %s\n",
		      szLibrary );
		return FAIL;
	}

	if ( (PassThruGetLastError = (PTGETLASTERROR)GetProcAddress ( hDLL, "PassThruGetLastError" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruGetLastError function in %s\n",
		      szLibrary );
		return FAIL;
	}

	if ( (PassThruIoctl = (PTIOCTL)GetProcAddress ( hDLL, "PassThruIoctl" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruIoctl function in %s\n",
		      szLibrary );
		return FAIL;
	}

	if ( (PassThruOpen = (PTOPEN)GetProcAddress ( hDLL, "PassThruOpen" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruOpen function in %s\n",
		      szLibrary );
		return FAIL;
	}

	if ( (PassThruClose = (PTCLOSE)GetProcAddress ( hDLL, "PassThruClose" )) == NULL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot find PassThruClose function in %s\n",
		      szLibrary );
		return FAIL;
	}

	return PASS;
}


/*******************************************************************************
**
**  Function:  ReadJ2534List
**
**  Purpose:   Read system regestry and populate list of available
**             interfaces.
**
*******************************************************************************/
STATUS ReadJ2534List ( char DeviceList[MAX_J2534_DEVICES][MAX_KEY_LEN],
                       char LibraryList[MAX_J2534_DEVICES][MAX_KEY_LEN],
                       unsigned long *pListIdx )
{
	unsigned long DeviceIdx;
	unsigned long ProtocolIdx;
	HKEY          hKey1;
	HKEY          hKey2;
	HKEY          hKey3;
	DWORD         KeySize;
	BYTE          KeyValue[240];
	DWORD         ProtocolFlag;
	FILETIME      lastTimeWritten;


	*pListIdx = 0;

	// Find all available interfaces in the registry
	if ( RegOpenKeyEx ( HKEY_LOCAL_MACHINE, "Software", 0, KEY_READ, &hKey1 ) != ERROR_SUCCESS )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot open HKEY_LOCAL_MACHINE->Software key\n" );
		return FAIL;
	}

	if ( RegOpenKeyEx ( hKey1, "PassThruSupport.04.04", 0, KEY_READ, &hKey2 ) != ERROR_SUCCESS )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot open HKEY_LOCAL_MACHINE->Software->PassThruSupport key\n" );
		return FAIL;
	}

	// Check for up to MAX_J2534_DEVICES vendors
	for ( DeviceIdx = 0;
	      DeviceIdx < MAX_J2534_DEVICES;
	      DeviceIdx++ )
	{
		KeySize = sizeof ( KeyValue );
		if ( RegEnumKeyEx ( hKey2, DeviceIdx, KeyValue, &KeySize, NULL, NULL, NULL, &lastTimeWritten ) != ERROR_SUCCESS )
		{
			// If no more vendors, stop looking
			break;  // leave device for loop
		}

		if ( RegOpenKeyEx ( hKey2, KeyValue, 0, KEY_READ, &hKey3 ) != ERROR_SUCCESS )
		{
			break;  // leave device for loop
		}

		KeySize = sizeof ( KeyValue );
		if ( RegQueryValueEx ( hKey3, "Name", 0, 0, KeyValue, &KeySize ) == ERROR_SUCCESS )
		{
			strcpy_s ( DeviceList[DeviceIdx], MAX_KEY_LEN, KeyValue );
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Device = %s\n",
			      KeyValue );
		}

		KeyValue[0] = 0;
		for ( ProtocolIdx = 0;
		      ProtocolIdx < NUM_PROTOCOLS;
		      ProtocolIdx++ )
		{
			KeySize = sizeof ( ProtocolFlag );
			if ( RegQueryValueEx ( hKey3, szPROTOCOLS[ProtocolIdx], 0, 0, (BYTE *)&ProtocolFlag, &KeySize ) == ERROR_SUCCESS )
			{
				if ( ProtocolFlag != 0 )
				{
					strcat_s ( KeyValue, sizeof ( KeyValue ) - strlen ( KeyValue ), szPROTOCOLS[ProtocolIdx] );
					strcat_s ( KeyValue, sizeof ( KeyValue ) - strlen ( KeyValue ), "," );
				}
			}
		}
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "ProtocolsSupported = %s\n",
		      KeyValue );

		KeySize = sizeof ( KeyValue );
		if ( RegQueryValueEx ( hKey3, "FunctionLibrary", 0, 0, KeyValue, &KeySize ) == ERROR_SUCCESS )
		{
			strcpy_s ( LibraryList[DeviceIdx], MAX_KEY_LEN, KeyValue );
		}

		RegCloseKey ( hKey3 );
	}

	*pListIdx = DeviceIdx;

	RegCloseKey ( hKey2 );
	RegCloseKey ( hKey1 );

	return PASS;
}


/*******************************************************************************
**
**  Function:  SelectJ2534Device
**
**  Purpose:   Prompt user to select from the installed J2534 choices.
**
*******************************************************************************/
unsigned long SelectJ2534Device ( char DeviceList[MAX_J2534_DEVICES][MAX_KEY_LEN],
                                  char LibraryList[MAX_J2534_DEVICES][MAX_KEY_LEN],
                                  unsigned long ListIdx )
{
	unsigned long DeviceIdx;
	char          TempBuffer[1024];      // temporary buffer


	TempBuffer[0] = 0x00;       // initialize string  to NULL

	// Present list to user to choose appropriate tool (if more than one)
	if ( ListIdx > 1 )
	{
		Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTON, CUSTOM_PROMPT,
		      "(Q01) Select tool from following list:\n\n" );

		for ( DeviceIdx = 0;
		      DeviceIdx < ListIdx;
		      DeviceIdx++ )
		{
			sprintf_s ( TempBuffer, sizeof ( TempBuffer ),
			            "    %d %s (%s)\n",
			            (DeviceIdx + 1),
			            DeviceList[DeviceIdx],
			            LibraryList[DeviceIdx] );
			Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s",
			      TempBuffer );
		}

		do
		{
			Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "\nEnter device number (1-%d): ",
			      ListIdx );
			fgets ( TempBuffer, 4, stdin );
			DeviceIdx = atoi ( TempBuffer );
		}
		while ( DeviceIdx < 1 || DeviceIdx > ListIdx );

		Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT, "\n" );
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Device %d selected\n",
		      DeviceIdx );

		DeviceIdx -= 1;   // make zero-based index
	}
	else
	{
		DeviceIdx = 0;
	}

	// Define default value, in the event outside expected range.
	if ( DeviceIdx >= ListIdx )
	{
		DeviceIdx = 0;
	}

	return DeviceIdx;
}
