///////////////////////////////////////////////////////////////////////////////
// MW2MechImporter.cpp
// Copyright Bjoern Ganster
// Developed since 2009
///////////////////////////////////////////////////////////////////////////////

#include "MechVM.h"
#include "Mesh.h"
#include "MW2MechImporter.h"
#include "MechWarriorInstallers.h" // CD types
#include "MWBase.h"
#include "Vehicles.h"
#include "XMLTree.h"

#include <iostream>

#define FILE_NUMBER FILE_NO_MW2MECHIMPORTER_CPP

///////////////////////////////////////////////////////////////////////////////
// Constructor

MW2MechImporter::MW2MechImporter(int cdType)
: VirtualClass("MW2MechImporter", MW2MechImporterID),
  prj(),
  mechsImported(0),
  mCdType(cdType)
{
}

///////////////////////////////////////////////////////////////////////////////
// Destructor

MW2MechImporter::~MW2MechImporter()
{
	SET_SCOPE;
	for (size_t i = 0; i < textures.size(); i++) {
		MW2TextureTableEntry* entry = textures[i];
		delete entry;
	}
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////
// Open MW2.PRJ

bool MW2MechImporter::setPRJ(const PathChar* PRJFN)
{
	SET_SCOPE;
	if (!prj.open(PRJFN)) {
		COUT << "Could not open file for import: " << PRJFN << std::endl;
		RETURN false;
	}
	if (!prj.enterSubDir("BWD")) {
		COUT << "Strange PRJ file without BWD subdirectory" << std::endl;
		RETURN false;
	} else {
		RETURN true;
	}
}

///////////////////////////////////////////////////////////////////////////////
// Build texture table

bool MW2MechImporter::buildTextureTable(const char* BWDFN)
{
	SET_SCOPE;
	size_t fileNum = prj.getEntryNumberByName(BWDFN);
	if (fileNum < prj.getEntryCount()) {
		bool result = buildTextureTable((DWORD)fileNum);
		RETURN result;
	} else {
		COUT << BWDFN << " not found - cannot build texture table" << std::endl;
		RETURN false;
	}
}

bool MW2MechImporter::buildTextureTable(DWORD fileNum)
{
	// Enter BWD directory
	SET_SCOPE;
	if (!prj.enterSubDir("BWD")) {
		RETURN false;
	}

	if (fileNum >= prj.getEntryCount()) {
		RETURN false;
	}

	// Get memory block for BWD file
	size_t bwdSize = prj.getFileSize(fileNum);
	MemoryBlock mb(bwdSize);
	prj.getFileChunk(fileNum, 0, mb.getPtr<unsigned char>(), bwdSize);
	BGCPString BWDFN;
	prj.getEntryName(fileNum, BWDFN);
	COUT << "Analyzing ";
	BWDFN.printLn();

	// Scan BWD's
	size_t offset = 0x34, bmidCount = 0, inclCount = 0, palgCount = 0;
	WORD BMPJ = -1;

	while (offset < mb.getSize()) {
		const char* tagName = (const char*)mb.getPtr<unsigned char>(offset);
		if (memcmp(tagName, "INCL", 4) == 0) {
			DWORD inclFileNum = mb.get<WORD>(offset + 8);
			buildTextureTable(inclFileNum);
			inclCount++;
		} else if (memcmp(tagName, "BMPJ", 4) == 0) {
			BMPJ = mb.get<WORD>(offset + 8);
		} else if (memcmp(tagName, "BMID", 4) == 0) {
			WORD BMID = mb.get<WORD>(offset + 8);
			MW2TextureTableEntry* entry = new MW2TextureTableEntry();
			textures.push_back(entry);
			entry->BMID = BMID;
			entry->BMPJ = BMPJ;
			entry->texture = NULL;
			entry->manText = NULL;
			bmidCount++;
		} else if (memcmp(tagName, "PALG", 4) == 0) {
			palgCount++;
		}

		DWORD tagSize = mb.get<DWORD>(offset + 4);
		offset += tagSize;
	}

	BWDFN.print();
	COUT << " contains " << bmidCount << " BMID tags, " << inclCount << " INCL tags, "
		 << palgCount << " PALG tags" << std::endl;
	RETURN true;
}

///////////////////////////////////////////////////////////////////////////////
// Retrieve texture, loading it when necessary

ManagedTexture* MW2MechImporter::getTexture(TextureCompiler& tc, int textureID)
{
	SET_SCOPE;
	for (size_t i = 0; i < textures.size(); i++) {
		MW2TextureTableEntry* entry = textures[i];
		if (entry->BMID == textureID) {
			if (entry->texture == NULL) {
				BGCPString fn;
				prj.enterSubDir("CEL");
				if (prj.getEntryName(entry->BMPJ, fn)) {
					//COUT << "Loading ";
					//fn.printLn();
					MemoryBlock mb;
					prj.loadFile(fn, mb);
					FileType ft = getFileTypeFromExtension(fn.getAsPath());
					entry->texture = new Texture();
					switch (ft) {
						case FileType::TEX:
							prj.loadTEX(mb.getPtr<unsigned char>(), mb.getSize(), *entry->texture);
							break;
						case FileType::XEL:
							prj.loadXEL(mb, palette, *entry->texture);
							break;
						case FileType::MW2_555:
							Decode_MW2_555(mb, *entry->texture);
							break;
						default:
							COUT << "Unknown format: ";
							fn.printLn();
							break;
					}
					if (entry->texture != NULL) {
						entry->manText = tc.add(entry->texture);
					}
					RETURN entry->manText;
				} else {
					#ifdef _DEBUG
					COUT << "Cannot open texture with BMPJ=" << entry->BMPJ << ", BMID="
					     << entry->BMID << std::endl;
					#endif
				}
			} else {
				if (entry->manText == NULL)
					entry->manText = tc.add(entry->texture);
				RETURN entry->manText;
			}
		}
	}

	RETURN NULL;
}

///////////////////////////////////////////////////////////////////////////////
// Set palette

void MW2MechImporter::setPalette(MemoryBlock* mb)
{
	const int PAL_SIZE = 768;
	if (mb == NULL
	||  mb->getSize() != PAL_SIZE)
	{
		return;
	}

	/*palette.write(0, mb->getPtr<unsigned char>(), mb->getSize());

	size_t i = 0;
	while (i < palette.getSize()) {
		MW2TextureTableEntry* entry = new MW2TextureTableEntry;
		entry->BMID = i;
		entry->BMPJ = i;
		entry->texture = new Texture(4, 4, GL_RGB);
		unsigned char r = palette.get<uint8_t>(i++);
		unsigned char g = palette.get<uint8_t>(i++);
		unsigned char b = palette.get<uint8_t>(i++);
		for (int x = 0; x < 4; x++)
			for (int y = 0; y < 4; y++)
				entry->texture->setRGB(x, y, r, g, b);
	}*/

	SET_SCOPE;
	memcpy(palette.values, mb->getPtr<unsigned char>(), PAL_SIZE);
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Copy MW2 mech config file (*.MEK)

void MW2MechImporter::addMW2Stats(XMLTreeConstIterator& MW2MechTreeIter,
	const PathChar* outputPath)
{
	SET_SCOPE;
	BGCPString mechName, mechFN;
	MW2MechTreeIter.getAttributeValueByName("name", mechName);
	MW2MechTreeIter.getAttributeValueByName("mekfile", mechFN);

	if (mechName.empty() || mechFN.empty()) {
		RETURN;
	}

	prj.leaveDir();
	size_t mekDir = prj.getEntryNumberByName("MEK");
	if (!prj.enterSubDir(mekDir)) {
		COUT << "MEK dir not found!" << std::endl;
		RETURN;
	}
	size_t mekNum = prj.getEntryNumberByName(mechFN.getPtr<char>());

	if (mekNum >= prj.getEntryCount()) {
		COUT << "Cannot import ";
		mechFN.print();
		std::wcout << " of ";
		mechName.printLn();
		RETURN;
	}

	size_t mekSize = prj.getFileSize(mekNum);
	MemoryBlock mekF(mekSize);
	if (prj.getFileChunk(mekNum, 0, mekF.getPtr<unsigned char>(0), mekSize)) {
		BGCPString FN(outputPath, CP_SYSTEM);
		FN.addPathSep();
		FN += mechName;
		FN += ".mek";
		mekF.saveToFile(FN.getAsPath());
	} else {
		COUT << "Importing ";
		mechName.print();
		COUT << " from ";
		mechFN.printLn();
	}
	prj.leaveDir();
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////
// Load mech limb from MemoryBlock
// MechWarriorIIPRJ::loadWTB does something similar, but does not know about 
// textures, texture coordinates or a texture manager ...

Mesh* MW2MechImporter::loadLimb(const MemoryBlock* block, TextureCompiler& tc, const BGCPString& limbName)
{
	SET_SCOPE;
	if (block->getSize() <= 32
	||  memcmp (block->getPtr<char>(0), "WTBO", 4) != 0)
	{
		printf ("Not a WTBO file?\n");
		RETURN nullptr;
	}

	bool flipPolygons = true; // Change polygon winding
	//bool flipPolygons = false; // Do not change polygon winding
	size_t pointRecordSize = 40;
	size_t pointBaseOffset = 32;
	size_t shortPolyRecordSize = 40;
	size_t longPolyRecordSize = 46;
	size_t longPolyRecordVertices = 5;
	size_t pointCountInPolygon = 30;
	size_t firstPointInPolygon = 32;

	size_t pointCount = block->get<WORD>(0x18);
	size_t polygonCount = block->get<WORD>(0x1a);
	double scale = 0.01;

	auto subtype = block->get<uint8_t>(0x1c);
	if (subtype > 0) {
		// Try alternate access constants
		pointRecordSize = 16;
		pointBaseOffset = 32;
		shortPolyRecordSize = 12;
		longPolyRecordSize = 18;
		longPolyRecordVertices = 5;
		pointCountInPolygon = 2;
		firstPointInPolygon = 4;
	}

	if (mCdType == MW2_31stcc_3DFX) {
		// Starting at 24h, every 28h we find a DWORD with value 7fh...
		// (beginning of BM1_HIPS.WTB)
		pointRecordSize = 40;
		pointCountInPolygon = 0x1e;
		shortPolyRecordSize = 0x28; //???
		longPolyRecordSize = 0x2e;
		firstPointInPolygon = 0x20;
		scale = 1.0;
	}

	size_t bc = pointCount * pointRecordSize + polygonCount * shortPolyRecordSize + pointBaseOffset;
	if (bc > block->getSize() || pointCount == 0 && polygonCount == 0) {
		COUT << "Size prediction error for WTB" << std::endl;
		RETURN nullptr;
	}

	SET_OP;
	Mesh* mesh = new Mesh();
	mesh->setName(limbName);

	// Read vertices
	size_t offset = pointBaseOffset;
	size_t i = 0;

	if (mCdType != MW2_31stcc_3DFX) {
		while (i < pointCount && offset < block->getSize()) {
			int x = block->get<int>(offset);
			int y = block->get<int>(offset + 4);
			int z = block->get<int>(offset + 8);
			mesh->addPoint(scale * x, scale * y, scale * z);
			offset += pointRecordSize;
			++i;
		}
	} else {
		while (i < pointCount && offset < block->getSize()) {
			auto x = block->get<double>(offset+12);
			auto y = block->get<double>(offset+20);
			auto z = block->get<double>(offset+28);
			mesh->addPoint(x, y, z);
			offset += pointRecordSize;
			++i;
		}
	}

	// Read polygons
	auto zeroTexCoords = mesh->addTexCoords(0.0, 0.0); // todo: adding zero-tex-coords should not be be necessary ...
	while (offset + shortPolyRecordSize <= block->getSize()
	&&     mesh->getPolygonCount() < polygonCount)
	{
		size_t verticesInThisPoly = block->get<WORD>(offset + pointCountInPolygon);
		int textureNum = block->get<uint8_t>(offset) + 256;
		ManagedTexture* manText = getTexture(tc, textureNum);

		if (offset + 2 * verticesInThisPoly + firstPointInPolygon <= block->getSize()) {
			MeshPolygon* poly = mesh->addPolygon(verticesInThisPoly);
			poly->m_manText = manText;

			for (size_t i = 0; i < verticesInThisPoly; i++) {
				size_t polyIndex = i;
				if (flipPolygons)
					polyIndex = verticesInThisPoly - i - 1;
				size_t pointNum = block->get<WORD>(offset + firstPointInPolygon + i * 2);
				if (pointNum >= mesh->getVertexCount())
					pointNum = 0;
				poly->setVertexIndex(polyIndex, pointNum);

				// Retrieve uv coordinates
				// There is one pair of texture coordinates per vertex, but with the creation of texture
				// atlases, we need to manage texture coordinates per polygon and per vertex
				if (manText != nullptr && manText->texture != nullptr) {
					size_t uvOffset = pointBaseOffset + pointNum * pointRecordSize;
					int u = block->get<int16_t>(uvOffset + 12);
					int v = block->get<int16_t>(uvOffset + 14);
					manText->checkUVs(u, v);
					double ud = ((double)u) / ((double)manText->texture->getWidth());
					double vd = ((double)v) / ((double)manText->texture->getHeight());
					auto ti = mesh->addTexCoords(ud, vd);
					poly->setTexCoordIndex(polyIndex, ti);
				}
			}
		}

		// Find next polygon
		if (verticesInThisPoly < longPolyRecordVertices) {
			offset += shortPolyRecordSize;
		} else {
			offset += longPolyRecordSize;
		}
	}

	RETURN mesh;
}

////////////////////////////////////////////////////////////////////////////////
// Copy data from mw2-mechs.xml into a new .mvmk file

void MW2MechImporter::createMVMKFile(XMLTreeConstIterator& importXmlIter,
	const PathChar* outputPath, const PathChar* outputFN)
{
	SET_SCOPE;
	XMLTree outputXml;
	XMLTreeIterator outputXmlIter(&outputXml, outputXml.getRoot());

	BGCPString mechName;
	importXmlIter.getAttributeValueByName("name", mechName);
	BGCPString vehicleType;
	importXmlIter.getNodeName(vehicleType);
	size_t outputXmlIterNum = outputXmlIter.addChild(vehicleType);
	outputXmlIter.goToChild(outputXmlIterNum);

	// Copy attributes for the main node
	for (size_t i = 0; i < importXmlIter.getAttributeCount(); ++i) {
		BGCPString attrName, attrVal;
		importXmlIter.getAttributeName(i, attrName);
		importXmlIter.getAttributeValueByIndex(i, attrVal);
		outputXmlIter.addAttribute(attrName, attrVal);
	}

	size_t geometryTagNum = outputXmlIter.addChild("geometry");
	outputXmlIter.goToChild(geometryTagNum);
	outputXmlIter.addAttribute("path", mechName + BGCPString(".obj"));
	outputXmlIter.goToParent();

	for (size_t i = 0; i < importXmlIter.getChildCount(); i++) {
		importXmlIter.goToChild(i);
		if (importXmlIter.nodeNameEquals("stats")) {
			addMW2Stats(importXmlIter, outputPath);
		} else if (importXmlIter.nodeNameEquals("copyConfig")) {
			BGCPString filename;
			importXmlIter.getAttributeValueByName("filename", filename);
			BGCPString ifn (getDataDir());
			ifn.addPathSep("fixed configs");
			ifn.addPathSep(filename);
			BGCPString ofn (outputPath, CP_SYSTEM);
			ofn.addPathSep(filename);
			copyFile(ifn.getAsPath(), ofn.getAsPath());
		} else if (importXmlIter.nodeNameEquals("config")
		|| importXmlIter.nodeNameEquals("anim")
		|| importXmlIter.nodeNameEquals("cockpit-layout")
		|| importXmlIter.nodeNameEquals("critical-layout"))
		{
			copyNodesRecursively(&importXmlIter, &outputXmlIter);
		} else if (importXmlIter.nodeNameEquals("geometry")) {
			BGCPString importPath = getDataDir();
			importPath.addPathSep("mech-import");
			BGCPString importFN;
			importXmlIter.getAttributeValueByName("path", importFN);
			importPath.addPathSep(importFN);
			BGCPString outputFN(outputPath, CP_SYSTEM);
			outputFN.addPathSep(importFN);
			copyFile(importPath.getAsPath(), outputFN.getAsPath());

			size_t newNode = outputXmlIter.addChild("geometry");
			outputXmlIter.goToChild(newNode);
			outputXmlIter.addAttribute("path", importFN);
			outputXmlIter.goToParent();
		} else {
			BGCPString limbName, nodeName;
			importXmlIter.getNodeName(nodeName);
			importXmlIter.getAttributeValueByName("mw2prj", limbName);
			importXmlIter.getAttributeValueByName("exportName", limbName);
			size_t limbNum = outputXmlIter.addChild(nodeName);
			bool hasLogoFlag = importXmlIter.hasAttribute("logo");
			outputXmlIter.goToChild(limbNum);
			BGCPString fixOption;
			importXmlIter.getAttributeValueByName("fix", fixOption);

			if (!limbName.empty()) {
				BGCPString limbPath = limbName;
				outputXmlIter.addAttribute("geometry", limbPath);

				BGCPString jxstr, jystr, jzstr;
				importXmlIter.getAttributeValueByName("jx", jxstr);
				importXmlIter.getAttributeValueByName("jy", jystr);
				importXmlIter.getAttributeValueByName("jz", jzstr);

				if (!jxstr.empty())
					outputXmlIter.addAttribute("jx", jxstr);
				if (!jystr.empty())
					outputXmlIter.addAttribute("jy", jystr);
				if (!jzstr.empty())
					outputXmlIter.addAttribute("jz", jzstr);
				if (hasLogoFlag)
					outputXmlIter.addAttribute("logo", "1");
				if (fixOption.startsWith("makeShadowProxy")) {
					outputXmlIter.addAttribute("shadowProxy", limbName + "_shadow_proxy");
				}

				BGCPString shadowProxyName;
				if (importXmlIter.getAttributeValueByName("shadowProxy", shadowProxyName)) {
					outputXmlIter.addAttribute("shadowProxy", shadowProxyName);
				}
			} else {
				outputXmlIter.copyFromChilds(importXmlIter);
			}
			outputXmlIter.goToParent();
		}
		importXmlIter.goToParent();
	}

	outputXml.saveToFile(outputFN, CP_ASCII);
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Copy single mech's limbs from MW2.PRJ

bool MW2MechImporter::copyMechComponents(XMLTreeConstIterator& MW2MechTreeIter,
	const PathChar* targetPath, const char* mechName)
{
	SET_SCOPE;
	RenderableObject limbs("Mech Part", RenderableObjectID);

	// It's tempting to make this a member, but it would have to be cleaned for
	// every mech. Therefore, it's easier if it's a local variable instead.
	TextureCompiler tc;

	for (size_t i = 0; i < MW2MechTreeIter.getChildCount(); i++) {
		MW2MechTreeIter.goToChild(i);
		BGCPString fixOption;
		MW2MechTreeIter.getAttributeValueByName("fix", fixOption);

		BGCPString limbName;
		MW2MechTreeIter.getAttributeValueByName("mw2prj", limbName);
		if (!limbName.empty()) {
			prj.enterSubDir("POLY");
			size_t fileNum = prj.getEntryNumberByName(limbName.getPtr<char>());
			if (fileNum < prj.getEntryCount()) {
				// Import limb from MW2 PRJ
				MemoryBlock mb;
				if (prj.getFile(fileNum, mb)) {
					Mesh* mesh = loadLimb(&mb, tc, limbName);
					if (mesh != NULL) {
						// Transform vertices
						// Read scale factor normally (assuming a default of 1.0), then apply a 0.01 scale factor later
						// Reason: we want to use scale factors in the order of 1.0 without having to remember the additional scale factor
						double sx = MW2MechTreeIter.getDoubleAttributeByName("sx", 1.0);
						double sy = MW2MechTreeIter.getDoubleAttributeByName("sy", 1.0);
						double sz = MW2MechTreeIter.getDoubleAttributeByName("sz", 1.0);
						double tx = MW2MechTreeIter.getDoubleAttributeByName("tx");
						double ty = MW2MechTreeIter.getDoubleAttributeByName("ty");
						double tz = MW2MechTreeIter.getDoubleAttributeByName("tz");
						Matrix M;

						double ry = MW2MechTreeIter.getDoubleAttributeByName("ry");
						if (fabs(ry) > 0.1) {
							M = RotationMatrix(M_PI * ry / 180.0, 0, 1, 0) * TranslationMatrix(tx, ty, tz) * ScaleMatrix(sx, sy, sz);
						} else
							M = TranslationMatrix(tx, ty, tz) * ScaleMatrix(sx, sy, sz);

						mesh->applyMatrix(M);
						MW2MechTreeIter.getAttributeValueByName("exportName", limbName);
						mesh->setName(limbName);
						limbs.addChild(mesh);

						// Attempt to fix holes in the mesh
						if (fixOption == "makeShadowProxy") {
							// Create a watertight shadow proxy
							Mesh* shadowProxy = new Mesh;
							shadowProxy->copyMesh(mesh);
							shadowProxy->findMeshEdges();
							shadowProxy->fillHoles();
							shadowProxy->setName(limbName + "_shadow_proxy");
							mesh->mShadowProxy = shadowProxy;
						} else if (fixOption.startsWith("makeShadowProxyWithPoint=")) {
							BGCPString coordsStr = fixOption.copyPart(25, fixOption.getByteCount()-25);
							auto coordsStrSplit = coordsStr.split(",", true);
							if (coordsStrSplit.size() >= 2) {
								Point3D p (coordsStrSplit[0].toDouble(), coordsStrSplit[1].toDouble(), coordsStrSplit[2].toDouble());
								Mesh* shadowProxy = new Mesh;
								shadowProxy->copyMesh(mesh);
								shadowProxy->findMeshEdges();
								shadowProxy->fillHoles3(p);
								shadowProxy->setName(limbName + "_shadow_proxy");
								mesh->mShadowProxy = shadowProxy;
							}
						} else if (fixOption != "none") {
							// Fix the mesh itself ...
							mesh->findMeshEdges();
							if ((mesh->getFlags() & RenderableObject::Flag_waterTight) == 0) {
								mesh->fillHoles();
							}
						}
					}
				}
				//imported = true;
			} else {
				log(BGCPString("Failed to import ") + BGCPString(mechName) + BGCPString(", ") + limbName + BGCPString(" could not be found"));
				MW2MechTreeIter.goToParent();
				RETURN false;
			}
		}

		MW2MechTreeIter.goToParent();
	}

	if (limbs.getObjectCount() > 0) {
		bgmkdir(targetPath);

		// Build texture atlas
		if (tc.getTextureCount() > 0) {
			Texture* atlas = tc.buildAtlas();
			if (atlas != nullptr) {
				BGCPString atlasFN(targetPath, CP_SYSTEM);
				atlasFN.addPathSep(BGCPString(mechName) + ".BMP");
				atlas->saveToFile(atlasFN.getAsPath(), 0, 0, atlas->getWidth(), atlas->getHeight());
			}

			// Adjust UVs
			for (size_t i = 0; i < limbs.getObjectCount(); i++) {
				Mesh* mesh = dynamic_cast<Mesh*>(limbs.getObject(i));
				for (size_t j = 0; j < mesh->getPolygonCount(); j++) {
					MeshPolygon* poly = mesh->getPolygon(j);
					ManagedTexture* manText = poly->m_manText;
					if (manText != NULL) {
						for (size_t k = 0; k < poly->getVertexCount(); k++) {
							double u, v;
							auto ti = poly->getTexCoordIndex(k);
							mesh->getTexCoords(ti, u, v);
							u = manText->transformU(u);
							v = manText->transformV(v);
							mesh->setTexCoords(ti, u, v);
						}
					}
				}
			}

			tc.clearAtlas();
		}

		BGCPString objFN(targetPath, CP_SYSTEM);
		objFN.addPathSep(BGCPString(mechName) + BGCPString(".obj"));
		limbs.saveToObj(objFN.getAsPath(), true);
	}

	// Clean up managed textures
	for (size_t i = 0; i < textures.size(); ++i) {
		MW2TextureTableEntry* entry = textures[i];
		if (entry->manText != NULL) {
			delete entry->manText;
			entry->manText = NULL;
		}
	}

	if (limbs.getObjectCount() > 0) {
		COUT << "Imported " << mechName << std::endl;
		RETURN true;
	} else {
		COUT << "Failed to import " << mechName << " (no limbs found)" << std::endl;
		RETURN true;
	}
}

///////////////////////////////////////////////////////////////////////////////
// Import

int MW2MechImporter::import()
{
	// Choose palette and texture based on cd type
	SET_SCOPE;

	const char* palName = "PINK_DA.COL";
	const char* textureTableName = "BET1MAP1.BWD";
	switch (mCdType) {
		case MW2_Mercs_DOS:
		case MW2_Mercs_3DFX:
		case MW2_Mercs_TT_BP:
		case MW2_Mercs_TITAN:
			// The DOS version still uses a palette, TT stores the palette in the texture
			//palName =  "cy0npal1.COL";
			palName =  "brow0nda.col";
			textureTableName = "BET1MAP1.BWD";
			break;
		default:
			palName = "PINK_DA.COL";
			textureTableName = "MW2_MAP1.BWD";
			break;
	}

	// Load MAP files
	prj.enterSubDir("BWD");
	buildTextureTable(textureTableName);

	// Choose palette
	prj.enterSubDir("PAL");
	auto fileNum = prj.getEntryNumberByName(palName);
	MemoryBlock mb;
	if (prj.getFile(fileNum, mb)) {
		setPalette(&mb);
	} else {
		COUT << "Missing palette: " << palName << std::endl;
	}

	// Load import control file
	BGCPString mw2path = getDataDir();
	mw2path.addPathSep("mw2-mechs.xml");
	XMLTree MW2MechTree;

	if (!prj.enterSubDir("POLY")) {
		COUT << "Strange PRJ file without POLY subdirectory" << std::endl;
		RETURN 0;
	}

	if (!MW2MechTree.loadFromFile(mw2path.getAsPath())) {
		COUT << "Import file missing: " << mw2path.getAsPath() << std::endl;
		RETURN 0;
	}

	XMLTreeConstIterator MW2MechTreeIter(MW2MechTree.getRoot());
	mechsImported = 0;

	for (size_t i = 0; i < MW2MechTreeIter.getChildCount(); i++) {
		// Take mech from MW2MechTree
		MW2MechTreeIter.goToChild(i);
		BGCPString mechName;
		if (MW2MechTreeIter.getAttributeValueByName("name", mechName)) {
			BGCPString targetPath(getDataDir());
			targetPath.addPathSep();
			targetPath += "mechs";
			targetPath.addPathSep();
			targetPath += "mw2-";
			targetPath += mechName;

			BGCPString outFN = targetPath;
			outFN.addPathSep();
			outFN += mechName;
			outFN += ".mvmk";

			if (!fileExists(outFN.getAsPath())) {
				// Copy files from MW2 project file
				if (copyMechComponents(MW2MechTreeIter, targetPath.getAsPath(), mechName.getPtr<char>())) {
					createMVMKFile(MW2MechTreeIter, targetPath.getAsPath(), outFN.getAsPath());
					mechsImported++;
				}
			}
		}
		MW2MechTreeIter.goToParent();
	}

	RETURN mechsImported;
}

///////////////////////////////////////////////////////////////////////////////
// Import mechs from a MW2 installation

size_t importMW2Files(const PathChar* source, int cdType)
{
	// Check for MW2 installation
	SET_SCOPE;
	BGCPString PRJPath = BGCPString(source, CP_SYSTEM);
	BGCPString netMechPath = PRJPath;
	PRJPath.addPathSep("mw2.prj");
	netMechPath.addPathSep("net.prj");

	MW2MechImporter importer(cdType);
	if (importer.setPRJ(PRJPath.getAsPath())) {
		COUT << "Importing from MW2 installation\n";
		importer.import();
	} else if (importer.setPRJ(netMechPath.getAsPath())) {
		COUT << "Importing from MW2 installation\n";
		importer.import();
	} else {
		COUT << "Cannot import - MW2.PRJ not found\n";
	}

	RETURN importer.getImportedMechCount();
}

// Get number of imported mechs
size_t MW2MechImporter::getImportedMechCount() const
{
	return mechsImported;
}
