#include "pch.h"
#include "loader.h"
#include "GroupData.h"
#include "TPinballComponent.h"
#include "pb.h"
#include "Sound.h"
#include "zdrv.h"


errorMsg loader::loader_errors[] =
{
	errorMsg{0, "Bad Handle"},
	errorMsg{1, "No Type Field"},
	errorMsg{2, "No Attributes Field"},
	errorMsg{3, "Wrong Type: MATERIAL Expected"},
	errorMsg{4, "Wrong Type: KICKER Expected"},
	errorMsg{5, "Wrong Type: AN_OBJECT Expected"},
	errorMsg{6, "Wrong Type: A_STATE Expected"},
	errorMsg{7, "STATES (re)defined in a state"},
	errorMsg{9, "Unrecognized Attribute"},
	errorMsg{0x0A, "Unrecognized float Attribute"},
	errorMsg{0x0B, "No float Attributes Field"},
	errorMsg{0x0D, "float Attribute not found"},
	errorMsg{0x0C, "state_index out of range"},
	errorMsg{0x0F, "loader_material() reports failure"},
	errorMsg{0x0E, "loader_kicker() reports failure"},
	errorMsg{0x10, "loader_state_id() reports failure"},
	errorMsg{0x8, "# walls doesn't match data size"},
	errorMsg{0x11, "loader_query_visual_states()"},
	errorMsg{0x12, "loader_query_visual()"},
	errorMsg{0x15, "loader_material()"},
	errorMsg{0x14, "loader_kicker()"},
	errorMsg{0x16, "loader_query_attribute()"},
	errorMsg{0x17, "loader_query_iattribute()"},
	errorMsg{0x13, "loader_query_name()"},
	errorMsg{0x18, "loader_state_id()"},
	errorMsg{0x19, "loader_get_sound_id()"},
	errorMsg{0x1A, "sound reference is not A_SOUND record"},
	errorMsg{-1, "Unknown"},
};

int loader::sound_count = 1;
int loader::loader_sound_count;
DatFile* loader::loader_table;
DatFile* loader::sound_record_table;
soundListStruct loader::sound_list[65];

int loader::error(int errorCode, int captionCode)
{
	auto curCode = loader_errors;
	const char *errorText = nullptr, *errorCaption = nullptr;
	auto index = 0;
	while (curCode->Code >= 0)
	{
		if (errorCode == curCode->Code)
			errorText = curCode->Message;
		if (captionCode == curCode->Code)
			errorCaption = curCode->Message;
		curCode++;
		index++;
	}

	if (!errorText)
		errorText = loader_errors[index].Message;
	pb::ShowMessageBox(SDL_MESSAGEBOX_ERROR, errorCaption, errorText);
	return -1;
}

void loader::default_vsi(visualStruct* visual)
{
	visual->CollisionGroup = 0;
	visual->Kicker.Threshold = 8.9999999e10f;
	visual->Kicker.HardHitSoundId = 0;
	visual->Smoothness = 0.94999999f;
	visual->Elasticity = 0.60000002f;
	visual->FloatArrCount = 0;
	visual->SoftHitSoundId = 0;
	visual->Bitmap = { nullptr, nullptr };
	visual->SoundIndex3 = 0;
	visual->SoundIndex4 = 0;
}

void loader::loadfrom(DatFile* datFile)
{
	loader_table = datFile;
	sound_record_table = loader_table;

	for (auto groupIndex = 0; groupIndex < static_cast<int>(datFile->Groups.size()); ++groupIndex)
	{
		auto value = reinterpret_cast<int16_t*>(datFile->field(groupIndex, FieldTypes::ShortValue));
		if (value && *value == 202)
		{
			if (sound_count < 65)
			{
				sound_list[sound_count] = {nullptr, groupIndex, 0, 0};
				sound_count++;
			}
		}
	}
	loader_sound_count = sound_count;
}

void loader::unload()
{
	for (int index = 1; index < sound_count; ++index)
	{
		Sound::FreeSound(sound_list[index].WavePtr);
		sound_list[index] = {};
	}

	sound_count = 1;
}

int loader::get_sound_id(int groupIndex)
{
	int16_t soundIndex = 1;
	if (sound_count <= 1)
	{
		error(25, 26);
		return -1;
	}

	while (sound_list[soundIndex].GroupIndex != groupIndex)
	{
		++soundIndex;
		if (soundIndex >= sound_count)
		{
			error(25, 26);
			return -1;
		}
	}

	if (!sound_list[soundIndex].Loaded && !sound_list[soundIndex].WavePtr)
	{
		WaveHeader wavHeader{};

		int soundGroupId = sound_list[soundIndex].GroupIndex;
		sound_list[soundIndex].Duration = 0.0;
		if (soundGroupId > 0 && !pb::quickFlag)
		{
			auto value = reinterpret_cast<int16_t*>(loader_table->field(soundGroupId,
			                                                            FieldTypes::ShortValue));
			if (value && *value == 202)
			{
				// File name is in lower case, while game data is usually in upper case.
				std::string fileName = loader_table->field(soundGroupId, FieldTypes::String);
				if (pb::FullTiltMode)
				{
					// FT sounds are in SOUND subfolder
					fileName.insert(0, 1, PathSeparator);
					fileName.insert(0, "sound");
				}

				std::string filePath;
				float duration = -1;
				for (int i = 0; i < 2; i++)
				{
					if (i == 1)
						std::transform(fileName.begin(), fileName.end(), fileName.begin(),
						               [](unsigned char c) { return std::toupper(c); });

					filePath = pb::make_path_name(fileName);
					auto file = fopenu(filePath.c_str(), "rb");
					if (file)
					{
						fread(&wavHeader, 1, sizeof wavHeader, file);
						fclose(file);
						auto sampleCount = wavHeader.data_size / (wavHeader.channels * (wavHeader.bits_per_sample /
							8.0));
						duration = static_cast<float>(sampleCount / wavHeader.sample_rate);
						break;
					}
				}

				sound_list[soundIndex].Duration = duration;
				sound_list[soundIndex].WavePtr = Sound::LoadWaveFile(filePath);
			}
		}
	}

	++sound_list[soundIndex].Loaded;
	return soundIndex;
}


int loader::query_handle(LPCSTR lpString)
{
	return loader_table->record_labeled(lpString);
}

short loader::query_visual_states(int groupIndex)
{
	short result;
	if (groupIndex < 0)
		return error(0, 17);
	auto shortArr = reinterpret_cast<int16_t*>(loader_table->field(groupIndex, FieldTypes::ShortArray));
	if (shortArr && *shortArr == 100)
		result = shortArr[1];
	else
		result = 1;
	return result;
}

char* loader::query_name(int groupIndex)
{
	if (groupIndex < 0)
	{
		error(0, 19);
		return nullptr;
	}

	return loader_table->field(groupIndex, FieldTypes::GroupName);
}

int16_t* loader::query_iattribute(int groupIndex, int firstValue, int* arraySize)
{
	if (groupIndex < 0)
	{
		error(0, 22);
		return nullptr;
	}

	for (auto skipIndex = 0;; ++skipIndex)
	{
		auto shortArr = reinterpret_cast<int16_t*>(loader_table->field_nth(groupIndex,
		                                                                   FieldTypes::ShortArray, skipIndex));
		if (!shortArr)
			break;
		if (*shortArr == firstValue)
		{
			*arraySize = loader_table->field_size(groupIndex, FieldTypes::ShortArray) / 2 - 1;
			return shortArr + 1;
		}
	}

	error(2, 23);
	*arraySize = 0;
	return nullptr;
}

float* loader::query_float_attribute(int groupIndex, int groupIndexOffset, int firstValue)
{
	if (groupIndex < 0)
	{
		error(0, 22);
		return nullptr;
	}

	int stateId = state_id(groupIndex, groupIndexOffset);
	if (stateId < 0)
	{
		error(16, 22);
		return nullptr;
	}

	for (auto skipIndex = 0;; ++skipIndex)
	{
		auto floatArr = reinterpret_cast<float*>(loader_table->field_nth(stateId, FieldTypes::FloatArray,
		                                                                 skipIndex));
		if (!floatArr)
			break;
		if (static_cast<int16_t>(floor(*floatArr)) == firstValue)
			return floatArr + 1;
	}

	error(13, 22);
	return nullptr;
}

float loader::query_float_attribute(int groupIndex, int groupIndexOffset, int firstValue, float defVal)
{
	if (groupIndex < 0)
	{
		error(0, 22);
		return NAN;
	}

	int stateId = state_id(groupIndex, groupIndexOffset);
	if (stateId < 0)
	{
		error(16, 22);
		return NAN;
	}

	for (auto skipIndex = 0;; ++skipIndex)
	{
		auto floatArr = reinterpret_cast<float*>(loader_table->field_nth(stateId,
		                                                                 FieldTypes::FloatArray, skipIndex));
		if (!floatArr)
			break;
		if (static_cast<int16_t>(floor(*floatArr)) == firstValue)
			return floatArr[1];
	}

	if (!isnan(defVal))
		return defVal;
	error(13, 22);
	return NAN;
}

int loader::material(int groupIndex, visualStruct* visual)
{
	if (groupIndex < 0)
		return error(0, 21);
	auto shortArr = reinterpret_cast<int16_t*>(loader_table->field(groupIndex, FieldTypes::ShortValue));
	if (!shortArr)
		return error(1, 21);
	if (*shortArr != 300)
		return error(3, 21);
	auto floatArr = reinterpret_cast<float*>(loader_table->field(groupIndex, FieldTypes::FloatArray));
	if (!floatArr)
		return error(11, 21);

	int floatArrLength = loader_table->field_size(groupIndex, FieldTypes::FloatArray) / 4;
	for (auto index = 0; index < floatArrLength; index += 2)
	{
		switch (static_cast<int>(floor(floatArr[index])))
		{
		case 301:
			visual->Smoothness = floatArr[index + 1];
			break;
		case 302:
			visual->Elasticity = floatArr[index + 1];
			break;
		case 304:
			visual->SoftHitSoundId = get_sound_id(static_cast<int>(floor(floatArr[index + 1])));
			break;
		default:
			return error(9, 21);
		}
	}
	return 0;
}

float loader::play_sound(int soundIndex, TPinballComponent *soundSource, const char *info)
{
	if (soundIndex <= 0)
		return 0.0;
	Sound::PlaySound(sound_list[soundIndex].WavePtr, pb::time_ticks, soundSource, info);
	return sound_list[soundIndex].Duration;
}

int loader::state_id(int groupIndex, int groupIndexOffset)
{
	auto visualState = query_visual_states(groupIndex);
	if (visualState <= 0)
		return error(12, 24);
	auto shortArr = reinterpret_cast<int16_t*>(loader_table->field(groupIndex, FieldTypes::ShortValue));
	if (!shortArr)
		return error(1, 24);
	if (*shortArr != 200)
		return error(5, 24);
	if (groupIndexOffset > visualState)
		return error(12, 24);
	if (!groupIndexOffset)
		return groupIndex;

	groupIndex += groupIndexOffset;
	shortArr = reinterpret_cast<int16_t*>(loader_table->field(groupIndex, FieldTypes::ShortValue));
	if (!shortArr)
		return error(1, 24);
	if (*shortArr != 201)
		return error(6, 24);
	return groupIndex;
}

int loader::kicker(int groupIndex, visualKickerStruct* kicker)
{
	if (groupIndex < 0)
		return error(0, 20);
	auto shortArr = reinterpret_cast<int16_t*>(loader_table->field(groupIndex, FieldTypes::ShortValue));
	if (!shortArr)
		return error(1, 20);
	if (*shortArr != 400)
		return error(4, 20);
	auto floatArr = reinterpret_cast<float*>(loader_table->field(groupIndex, FieldTypes::FloatArray));
	if (!floatArr)
		return error(11, 20);
	int floatArrLength = loader_table->field_size(groupIndex, FieldTypes::FloatArray) / 4;
	if (floatArrLength <= 0)
		return 0;

	for (auto index = 0; index < floatArrLength;)
	{
		int floorVal = static_cast<int>(floor(*floatArr++));
		switch (floorVal)
		{
		case 401:
			kicker->Threshold = *floatArr;
			break;
		case 402:
			kicker->Boost = *floatArr;
			break;
		case 403:
			kicker->ThrowBallMult = *floatArr;
			break;
		case 404:
			kicker->ThrowBallDirection = *reinterpret_cast<vector3*>(floatArr);
			floatArr += 3;
			index += 4;
			break;
		case 405:
			kicker->ThrowBallAngleMult = *floatArr;
			break;
		case 406:
			kicker->HardHitSoundId = get_sound_id(static_cast<int>(floor(*floatArr)));
			break;
		default:
			return error(10, 20);
		}
		if (floorVal != 404)
		{
			floatArr++;
			index += 2;
		}
	}
	return 0;
}


int loader::query_visual(int groupIndex, int groupIndexOffset, visualStruct* visual)
{
	default_vsi(visual);
	if (groupIndex < 0)
		return error(0, 18);
	auto stateId = state_id(groupIndex, groupIndexOffset);
	if (stateId < 0)
		return error(16, 18);

	auto bmp = loader_table->GetBitmap(stateId);
	auto zMap = loader_table->GetZMap(stateId);
	visual->Bitmap = { bmp, zMap };

	auto shortArr = reinterpret_cast<int16_t*>(loader_table->field(stateId, FieldTypes::ShortArray));
	if (shortArr)
	{
		unsigned int shortArrSize = loader_table->field_size(stateId, FieldTypes::ShortArray);
		for (auto index = 0u; index < shortArrSize / 2;)
		{
			switch (shortArr[0])
			{
			case 100:
				if (groupIndexOffset)
					return error(7, 18);
				break;
			case 300:
				if (material(shortArr[1], visual))
					return error(15, 18);
				break;
			case 304:
				visual->SoftHitSoundId = get_sound_id(shortArr[1]);
				break;
			case 400:
				if (kicker(shortArr[1], &visual->Kicker))
					return error(14, 18);
				break;
			case 406:
				visual->Kicker.HardHitSoundId = get_sound_id(shortArr[1]);
				break;
			case 602:
				visual->CollisionGroup |= 1 << shortArr[1];
				break;
			case 1100:
				visual->SoundIndex4 = get_sound_id(shortArr[1]);
				break;
			case 1101:
				visual->SoundIndex3 = get_sound_id(shortArr[1]);
				break;
			case 1500:
				shortArr += 7;
				index += 7;
				break;
			default:
				return error(9, 18);
			}
			shortArr += 2;
			index += 2;
		}
	}

	if (!visual->CollisionGroup)
		visual->CollisionGroup = 1;
	auto floatArr = reinterpret_cast<float*>(loader_table->field(stateId, FieldTypes::FloatArray));
	if (!floatArr)
		return 0;
	if (*floatArr != 600.0f)
		return 0;

	visual->FloatArrCount = loader_table->field_size(stateId, FieldTypes::FloatArray) / 4 / 2 - 2;
	auto floatVal = static_cast<int>(floor(floatArr[1]) - 1.0f);
	switch (floatVal)
	{
	case 0:
		visual->FloatArrCount = 1;
		break;
	case 1:
		visual->FloatArrCount = 2;
		break;
	default:
		if (floatVal != visual->FloatArrCount)
			return error(8, 18);
		break;
	}

	visual->FloatArr = floatArr + 2;
	return 0;
}
