#include "ips.h"

int8_t IPSOpenPatch(struct IPSPatch *ips, const char *fileName)
{
	char   header[5];
	size_t nRead;
	
	ips->patch = fopen(fileName, "rb");
	if(ips->patch == NULL)
	{
		return IPS_ERROR;
	}
	
	fseek(ips->patch, 0, SEEK_SET);
	
	nRead = fread(header, 1, 5, ips->patch);
	if(nRead != 5)
	{
		return IPS_ERROR_FILE_TYPE;
	}
	
	if((header[0] != 'P') ||
	   (header[1] != 'A') ||
	   (header[2] != 'T') ||
	   (header[3] != 'C') ||
	   (header[4] != 'H'))
	{
		return IPS_ERROR_FILE_TYPE;
	}
	
	return IPS_OK;
}

int8_t IPSOpenInOUT(struct IPSPatch *ips,
                    const char *inName, const char *outName)
{
	int     err;
	size_t  nRead;
	long    nTotal;
	uint8_t buffer[256];
		
	if(strcmp(inName, outName) == 0)
	{
		char     tmpFileName[64];
		uint32_t fileExtOffset;
		
		strncpy(tmpFileName, inName, 52);
		
		fileExtOffset = strlen(inName);
		if(fileExtOffset > 52)
		{
			fileExtOffset = 52;
		}
		
		tmpFileName[fileExtOffset++] = '.';
		tmpFileName[fileExtOffset++] = 's';
		tmpFileName[fileExtOffset++] = 'a';
		tmpFileName[fileExtOffset++] = 'v';
		tmpFileName[fileExtOffset++] = '\0';
		
		err = rename(inName, tmpFileName);
		if(err < 0)
		{
			return IPS_ERROR_SAVE;
		}
		
		ips->source = fopen(tmpFileName, "rb");
	}
	else
	{
		ips->source = fopen(inName, "rb");
	}
	
	if(ips->source == NULL)
	{
		return IPS_ERROR;
	}
	fseek(ips->source, 0, SEEK_SET);
	
	ips->destination = fopen(outName, "wb");
	if(ips->destination == NULL)
	{
		return IPS_ERROR;
	}
	
	fseek(ips->source, 0, SEEK_END);
	nTotal  = ftell(ips->source);
	fseek(ips->source, 0, SEEK_SET);
	nTotal -= ftell(ips->source);
	
	ips->sourceSize = (uint32_t)nTotal;
	if(nTotal <= 0)
	{
		return IPS_ERROR;
	}

	fseek(ips->destination, 0, SEEK_SET);
	for(nTotal=0; nTotal<ips->sourceSize; nTotal+=nRead)
	{
		nRead = fread(buffer, 1, 256, ips->source);
		fwrite(buffer, 1, nRead, ips->destination);
	}		
	fflush(ips->destination);

	fseek(ips->destination, 0, SEEK_SET);
	
	return IPS_OK;
}

int8_t IPSOpen(struct IPSPatch *ips,
               const char *patchName,
               const char *inName,
               const char *outName)
{
	int8_t err;
	
	ips->source      = NULL;
	ips->destination = NULL;
	ips->patch       = NULL;
	
	ips->record.offset = 0;
	ips->record.size   = 0;
	
	err = IPSOpenPatch(ips, patchName);
	if(err != IPS_OK)
	{
		goto error;
	}
	
	err = IPSOpenInOUT(ips, inName, outName);
	if(err != IPS_OK)
	{
		goto error;
	}
		
	return IPS_OK;
error:
	if(ips->patch != NULL)
	{
		fclose(ips->patch);
	}
	if(ips->source != NULL)
	{
		fclose(ips->source);
	}
	if(ips->destination != NULL)
	{
		fclose(ips->destination);
	}
	ips->source      = NULL;
	ips->destination = NULL;
	ips->patch       = NULL;

	return IPS_ERROR;
}

void IPSClose(struct IPSPatch *ips)
{
	fclose(ips->patch);
	fclose(ips->source);
	fclose(ips->destination);
}

int8_t IPSReadRecord(struct IPSPatch *ips)
{
	uint8_t buffer[5];
	size_t  nRead;
	
	nRead = fread(buffer, 1, 3, ips->patch);
	if(nRead < 3)
	{
		return IPS_ERROR_READ;
	}
	if((buffer[0] == 'E') &&
	   (buffer[1] == 'O') &&
	   (buffer[2] == 'F'))
	{
		return IPS_PATCH_END;
	}
	
	ips->record.offset = (buffer[0] << 16) | 
	                     (buffer[1] <<  8) |
	                     (buffer[2]      );
	
	nRead = fread(buffer, 1, 2, ips->patch);
	if(nRead < 2)
	{
		return IPS_ERROR_READ;
	}
	ips->record.size = (buffer[0] << 8) | 
	                   (buffer[1]     );

	if(ips->record.size == 0)
	{
		ips->record.offset |= IPS_RECORD_RLE << 24;
		nRead = fread(buffer, 1, 2, ips->patch);
		if(nRead < 2)
		{
			return IPS_ERROR_READ;
		}
		ips->record.size = (buffer[0] << 8) | 
		                   (buffer[1]     );
		
		nRead = fread(&(ips->record.rleData), 1, 1, ips->patch);
		if(nRead < 1)
		{
			return IPS_ERROR_READ;
		}
	}
	
	return IPS_OK;
}

int8_t IPSProcessRecord (struct IPSPatch *ips)
{
	uint32_t i;
	uint8_t  byte;

	fseek(ips->destination, IPS_RECORD_OFFSET(ips->record), SEEK_SET);
	
	if(IPS_RECORD_INFO(ips->record) == IPS_RECORD_RLE)
	{
		for(i=0; i<ips->record.size; ++i)
		{
			fwrite(&(ips->record.rleData), 1, 1, ips->destination);
		}
		return IPS_OK;
	}
	
	for(i=0; i<ips->record.size; ++i)
	{
		fread (&byte, 1, 1, ips->patch);
		fwrite(&byte, 1, 1, ips->destination);
	}
	
	return IPS_OK;
}
