#include "lc_global.h"
#include "minifig.h"
#include "lc_colors.h"
#include "pieceinf.h"
#include "lc_model.h"
#include "lc_library.h"
#include "lc_application.h"
#include "lc_file.h"
#include "lc_profile.h"

const char* MinifigWizard::mSectionNames[LC_MFW_NUMITEMS] =
{
	"HATS",   // LC_MFW_HATS
	"HATS2",  // LC_MFW_HATS2
	"HEAD",   // LC_MFW_HEAD
	"NECK",   // LC_MFW_NECK
	"BODY",   // LC_MFW_BODY
	"BODY2",  // LC_MFW_BODY2
	"BODY3",  // LC_MFW_BODY3
	"RARM",   // LC_MFW_RARM
	"LARM",   // LC_MFW_LARM
	"RHAND",  // LC_MFW_RHAND
	"LHAND",  // LC_MFW_LHAND
	"RHANDA", // LC_MFW_RHANDA
	"LHANDA", // LC_MFW_LHANDA
	"RLEG",   // LC_MFW_RLEG
	"LLEG",   // LC_MFW_LLEG
	"RLEGA",  // LC_MFW_RLEGA
	"LLEGA",  // LC_MFW_LLEGA
};

MinifigWizard::MinifigWizard()
	: mModel(new lcModel(QString(), nullptr, false))
{
	LoadSettings();
	LoadTemplates();

	lcMinifigTemplate& Template = mTemplates[GetDefaultTemplateName()];
	const int ColorCodes[LC_MFW_NUMITEMS] = { 4, 7, 14, 7, 1, 0, 7, 4, 4, 14, 14, 7, 7, 0, 0, 7, 7 };
	const char* const Pieces[LC_MFW_NUMITEMS] = { "3624.dat", "", "3626bp01.dat", "", "973.dat", "3815.dat", "", "3819.dat", "3818.dat", "3820.dat", "3820.dat", "", "", "3817.dat", "3816.dat", "", "" };

	for (int PartIndex = 0; PartIndex < LC_MFW_NUMITEMS; PartIndex++)
	{
		Template.Parts[PartIndex] = Pieces[PartIndex];
		Template.ColorCodes[PartIndex] = ColorCodes[PartIndex];
		Template.Angles[PartIndex] = 0.0f;
	}

	memset(&mMinifig, 0, sizeof(mMinifig));
}

MinifigWizard::~MinifigWizard()
{
	lcPiecesLibrary* Library = lcGetPiecesLibrary();

	for (int i = 0; i < LC_MFW_NUMITEMS; i++)
		if (mMinifig.Parts[i])
			Library->ReleasePieceInfo(mMinifig.Parts[i]); // todo: don't call ReleasePieceInfo here because it may release textures and they need a GL context current

	SaveTemplates();
}

QString MinifigWizard::GetDefaultTemplateName()
{
	return QCoreApplication::translate("MinifigWizard", "Default");
}

void MinifigWizard::LoadSettings()
{
	QString CustomSettingsPath = lcGetProfileString(LC_PROFILE_MINIFIG_SETTINGS);

	if (!CustomSettingsPath.isEmpty())
	{
		lcDiskFile DiskSettings(CustomSettingsPath);

		if (DiskSettings.Open(QIODevice::ReadOnly))
		{
			ParseSettings(DiskSettings);
			return;
		}
	}

	lcDiskFile MinifigFile(":/resources/minifig.ini");

	if (MinifigFile.Open(QIODevice::ReadOnly))
		ParseSettings(MinifigFile);
}

void MinifigWizard::ParseSettings(lcFile& Settings)
{
	for (int SectionIndex = 0; SectionIndex < LC_MFW_NUMITEMS; SectionIndex++)
	{
		std::vector<lcMinifigPieceInfo>& InfoArray = mSettings[SectionIndex];

		InfoArray.clear();
		Settings.Seek(0, SEEK_SET);

		char Line[1024];
		bool FoundSection = false;
		const char* SectionName = mSectionNames[SectionIndex];
		const size_t SectionNameLength = strlen(SectionName);

		while (Settings.ReadLine(Line, sizeof(Line)))
		{
			if (Line[0] == '[' && !strncmp(Line + 1, SectionName, SectionNameLength) && Line[SectionNameLength + 1] == ']')
			{
				FoundSection = true;
				break;
			}
		}

		if (!FoundSection)
		{
			lcMinifigPieceInfo MinifigInfo;
			strncpy(MinifigInfo.Description, "None", sizeof(MinifigInfo.Description));
			MinifigInfo.Description[sizeof(MinifigInfo.Description)-1] = 0;
			MinifigInfo.Offset = lcMatrix44Identity();
			MinifigInfo.Info = nullptr;

			InfoArray.emplace_back(std::move(MinifigInfo));
			continue;
		}

		while (Settings.ReadLine(Line, sizeof(Line)))
		{
			if (Line[0] == '[')
				break;

			char* DescriptionStart = strchr(Line, '"');
			if (!DescriptionStart)
				continue;
			DescriptionStart++;
			char* DescriptionEnd = strchr(DescriptionStart, '"');
			if (!DescriptionEnd)
				continue;
			*DescriptionEnd = 0;
			DescriptionEnd++;

			char* NameStart = strchr(DescriptionEnd, '"');
			if (!NameStart)
				continue;
			NameStart++;
			char* NameEnd = strchr(NameStart, '"');
			if (!NameEnd)
				continue;
			*NameEnd = 0;
			NameEnd++;

			PieceInfo* Info = lcGetPiecesLibrary()->FindPiece(NameStart, nullptr, false, false);
			if (!Info && *NameStart)
				continue;

			float Mat[12];
			int Flags;

			if (sscanf(NameEnd, "%d %g %g %g %g %g %g %g %g %g %g %g %g",
					   &Flags, &Mat[0], &Mat[1], &Mat[2], &Mat[3], &Mat[4], &Mat[5], &Mat[6], 
					   &Mat[7], &Mat[8], &Mat[9], &Mat[10], &Mat[11]) != 13)
				continue;

			lcMatrix44 Offset = lcMatrix44Identity();
			float* OffsetMatrix = &Offset[0][0];

			OffsetMatrix[0] =  Mat[0];
			OffsetMatrix[8] = -Mat[1];
			OffsetMatrix[4] =  Mat[2];
			OffsetMatrix[2] = -Mat[3];
			OffsetMatrix[10] = Mat[4];
			OffsetMatrix[6] = -Mat[5];
			OffsetMatrix[1] =  Mat[6];
			OffsetMatrix[9] = -Mat[7];
			OffsetMatrix[5] =  Mat[8];
			OffsetMatrix[12] =  Mat[9];
			OffsetMatrix[14] = -Mat[10];
			OffsetMatrix[13] =  Mat[11];

			lcMinifigPieceInfo MinifigInfo;
			strncpy(MinifigInfo.Description, DescriptionStart, sizeof(MinifigInfo.Description));
			MinifigInfo.Description[sizeof(MinifigInfo.Description)-1] = 0;
			MinifigInfo.Offset = Offset;
			MinifigInfo.Info = Info;

			InfoArray.emplace_back(std::move(MinifigInfo));
		}
	}
}

void MinifigWizard::LoadTemplate(const QString& TemplateName)
{
	const std::map<QString, lcMinifigTemplate>::iterator& TemplateIt = mTemplates.find(TemplateName);

	if (TemplateIt == mTemplates.end())
		return;

	const lcMinifigTemplate& Template = TemplateIt->second;
	lcPiecesLibrary* Library = lcGetPiecesLibrary();

	for (int PartIndex = 0; PartIndex < LC_MFW_NUMITEMS; PartIndex++)
	{
		mMinifig.ColorIndices[PartIndex] = lcGetColorIndex(Template.ColorCodes[PartIndex]);
		mMinifig.Angles[PartIndex] = Template.Angles[PartIndex];
		mMinifig.Matrices[PartIndex] = lcMatrix44Identity();

		if (mMinifig.Parts[PartIndex])
			Library->ReleasePieceInfo(mMinifig.Parts[PartIndex]);

		PieceInfo* Info = Library->FindPiece(Template.Parts[PartIndex].toLatin1(), nullptr, false, false);

		mMinifig.Parts[PartIndex] = Info;

		if (Info)
			Library->LoadPieceInfo(Info, false, true);
	}

	Library->WaitForLoadQueue();
	Calculate();
}

void MinifigWizard::SaveTemplate(const QString& TemplateName, const lcMinifigTemplate& Template)
{
	mTemplates[TemplateName] = Template;
}

void MinifigWizard::DeleteTemplate(const QString& TemplateName)
{
	mTemplates.erase(TemplateName);
}

void MinifigWizard::AddTemplatesJson(const QByteArray& TemplateData)
{
	QJsonDocument Document = QJsonDocument::fromJson(TemplateData);
	QJsonObject RootObject = Document.object();

	int Version = RootObject["Version"].toInt(0);
	QJsonObject TemplatesObject;

	if (Version > 0)
		TemplatesObject = RootObject["Templates"].toObject();
	else
		TemplatesObject = RootObject;

	for (QJsonObject::const_iterator ElementIt = TemplatesObject.constBegin(); ElementIt != TemplatesObject.constEnd(); ElementIt++)
	{
		if (!ElementIt.value().isObject())
			continue;

		QJsonObject TemplateObject = ElementIt.value().toObject();
		lcMinifigTemplate Template;

		for (int PartIdx = 0; PartIdx < LC_MFW_NUMITEMS; PartIdx++)
		{
			QJsonObject PartObject = TemplateObject.value(QLatin1String(mSectionNames[PartIdx])).toObject();

			Template.Parts[PartIdx] = PartObject["Id"].toString();
			Template.ColorCodes[PartIdx] = PartObject["Color"].toInt();
			Template.Angles[PartIdx] = PartObject["Angle"].toDouble();
		}

		mTemplates.emplace(ElementIt.key(), std::move(Template));
	}
}

QByteArray MinifigWizard::GetTemplatesJson() const
{
	QJsonObject TemplatesObject;

	for (const auto& TemplateEntry : mTemplates)
	{
		const lcMinifigTemplate& Template = TemplateEntry.second;
		QJsonObject TemplateObject;

		for (int PartIdx = 0; PartIdx < LC_MFW_NUMITEMS; PartIdx++)
		{
			QJsonObject PartObject;

			PartObject["Id"] = Template.Parts[PartIdx];
			PartObject["Color"] = Template.ColorCodes[PartIdx];
			PartObject["Angle"] = Template.Angles[PartIdx];

			TemplateObject[QLatin1String(mSectionNames[PartIdx])] = PartObject;
		}

		TemplatesObject[TemplateEntry.first] = TemplateObject;
	}

	QJsonObject RootObject;
	RootObject["Templates"] = TemplatesObject;
	RootObject["Version"] = 1;

	return QJsonDocument(RootObject).toJson();
}

void MinifigWizard::LoadTemplates()
{
	mTemplates.clear();

	QSettings Settings;
	Settings.beginGroup("Minifig");
	QByteArray TemplateData = Settings.value("Templates").toByteArray();

	AddTemplatesJson(TemplateData);
}

void MinifigWizard::SaveTemplates()
{
	QSettings Settings;
	Settings.beginGroup("Minifig");
	Settings.setValue("Templates", GetTemplatesJson());
}

void MinifigWizard::Calculate()
{
	float HeadOffset = 0.0f;
	lcMatrix44 Root, Mat, Mat2;

	PieceInfo** Parts = mMinifig.Parts;
	const float* Angles = mMinifig.Angles;
	lcMatrix44* Matrices = mMinifig.Matrices;

	const bool DroidTorso = Parts[LC_MFW_BODY] && !qstricmp(Parts[LC_MFW_BODY]->mFileName, "30375.dat");
	const bool SkeletonTorso = Parts[LC_MFW_BODY] && !qstricmp(Parts[LC_MFW_BODY]->mFileName, "6260.dat");

	if (Parts[LC_MFW_BODY3])
		Root = lcMatrix44Translation(lcVector3(0, 0, 74.0f));
	else
		Root = lcMatrix44Translation(lcVector3(0, 0, 72.0f));
	Matrices[LC_MFW_BODY] = lcMul(mSettings[LC_MFW_BODY][GetSelectionIndex(LC_MFW_BODY)].Offset, Root);

	if (Parts[LC_MFW_NECK])
	{
		Matrices[LC_MFW_NECK] = lcMul(mSettings[LC_MFW_NECK][GetSelectionIndex(LC_MFW_NECK)].Offset, Root);
		HeadOffset = 0.08f;
	}

	if (Parts[LC_MFW_HEAD])
	{
		Mat = lcMatrix44RotationZ(-LC_DTOR * Angles[LC_MFW_HEAD]);
		Mat.SetTranslation(lcVector3(0.0f, 0.0f, 24.0f + HeadOffset));
		Mat = lcMul(mSettings[LC_MFW_HEAD][GetSelectionIndex(LC_MFW_HEAD)].Offset, Mat);
		Matrices[LC_MFW_HEAD] = lcMul(Mat, Root);
	}

	if (Parts[LC_MFW_HATS])
	{
		Mat = lcMatrix44RotationZ(-LC_DTOR * Angles[LC_MFW_HATS]);
		Mat = lcMul(mSettings[LC_MFW_HATS][GetSelectionIndex(LC_MFW_HATS)].Offset, Mat);
		Matrices[LC_MFW_HATS] = lcMul(Mat, Matrices[LC_MFW_HEAD]);
	}

	if (Parts[LC_MFW_HATS2])
	{
		Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_HATS2]);
		Mat = lcMul(mSettings[LC_MFW_HATS2][GetSelectionIndex(LC_MFW_HATS2)].Offset, Mat);
		Matrices[LC_MFW_HATS2] = lcMul(Mat, Matrices[LC_MFW_HATS]);
	}

	if (Parts[LC_MFW_RARM])
	{
		Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_RARM]);

		if (DroidTorso || SkeletonTorso)
			Mat2 = lcMatrix44Identity();
		else
			Mat2 = lcMatrix44RotationY(-LC_DTOR * 9.791f);
		Mat2.SetTranslation(lcVector3(15.552f, 0, -8.88f));

		Mat = lcMul(mSettings[LC_MFW_RARM][GetSelectionIndex(LC_MFW_RARM)].Offset, Mat);
		Mat = lcMul(Mat, Mat2);
		Matrices[LC_MFW_RARM] = lcMul(Mat, Root);
	}

	if (Parts[LC_MFW_RHAND])
	{
		Mat = lcMatrix44RotationY(-LC_DTOR * Angles[LC_MFW_RHAND]);
		Mat2 = lcMatrix44RotationX(LC_DTOR * 45);
		Mat = lcMul(mSettings[LC_MFW_RHAND][GetSelectionIndex(LC_MFW_RHAND)].Offset, Mat);
		Mat = lcMul(Mat, Mat2);
		Mat.SetTranslation(lcVector3(5.0f, -10.0f, -19.0f));
		Matrices[LC_MFW_RHAND] = lcMul(Mat, Matrices[LC_MFW_RARM]);
	}

	if (Parts[LC_MFW_RHANDA])
	{
		Mat = lcMatrix44RotationZ(LC_DTOR * Angles[LC_MFW_RHANDA]);
		Mat.SetTranslation(lcVector3(0, -9.25f, 0));
		Mat = lcMul(mSettings[LC_MFW_RHANDA][GetSelectionIndex(LC_MFW_RHANDA)].Offset, Mat);
		Mat = lcMul(Mat, lcMatrix44RotationX(LC_DTOR * 15.0f));
		Matrices[LC_MFW_RHANDA] = lcMul(Mat, Matrices[LC_MFW_RHAND]);
	}

	if (Parts[LC_MFW_LARM])
	{
		Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_LARM]);

		if (DroidTorso || SkeletonTorso)
			Mat2 = lcMatrix44Identity();
		else
			Mat2 = lcMatrix44RotationY(LC_DTOR * 9.791f);
		Mat2.SetTranslation(lcVector3(-15.552f, 0.0f, -8.88f));

		Mat = lcMul(mSettings[LC_MFW_LARM][GetSelectionIndex(LC_MFW_LARM)].Offset, Mat);
		Mat = lcMul(Mat, Mat2);
		Matrices[LC_MFW_LARM] = lcMul(Mat, Root);
	}

	if (Parts[LC_MFW_LHAND])
	{
		Mat = lcMatrix44RotationY(-LC_DTOR * Angles[LC_MFW_LHAND]);
		Mat2 = lcMatrix44RotationX(LC_DTOR * 45);
		Mat = lcMul(mSettings[LC_MFW_LHAND][GetSelectionIndex(LC_MFW_LHAND)].Offset, Mat);
		Mat = lcMul(Mat, Mat2);
		Mat.SetTranslation(lcVector3(-5.0f, -10.0f, -19.0f));
		Matrices[LC_MFW_LHAND] = lcMul(Mat, Matrices[LC_MFW_LARM]);
	}

	if (Parts[LC_MFW_LHANDA])
	{
		Mat = lcMatrix44RotationZ(LC_DTOR * Angles[LC_MFW_LHANDA]);
		Mat.SetTranslation(lcVector3(0, -9.25f, 0));
		Mat = lcMul(mSettings[LC_MFW_LHANDA][GetSelectionIndex(LC_MFW_LHANDA)].Offset, Mat);
		Mat = lcMul(Mat, lcMatrix44RotationX(LC_DTOR * 15.0f));
		Matrices[LC_MFW_LHANDA] = lcMul(Mat, Matrices[LC_MFW_LHAND]);
	}

	if (Parts[LC_MFW_BODY2])
	{
		Mat = lcMatrix44Identity();
		Mat.SetTranslation(lcVector3(0, 0, -32.0f));
		Mat = lcMul(mSettings[LC_MFW_BODY2][GetSelectionIndex(LC_MFW_BODY2)].Offset, Mat);
		Matrices[LC_MFW_BODY2] = lcMul(Mat, Root);
	}

	if (Parts[LC_MFW_BODY3])
	{
		Mat = lcMatrix44Identity();
		Mat.SetTranslation(lcVector3(0, 0, -32.0f));
		Mat = lcMul(mSettings[LC_MFW_BODY3][GetSelectionIndex(LC_MFW_BODY3)].Offset, Mat);
		Matrices[LC_MFW_BODY3] = lcMul(Mat, Root);
	}

	if (Parts[LC_MFW_RLEG])
	{
		Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_RLEG]);
		Mat.SetTranslation(lcVector3(0, 0, -44.0f));
		Mat = lcMul(mSettings[LC_MFW_RLEG][GetSelectionIndex(LC_MFW_RLEG)].Offset, Mat);
		Matrices[LC_MFW_RLEG] = lcMul(Mat, Root);
	}

	if (Parts[LC_MFW_RLEGA])
	{
		const lcVector3 Center(-10.0f, -1.0f, -28.0f);
		Mat = lcMatrix44RotationZ(LC_DTOR * Angles[LC_MFW_RLEGA]);
		Mat2 = mSettings[LC_MFW_RLEGA][GetSelectionIndex(LC_MFW_RLEGA)].Offset;
		Mat2.SetTranslation(lcMul31(-Center, Mat2));
		Mat = lcMul(Mat2, Mat);
		Mat.SetTranslation(lcMul31(Center, Mat2));
		Matrices[LC_MFW_RLEGA] = lcMul(Mat, Matrices[LC_MFW_RLEG]);
	}

	if (Parts[LC_MFW_LLEG])
	{
		Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_LLEG]);
		Mat.SetTranslation(lcVector3(0, 0, -44.0f));
		Mat = lcMul(mSettings[LC_MFW_LLEG][GetSelectionIndex(LC_MFW_LLEG)].Offset, Mat);
		Matrices[LC_MFW_LLEG] = lcMul(Mat, Root);
	}

	if (Parts[LC_MFW_LLEGA])
	{
		const lcVector3 Center(10.0f, -1.0f, -28.0f);
		Mat = lcMatrix44RotationZ(LC_DTOR * Angles[LC_MFW_LLEGA]);
		Mat2 = mSettings[LC_MFW_LLEGA][GetSelectionIndex(LC_MFW_LLEGA)].Offset;
		Mat2.SetTranslation(lcMul31(-Center, Mat2));
		Mat = lcMul(Mat2, Mat);
		Mat.SetTranslation(lcMul31(Center, Mat2));
		Matrices[LC_MFW_LLEGA] = lcMul(Mat, Matrices[LC_MFW_LLEG]);
	}

	mModel->SetMinifig(mMinifig);
}

int MinifigWizard::GetSelectionIndex(int Type) const
{
	const std::vector<lcMinifigPieceInfo>& InfoArray = mSettings[Type];

	for (size_t Index = 0; Index < InfoArray.size(); Index++)
		if (InfoArray[Index].Info == mMinifig.Parts[Type])
			return (int)Index;

	return 0;
}

void MinifigWizard::SetPieceInfo(int Type, PieceInfo* Info)
{
	lcPiecesLibrary* Library = lcGetPiecesLibrary();

	if (mMinifig.Parts[Type])
		Library->ReleasePieceInfo(mMinifig.Parts[Type]);

	mMinifig.Parts[Type] = Info;

	if (mMinifig.Parts[Type])
		Library->LoadPieceInfo(mMinifig.Parts[Type], true, true);

	Calculate();
}

void MinifigWizard::SetColorIndex(int Type, int ColorIndex)
{
	mMinifig.ColorIndices[Type] = ColorIndex;

	Calculate();
}

void MinifigWizard::SetAngle(int Type, float Angle)
{
	mMinifig.Angles[Type] = Angle;

	Calculate();
}
