/*
********************************************************************************
** SAE J1699-3 Test Source Code
**
**  Copyright (C) 2018 EnGenius. http://j1699-3.sourceforge.net/
**
** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**
**  This program is free software; you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation; either version 2 of the License, or
**  (at your option) any later version.
**
**  This program is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with this program; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**
** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**
** This source code, when compiled and used with an SAE J2534-compatible pass
** thru device, is intended to run the tests described in the SAE J1699-3
** document in an automated manner.
**
** This computer program is based upon SAE Technical Report J1699,
** which is provided "AS IS"
**
** See j1699.c for details of how to build and run this test.
**
********************************************************************************
*/
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include "j2534.h"
#include "j1699.h"

#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 *gDTC_Ignore_List = NULL;

unsigned long   gECU_Ignore_List_Count = 0;
unsigned long  *gECU_Ignore_List = NULL;


int SectionLookUp (char *String);
int ReadDtcs   ( FILE *fp );
int ReadECUIds ( FILE *fp );

int getSectionName (char *InputString, char *SectionString);
int getKeyName (char *InputString, char *KeyString);


/*
***************************************************************************************************
** ReadIni - Function to read the initialization file
***************************************************************************************************
*/
void ReadIni ( void )
{
	FILE *FilePointer;
	char String[MAX_STRING_SIZE];
	char SectionString[MAX_STRING_SIZE];
	int IniType;

	// open the ini file for reading, if it exists
	if ( (FilePointer = fopen ( "j1699_config.ini", "rt" )) != NULL )
	{
		/* 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, FilePointer ) )
		{
			/* 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 ( FilePointer );
							break;

						case ECU_IGNORE:
							// Read ECU IDs to Ignore
							ReadECUIds ( FilePointer );
							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 ****" );
	} // if the INI file exists
}


/*
***************************************************************************************************
** getSectionName - Function to 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 *InputString, char *SectionString )
{
	int Result = PASS;
	int InputIndex;
	int SectionIndex = 0;
	int Length = strlen( InputString );


	// 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 && InputString[InputIndex] != ']'; InputIndex++)
	{
		// If standard character, copy it
		if ( InputString[InputIndex] >= '!' && InputString[InputIndex] <= '}' )
		{
			SectionString[SectionIndex++] = InputString[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
	SectionString[SectionIndex] = (char)NULL;

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

	return (Result);
}


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

	// 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 ( InputString[InputIndex] == ' ' ||
		     InputString[InputIndex] == '\t' ||
		     InputString[InputIndex] == '\n' ||
		     InputString[InputIndex] == ';' ||
		     InputString[InputIndex] == '=' )
		{
			break;
		}
		// If standard character, copy it
		else if ( InputString[InputIndex] >= '!' && InputString[InputIndex] <= '}' )
		{
			KeyString[KeyIndex++] = InputString[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
	KeyString[KeyIndex] = (char)NULL;

	return (Result);
}




/*
***************************************************************************************************
** ReadHex - Function to read a hexidecimal string in to an unsigned short
***************************************************************************************************
*/
int getHexValue ( char * InputString, unsigned short * HexNumber)
{
	int Result = PASS;
	int InputIndex;
	int HexIndex = 0;
	int Length = strlen( InputString );
	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 ( InputString[InputIndex] == ' ' ||
		     InputString[InputIndex] == '\t' ||
		     InputString[InputIndex] == '\n' ||
		     InputString[InputIndex] == ';' ||
		     InputString[InputIndex] == '=' )
		{
			break;
		}
	}
	if ( InputString[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 ( InputString[InputIndex] == ' ' ||
		     InputString[InputIndex] == '\t' ||
		     InputString[InputIndex] == '\n' ||
		     InputString[InputIndex] == ';' )
		{
			break;
		}
		else if ( (InputString[InputIndex] >= 'a' && InputString[InputIndex] <= 'f') ||
		          (InputString[InputIndex] >= 'A' && InputString[InputIndex] <= 'F') ||
		          (InputString[InputIndex] >= '0' && InputString[InputIndex] <= '9') )
		{
			HexString[HexIndex++] = InputString[InputIndex];
		}
	}
	// terminate output string
	HexString[HexIndex] = (char)NULL;

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

	return (Result);
}


/*
***************************************************************************************************
** ReadInt - Function to read an number string in to an integer
***************************************************************************************************
*/
int getIntValue ( char * InputString, int * Number )
{
	int Result = PASS;
	int InputIndex;
	int NumIndex = 0;
	int Length = strlen( InputString );
	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 ( InputString[InputIndex] == ' ' ||
		     InputString[InputIndex] == '\t' ||
		     InputString[InputIndex] == '\n' ||
		     InputString[InputIndex] == ';' ||
		     InputString[InputIndex] == '=' )
		{
			break;
		}
	}
	if ( InputString[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 ( InputString[InputIndex] == ' ' ||
		     InputString[InputIndex] == '\t' ||
		     InputString[InputIndex] == '\n' ||
		     InputString[InputIndex] == ';' )
		{
			break;
		}
		else if ( InputString[InputIndex] >= '0' && InputString[InputIndex] <= '9' )
		{
			NumString[NumIndex++] = InputString[InputIndex];
		}
	}
	// terminate output string
	NumString[NumIndex] = (char)NULL;

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

	return (Result);
}


/*
***************************************************************************************************
** ReadInt - Function to read a string
***************************************************************************************************
*/
int getStringValue ( char * InputString, char * OutputString)
{
	int Result = PASS;
	int InputIndex;
	int OutputIndex = 0;
	int Length = strlen( InputString );

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

	if ( InputString[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 ( InputString[InputIndex] == '\t' ||
		     InputString[InputIndex] == '\n' ||
		     InputString[InputIndex] == ';' ||
		     InputString[InputIndex] == '"' )
		{
			break;
		}
		else
		{
			OutputString[OutputIndex++] = InputString[InputIndex];
		}
	}
	if ( InputString[InputIndex] != '"' )
	{
		Log( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		     "String VALUE must end with \".\n" );
		Result = FAIL;
	}

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

	return (Result);
}


/*
***************************************************************************************************
** SectionLookUp - Function to match the input string with a defined SECTION
***************************************************************************************************
*/
int SectionLookUp (char * String)
{
	int Index = 0;
	int Type = 0;
	int Result = FAIL;


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

	return (Type);
}


/*
***************************************************************************************************
** ReadDtc - Function to read a DTC string
***************************************************************************************************
*/
int ReadDtcs ( FILE *fp )
{
	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 ( fp, &FilePosition );
	// count number of Key lines until delimiter or END_OF_FILE
	while ( fgets ( String, MAX_STRING_SIZE, fp ) )
	{
		if ( String[0] == '[' )
		{
			break;
		}
		// ignore comment or empty lines
		else if ( String[0] != '\t' &&
		          String[0] != '\n' &&
		          String[0] != ' '  &&
		          String[0] != ';' )
		{
			DTCCount++;
		}
	}
	if ( gDTC_Ignore_List == NULL )
	{
		gDTC_Ignore_List = (unsigned short *) malloc( sizeof(short) * DTCCount );
	}


	fsetpos ( fp, &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, fp ) )
	{
		/* 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
			gDTC_Ignore_List[gDTC_Ignore_List_Count++] = DTC_type | (unsigned short) strtol (&DTCString[1], NULL, 16);
		}
		else
		{
			Result = FAIL;
		}
	} // while fgets

	return (Result);
}


/*
***************************************************************************************************
** ReadECUIds - Function to read a ECU ID string
***************************************************************************************************
*/
int ReadECUIds ( FILE *fp )
{
	fpos_t FilePosition;
	char String[MAX_STRING_SIZE];
	char ECUString[MAX_STRING_SIZE];
	unsigned int  ECUCount = 0;
	int  Result = PASS;


	fgetpos ( fp, &FilePosition );
	// count number of Key lines until delimiter or END_OF_FILE
	while ( fgets ( String, MAX_STRING_SIZE, fp ) )
	{
		if ( String[0] == '[' )
		{
			break;
		}
		// ignore comment or empty lines
		else if ( String[0] != '\t' &&
		          String[0] != '\n' &&
		          String[0] != ' '  &&
		          String[0] != ';' )
		{
			ECUCount++;
		}
	}
	if ( gECU_Ignore_List == NULL )
	{
		gECU_Ignore_List = (unsigned long *) malloc( sizeof(short) * ECUCount );
	}


	fsetpos ( fp, &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, fp ) )
	{
		/* 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 )
		{
			gECU_Ignore_List[gECU_Ignore_List_Count++] = (unsigned long) strtol (&ECUString[0], NULL, 16);
		}
		else
		{
			Result = FAIL;
		}
	} // while fgets

	return (Result);
}
