#include <stdio.h>
#include <windows.h>
#include "identifiers.h"
#include "qbtypes.h"
#include "songlistedit.h"
#include "qbkey32.h"
#include "write_endian.h"

 /* This program is free software. It comes without any warranty, to
 * the extent permitted by applicable law. You can redistribute it
 * and/or modify it under the terms of the Do What The Fuck You Want
 * To Public License, Version 2, as published by Sam Hocevar. See
 * http://sam.zoy.org/wtfpl/COPYING for more details. */

/* Contains instructuions to read and parse the songlist QB file,
** and some functions for the edit dialog */

FILE *OpenSongList(HWND hDlg, HINSTANCE hInstance, char *songlistName)
{
	OPENFILENAME openQB;
	FILE *qbFile;

	if (songlistName[0] != '\0')
	{
		if (MessageBox(hDlg, "It seems that you've already opened a QB file. Do you want to load another?", "Warning", MB_YESNO | MB_ICONWARNING) == IDNO)
		{
			return NULL;
		}
	}

	#if BIG_ENDIAN == 0
		strcpy(songlistName, "songlist.qb.ps2");
	#elif BIG_ENDIAN == 1
		strcpy(songlistName, "songlist.qb.ngc");
	#endif

	openQB.lStructSize = sizeof(OPENFILENAME);
	openQB.hwndOwner = hDlg;
	openQB.hInstance = hInstance;
	openQB.lpstrFilter = "GH3 Database (QB)\0*.qb.ps2;*.qb.ngc;\0All files\0*.*;\0\0";
	openQB.lpstrCustomFilter = NULL;
	openQB.nMaxCustFilter = 0; // Ignored
	openQB.nFilterIndex = 1;
	openQB.lpstrFile = songlistName; // ???
	openQB.nMaxFile = DEFAULT_BUFFER;
	openQB.lpstrFileTitle = NULL;
	openQB.nMaxFileTitle = 0; // Ignored
	openQB.lpstrInitialDir = NULL; // ???
	openQB.lpstrTitle = "Select your songlist.qb.ps2 file...";
	openQB.Flags = 0;
	openQB.nFileOffset = 0;
	openQB.nFileExtension = 0;
	openQB.lpstrDefExt = "qb";
	openQB.lCustData = 0;
	openQB.lpfnHook = NULL; // Ignored
	openQB.lpTemplateName = NULL; // Ignored

	if (GetOpenFileName(&openQB) == 0)
	{
		songlistName[0] = '\0';
		return NULL;
	}

	qbFile = fopen(songlistName, "rb");
	if (qbFile == NULL)
	{
		BOX("Can't open the specified QB file!");
		return NULL;
	}

	return qbFile;
}

int parseStruct(HWND hDlg, FILE *qbFile, unsigned int sectionId, int action, QBReturnInfo *qbInfo)
{

	unsigned int pointerNext, type, entryName;
	QBSongInfo *currSong;
	char buffer[DEFAULT_BUFFER];

	/*
	sectionId --> The ID of the section. Received from the QB parser.
	action --> The action:
		0 --> Nothing
		1 --> Get song info to qbInfo
	qbInfo --> The structure to put things inside...
	*/

	/* Get memory for the new song is action == 1 */
	if (action == 1)
	{
		// Add one entry to the list of songs
		qbInfo->songs = (QBSongInfo **) realloc(qbInfo->songs, (qbInfo->numSongs + 1) * sizeof(QBSongInfo *));
		if (qbInfo->songs == NULL)
		{
			BOX("Can't get enough memory!");
			return -1;
		}

		// Get size for one song
		qbInfo->songs[qbInfo->numSongs] = (QBSongInfo *) malloc(sizeof(QBSongInfo));
		if (qbInfo->songs[qbInfo->numSongs] == NULL)
		{
			BOX("Can't get enough memory!");
			return -1;
		}

		// Make a pointer to the current structure to make code cleaner
		currSong = qbInfo->songs[qbInfo->numSongs];

		// Fill with default info (Long, long code!)
		currSong->internalFlags = 0;
		currSong->songNameChecksum = 0x00000000;
		currSong->songInternalName[0] = '\0';
		currSong->songName[0] = '\0';
		currSong->songAuthor[0] = '\0';
		currSong->songYear[0] = '\0';
		currSong->artistTextKey = 0x00000000;
		currSong->artistText[0] = '\0';
		currSong->isOriginalArtist = 0;
		currSong->startupScript = 0x00000000;
		currSong->gameVersion = 0x00000000;
		currSong->useLeaderboard = 0;
		currSong->gemOffset = 0;
		currSong->fretbarOffset = 0;
		currSong->inputOffset = 0;
		currSong->bossId = 0x00000000;
		currSong->forcedBassist[0] = '\0';
		currSong->singerType = 0x00000000;
		currSong->useKeyboard = 0x00000000;
		currSong->exitScript = 0x00000000;
		currSong->nullParam = 0x00000000;
		currSong->countOff[0] = '\0';
		currSong->useRhythmTrack = 0;
		currSong->bandVolume = 0.0;
		currSong->guitarVolume = 0.0;
		currSong->hammerOnMeasureScale = 1.0;
	}

	/* Parse structure... */
	freadBe(&pointerNext, 4, 1, qbFile);

	while (pointerNext != 0)
	{
		fseek(qbFile, pointerNext, SEEK_SET);

		fread(&type, 4, 1, qbFile);
		freadBe(&entryName, 4, 1, qbFile);

		if (type == STRUCT_CRC || type == STRUCT_CRC_BI) // CRC
		{
			unsigned int entryValue;
			freadBe(&entryValue, 4, 1, qbFile);
			freadBe(&pointerNext, 4, 1, qbFile);
			
			// Get songlist variables
			if (action == 1)
			{
				if (entryName == makeQBKey("checksum"))
				{
					currSong->songNameChecksum = entryValue;
					currSong->internalFlags |= IS_SONGNAMECHECKSUM;
				}
				else if (entryName == makeQBKey("artist_text"))
				{
					// Apply a small hack that makes Talk Dirty to Me to another string...
					if (entryValue == makeQBKey("string_ps2_as_made_famous_by"))
						currSong->artistTextKey = makeQBKey("artist_text_as_made_famous_by");
					else
						currSong->artistTextKey = entryValue;

					currSong->internalFlags |= IS_ARTISTTEXTKEY;
				}
				else if (entryName == makeQBKey("version"))
				{
					currSong->gameVersion = entryValue;
					currSong->internalFlags |= IS_GAMEVERSION;
				}
				else if (entryName == makeQBKey("singer"))
				{
					currSong->singerType = entryValue;
					currSong->internalFlags |= IS_SINGERTYPE;
				}
				else if (entryName == makeQBKey("keyboard"))
				{
					currSong->useKeyboard = entryValue;
					currSong->internalFlags |= IS_USEKEYBOARD;
				}
				else if (entryName == makeQBKey("boss"))
				{
					currSong->bossId = entryValue;
					currSong->internalFlags |= IS_BOSSID;
				}
				else if (entryName == makeQBKey("startup_script"))
				{
					currSong->startupScript = entryValue;
					currSong->internalFlags |= IS_STARTUPSCRIPT;
				}
				else if (entryName == makeQBKey("exit_script"))
				{
					currSong->exitScript = entryValue;
					currSong->internalFlags |= IS_EXITSCRIPT;
				}
				else if (entryName == 0x00000000) // ???
				{
					currSong->nullParam = entryValue;
					currSong->internalFlags |= IS_NULLPARAM;
				}
				else
				{
					sprintf(buffer, "CRC (%.8X) error: %.8X > %.8X", type, entryName, entryValue);
					BOX(buffer);
				}
			}
						
		}
		else if (type == STRUCT_INTEGER) // Integer
		{
			int entryValue;
			freadBe(&entryValue, 4, 1, qbFile);
			freadBe(&pointerNext, 4, 1, qbFile);

			// Get songlist variables
			if (action == 1)
			{

				if (entryName == makeQBKey("original_artist"))
				{
					currSong->isOriginalArtist = entryValue;
					currSong->internalFlags |= IS_ISORIGINALARTIST;
				}
				else if (entryName == makeQBKey("leaderboard"))
				{
					currSong->useLeaderboard = entryValue;
					currSong->internalFlags |= IS_USELEADERBOARD;
				}
				else if (entryName == makeQBKey("gem_offset"))
				{
					currSong->gemOffset = entryValue;
					currSong->internalFlags |= IS_GEMOFFSET;
				}
				else if (entryName == makeQBKey("fretbar_offset"))
				{
					currSong->fretbarOffset = entryValue;
					currSong->internalFlags |= IS_FRETBAROFFSET;
				}
				else if (entryName == makeQBKey("input_offset"))
				{
					currSong->inputOffset = entryValue;
					currSong->internalFlags |= IS_INPUTOFFSET;
				}
				else if (entryName == makeQBKey("rhythm_track"))
				{
					currSong->useRhythmTrack = entryValue;
					currSong->internalFlags |= IS_USERHYTHMTRACK;
				}
				else
				{
					sprintf(buffer, "Integer error: %.8X > %i", entryName, entryValue);
					BOX(buffer);
				}
			}
		}
		else if (type == STRUCT_STRING) // String
		{
			int pointerString;
			char string[DEFAULT_BUFFER];

			freadBe(&pointerString, 4, 1, qbFile);
			freadBe(&pointerNext, 4, 1, qbFile);

			fseek(qbFile, pointerString, SEEK_SET);

			getString(string, qbFile);

			// Get songlist variables
			if (action == 1)
			{
				if (entryName == makeQBKey("name"))
				{
					strcpy(currSong->songInternalName, string);
					currSong->internalFlags |= IS_SONGINTERNALNAME;
				}
				else if (entryName == makeQBKey("title"))
				{
					strcpy(currSong->songName, string);
					currSong->internalFlags |= IS_SONGNAME;
				}
				else if (entryName == makeQBKey("artist"))
				{
					strcpy(currSong->songAuthor, string);
					currSong->internalFlags |= IS_SONGAUTHOR;
				}
				else if (entryName == makeQBKey("year"))
				{
					strcpy(currSong->songYear, string);
					currSong->internalFlags |= IS_SONGYEAR;
				}
				else if (entryName == makeQBKey("artist_text"))
				{
					strcpy(currSong->artistText, string);
					currSong->internalFlags |= IS_ARTISTTEXT;
				}
				else if (entryName == makeQBKey("countoff"))
				{
					strcpy(currSong->countOff, string);
					currSong->internalFlags |= IS_COUNTOFF;
				}
				else if (entryName == makeQBKey("bassist"))
				{
					strcpy(currSong->forcedBassist, string);
					currSong->internalFlags |= IS_FORCEDBASSIST;
				}
				else
				{
					sprintf(buffer, "String error: %.8X > %s", entryName, string);
					BOX(buffer);
				}
			}
		}
		else if (type == STRUCT_SUBITEM) // Sub-struct
		{
			unsigned int pointerStruct;
			freadBe(&pointerStruct, 4, 1, qbFile);
			freadBe(&pointerNext, 4, 1, qbFile);

			fseek(qbFile, pointerStruct + 4, SEEK_SET);

			if (sectionId == makeQBKey("permanent_songlist_props"))
				parseStruct(hDlg, qbFile, sectionId, 1, qbInfo);
			else
				parseStruct(hDlg, qbFile, sectionId, 0, qbInfo);
		}
		else if (type == STRUCT_FLOAT) // Float
		{
			float entryValue;
			freadBe(&entryValue, 4, 1, qbFile);
			freadBe(&pointerNext, 4, 1, qbFile);

			if (action == 1)
			{
				if (entryName == makeQBKey("band_playback_volume"))
				{
					currSong->bandVolume = entryValue;
					currSong->internalFlags |= IS_BANDVOLUME;
				}
				else if (entryName == makeQBKey("guitar_playback_volume"))
				{
					currSong->guitarVolume = entryValue;
					currSong->internalFlags |= IS_GUITARVOLUME;
				}
				else if (entryName == makeQBKey("hammer_on_measure_scale"))
				{
					currSong->hammerOnMeasureScale = entryValue;
					currSong->internalFlags |= IS_HAMMERONMEASURESCALE;
				}
				else
				{
					sprintf(buffer, "Float error: %.8X > %f", entryName, entryValue);
					BOX(buffer);
				}
			}

		}
		else // Not known
		{
			sprintf(buffer, "Unknown struct type %.8X (Name %.8X)!", type, entryName);
			BOX(buffer);
			break;
		}
	}

	// Add one new song...
	if (action == 1)
		qbInfo->numSongs++;

	return 0;
}

void getString(char *string, FILE *qbFile)
{
	int i;
				
	for (i = 0; ; i++)
	{
		string[i] = fgetc(qbFile);
		if (string[i] == '\0')
		{
			break;
		}
	}

	while ((ftell(qbFile) % 4) != 0)
	{
		fseek(qbFile, 1, SEEK_CUR);
	}
}

QBReturnInfo *ParseQB(HWND hDlg, FILE *qbFile)
{
	QBHeader header;
	QBReturnInfo *qbInfo;
	unsigned int type, currentSection;
	char buffer[DEFAULT_BUFFER];

	// Read QB
	freadBe(&header, 4, 2, qbFile);
	fseek(qbFile, 20, SEEK_CUR);

	// Get a pointer to the return info...
	qbInfo = (QBReturnInfo *) calloc(1, sizeof(QBReturnInfo));
	if (qbInfo == NULL)
	{
		BOX("Can't get enough memory for a QBReturnInfo structure!");
		return NULL;
	}

	// Fill with empty values
	qbInfo->textBy[0] = '\0';
	qbInfo->textAsMadeFamousBy[0] = '\0';
	qbInfo->numSongIdentifiers = 0;
	qbInfo->songIdentifiers = NULL;
	qbInfo->numSongs = 0;
	qbInfo->songs = NULL;
	qbInfo->numScripts = 0;
	qbInfo->scripts = NULL;

	// Parse QB
	while (ftell(qbFile) < header.fileSize)
	{
		fread(&type, 4, 1, qbFile);

		if (type == SECTION_ARRAY || type == SECTION_STRUCT_BI || type == SECTION_STRING || type == SECTION_SCRIPT) // Section types
		{
			int sectionId, fileId, pointerStart, reserved;

			freadBe(&sectionId, 4, 1, qbFile);
			freadBe(&fileId, 4, 1, qbFile);
			freadBe(&pointerStart, 4, 1, qbFile);
			freadBe(&reserved, 4, 1, qbFile);
			
			currentSection = sectionId;

			fseek(qbFile, pointerStart, SEEK_SET);

			if (type == SECTION_STRING)
			{
				char string[DEFAULT_BUFFER];
				getString(string, qbFile);

				// Get useful to the return structure...
				if (currentSection == makeQBKey("artist_text_by"))
					strcpy(qbInfo->textBy, string);
				else if (currentSection == makeQBKey("artist_text_as_made_famous_by"))
					strcpy(qbInfo->textAsMadeFamousBy, string);
			}
			else if (type == SECTION_SCRIPT)
			{
				ScriptInfo *currScript;

				/* Set up memory */
				qbInfo->scripts = (ScriptInfo **) realloc(qbInfo->scripts, (qbInfo->numScripts + 1) * sizeof(ScriptInfo *));
				if (qbInfo->scripts == NULL)
				{
					BOX("Can't get enough memory for the scripts!");
					return NULL;
				}

				qbInfo->scripts[qbInfo->numScripts] = (ScriptInfo *) malloc(sizeof(ScriptInfo));
				if (qbInfo->scripts[qbInfo->numScripts] == NULL)
				{
					BOX("Can't get enough memory for the scripts!");
					return NULL;
				}

				currScript = qbInfo->scripts[qbInfo->numScripts]; // Quick pointer

				// Fill structure...
				currScript->sectionName = sectionId;

				freadBe(&currScript->unknownNumber, 4, 1, qbFile);
				freadBe(&currScript->uncompressedSize, 4, 1, qbFile);
				freadBe(&currScript->compressedSize, 4, 1, qbFile);

				// Get script data
				currScript->scriptData = (char *) malloc(currScript->compressedSize);
				if (currScript->scriptData == NULL)
				{
					BOX("Can't get enough memory for the scripts!");
					return NULL;
				}

				fread(currScript->scriptData, currScript->compressedSize, 1, qbFile);
				
				qbInfo->numScripts++;

				// Padding...
				while ((ftell(qbFile) % 4) != 0)
					fseek(qbFile, 1, SEEK_CUR);


			}
		}
		else if (type == ARRAY_QBKEY) // Array types
		{
			int numEntries, pointerStart;
			unsigned int *values;

			freadBe(&numEntries, 4, 1, qbFile);
			if (numEntries > 1)
			{
				freadBe(&pointerStart, 4, 1, qbFile);
				fseek(qbFile, pointerStart, SEEK_SET);
			}

			// Read values
			values = (unsigned int *) malloc(numEntries * 4);
			if (values == NULL)
			{
				BOX("Can't get enough memory!");
				return NULL;
			}

			freadBe(values, 4, numEntries, qbFile);

			if (currentSection == makeQBKey("gh3_songlist"))
			{
				qbInfo->numSongIdentifiers = numEntries;
				qbInfo->songIdentifiers = values;
			}
			else
				free(values);
		}
		else if (type == STRUCT_INIT) // Structure
		{
			parseStruct(hDlg, qbFile, currentSection, 0, qbInfo);
		}
		else if (type == ARRAY_VECTOR) // Vector (used also as NULL array)
		{
			float float1, float2;
			freadBe(&float1, 4, 1, qbFile);
			freadBe(&float2, 4, 1, qbFile);
		}
		else
		{
			sprintf(buffer, "Unknown type %.8X!", type);
			BOX(buffer);
			break;
		}
	}

	return qbInfo;
}

void makeListbox(HWND hDlg, QBReturnInfo *qbInfo)
{
	int i;
	char entry[DEFAULT_BUFFER];

	// Empty listbox
	SendDlgItemMessage(hDlg, MAIN_SONGLIST, LB_RESETCONTENT, 0, 0);

	// Make entries
	for (i = 0; i < qbInfo->numSongs; i++)
	{
		sprintf(entry, "%s", qbInfo->songs[i]->songName);
		SendDlgItemMessage(hDlg, MAIN_SONGLIST, LB_ADDSTRING, 0, (LPARAM) entry);
	}
}

void getSelectionData(HWND hDlg, QBReturnInfo *qbInfo)
{
	int item;
	QBSongInfo *selSong;
	char buffer[DEFAULT_BUFFER << 2];

	item = SendDlgItemMessage(hDlg, MAIN_SONGLIST, LB_GETCURSEL, 0, 0);

	buffer[0] = '\0';

	if (item != LB_ERR)
	{
		selSong = qbInfo->songs[item];

		if (selSong->internalFlags & IS_SONGINTERNALNAME)
		{
			strcat(buffer, "Internal name: ");
			strcat(buffer, selSong->songInternalName);
			strcat(buffer, "\n");
		}
		
		if (selSong->internalFlags & IS_SONGNAME)
		{
			strcat(buffer, "Name: ");
			strcat(buffer, selSong->songName);
			strcat(buffer, "\n");
		}

		if (selSong->internalFlags & IS_SONGAUTHOR)
		{
			strcat(buffer, "Artist: ");
			strcat(buffer, selSong->songAuthor);
			strcat(buffer, "\n");
		}

		if (selSong->internalFlags & IS_SONGYEAR)
		{
			strcat(buffer, "Year: ");
			strcat(buffer, selSong->songYear);
		}

		SetDlgItemText(hDlg, MAIN_SONGINFO, buffer);
	}
}

int FreeQbInfo(QBReturnInfo *qbInfo)
{
	int i;

	/* Song identifiers */
	free(qbInfo->songIdentifiers);

	/* Scripts */
	for (i = 0; i < qbInfo->numScripts; i++)
	{
		free(qbInfo->scripts[i]->scriptData); // The data
		free(qbInfo->scripts[i]); // And the pointer
	}

	free(qbInfo->scripts); // And the pointer list!

	/* Song info */
	for (i = 0; i < qbInfo->numSongs; i++)
	{
		free(qbInfo->songs[i]); // Song info pointer
	}

	free(qbInfo->songs); // The pointer list

	/* R.I.P. qbInfo */
	free(qbInfo);

	//MessageBox(NULL, "Killed qbInfo...", NULL, 0);

	return 0;
}

int WishToSave(HWND hDlg, QBReturnInfo *qbInfo, BOOL modifiedFile)
{
	if (modifiedFile == FALSE)
		return 0;
	else
	{
		if (MessageBox(hDlg, "You have modified the file and you didn't save.\nDo you really want to close it without saving?", "Close file", MB_YESNO | MB_ICONWARNING) == IDYES)
			return 0;
		else
			return 1;
	}
}
