/*******************************************************************************
********************************************************************************
**
**  Copyright(c) 2022, Alliance for Automotive Innovation
**  Used only under license from the Alliance for Automotive Innovation. All Rights Reserved.
**
**  Project:  J1699-3
**  FileName: LogPrint.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
**
*******************************************************************************/


#include <stdio.h>    // C Library input and output declarations
#include <string.h>   // C Library character array declarations
#include <windows.h>  // Windows API declarations
#include "j2534.h"    // j1699 project j2534 declarations
#include "j1699.h"    // j1699 project general declarations


// Function Prototypes
int SectionLookUp  ( char *pString );
int ReadDtcs       ( FILE *pFile );
int ReadECUIds     ( FILE *pFile );

int getSectionName ( char *pInputString, char *pSectionString );
int getKeyName     ( char *pInputString, char *pKeyString );


// Variables
#define MAX_STRING_SIZE  515

typedef enum
{
	UNDEFINED=0,
	DTC_IGNORE=1,
	ECU_IGNORE=2
} INITYPE;

typedef struct
{
	char TypeString[258];
	int  Type;
}INI_SECTION;
INI_SECTION Ini_List[] =
{
	"PendDTC_Aft_Repair_To_Ignore", DTC_IGNORE,
	"ECU_IDs_To_Ignore", ECU_IGNORE,
	(char)NULL, (int)NULL
};

unsigned short  gDTC_Ignore_List_Count = 0;
unsigned short *gpDTC_Ignore_List = NULL;

unsigned short  gECU_Ignore_List_Count = 0;
unsigned long  *gpECU_Ignore_List = NULL;


/*******************************************************************************
**
**  Function:  ReadIni
**
**  Purpose:   Read the initialization file
**
*******************************************************************************/
void ReadIni ( void )
{
	FILE *pFile;
	char  String[MAX_STRING_SIZE];
	char  SectionString[MAX_STRING_SIZE];
	int   IniType;


	// open the ini file for reading, if it exists
	if ( fopen_s ( &pFile, "j1699_config.ini", "rt" ) == 0 )
	{
		// Echo j1699_config.ini contents to log file
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s\n",
		      "**** CONTENTS OF J1699_CONFIG.INI ****" );

		// read until END_OF_FILE
		while ( fgets ( String, MAX_STRING_SIZE, pFile ) )
		{
			// Echo j1699_config.ini contents to log file
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s",
			      String );

			if ( String[0] == '[' )
			{
				if ( getSectionName ( String, SectionString ) == PASS )
				{
					IniType = SectionLookUp ( SectionString );

					// Format the string depending on the log type
					switch ( IniType )
					{
						case DTC_IGNORE:
							// Read pending DTCs to Ignore
							ReadDtcs ( pFile );
						break;

						case ECU_IGNORE:
							// Read ECU IDs to Ignore
							ReadECUIds ( pFile );
						break;

						default:
							Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "%s\n",
							      "Unrecognized INI SECTION" );
						break;
					}  // switch
				}  // if getSectionName
			} // if '['
		} // while (fgets)

		// Indicate end of j1699_config.ini contents to log file
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s\n",
		      "**** END OF J1699_CONFIG.INI ****" );

		fclose ( pFile );
	} // if the INI file exists
}


/*******************************************************************************
**
**  Function:  getSectionName
**
**  Purpose:   Copy a SECTION name from an input string
**
**  Return FAIL if space or non-standard character ('!'(33) to '}' (125)) in name or not terminated with ']'
**
*******************************************************************************/
int getSectionName ( char *pInputString,
                     char *pSectionString )
{
	int Result = PASS;
	int InputIndex;
	int SectionIndex = 0;
	int Length = strlen ( pInputString );


	// Copy SECTION name (non-space text between '[' and ']') from input string
	// To have arrrived in this function, have already determined that 1st char is '['
	for ( InputIndex = 1;
	      InputIndex < Length &&
	      pInputString[InputIndex] != ']';
	      InputIndex++ )
	{
		// If standard character, copy it
		if ( pInputString[InputIndex] >= '!' &&
		     pInputString[InputIndex] <= '}' )
		{
			pSectionString[SectionIndex++] = pInputString[InputIndex];
		}
		else
		{
			Result = FAIL;
		}
	}
	if ( Result == FAIL )
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s\n",
		      "INI SECTION name contains non-standard text character." );
	}

	// terminate output string
	pSectionString[SectionIndex] = (char)NULL;

	if ( pInputString[InputIndex] != ']' )
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s\n",
		      "INI SECTION name not properly terminated with ]." );
		Result = FAIL;
	}

	return Result;
}


/*******************************************************************************
**
**  Function:  getKeyName
**
**  Purpose:   Copy a KEY names from an input string
**
**  Return FAIL if space or non-standard character ('!'(33) to '}' (125)) in name
**
*******************************************************************************/
int getKeyName ( char *pInputString,
                 char *pKeyString )
{
	int Result = PASS;
	int InputIndex;
	int KeyIndex = 0;
	int Length = strlen( pInputString );


	// Copy KEY name (non-space text before '\n', ';' or '=') from input string
	for ( InputIndex = 0;
	      InputIndex < Length;
	      InputIndex++ )
	{
		// If end of name delimiter, stop copying
		if ( pInputString[InputIndex] == ' '  ||
		     pInputString[InputIndex] == '\t' ||
		     pInputString[InputIndex] == '\n' ||
		     pInputString[InputIndex] == ';'  ||
		     pInputString[InputIndex] == '=' )
		{
			break;
		}
		// If standard character, copy it
		else if ( pInputString[InputIndex] >= '!' && pInputString[InputIndex] <= '}' )
		{
			pKeyString[KeyIndex++] = pInputString[InputIndex];
		}
		// Otherwise, not a valid character, fail
		else
		{
			Result = FAIL;
		}
	}
	if ( Result == FAIL )
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s\n",
		      "INI KEY name contains non-standard text." );
	}

	// terminate output string
	pKeyString[KeyIndex] = (char)NULL;

	return Result;
}


/*******************************************************************************
**
**  Function:  ReadHex
**
**  Purpose:   Read a hexidecimal string in to an unsigned short
**
*******************************************************************************/
int getHexValue ( char            *pInputString,
                  unsigned short  *pHexNumber )
{
	int  Result = PASS;
	int  InputIndex;
	int  HexIndex = 0;
	int  Length = strlen ( pInputString );
	char HexString[16];


	// find begining of hexadecimal value (after '=') from input string
	for ( InputIndex = 0;
	      InputIndex < Length;
	      InputIndex++ )
	{
		// If end of name delimiter, stop
		if ( pInputString[InputIndex] == ' '  ||
		     pInputString[InputIndex] == '\t' ||
		     pInputString[InputIndex] == '\n' ||
		     pInputString[InputIndex] == ';'  ||
		     pInputString[InputIndex] == '=' )
		{
			break;
		}
	}
	if ( pInputString[InputIndex] != '=' )
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "VALUE for KEY not found.\n" );
		return FAIL;
	}
	InputIndex++;

	// find begining of hexadecimal value (after '=') from input string
	for ( ;
	      InputIndex < Length;
	      InputIndex++ )
	{
		// If end of value delimiter, stop
		if ( pInputString[InputIndex] == ' '  ||
		     pInputString[InputIndex] == '\t' ||
		     pInputString[InputIndex] == '\n' ||
		     pInputString[InputIndex] == ';' )
		{
			break;
		}
		else if ( (pInputString[InputIndex] >= 'a' && pInputString[InputIndex] <= 'f') ||
		          (pInputString[InputIndex] >= 'A' && pInputString[InputIndex] <= 'F') ||
		          (pInputString[InputIndex] >= '0' && pInputString[InputIndex] <= '9') )
		{
			HexString[HexIndex++] = pInputString[InputIndex];
		}
	}
	// terminate output string
	HexString[HexIndex] = (char)NULL;

	// convert string to base 16 (hexadecimal) unsigned short
	*pHexNumber = (unsigned short) strtol ( &HexString[0], NULL, 16 );

	return Result;
}


/*******************************************************************************
**
**  Function:  ReadInt
**
**  Purpose:   Read an number string in to an integer
**
*******************************************************************************/
int getIntValue ( char *pInputString,
                  int  *pNumber )
{
	int  Result = PASS;
	int  InputIndex;
	int  NumIndex = 0;
	int  Length = strlen ( pInputString );
	char NumString[16];


	// find begining of hexadecimal value (after '=') from input string
	for ( InputIndex = 0;
	      InputIndex < Length;
	      InputIndex++ )
	{
		// If end of name delimiter, stop
		if ( pInputString[InputIndex] == ' '  ||
		     pInputString[InputIndex] == '\t' ||
		     pInputString[InputIndex] == '\n' ||
		     pInputString[InputIndex] == ';'  ||
		     pInputString[InputIndex] == '=' )
		{
			break;
		}
	}
	if ( pInputString[InputIndex] != '=' )
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "VALUE for KEY not found.\n" );
		return FAIL;
	}
	InputIndex++;

	// copy integer value (after '=') from input string
	for ( ;
	      InputIndex < Length;
	      InputIndex++ )
	{
		// If end of value delimiter, stop
		if ( pInputString[InputIndex] == ' '  ||
		     pInputString[InputIndex] == '\t' ||
		     pInputString[InputIndex] == '\n' ||
		     pInputString[InputIndex] == ';' )
		{
			break;
		}
		else if ( pInputString[InputIndex] >= '0' && pInputString[InputIndex] <= '9' )
		{
			NumString[NumIndex++] = pInputString[InputIndex];
		}
	}
	// terminate output string
	NumString[NumIndex] = (char)NULL;

	// convert string to base 10 (decimal) integer
	*pNumber = (int) strtol ( &NumString[0], NULL, 10 );

	return Result;
}


/*******************************************************************************
**
**  Function:  ReadInt
**
**  Purpose:   Read a string
**
*******************************************************************************/
int getStringValue ( char *pInputString,
                     char *pOutputString )
{
	int Result = PASS;
	int InputIndex;
	int OutputIndex = 0;
	int Length = strlen ( pInputString );


	// find begining of string value (after '=') from input string
	for ( InputIndex = 0;
	      InputIndex < Length;
	      InputIndex++ )
	{
		// If end of name delimiter, stop
		if ( pInputString[InputIndex] == ' '  ||
		     pInputString[InputIndex] == '\t' ||
		     pInputString[InputIndex] == '\n' ||
		     pInputString[InputIndex] == ';'  ||
		     pInputString[InputIndex] == '=' )
		{
			break;
		}
	}

	if ( pInputString[InputIndex] != '=' )
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "VALUE for KEY not found.\n" );
		return FAIL;
	}
	InputIndex++;

	if ( pInputString[InputIndex] != '"' )
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "String VALUE must begin with \".\n" );
		return FAIL;
	}
	InputIndex++;

	// copy string value (after '=' and '"') from input string
	for ( ;
	      InputIndex < Length;
	      InputIndex++ )
	{
		// If end of value delimiter, stop
		if ( pInputString[InputIndex] == '\t' ||
		     pInputString[InputIndex] == '\n' ||
		     pInputString[InputIndex] == ';'  ||
		     pInputString[InputIndex] == '"' )
		{
			break;
		}
		else
		{
			pOutputString[OutputIndex++] = pInputString[InputIndex];
		}
	}

	if ( pInputString[InputIndex] != '"' )
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "String VALUE must end with \".\n" );
		Result = FAIL;
	}

	// terminate output string
	pOutputString[OutputIndex] = (char)NULL;

	return Result;
}


/*******************************************************************************
**
**  Function:  SectionLookUp
**
**  Purpose:   Match the input string with a defined SECTION
**
*******************************************************************************/
int SectionLookUp ( char *pString )
{
	int Index = 0;
	int Type = 0;
	int Result = FAIL;


	while ( Ini_List[Index].TypeString[0] != (char)NULL )
	{
		if ( strcmp ( pString, Ini_List[Index].TypeString ) == 0 )
		{
			Type = Ini_List[Index].Type;
			Result = PASS;
			break;
		}
		Index++;
	 }

	return Type;
}


/*******************************************************************************
**
**  Function:  ReadDtc
**
**  Purpose:   Read a DTC string
**
*******************************************************************************/
int ReadDtcs ( FILE *pFile )
{
	fpos_t FilePosition;
	char   String[MAX_STRING_SIZE];
	char   DTCString[MAX_STRING_SIZE];
	int    DTCCount = 0;
	unsigned short DTC_type = 0;
	int    Result = PASS;


	fgetpos ( pFile, &FilePosition );
	// count number of Key lines until delimiter or END_OF_FILE
	while ( fgets ( String, MAX_STRING_SIZE, pFile ) )
	{
		if ( String[0] == '[' )
		{
			break;
		}
		// ignore comment or empty lines
		else if ( String[0] != '\t' &&
		          String[0] != '\n' &&
		          String[0] != ' '  &&
		          String[0] != ';' )
		{
			DTCCount++;
		}
	}
	if ( gpDTC_Ignore_List == NULL )
	{
		if ( (gpDTC_Ignore_List = (unsigned short*) malloc ( sizeof ( short ) * DTCCount )) == NULL )
		{
			Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_PROMPT,
			      "Unable to allocate memory (%d bytes) for gpDTC_Ignore_List.",
			      sizeof ( short ) * DTCCount );
			exit ( FAIL );
		}
	}


	fsetpos ( pFile, &FilePosition );
	gDTC_Ignore_List_Count = 0;
	// get DTCs until delimiter or END_OF_FILE
	while ( gDTC_Ignore_List_Count < DTCCount && fgets ( String, MAX_STRING_SIZE, pFile ) )
	{
		// Echo j1699_config.ini contents to log file
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s",
		      String );

		// ignore comment or empty lines
		if ( String[0] == '\t' ||
		     String[0] == '\n' ||
		     String[0] == ' '  ||
		     String[0] == ';' )
		{
			continue;
		}
		// SECTION name delimits the end of KEY lisr
		else if ( String[0] == '[' )
		{
			break;
		}
		else if ( getKeyName ( String, DTCString ) == PASS )
		{
			switch ( DTCString[0] )
			{
				case 'P':
				case 'p':
					DTC_type = 0x0000;
				break;

				case 'C':
				case 'c':
					DTC_type = 0x4000;
				break;

				case 'B':
				case 'b':
					DTC_type = 0x8000;
				break;

				case 'U':
				case 'u':
					DTC_type = 0xC000;
				break;

				default:
					// Echo j1699_config.ini contents to log file
					Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "Unknown DTC type %s\n",
					      DTCString );
					Result = FAIL;
					continue;
			} // switch
			gpDTC_Ignore_List[gDTC_Ignore_List_Count++] = (DTC_type | (unsigned short) strtol ( &DTCString[1], NULL, 16 ));
		}
		else
		{
			Result = FAIL;
		}
	} // while fgets

	return Result;
}


/*******************************************************************************
**
**  Function:  ReadECUIds
**
**  Purpose:   Read a ECU ID string
**
*******************************************************************************/
int ReadECUIds ( FILE *pFile )
{
	fpos_t FilePosition;
	char   String[MAX_STRING_SIZE];
	char   ECUString[MAX_STRING_SIZE];
	unsigned int  ECUCount = 0;
	int    Result = PASS;


	fgetpos ( pFile, &FilePosition );
	// count number of Key lines until delimiter or END_OF_FILE
	while ( fgets ( String, MAX_STRING_SIZE, pFile ) )
	{
		if ( String[0] == '[' )
		{
			break;
		}
		// ignore comment or empty lines
		else if ( String[0] != '\t' &&
		          String[0] != '\n' &&
		          String[0] != ' '  &&
		          String[0] != ';' )
		{
			ECUCount++;
		}
	}
	if ( gpECU_Ignore_List == NULL )
	{
		if ( (gpECU_Ignore_List = (unsigned int*) malloc ( sizeof ( int ) * ECUCount )) == NULL )
		{
			Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_PROMPT,
			      "Unable to allocate memory (%d bytes) for gpECU_Ignore_List.",
				sizeof(int) * ECUCount);
			exit ( FAIL );

		}
	}


	fsetpos ( pFile, &FilePosition );
	gECU_Ignore_List_Count = 0;
	// get ECU Ids until delimiter or END_OF_FILE
	while ( gECU_Ignore_List_Count < ECUCount && fgets ( String, MAX_STRING_SIZE, pFile ) )
	{
		// Echo j1699_config.ini contents to log file
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s",
		      String );

		// ignore comment or empty lines
		if ( String[0] == '\t' ||
		     String[0] == '\n' ||
		     String[0] == ' '  ||
		     String[0] == ';' )
		{
			continue;
		}
		// SECTION name delimits the end of KEY list
		else if ( String[0] == '[' )
		{
			break;
		}
		else if ( getKeyName ( String, ECUString ) == PASS )
		{
			gpECU_Ignore_List[gECU_Ignore_List_Count++] = (unsigned long) strtol ( &ECUString[0], NULL, 16 );
		}
		else
		{
			Result = FAIL;
		}
	} // while fgets

	return Result;
}
