#include "stdafx.h"
#include <algorithm>
#include "DLCManager.h"
#include "DLCPack.h"
#include "DLCFile.h"
#include "..\..\..\Minecraft.World\StringHelpers.h"
#include "..\..\Minecraft.h"
#include "..\..\TexturePackRepository.h"

WCHAR *DLCManager::wchTypeNamesA[]=
{
	L"DISPLAYNAME",
	L"THEMENAME",
	L"FREE", 
	L"CREDIT",
	L"CAPEPATH",
	L"BOX",
	L"ANIM",
	L"PACKID",
	L"NETHERPARTICLECOLOUR",
	L"ENCHANTTEXTCOLOUR",
	L"ENCHANTTEXTFOCUSCOLOUR",
	L"DATAPATH",
	L"PACKVERSION",
};

DLCManager::DLCManager()
{
	//m_bNeedsUpdated = true;
	m_bNeedsCorruptCheck = true;
}

DLCManager::~DLCManager()
{
	for(AUTO_VAR(it, m_packs.begin()); it != m_packs.end(); ++it)
	{
		DLCPack *pack = *it;
		delete pack;
	}
}

DLCManager::EDLCParameterType DLCManager::getParameterType(const wstring &paramName)
{
	EDLCParameterType type = e_DLCParamType_Invalid;

	for(DWORD i = 0; i < e_DLCParamType_Max; ++i)
	{
		if(paramName.compare(wchTypeNamesA[i]) == 0)
		{
			type = (EDLCParameterType)i;
			break;
		}
	}

	return type;
}

DWORD DLCManager::getPackCount(EDLCType type /*= e_DLCType_All*/)
{
	DWORD packCount = 0;
	if( type != e_DLCType_All )
	{
		for(AUTO_VAR(it, m_packs.begin()); it != m_packs.end(); ++it)
		{
			DLCPack *pack = *it;
			if( pack->getDLCItemsCount(type) > 0 )
			{
				++packCount;
			}
		}
	}
	else
	{
		packCount = (DWORD)m_packs.size();
	}
	return packCount;
}

void DLCManager::addPack(DLCPack *pack)
{
	m_packs.push_back(pack);
}

void DLCManager::removePack(DLCPack *pack)
{
	if(pack != NULL)
	{
		AUTO_VAR(it, find(m_packs.begin(),m_packs.end(),pack));
		if(it != m_packs.end() ) m_packs.erase(it);
		delete pack;
	}
}

DLCPack *DLCManager::getPack(const wstring &name)
{
	DLCPack *pack = NULL;
	//DWORD currentIndex = 0;
	DLCPack *currentPack = NULL;
	for(AUTO_VAR(it, m_packs.begin()); it != m_packs.end(); ++it)
	{
		currentPack = *it;
		wstring wsName=currentPack->getName();

		if(wsName.compare(name) == 0)
		{
			pack = currentPack;
			break;
		}
	}
	return pack;
}


DLCPack *DLCManager::getPack(DWORD index, EDLCType type /*= e_DLCType_All*/)
{
	DLCPack *pack = NULL;
	if( type != e_DLCType_All )
	{
		DWORD currentIndex = 0;
		DLCPack *currentPack = NULL;
		for(AUTO_VAR(it, m_packs.begin()); it != m_packs.end(); ++it)
		{
			currentPack = *it;
			if(currentPack->getDLCItemsCount(type)>0)
			{
				if(currentIndex == index)
				{
					pack = currentPack;
					break;
				}
				++currentIndex;
			}
		}
	}
	else
	{
		if(index >= m_packs.size())
		{
			app.DebugPrintf("DLCManager: Trying to access a DLC pack beyond the range of valid packs\n");
			__debugbreak();
		}
		pack = m_packs[index];
	}

	return pack;
}

DWORD DLCManager::getPackIndex(DLCPack *pack, bool &found, EDLCType type /*= e_DLCType_All*/)
{
	DWORD foundIndex = 0;
	found = false;
	if(pack == NULL)
	{
		app.DebugPrintf("DLCManager: Attempting to find the index for a NULL pack\n");
		//__debugbreak();
		return foundIndex;
	}
	if( type != e_DLCType_All )
	{
		DWORD index = 0;
		for(AUTO_VAR(it, m_packs.begin()); it != m_packs.end(); ++it)
		{
			DLCPack *thisPack = *it;
			if(thisPack->getDLCItemsCount(type)>0)
			{
				if(thisPack == pack)
				{
					found = true;
					foundIndex = index;
					break;
				}
				++index;
			}
		}
	}
	else
	{
		DWORD index = 0;
		for(AUTO_VAR(it, m_packs.begin()); it != m_packs.end(); ++it)
		{
			DLCPack *thisPack = *it;
			if(thisPack == pack)
			{
				found = true;
				foundIndex = index;
				break;
			}
			++index;
		}
	}
	return foundIndex;
}

DWORD DLCManager::getPackIndexContainingSkin(const wstring &path, bool &found)
{
	DWORD foundIndex = 0;
	found = false;
	DWORD index = 0;
	for(AUTO_VAR(it, m_packs.begin()); it != m_packs.end(); ++it)
	{
		DLCPack *pack = *it;
		if(pack->getDLCItemsCount(e_DLCType_Skin)>0)
		{
			if(pack->doesPackContainSkin(path))
			{
				foundIndex = index;
				found = true;
				break;
			}
			++index;
		}
	}
	return foundIndex;
}

DLCPack *DLCManager::getPackContainingSkin(const wstring &path)
{
	DLCPack *foundPack = NULL;
	for(AUTO_VAR(it, m_packs.begin()); it != m_packs.end(); ++it)
	{
		DLCPack *pack = *it;
		if(pack->getDLCItemsCount(e_DLCType_Skin)>0)
		{
			if(pack->doesPackContainSkin(path))
			{
				foundPack = pack;
				break;
			}
		}
	}
	return foundPack;
}

DLCSkinFile *DLCManager::getSkinFile(const wstring &path)
{
	DLCSkinFile *foundSkinfile = NULL;
	for(AUTO_VAR(it, m_packs.begin()); it != m_packs.end(); ++it)
	{
		DLCPack *pack = *it;
		foundSkinfile=pack->getSkinFile(path);
		if(foundSkinfile!=NULL)
		{
			break;
		}
	}
	return foundSkinfile;
}

DWORD DLCManager::checkForCorruptDLCAndAlert(bool showMessage /*= true*/)
{
	DWORD corruptDLCCount = m_dwUnnamedCorruptDLCCount;	
	DLCPack *pack = NULL;
	DLCPack *firstCorruptPack = NULL;

	for(AUTO_VAR(it, m_packs.begin()); it != m_packs.end(); ++it)
	{
		pack = *it;
		if( pack->IsCorrupt() )
		{
			++corruptDLCCount;
			if(firstCorruptPack == NULL) firstCorruptPack = pack;
		}
	}

	if(corruptDLCCount > 0 && showMessage)
	{
		UINT uiIDA[1];
		uiIDA[0]=IDS_CONFIRM_OK;
		if(corruptDLCCount == 1 && firstCorruptPack != NULL)
		{
			// pass in the pack format string
			WCHAR wchFormat[132];
			swprintf(wchFormat, 132, L"%ls\n\n%%ls", firstCorruptPack->getName().c_str());

			C4JStorage::EMessageResult result = ui.RequestMessageBox( IDS_CORRUPT_DLC_TITLE, IDS_CORRUPT_DLC, uiIDA,1,ProfileManager.GetPrimaryPad(),NULL,NULL, app.GetStringTable(),wchFormat);

		}
		else
		{
			C4JStorage::EMessageResult result = ui.RequestMessageBox( IDS_CORRUPT_DLC_TITLE, IDS_CORRUPT_DLC_MULTIPLE, uiIDA,1,ProfileManager.GetPrimaryPad(),NULL,NULL, app.GetStringTable());
		}
	}

	SetNeedsCorruptCheck(false);

	return corruptDLCCount;
}

bool DLCManager::readDLCDataFile(DWORD &dwFilesProcessed, const wstring &path, DLCPack *pack, bool fromArchive)
{
	return readDLCDataFile( dwFilesProcessed, wstringtofilename(path), pack, fromArchive);
}


bool DLCManager::readDLCDataFile(DWORD &dwFilesProcessed, const string &path, DLCPack *pack, bool fromArchive)
{
	wstring wPath = convStringToWstring(path);
	if (fromArchive && app.getArchiveFileSize(wPath) >= 0)
	{
		byteArray bytes = app.getArchiveFile(wPath);
		return processDLCDataFile(dwFilesProcessed, bytes.data, bytes.length, pack);
	}
	else if (fromArchive) return false;

	HANDLE file = CreateFile(path.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if( file == INVALID_HANDLE_VALUE )
	{
		DWORD error = GetLastError();
		app.DebugPrintf("Failed to open DLC data file with error code %d (%x)\n", error, error);
		if( dwFilesProcessed == 0 ) removePack(pack);
		assert(false);
		return false;
	}

	DWORD bytesRead,dwFileSize = GetFileSize(file,NULL);
	PBYTE pbData =  (PBYTE) new BYTE[dwFileSize];
	BOOL bSuccess = ReadFile(file,pbData,dwFileSize,&bytesRead,NULL);
	if(bSuccess==FALSE)
	{
		// need to treat the file as corrupt, and flag it, so can't call fatal error
		//app.FatalLoadError();
	}
	else
	{
		CloseHandle(file);
	}
	if(bSuccess==FALSE)
	{
		// Corrupt or some other error. In any case treat as corrupt
		app.DebugPrintf("Failed to read %s from DLC content package\n", path.c_str());
		pack->SetIsCorrupt( true );
		SetNeedsCorruptCheck(true);
		return false;
	}
	return processDLCDataFile(dwFilesProcessed, pbData, bytesRead, pack);
}

bool DLCManager::processDLCDataFile(DWORD &dwFilesProcessed, PBYTE pbData, DWORD dwLength, DLCPack *pack)
{
	unordered_map<int, DLCManager::EDLCParameterType> parameterMapping;
	unsigned int uiCurrentByte=0;

	// File format defined in the DLC_Creator
	// File format: Version 2
	// unsigned long, version number
	// unsigned long, t = number of parameter types
	// t * DLC_FILE_PARAM structs mapping strings to id's
	// unsigned long, n = number of files
	// n * DLC_FILE_DETAILS describing each file in the pack
	// n * files of the form
	// // unsigned long, p = number of parameters
	// // p * DLC_FILE_PARAM describing each parameter for this file
	// // ulFileSize bytes of data blob of the file added
	unsigned int uiVersion=*(unsigned int *)pbData;
	uiCurrentByte+=sizeof(int);

	if(uiVersion < CURRENT_DLC_VERSION_NUM)
	{
		if(pbData!=NULL) delete [] pbData;
		app.DebugPrintf("DLC version of %d is too old to be read\n", uiVersion);
		return false;
	}
	pack->SetDataPointer(pbData);
	unsigned int uiParameterCount=*(unsigned int *)&pbData[uiCurrentByte];
	uiCurrentByte+=sizeof(int);
	C4JStorage::DLC_FILE_PARAM *pParams = (C4JStorage::DLC_FILE_PARAM *)&pbData[uiCurrentByte];
	//DWORD dwwchCount=0;
	for(unsigned int i=0;i<uiParameterCount;i++)
	{
		// Map DLC strings to application strings, then store the DLC index mapping to application index
		wstring parameterName((WCHAR *)pParams->wchData);
		DLCManager::EDLCParameterType type = DLCManager::getParameterType(parameterName);
		if( type != DLCManager::e_DLCParamType_Invalid )
		{
			parameterMapping[pParams->dwType] = type;
		}
		uiCurrentByte+= sizeof(C4JStorage::DLC_FILE_PARAM)+(pParams->dwWchCount*sizeof(WCHAR));
		pParams = (C4JStorage::DLC_FILE_PARAM *)&pbData[uiCurrentByte];
	}
	//ulCurrentByte+=ulParameterCount * sizeof(C4JStorage::DLC_FILE_PARAM);

	unsigned int uiFileCount=*(unsigned int *)&pbData[uiCurrentByte];
	uiCurrentByte+=sizeof(int);
	C4JStorage::DLC_FILE_DETAILS *pFile = (C4JStorage::DLC_FILE_DETAILS *)&pbData[uiCurrentByte];

	DWORD dwTemp=uiCurrentByte;
	for(unsigned int i=0;i<uiFileCount;i++)
	{
		dwTemp+=sizeof(C4JStorage::DLC_FILE_DETAILS)+pFile->dwWchCount*sizeof(WCHAR);
		pFile = (C4JStorage::DLC_FILE_DETAILS *)&pbData[dwTemp];
	}
	PBYTE pbTemp=((PBYTE )pFile);//+ sizeof(C4JStorage::DLC_FILE_DETAILS)*ulFileCount;
	pFile = (C4JStorage::DLC_FILE_DETAILS *)&pbData[uiCurrentByte];

	for(unsigned int i=0;i<uiFileCount;i++)
	{
		DLCManager::EDLCType type = (DLCManager::EDLCType)pFile->dwType;

		DLCFile *dlcFile = NULL;
		DLCPack *dlcTexturePack = NULL;

		if(type == e_DLCType_TexturePack)
		{
			dlcTexturePack = new DLCPack(pack->getName(), pack->getLicenseMask());
		}
		else if(type != e_DLCType_PackConfig)
		{
			dlcFile = pack->addFile(type,(WCHAR *)pFile->wchFile);
		}

		// Params
		uiParameterCount=*(unsigned int *)pbTemp;
		pbTemp+=sizeof(int);
		pParams = (C4JStorage::DLC_FILE_PARAM *)pbTemp;
		for(unsigned int j=0;j<uiParameterCount;j++)
		{
			//DLCManager::EDLCParameterType paramType = DLCManager::e_DLCParamType_Invalid;

			AUTO_VAR(it, parameterMapping.find( pParams->dwType ));

			if(it != parameterMapping.end() )
			{
				if(type == e_DLCType_PackConfig)
				{
					pack->addParameter(it->second,(WCHAR *)pParams->wchData);
				}
				else
				{
					if(dlcFile != NULL) dlcFile->addParameter(it->second,(WCHAR *)pParams->wchData);
					else if(dlcTexturePack != NULL) dlcTexturePack->addParameter(it->second, (WCHAR *)pParams->wchData);
				}
			}
			pbTemp+=sizeof(C4JStorage::DLC_FILE_PARAM)+(sizeof(WCHAR)*pParams->dwWchCount);
			pParams = (C4JStorage::DLC_FILE_PARAM *)pbTemp;
		}
		//pbTemp+=ulParameterCount * sizeof(C4JStorage::DLC_FILE_PARAM);

		if(dlcTexturePack != NULL)
		{
			DWORD texturePackFilesProcessed = 0;
			bool validPack = processDLCDataFile(texturePackFilesProcessed,pbTemp,pFile->uiFileSize,dlcTexturePack);
			pack->SetDataPointer(NULL); // If it's a child pack, it doesn't own the data
			if(!validPack || texturePackFilesProcessed == 0)
			{
				delete dlcTexturePack;
				dlcTexturePack = NULL;
			}
			else
			{
				pack->addChildPack(dlcTexturePack);

				if(dlcTexturePack->getDLCItemsCount(DLCManager::e_DLCType_Texture) > 0)
				{
					Minecraft::GetInstance()->skins->addTexturePackFromDLC(dlcTexturePack, dlcTexturePack->GetPackId() );
				}
			}
			++dwFilesProcessed;
		}
		else if(dlcFile != NULL)
		{
			// Data
			dlcFile->addData(pbTemp,pFile->uiFileSize);

			// TODO - 4J Stu Remove the need for this vSkinNames vector, or manage it differently
			switch(pFile->dwType)
			{
			case DLCManager::e_DLCType_Skin:
				app.vSkinNames.push_back((WCHAR *)pFile->wchFile);
				break;
			}

			++dwFilesProcessed;
		}

		// Move the pointer to the start of the next files data;
		pbTemp+=pFile->uiFileSize;
		uiCurrentByte+=sizeof(C4JStorage::DLC_FILE_DETAILS)+pFile->dwWchCount*sizeof(WCHAR);

		pFile=(C4JStorage::DLC_FILE_DETAILS *)&pbData[uiCurrentByte];
	}

	if( pack->getDLCItemsCount(DLCManager::e_DLCType_GameRules) > 0
		|| pack->getDLCItemsCount(DLCManager::e_DLCType_GameRulesHeader) > 0)
	{
		app.m_gameRules.loadGameRules(pack);
	}

	if(pack->getDLCItemsCount(DLCManager::e_DLCType_Audio) > 0)
	{
		//app.m_Audio.loadAudioDetails(pack);
	}
	// TODO Should be able to delete this data, but we can't yet due to how it is added to the Memory textures (MEM_file)

	return true;
}

DWORD DLCManager::retrievePackIDFromDLCDataFile(const string &path, DLCPack *pack)
{
	DWORD packId = 0;
	wstring wPath = convStringToWstring(path);

	HANDLE file = CreateFile(path.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if( file == INVALID_HANDLE_VALUE )
	{
		return 0;
	}

	DWORD bytesRead,dwFileSize = GetFileSize(file,NULL);
	PBYTE pbData =  (PBYTE) new BYTE[dwFileSize];
	BOOL bSuccess = ReadFile(file,pbData,dwFileSize,&bytesRead,NULL);
	if(bSuccess==FALSE)
	{
		// need to treat the file as corrupt, and flag it, so can't call fatal error
		//app.FatalLoadError();
	}
	else
	{
		CloseHandle(file);
	}
	if(bSuccess==FALSE)
	{
		// Corrupt or some other error. In any case treat as corrupt
		app.DebugPrintf("Failed to read %s from DLC content package\n", path.c_str());
		delete [] pbData;
		return 0;
	}
	packId=retrievePackID(pbData, bytesRead, pack);
	delete [] pbData;

	return packId;
}

DWORD DLCManager::retrievePackID(PBYTE pbData, DWORD dwLength, DLCPack *pack)
{
	DWORD packId=0;
	bool bPackIDSet=false;
	unordered_map<int, DLCManager::EDLCParameterType> parameterMapping;
	unsigned int uiCurrentByte=0;

	// File format defined in the DLC_Creator
	// File format: Version 2
	// unsigned long, version number
	// unsigned long, t = number of parameter types
	// t * DLC_FILE_PARAM structs mapping strings to id's
	// unsigned long, n = number of files
	// n * DLC_FILE_DETAILS describing each file in the pack
	// n * files of the form
	// // unsigned long, p = number of parameters
	// // p * DLC_FILE_PARAM describing each parameter for this file
	// // ulFileSize bytes of data blob of the file added
	unsigned int uiVersion=*(unsigned int *)pbData;
	uiCurrentByte+=sizeof(int);

	if(uiVersion < CURRENT_DLC_VERSION_NUM)
	{
		app.DebugPrintf("DLC version of %d is too old to be read\n", uiVersion);
		return 0;
	}
	pack->SetDataPointer(pbData);
	unsigned int uiParameterCount=*(unsigned int *)&pbData[uiCurrentByte];
	uiCurrentByte+=sizeof(int);
	C4JStorage::DLC_FILE_PARAM *pParams = (C4JStorage::DLC_FILE_PARAM *)&pbData[uiCurrentByte];
	for(unsigned int i=0;i<uiParameterCount;i++)
	{
		// Map DLC strings to application strings, then store the DLC index mapping to application index
		wstring parameterName((WCHAR *)pParams->wchData);
		DLCManager::EDLCParameterType type = DLCManager::getParameterType(parameterName);
		if( type != DLCManager::e_DLCParamType_Invalid )
		{
			parameterMapping[pParams->dwType] = type;
		}
		uiCurrentByte+= sizeof(C4JStorage::DLC_FILE_PARAM)+(pParams->dwWchCount*sizeof(WCHAR));
		pParams = (C4JStorage::DLC_FILE_PARAM *)&pbData[uiCurrentByte];
	}

	unsigned int uiFileCount=*(unsigned int *)&pbData[uiCurrentByte];
	uiCurrentByte+=sizeof(int);
	C4JStorage::DLC_FILE_DETAILS *pFile = (C4JStorage::DLC_FILE_DETAILS *)&pbData[uiCurrentByte];

	DWORD dwTemp=uiCurrentByte;
	for(unsigned int i=0;i<uiFileCount;i++)
	{
		dwTemp+=sizeof(C4JStorage::DLC_FILE_DETAILS)+pFile->dwWchCount*sizeof(WCHAR);
		pFile = (C4JStorage::DLC_FILE_DETAILS *)&pbData[dwTemp];
	}
	PBYTE pbTemp=((PBYTE )pFile);
	pFile = (C4JStorage::DLC_FILE_DETAILS *)&pbData[uiCurrentByte];

	for(unsigned int i=0;i<uiFileCount;i++)
	{
		DLCManager::EDLCType type = (DLCManager::EDLCType)pFile->dwType;

		// Params
		uiParameterCount=*(unsigned int *)pbTemp;
		pbTemp+=sizeof(int);
		pParams = (C4JStorage::DLC_FILE_PARAM *)pbTemp;
		for(unsigned int j=0;j<uiParameterCount;j++)
		{
			AUTO_VAR(it, parameterMapping.find( pParams->dwType ));

			if(it != parameterMapping.end() )
			{
				if(type==e_DLCType_PackConfig)
				{
					if(it->second==e_DLCParamType_PackId)
					{				
						wstring wsTemp=(WCHAR *)pParams->wchData;
						std::wstringstream ss;
						// 4J Stu - numbered using decimal to make it easier for artists/people to number manually
						ss << std::dec << wsTemp.c_str();
						ss >> packId;
						bPackIDSet=true;
						break;
					}
				}
			}
			pbTemp+=sizeof(C4JStorage::DLC_FILE_PARAM)+(sizeof(WCHAR)*pParams->dwWchCount);
			pParams = (C4JStorage::DLC_FILE_PARAM *)pbTemp;
		}

		if(bPackIDSet) break;
		// Move the pointer to the start of the next files data;
		pbTemp+=pFile->uiFileSize;
		uiCurrentByte+=sizeof(C4JStorage::DLC_FILE_DETAILS)+pFile->dwWchCount*sizeof(WCHAR);

		pFile=(C4JStorage::DLC_FILE_DETAILS *)&pbData[uiCurrentByte];
	}

	parameterMapping.clear();
	return packId;
}