#include <stdio.h>
#include <stdlib.h>
#include "datedit.h"

void EndianSwap(unsigned int *x)
{
    *x = (*x>>24) | ((*x<<8) & 0x00FF0000) | ((*x>>8) & 0x0000FF00) | (*x<<24);
}

int ReadDatInfo(DATInfo *info)
{
	FILE *datFile, *wadFile;
	unsigned int i;
	
	initCRC32(); // Make CRC table
	
	printf("Reading DAT file...\n");
	
	// Open files
	datFile = fopen(info->datFileName, "rb");
	if (datFile == NULL)
	{
		printf("FATAL ERROR: Can't open the DAT file (%s)!\n", info->datFileName);
		exit(-1);
	}

	wadFile = fopen(info->wadFileName, "rb");
	if (wadFile == NULL)
	{
		printf("FATAL ERROR: Can't open the WAD file (%s)!\n", info->wadFileName);
		exit(-1);
	}

	// Read header
	fread_be(&info->header.numFiles, 4, 1, datFile);
	fread_be(&info->header.usedSize, 4, 1, datFile);

	// Read entries
	info->entries = calloc(info->header.numFiles, sizeof(DATEntry));
	if (info->entries == NULL)
	{
		printf("FATAL ERROR: Can't get enough memory for the DAT entries!\n");
		exit(-1);
	}

	for (i = 0; i < info->header.numFiles; i++)
	{
		fread_be(&info->entries[i].qbKey, 4, 1, datFile);
		fread_be(&info->entries[i].offset, 4, 1, datFile);
		fread_be(&info->entries[i].fileSize, 4, 1, datFile);
		fread(&info->entries[i].reserved, 8, 1, datFile);

		// Read data
		info->entries[i].fileData = (char *) malloc(info->entries[i].fileSize);
		if (info->entries[i].fileData == NULL)
		{
			printf("FATAL ERROR: Can't get enough memory for the DAT entries!\n");
			exit(-1);
		}

		fseek(wadFile, info->entries[i].offset, SEEK_SET);

		fread(info->entries[i].fileData, info->entries[i].fileSize, 1, wadFile);

		// Get filename
		memcpy(info->entries[i].fileNameInFsb, info->entries[i].fileData + 26, FMOD_FNAME_SIZE);
		info->entries[i].fileNameInFsb[FMOD_FNAME_SIZE] = '\0';
	}

	fclose(datFile);
	fclose(wadFile);

	printf("\n");
	
	return 0;
}

// Clean all info
int FreeDatInfo (DATInfo *info)
{
	unsigned int i;

	for (i = 0; i < info->header.numFiles; i++)
		free(info->entries[i].fileData);

	free(info->entries);

	info->header.numFiles = 0;
	info->header.usedSize = 0;

	info->datFileName[0] = '\0';
	info->wadFileName[0] = '\0';

	return 0;
}

int MakeAudioList(DATInfo *info)
{
	unsigned int i;

	printf("DAT file: %s\nWAD file: %s\n\n", info->datFileName, info->wadFileName);
	printf("Number of files: %i\n", info->header.numFiles);
	printf("Used filesize: %i\n\n", info->header.usedSize);
	
	printf("Files:\n");
	for (i = 0; i < info->header.numFiles; i++)
	{
        printf("\tFile %i:\n", i);
        
		printf("\t\tFile name (in FSB): %s\n", info->entries[i].fileNameInFsb);
		printf("\t\tIdentifier (QBKey): %.8X\n", info->entries[i].qbKey);
		printf("\t\tOffset: %u\n", info->entries[i].offset);
		printf("\t\tFilesize: %u\n", info->entries[i].fileSize);
	}

	printf("\n");
	return 0;
}

int MakeAudioListSimple(DATInfo *info)
{
	unsigned int i;
	
	for (i = 0; i < info->header.numFiles; i++)
		printf("%s\n", info->entries[i].fileNameInFsb);
	
    printf("\n");
	return 0;
}

int CheckFileIdExists(DATInfo *info, unsigned int fileId)
{
    if (fileId >= info->header.numFiles)
    {
       printf("ERROR: The specified ID (%u) doesn't exist!\n", fileId);
       return -1;
    }
    
    return 0;
}

int QbKeyStrToFileId(DATInfo *info, char *qbKeyStr, int *extraParam)
{
    unsigned int qbKey, i;
    
    qbKey = makeQBKey(qbKeyStr);
    
    for (i = 0; i < info->header.numFiles; i++)
    {
        if (info->entries[i].qbKey == qbKey)
        {
           *extraParam = i;
           return 0;
        }
    }
    
    printf("ERROR: No file matches with %s (%.8X)!\n", qbKeyStr, qbKey);
    return -1;
}

int ExtractAllFiles(DATInfo *info)
{
    unsigned int i;
    
    for (i = 0; i < info->header.numFiles; i++)
        ExtractWadFile(info, i, "*default*");
    
    return 0;
}

int ExtractWadFile(DATInfo *info, unsigned int fileId, char *outFileName)
{
	FILE *outFile;
	
	printf("Extracting file %u (%s)...\n", fileId, info->entries[fileId].fileNameInFsb);

    // Open output file    
    if (strcmp(outFileName, "*default*") == 0)
    {
       outFileName = info->entries[fileId].fileNameInFsb;
    }
    
	outFile = fopen(outFileName, "wb");
	if (outFile == NULL)
	{
		printf("ERROR: Can't open the specified file to extract (%s)", outFileName);
		return -1;
	}
	
	// Write
    fwrite(info->entries[fileId].fileData, info->entries[fileId].fileSize, 1, outFile);

    // Close
    fclose(outFile);
   	
	return 0;
}

int ReplaceWadItem(DATInfo *info, unsigned int fileId, char *addFileName)
{
	FILE *fsbFile;
	char *fsbData, errorResponse, newFileNameInFsb[FMOD_FNAME_SIZE + 1];
	unsigned int fsbSize, movementStart, i, newUsedSize, tempSize1, tempSize2, movement, *fsbDataInt, flags, newVersion;
    
    printf("Replacing file %u (%s)...\n", fileId, info->entries[fileId].fileNameInFsb);
    
	// Open FSB
	fsbFile = fopen(addFileName, "rb");
	if (fsbFile == NULL)
	{
		printf("ERROR: Can't open the specified file to add it in the DAT!\n");
		return -1;
	}

	// Get size
	fseek(fsbFile, 0, SEEK_END);
	fsbSize = ftell(fsbFile);
	fseek(fsbFile, 0, SEEK_SET);

	// Copy to memory
	fsbData = malloc(fsbSize);
	if (fsbData == NULL)
	{
		printf("ERROR: Can't get enough memory!\n");
		return -1;
	}

	fread(fsbData, fsbSize, 1, fsbFile);
	fsbDataInt = (int *) fsbData; // To read data as int...

	fclose(fsbFile); // Close file
	// Calculate
	tempSize1 = fsbSize;
	tempSize2 = info->entries[fileId].fileSize;
	
	while ((tempSize1 % FSB_PADDING) != 0)
		tempSize1++;

	while ((tempSize2 % FSB_PADDING) != 0)
		tempSize2++;

	movement = tempSize1 - tempSize2;
	movementStart = info->entries[fileId].offset + info->entries[fileId].fileSize;

	while ((movementStart % FSB_PADDING) != 0)
		movementStart++;

	// Various checks...
	newVersion = fsbDataInt[4]; // Get version (yes, bad way >_>)

	if (GetLowShort(newVersion) != 1 || GetHighShort(newVersion) != 3) // Check version
	{
		printf("\nWARNING: This program is designed to work with FSB v3.1, and you're trying to use a FSB %i.%i.\nDo you really want to do that?\n", GetHighShort(newVersion), GetLowShort(newVersion));
		
		while (1)
		{
            printf("Answer with 'y' (yes) or 's' (skip file): ");         
            scanf("%c", &errorResponse);
    		fflush(stdin);
    		
    		errorResponse = tolower(errorResponse);
    		
    		if (errorResponse == 'y')
    		{
                break;
            }
            else if (errorResponse == 's')
            {
                 free(fsbData);
                 return -1;
            }                                
        }
        
        printf("\n");
	}

	memcpy(newFileNameInFsb, fsbData + 26, FMOD_FNAME_SIZE); // Get name
	newFileNameInFsb[FMOD_FNAME_SIZE] = '\0';

	if (strcmp(info->entries[fileId].fileNameInFsb, newFileNameInFsb) != 0) // Check name
	{
		printf("\nWARNING: The internal filenames of the original and modified FSB file are different:\n\nOriginal: %s\nModified: %s\n\nDo you want to keep the orignal name?\n", info->entries[fileId].fileNameInFsb, newFileNameInFsb);

		while (1)
		{
            printf("Answer with 'y' (yes) or 'n' (no): ");         
            scanf("%c", &errorResponse);
    		fflush(stdin);
    		
    		errorResponse = tolower(errorResponse);
    		
    		if (errorResponse == 'y')
    		{
   			   // Copy new filename from memory to file
			   memcpy(fsbData + 26, info->entries[fileId].fileNameInFsb, FMOD_FNAME_SIZE);
			   
			   break;
            }
            else if (errorResponse == 'n')
            {
                 // Copy new filename from file to memory
    			memcpy(info->entries[fileId].fileNameInFsb, fsbData + 26, FMOD_FNAME_SIZE);
    			info->entries[fileId].fileNameInFsb[FMOD_FNAME_SIZE] = '\0';
    			
    			break;
            }                                   
        }
	}

	// Add FSOUND_LOADMEMORYIOP
	flags = fsbDataInt[18];
	flags |= FSOUND_LOADMEMORYIOP;
	fsbDataInt[18] = flags;

	// Swap new and old files
	free(info->entries[fileId].fileData);
	info->entries[fileId].fileData = fsbData;
	info->entries[fileId].fileSize = fsbSize;

	// Repoint entries and get new used size
	newUsedSize = 0;

	for (i = 0; i < info->header.numFiles; i++)
	{
		if ((info->entries[i].offset + info->entries[i].fileSize) > newUsedSize) // Calculate new used size
		{
			newUsedSize = (info->entries[i].offset + info->entries[i].fileSize);
		}

		if (i == fileId) // Skip current file
			continue;

		if (info->entries[i].offset >= movementStart) // Delay files
		{
			info->entries[i].offset += movement;
		}
	}

	info->header.usedSize = newUsedSize;

	
	return 0;
}

int WriteOut(DATInfo *info)
{
	FILE *datFile, *wadFile;
	unsigned int i, j, findOffset;
	
	printf("Saving DAT/WAD files...\n");
	
	// Write DAT
	datFile = fopen(info->datFileName, "wb");
	if (datFile == NULL)
	{               
		printf("FATAL ERROR: Can't open the DAT file!\n");
		exit(-1);
	}
	
	// File header
	fwrite_be(&info->header.numFiles, 4, 1, datFile);
	fwrite_be(&info->header.usedSize, 4, 1, datFile);

	// Entries
	for (i = 0; i < info->header.numFiles; i++)
	{
		fwrite_be(&info->entries[i].qbKey, 4, 1, datFile);
		fwrite_be(&info->entries[i].offset, 4, 1, datFile);
		fwrite_be(&info->entries[i].fileSize, 4, 1, datFile);
		fwrite(&info->entries[i].reserved, 1, 8, datFile);
	}

	fclose(datFile);
	
	// Write WAD
	wadFile = fopen(info->wadFileName, "wb");
	if (wadFile == NULL)
	{
		printf("FATAL ERROR: Can't open the WAD file!");
		exit(-1);
	}

	// Write FSB files to WAD
	findOffset = 0;
	for (i = 0; i < info->header.numFiles; i++)
	{
		// Bruteforce matching FTW >_>
		for (j = 0; j < info->header.numFiles; j++)
		{
			if (findOffset == info->entries[j].offset)
			{
				findOffset += info->entries[j].fileSize;

				// Write FSB
				fwrite(info->entries[j].fileData, info->entries[j].fileSize, 1, wadFile);

				while ((findOffset % FSB_PADDING) != 0)
				{
					fputc(0, wadFile);
					findOffset++;
				}

				break;
			}
		}

		// The offset can't be found = ERROR
		if (j == info->header.numFiles)
		{
			printf("FATAL ERROR: Internal error when writting the WAD file!\n");
			exit(-1);
		}
	}

	fclose(wadFile);
	
	return 0;
}

size_t fread_be (void *ptr, size_t size, size_t count, FILE *stream)
{
	size_t retVal;
	int *integerIn;

	if ((size * count) == 4)
	{
		retVal = fread(ptr, size, count, stream);

		integerIn = (int *) ptr;
		EndianSwap(integerIn);
	}
	else
	{
		retVal = fread(ptr, size, count, stream);
	}

	return retVal;
}

size_t fwrite_be (const void *ptr, size_t size, size_t count, FILE *stream)
{
	size_t retVal;
	int *integerOut;

	if ((size * count) == 4)
	{
		integerOut = (int *) ptr;
		EndianSwap(integerOut);

		retVal = fwrite(integerOut, size, count, stream);

		EndianSwap(integerOut);
	}
	else
	{
		retVal = fwrite(ptr, size, count, stream);
	}

	return retVal;
}
