////////////////////////////////////////////////////////////////////////////////
// MechLab.cpp
// Mech Configuration window for MechVM
// Copyright Bjoern Ganster
// Developed since 2007
////////////////////////////////////////////////////////////////////////////////

#include "MechLab.h"

#include "BGCPString.h"
#include "BipedMech.h"
#include "dialogs.h"
#include "FileList.h"
#include "Font.h"
#include "MechVM.h"
#include "Config.h"
#include "GLCheckBox.h"
#include "XMLTree.h"

#include "GL/glu.h"

#include <iostream>

#define FILE_NUMBER FILE_NO_MECHLAB_CPP

const double FOVY = 60.0;
int y = 100;
int labelW = 300;
int marginX = 20;
int marginY = 10;
int sliderW = 400;
int editWidth = 300;

////////////////////////////////////////////////////////////////////////////////

const int MechLab_MechsTableID = 1024;
const int MechLab_WeaponsTableID = 1025;
const int MechLab_SectionID = 1026;
const int MechLab_SectionSelectionTable = 1029;
const int MechLab_WeaponGroupTable = 1030;

const int MechLab_TorsoTilt = 1500;
const int MechLab_TorsoTwist = 1501;
const int MechLab_ArmTilt = 1502;
const int MechLab_ArmTwist = 1503;

// +0 accessing the slider, +1 for label
const int MechLab_Equipment_EngineRatingID = 2048;
const int MechLab_Equipment_EngineTypeID = 2050;
const int MechLab_Equipment_HeatSinkCount = 2052;
const int MechLab_Equipment_HeatSinkType = 2054;
const int MechLab_Equipment_InternalStructureType = 2056;
const int MechLab_Equipment_ArmorType = 2058;
const int MechLab_Equipment_LeftLowerArmActuator = 2060;
const int MechLab_Equipment_LeftHandActuator = 2062;
const int MechLab_Equipment_RightLowerArmActuator = 2064;
const int MechLab_Equipment_RightHandActuator = 2066;
const int MechLab_Equipment_ArmorFront = 2068; // uses 8 IDs
const int MechLab_Equipment_ArmorRear = 2084; // uses 8 IDs
const int MechLab_CommentID = 2094;
const int WeaponGroupAssignerID = 2100;

////////////////////////////////////////////////////////////////////////////////

void MechList::addMech(const BGCPString& name, const BGCPString& xml,
	const BGCPString& mek, const BGCPString& textFN, const PathChar* mechDir)
{
	SET_SCOPE;
	MechData data;
	data.mechName = name;
	data.mechXML = xml;
	data.mechCFG = mek;
	data.mechTexture = textFN;
	data.mechDir = BGCPString(mechDir, CP_SYSTEM);
	mechData.push_back(data);
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////

void MechList::addMVMK(const PathChar* mechDir, const PathChar* MVMKfile)
{
	SET_SCOPE;
	BGCPString textureFN;
	BGCPString textureMask(mechDir, CP_SYSTEM);
	textureMask.addPathSep("*.BMP");
	FileList textList;
	if (textList.start(textureMask.getAsPath())) {
		textureFN = BGCPString(mechDir, CP_SYSTEM);
		textureFN.addPathSep();
		textureFN += BGCPString(textList.getFileName(), CP_SYSTEM);
	}

	textureMask = BGCPString(mechDir, CP_SYSTEM);
	textureMask.addPathSep("*.png");
	if (textList.start(textureMask.getAsPath())) {
		textureFN = BGCPString(mechDir, CP_SYSTEM);
		textureFN.addPathSep();
		textureFN += BGCPString(textList.getFileName(), CP_SYSTEM);
	}

	int configs = 0;
	XMLTree xmlTree;
	xmlTree.loadFromFile(MVMKfile);
	XMLTreeNode* root = xmlTree.getRoot();
	if (root != nullptr) {
		XMLTreeConstIterator iter(root);
		for (size_t i = 0; i < iter.getChildCount(); ++i) {
			iter.goToChild(i);
			if (iter.nodeNameEquals("config")) {
				BGCPString mechName, cfgFN;
				if (iter.getAttributeValueByName("mechName", mechName)
				&&  iter.getAttributeValueByName("file", cfgFN)) 
				{
					BGCPString fullCfgFN = BGCPString(mechDir, CP_SYSTEM);
					fullCfgFN.addPathSep();
					fullCfgFN += cfgFN;
					addMech(mechName, BGCPString(MVMKfile, CP_SYSTEM), fullCfgFN, textureFN, mechDir);
					++configs;
				}
			}
			iter.goToParent();
		}

		// If we did not find a config, add one here
		if (configs == 0) {
			// MVMK does not mention a config, use all configs in that directory
			BGCPString mechName;
			iter.getAttributeValueByName("name", mechName);
			BGCPString mask2(mechDir, CP_SYSTEM);
			mask2.addPathSep("*.mek"); // must be lower case for Linux
			FileList mekList;
			if (mekList.start(mask2.getAsPath())) {
				do {
					const PathChar* mekFN = mekList.getFileName();
					BGCPString cfgFN(mechDir, CP_SYSTEM); // must be reset
					cfgFN.addPathSep();
					cfgFN += BGCPString(mekFN, CP_SYSTEM);
					mechName = BGCPString(mekFN, CP_SYSTEM);
					addMech(mechName, BGCPString(MVMKfile, CP_SYSTEM), cfgFN, textureFN, mechDir);
				} while (++mekList);
			} else {
				addMech(mechName, BGCPString(MVMKfile, CP_SYSTEM), nullptr, textureFN, mechDir);
			}
		}
	}
	
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Build list of mechs to populate the chassis table

void MechList::buildMechList(const PathChar* mechDir)
{
	// Search all subdirectories for mech definitions
	SET_SCOPE;
	BGCPString searchPath = getDataDir();
	searchPath.addPathSep(MECH_SUB_DIR);
	BGCPString searchMask = searchPath;
	searchMask.addPathSep("*");

	BGCPString msg = BGCPString("Searching ") + searchPath + BGCPString(" for mechs\n");
	msg.print();

	FileList list;
	if (list.start(searchMask.getAsPath())) {
		do {
			SET_OP;
			BGCPString mechDir = searchPath;
			mechDir.addPathSep(BGCPString(list.getFileName(), CP_SYSTEM));

			BGCPString mask = mechDir;
			mask.addPathSep("*.mvmk"); // must be lower case for Linux
			FileList MVMKlist;
			bool found = MVMKlist.start(mask.getAsPath());

			while (found) {
				BGCPString MVMKfile(mechDir);
				MVMKfile.addPathSep(BGCPString(MVMKlist.getFileName(), CP_SYSTEM));
				addMVMK(mechDir.getAsPath(), MVMKfile.getAsPath());
				found = ++MVMKlist;
			}
		} while (++list);
	}

	COUT << "Found " << mechData.size() << " mechs in " << mechDir << std::endl;
	RETURN;
}

bool MechList::getMechName(size_t index, BGCPString& result) const
{
	if (index < mechData.size()) {
		result = mechData[index].mechName;
		return true;
	} else {
		result.clear();
		return false;
	}
}

BGCPString MechList::getMVMKFN(size_t index) const
{
	BGCPString result;
	if (index < mechData.size()) {
		result = mechData[index].mechXML;
	}
	return result;
}

BGCPString MechList::getTextureFN(size_t index) const
{
	if (index < mechData.size()) {
		return mechData[index].mechTexture;
	} else {
		return BGCPString();
	}
}

BGCPString MechList::getConfigFN(size_t index) const
{
	if (index < mechData.size()) {
		return mechData[index].mechCFG;
	} else {
		return BGCPString();
	}
}

Vehicle* MechList::loadMech(size_t index)
{
	SET_SCOPE;
	if (index >= mechData.size()) {
		RETURN NULL;
	}

	// Load mech geometry from XML description
	SET_OP;
	auto& data = mechData[index];
	BGCPString partPathStr = mechData[index].mechDir;
	Vehicle* vehicle = loadVehicle(data.mechName, data.mechXML, partPathStr, data.mechTexture, "Clan Jade Falcon");

	if (vehicle != NULL) {
		SET_OP;
		vehicle->loadConfig(mechData[index].mechCFG.getAsPath());
		vehicle->updateLoadoutGeometry();
	}

	RETURN vehicle;
}

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

MechLab::MechLab(int buttons, GLWindow* returnWin)
: GLWindowContainer("MechLab", MechLab_ID),
  mReturnWin (returnWin),
  vehicle(NULL),
  m_buttons (buttons),
  importGeometryButton (NULL),
  assignPoliesButton (NULL),
  createTextureButton (NULL),
  animateMechButton (NULL), 
  repairMechButton (NULL), 
  configureMechButton (NULL),
  equipmentButton (NULL), 
  weaponGroupsButton (NULL), 
  exitButton (NULL),
  label(NULL),
  leftButtonDown (false), 
  rightButtonDown (false),
  renderMech (true),
  LimitedStock (true),
  changed (false),
  currSection (0),
  mechTable (NULL), 
  weaponsTable (NULL),
  sectionTable (NULL),
  sectionSelectionTable (NULL),
  mechList("MechLab"),
  torsoTilt(0.0),
  torsoTwist(0.0)
{
	Config* config = getConfig();
	BGCPString mechDir(getDataDir());
	mechDir.addPathSep(MECH_SUB_DIR);
	mechList.buildMechList(mechDir.getAsPath());
	weaponsLocker = new WeaponsLocker();

	initDialog();

	Font* font = getDefaultFont();
	labelW = 16 * font->getMaxCharWidth();
}

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

MechLab::~MechLab()
{
	safeDelete(weaponsLocker);
	safeDelete(vehicle);
}

////////////////////////////////////////////////////////////////////////////////

void MechLab::initDialog()
{
	SET_SCOPE;
	clear();
	mArmorSumLabels.clear();

	if ((m_buttons & MechLab_selectChassisButtonID) != 0) {
		importGeometryButton = addToolbarButton(tr(20), MechLab_selectChassisButtonID);
	}
	#ifdef _DEBUG
	if ((m_buttons & MechLab_animateMechButtonID) != 0) {
		animateMechButton = addToolbarButton(tr(21), MechLab_animateMechButtonID);
	}
	#endif
	if ((m_buttons & MechLab_outfitMechButtonID) != 0) {
		animateMechButton = addToolbarButton(tr(22), MechLab_outfitMechButtonID);
	}
	if ((m_buttons & MechLab_equipmentButtonID) != 0) {
		equipmentButton = addToolbarButton(tr(23), MechLab_equipmentButtonID);
	}
	if ((m_buttons & MechLab_weaponGroupsID) != 0) {
		weaponGroupsButton = addToolbarButton(tr(44), MechLab_weaponGroupsID);
	}
	if ((m_buttons & MechLab_exitButtonID) != 0) {
		exitButton = addToolbarButton(tr(24), MechLab_exitButtonID);
	}

	int buttonHeight = getDefaultButtonHeight();
	label = new GLLabel (this, margin, buttonHeight+2*margin, width-2*margin, buttonHeight);
	label->setTitle ("");
	add (label);

	weaponsTable = NULL;
	mechTable = NULL;
	renderMech = true;
	RETURN;
}

GLButton* MechLab::addToolbarButton(const BGCPString& buttonName, int ID)
{
	SET_SCOPE;
	Font* font = getDefaultFont();
	int buttonWidth = 10 * font->getMaxCharWidth();
	size_t x = buttonDist + (buttonWidth+buttonDist) * windows.size();
	GLButton* newButton = new GLButton (this, (int) x, buttonTop, buttonWidth, getDefaultButtonHeight());
	newButton->setTitle(buttonName);
	newButton->userData = ID;
	newButton->setListener (this);
	add (newButton);
	RETURN newButton;
}

////////////////////////////////////////////////////////////////////////////////

void MechLab::addWeapons(const BGCPString& description, int year)
{
	if (description == "GBL")
		weaponsLocker->addClanWeapons(100, 100, 100, 100, 100, 100, 100, true);
	else if (description == "GBL_UW")
		weaponsLocker->addGBLUnderwaterWeapons(20, 50);
	else if (description == "31stcc")
		weaponsLocker->addClanWeapons(100, 100, 100, 100, 100, 100, 100, false);
	else if (description == "all")
		weaponsLocker->addWeaponsToStock (WeaponType::IS_Energy_Weapon_Base, WeaponType::CE_BOMB_100, 55);
	else if (description == "nmc1")
		weaponsLocker->addClanWeapons(100, 0, 0, 0, 0, 0, 100, false);
	else if (description == "nmc4")
		weaponsLocker->addClanWeapons(100, 2, 2, 100, 2, 2, 100, false);
	else if (description == "nmc5")
		weaponsLocker->addClanWeapons(100, 100, 100, 100, 4, 12, 100, false);
	else
		weaponsLocker->addISWeapons(20, 50, year);
}

////////////////////////////////////////////////////////////////////////////////

void MechLab::showChassisTable()
{
	Font* font = getDefaultFont();
	mechTable = new GLTableView (this, 20, 100, 14 * font->getMaxCharWidth(), height-120, MechLab_selectChassisButtonID);
	mechTable->setDataSource(this);
	mechTable->setColWidth(1, 50, 150);
	mechTable->userData = MechLab_MechsTableID;
	mechTable->setListener (this);
	mechTable->setRowHeights(getMechCount(), font->getCharHeight());
	mechTable->setSelectedRow(0);
	add (mechTable);
}

////////////////////////////////////////////////////////////////////////////////

//void MechLab::showMech(size_t num)
void MechLab::showMech(Vehicle* newVehicle)
{
	safeDelete(vehicle);
	vehicle = newVehicle;

	// Set display parameters
	viewDist = 30.0;
	eye = Point3D (0, 0, viewDist);
	center = Point3D (0, 0, 0);
	xDir = Point3D (1, 0, 0);
	yDir = Point3D (0, 1, 0);
	zDir = Point3D (0, 0, 1);
}

////////////////////////////////////////////////////////////////////////////////

void MechLab::setMechDir(const BGCPString& mechDir)
{
	vehicleDir = mechDir;
}

////////////////////////////////////////////////////////////////////////////////

void MechLab::showMechTonnage()
{
	SET_SCOPE;
	testHeap();
	vehicle->calcRealWeight();
	BGCPString msg = vehicle->getName();
	msg += ": ";
	msg += BGCPString::fromDouble(vehicle->getWeight(), 2);
	testHeap();
	msg += "t/";
	testHeap();
	int tonnage = vehicle->getInteger(Vehicle::Info_Tonnage);
	msg += BGCPString::fromInt(tonnage);
	testHeap();
	msg += "t";

	// Add warning?
	SET_OP;
	BGCPString bugs;
	if (!vehicle->check(bugs)) {
		msg += bugs;
	}

	SET_OP;
	label->setTitle(msg);
	Font* font = getDefaultFont();
	label->setLeft((getWidth()-font->textWidth(msg))/2);
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////

void MechLab::showAnimControls()
{
	SET_SCOPE;
	if (vehicle == NULL) {
		label->setTitle (tr(25));
		return;
	}

	SET_OP;
	int buttonHeight = getDefaultButtonHeight();
	createControls(MechLab_TorsoTilt, "Torso Tilt", 0, -30, 30);
	createControls(MechLab_TorsoTwist, "Torso Twist", 0, -90, 90);
	createControls(MechLab_ArmTilt, "Arm Tilt", 0, -30, 30);
	createControls(MechLab_ArmTwist, "Arm Twist", 0, -90, 90);
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////

void MechLab::showOutfitMechControls()
{
	SET_SCOPE;
	if (vehicle == NULL) {
		label->setTitle (tr(25));
		RETURN;
	}

	Font* font = getDefaultFont();
	int mcw = font->getMaxCharWidth();
	int top = buttonTop + getDefaultButtonHeight() + margin;
	int sectionSelectionTableW = 10 * mcw;
	int sectionSelectionTableH = 9 * font->getCharHeight();
	sectionSelectionTable = new GLTableView (this, margin, top, sectionSelectionTableW, sectionSelectionTableH, MechLab_SectionSelectionTable);
	sectionSelectionTable->setColWidth(3, 50, 100);
	sectionSelectionTable->setColWidth(0, 250, 100);
	sectionSelectionTable->setRowHeights(8, font->getCharHeight());
	sectionSelectionTable->userData = MechLab_SectionSelectionTable;
	sectionSelectionTable->setDataSource(this);
	sectionSelectionTable->setListener (this);
	sectionSelectionTable->setSelectedRow (currSection);
	add (sectionSelectionTable);

	top += sectionSelectionTableH + margin;
	int sectionTableH = 13*font->getCharHeight() + 5;
	sectionTable = new GLTableView (this, margin, top, sectionSelectionTableW, sectionTableH, MechLab_SectionID);
	sectionTable->setColWidth(0, 250, 20 * mcw);
	sectionTable->setRowHeights(12, font->getCharHeight());
	sectionTable->userData = MechLab_SectionID;
	sectionTable->setDataSource(this);
	sectionTable->setListener (this);
	add (sectionTable);

	top += sectionTableH + margin;
	weaponsTable = new GLTableView (this, margin, top, width-2*margin, 
		height-top-margin, MechLab_WeaponsTableID);
	weaponsTable->setColWidth(6, 6 * mcw, 6 * mcw);
	weaponsTable->setColWidth(0, 15 * mcw, 6 * mcw);
	weaponsTable->setRowHeights(WeaponsCount, font->getCharHeight());
	weaponsTable->userData = MechLab_WeaponsTableID;
	weaponsTable->setDataSource(this);
	weaponsTable->setListener (this);
	add (weaponsTable);

	showMechTonnage();
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////
// Create a pair of a label and another control
// type: 0=LineEdit, 1=Slider, 2=ComboBox

GLWindow* MechLab::createControls (int ID, const char* name, double value, double min, double max)
{
	SET_SCOPE;
	int num = (int) getWindowCount() / 2-2;
	int controlLeft, controlTop, groupHeight;
	GLLabel* label = NULL;
	GLWindow* result = NULL;

	int buttonHeight = getDefaultButtonHeight();
	controlLeft = margin;
	groupHeight = 2*buttonHeight + 2*margin;
	controlTop = num*groupHeight+buttonHeight+margin+100;

	label = new GLLabel (this, margin, num*groupHeight+100, 200, buttonHeight);
	GLSlider* slider = new GLSlider(this, controlLeft, controlTop, 
		200, buttonHeight);
	slider->setValue(value);
	slider->setMinMax(min, max);
	result = slider;

	add (label);
	label->setTitle (name);
	add (result);
	result->setListener (this);
	result->userData = ID;

	RETURN result;
}

void MechLab::addCheckbox (const char* labelTitle, int& y, bool initialState, int ID)
{
	SET_SCOPE;
	int buttonHeight = getDefaultButtonHeight();
	GLCheckBox* cb = new GLCheckBox (this, marginX, y, width, buttonHeight);
	cb->setTitle(labelTitle);
	cb->userData = ID;
	cb->setChecked(initialState);
	cb->setListener(this);
	add (cb);

	y += buttonHeight + marginY;
	RETURN;
}

GLSlider* MechLab::addSlider (const BGCPString& labelTitle, int min, int pos, int max, int& y, int ID)
{
	int buttonHeight = getDefaultButtonHeight();
	GLLabel* label = new GLLabel (this, marginX, y, labelW, buttonHeight);
	label->setTitle(labelTitle);
	add (label);

	GLSlider* slider = new GLSlider (this, labelW + 2*marginX, y, sliderW, buttonHeight);
	slider->setMinMax(min, max);
	slider->setValue(max-min);
	slider->setDialCount(max-min+1);
	slider->setValue(pos);
	slider->userData = ID;
	slider->setListener(this);
	add (slider);

	label =  new GLLabel (this, labelW + 3*marginX + sliderW, y, labelW, buttonHeight);
	label->setInt (pos);
	label->userData = ID+1;
	add (label);

	y += buttonHeight + marginY;
	return slider;
}

GLSlider* MechLab::addArmorSlider (int section, bool front, int& y)
{
	int pos, ID;
	int min = 0;
	int max = vehicle->getMaxArmor(section);
	BGCPString title = vehicle->getSectionName(section);
	int buttonHeight = getDefaultButtonHeight();

	if (front) {
		pos = vehicle->getArmor(section, Vehicle::Armor_Front);
		ID = MechLab_Equipment_ArmorFront+2*section;
	} else {
		title += " (rear)";
		pos = vehicle->getArmor(section, Vehicle::Armor_Rear);
		ID = MechLab_Equipment_ArmorRear+2*section;

		GLLabel* armorSumLabel = new GLLabel (this, (int) (1.3*labelW + 3*marginX + sliderW), y - buttonHeight + marginY/2, 
			editWidth, buttonHeight);
		armorSumLabel->setTitle("0/0");
		add (armorSumLabel);
		mArmorSumLabels[section] = armorSumLabel;
	}
	return addSlider (title, min, pos, max, y, ID);
}

GLComboBox* MechLab::addComboBox (const BGCPString& labelTitle, const BGCPString& cbTitle, int& y, int ID)
{
	int buttonHeight = getDefaultButtonHeight();
	GLLabel* label = new GLLabel (this, marginX, y, labelW, buttonHeight);
	label->setTitle(labelTitle);
	add (label);

	// ComboBox: Engine Type
	GLComboBox* cb = new GLComboBox (this, labelW + 2*marginX, y, sliderW, buttonHeight);
	cb->setText(cbTitle);
	cb->userData = ID;
	cb->setListener(this);
	add (cb);

	label =  new GLLabel (this,  labelW + 3*marginX + sliderW, y, editWidth, buttonHeight);
	label->userData = ID+1;
	add (label);
	y += buttonHeight + marginY;

	return cb;
}

///////////////////////////////////////////////////////////////////////////////

void MechLab::showEquipmentControls()
{
	if (vehicle == NULL) {
		label->setTitle (tr(25));
		return;
	}

	Font* font = getDefaultFont();
	int y = 3*margin + 2*font->getCharHeight();
	renderMech = false;

	int engineRating = vehicle->getInteger (Vehicle::Info_EngineRating);
	GLSlider* engineSlider = addSlider ("Engine Rating", 10, engineRating, 500, y,
		MechLab_Equipment_EngineRatingID);
	engineSlider->setDialCount(101);

	BGCPString engineTypeStr = "Standard";
	if (vehicle->getInteger(Vehicle::Info_EngineType) != ENGINE_TYPE_STD) 
		engineTypeStr = "XL";
	GLComboBox* cb = addComboBox ("Engine Type", engineTypeStr, y, MechLab_Equipment_EngineTypeID);
	cb->addChoice("Standard");
	cb->addChoice("XL");

	int heatSinks = vehicle->getInteger(Vehicle::Info_Heat_Sinks);
	addSlider ("Heat Sinks", 0, heatSinks, 51, y, MechLab_Equipment_HeatSinkCount);

	WeaponType hsType = vehicle->getHeatSinkType();
	BGCPString text = "Single";
	if (hsType != WeaponType::IS_Heat_Sink)
		text = "Double";
	cb = addComboBox ("Heat Sink Type", text, y, MechLab_Equipment_HeatSinkType);
	cb->addChoice("Single");
	cb->addChoice("Double");

	text = "Standard";
	if (vehicle->getInternalStructureType() != WeaponType::Unused_Critical_Slot)
		text = "Endo Steel";
	cb = addComboBox ("Internal Structure", text, y, 
		MechLab_Equipment_InternalStructureType);
	cb->addChoice("Standard");
	cb->addChoice("Endo Steel");

	text = "Standard";
	if (vehicle->getInteger(Vehicle::Info_Armor_Type) != ARMOR_TYPE_STD)
		text = "Ferro Fibrous";
	cb = addComboBox ("Armor Type", text, y, MechLab_Equipment_ArmorType);
	cb->addChoice("Standard");
	cb->addChoice("Ferro Fibrous");

	// Lower arm and hand activators
	int vehicleType = vehicle->getClassID();
	if (vehicleType == BipedMechID) {
		bool hasActuator = vehicle->countWeaponCriticals (Biped_Mech_Section_Left_Arm, 
			WeaponType::CE_Lower_Arm_Actuator) > 0;
		addCheckbox ("Left Lower Arm Actuator", y, hasActuator, 
			MechLab_Equipment_LeftLowerArmActuator);

		hasActuator = vehicle->countWeaponCriticals (Biped_Mech_Section_Left_Arm, 
			WeaponType::CE_Hand) > 0;
		addCheckbox ("Left Hand Actuator", y, hasActuator, 
			MechLab_Equipment_LeftHandActuator);

		hasActuator = vehicle->countWeaponCriticals (Biped_Mech_Section_Right_Arm, 
			WeaponType::CE_Lower_Arm_Actuator) > 0;
		addCheckbox ("Right Lower Arm Actuator", y, hasActuator, 
			MechLab_Equipment_RightLowerArmActuator);

		hasActuator = vehicle->countWeaponCriticals (Biped_Mech_Section_Right_Arm, 
			WeaponType::CE_Hand) > 0;
		addCheckbox ("Right Hand Actuator", y, hasActuator, 
			MechLab_Equipment_RightHandActuator);
	}

	// Armor
	auto sectionCount = vehicle->getSectionCount();
	mArmorSumLabels.resize(sectionCount);
	for (int section = 0; section < sectionCount; ++section) {
		addArmorSlider (section, true, y);
		if (vehicle->separateFrontAndRearArmor(section)) {
			addArmorSlider (section, false, y);
		}
	}

	updateEquipmentLabels();
}

///////////////////////////////////////////////////////////////////////////////

void MechLab::updateEquipmentLabels()
{
	// Engine rating label
	int engineRating = vehicle->getInteger(Vehicle::Info_EngineRating);
	int tonnage = vehicle->getInteger(Vehicle::Info_Tonnage);
	int engineType = vehicle->getInteger (Vehicle::Info_EngineType);
	int techType = vehicle->getInteger(Vehicle::Info_TechType);

	BGCPString text = BGCPString::fromInt(engineRating);
	if (engineType != ENGINE_TYPE_STD)
		text += "XL (";
	else
		text += " (";
	text += BGCPString::fromDouble(getEngineWeight(engineRating, engineType), 2);
	text += " t), ";
	if (tonnage > 0) {
		text += BGCPString::fromDouble(1.5*10.8*engineRating/tonnage, 1);
		text += "km/h";
	}
	GLLabel* label = findLabel (MechLab_Equipment_EngineRatingID+1);
	if (label != nullptr) {
		label->setTitle(text);
	}

	// Heat sink count label
	int heatSinkCount = vehicle->getInteger(Vehicle::Info_Heat_Sinks);
	text = BGCPString::fromInt(heatSinkCount);
	WeaponType heatSinkType = vehicle->getHeatSinkType();
	if (heatSinkType != WeaponType::IS_Heat_Sink) {
		text += "(";
		text += BGCPString::fromInt(2*heatSinkCount);
		text += "), ";
	} else
		text += ", ";

	if (heatSinkCount > 10) {
		text += BGCPString::fromInt (heatSinkCount-10);
		text += " t, ";
	} else
		text += " 0t, ";

	int HeatSinksInEngine = engineRating / 25;
	if (heatSinkCount > HeatSinksInEngine) {
		if (heatSinkType == WeaponType::IS_Heat_Sink)
			text += BGCPString::fromInt (heatSinkCount-HeatSinksInEngine);
		else if (techType == Tech_Type_Clan) {
			text += BGCPString::fromInt (2*(heatSinkCount-HeatSinksInEngine));
		} else
			text += BGCPString::fromInt (3*(heatSinkCount-HeatSinksInEngine));
		text += " criticals";
	} else
		text += "0 criticals";

	label = findLabel (MechLab_Equipment_HeatSinkCount+1);
	if (label != nullptr) {
		label->setTitle(text);
	}

	// Internal structure label
	label = findLabel (MechLab_Equipment_InternalStructureType+1);
	if (label != nullptr) {
		text = BGCPString::fromDouble(vehicle->calcInternalStructureWeight());
		text += " t";
		label->setTitle(text);
	}

	// Armor total
	//int ArmorType = vehicle->getInteger(Vehicle::Info_Armor_Type);
	label = findLabel (MechLab_Equipment_ArmorType+1);
	if (label != nullptr) {
		int armorPoints = vehicle->countArmorPoints();
		text = BGCPString::fromInt (armorPoints);
		text += " points, ";
		text += BGCPString::fromDouble(vehicle->calcArmorWeight());
		text += " t";
		label->setTitle(text);
	}

	// Front armor labels
	auto sectionCount = vehicle->getSectionCount();
	for (int i = 0; i < sectionCount; i++) {
		int armorPoints = vehicle->getArmor(i, Vehicle::Armor_Front);
		text = BGCPString::fromInt(armorPoints);
		if (i == 0) {
			text += "/9";
		} else if (i > 3) {
			text += "/";
			text += BGCPString::fromInt(2*vehicle->getInternalStructure(i));
		}
		GLLabel* label = findLabel (MechLab_Equipment_ArmorFront+2*i+1);
		if (label != nullptr) {
			label->setTitle(text);
		}
	}

	// Rear armor labels
	for (int i = 0; i < sectionCount; i++) {
		int armorPoints = vehicle->getArmor(i, Vehicle::Armor_Rear);
		text = BGCPString::fromInt(armorPoints);
		GLLabel* label = findLabel (MechLab_Equipment_ArmorRear+2*i+1);
		if (label != nullptr) {
			label->setTitle(text);
		}

		// Summed up armor
		if ((size_t) i < mArmorSumLabels.size() && mArmorSumLabels[i] != nullptr) {
			text = BGCPString::fromInt(vehicle->getArmor(i, Vehicle::Armor_Front) + 
				vehicle->getArmor(i, Vehicle::Armor_Rear));
			text += "/";
			text += BGCPString::fromInt(2*vehicle->getInternalStructure(i));
			mArmorSumLabels[i]->setTitle (text);
		}
	}

	showMechTonnage();
}

///////////////////////////////////////////////////////////////////////////////

void MechLab::showWeaponGroups()
{
	GLLabel* label = NULL;
	Font* font = getDefaultFont();
	int labelHeight = font->getCharHeight();
	label = new GLLabel (this, margin, 107, 200, labelHeight);
	label->setTitle(tr(45));
	add (label);
	int buttonHeight = getDefaultButtonHeight();
	GLLineEdit * edit = new GLLineEdit (this, margin + 200, 100, 
		300, buttonHeight);
	edit->userData = MechLab_CommentID;
	int buttonWidth = font->getMaxCharWidth();

	if (vehicle == NULL) {
		return;
	}

	edit->setText (vehicle->getComment());
	edit->setListener (this);
	add(edit);

	for (size_t i = 0; i < vehicle->getWeaponListEntryCount(); ++i) {
		const char* weaponName = vehicle->getWeaponListEntryName(i);
		int y = 107 +(i+1) * (labelHeight + margin);
		GLLabel* label = new GLLabel (this, margin, y, 
			200, labelHeight);
		label->setTitle(weaponName);
		add (label);

		for (size_t j = 0; j < MaxGroupCount; ++j) {
			BGCPString str = "X";
			if (vehicle->getGroup(i) != j) {
				str = BGCPString::fromInt(j+1);
			}
			GLButton* button = new GLButton (this, 200+(margin+buttonWidth)*j, y, buttonWidth, buttonHeight);
			button->setTitle(str);
			button->userData = WeaponGroupAssignerID + 5*i + j;
			button->setListener(this);
			add(button);
		}
	}
}

void MechLab::setBackground(const Texture& texture)
{
	mBackground = texture;
}

////////////////////////////////////////////////////////////////////////////////
// Receive messages

GLLabel* MechLab::findLabel (int ID)
{
   for (size_t i = 0; i < windows.size(); i++) {
      GLWindow* win = windows[i];
      if (win->userData == ID) {
         GLLabel* label = dynamic_cast <GLLabel*> (win);
         if (label != NULL)
            return label;
      }
   }

   return nullptr;
}

void MechLab::receiveMessage (VirtualClass* sender, int MessageType, VirtualClass* data)
{
	SET_SCOPE;
	GLWindow* win = dynamic_cast <GLWindow*> (sender);
	size_t row;
	WeaponType weapon;
	MechVM* mechvm = NULL;
	bool reallyExit = true;

	if (win == NULL)
		return;

	GLSlider* slider = dynamic_cast <GLSlider*> (win);
	GLComboBox* cb = dynamic_cast <GLComboBox*> (win);
	GLLineEdit* edit = dynamic_cast <GLLineEdit*> (sender);

	switch (win->userData) {
		case MechLab_selectChassisButtonID:
			SET_OP;
			initDialog ();
			showChassisTable();
			break;
		case MechLab_animateMechButtonID:
			SET_OP;
			initDialog ();
			showAnimControls();
			break;
		case MechLab_outfitMechButtonID:
			SET_OP;
			initDialog ();
			showOutfitMechControls();
			break;
		case MechLab_equipmentButtonID:
			SET_OP;
			initDialog ();
			showEquipmentControls();
			break;
		case MechLab_weaponGroupsID:
			SET_OP;
			initDialog();
			showWeaponGroups();
			break;
		case MechLab_exitButtonID:
			SET_OP;
			if (vehicle != NULL && changed) {
				BGCPString dummy;
				if (vehicle->check(dummy)) {
					BGCPString MEKFN = selectMEKFNInDirectory(vehicleDir.getAsPath());
					BGCPString MEKFNWithExt = MEKFN + BGCPString(".mek");
					auto dialogResult = messageBox("Save changes?", MEKFNWithExt, MB_YESNOCANCEL);
					if (dialogResult == IDYES) 
					{
						vehicle->saveToMW2MEK(MEKFNWithExt.getAsPath());
						MechShell* shell = dynamic_cast<MechShell*> (mReturnWin);
						if (shell != NULL) {
							shell->useConfig(MEKFN.getAsPath());
						}
					} else if (dialogResult == IDCANCEL) {
						reallyExit = false;
					}
				} else
					if (messageBox("Mech Config Invalid", "Really Exit?", MB_YESNO) == IDNO) {
						reallyExit = false;
					}
			}

			if (reallyExit) {
				MechVM* mechVM = getMechVm();
				if (mReturnWin != nullptr) {
					mechVM->setScreen(mReturnWin);
					//delete this; // todo: causes heap corruption
				} else {
					mechVM->restore();
				}
			}
			break;
		case MechLab_MechsTableID:
			SET_OP;
			row = mechTable->getSelectedRow();
			showMech(row);
			break;
		case MechLab_WeaponsTableID:
			SET_OP;
			row = weaponsTable->getSelectedRow();
			if (row > 0) {
				weapon = weaponsLocker->getWeaponType(row-1);
				if (vehicle->userMayAddDirectly(currSection, weapon)) {
					if (vehicle->addWeapon(currSection, weapon)) {
						weaponsLocker->removeWeapon (weapon, 1);
						changed = true;
					}
				}
				showMechTonnage();
			}
			break;
		case MechLab_SectionID:
			{
				SET_OP;
				row = sectionTable->getSelectedRow();
				WeaponType weaponNo = vehicle->getCriticals(currSection, row);
				if (vehicle->userMayRemoveDirectly(weaponNo)) {
					vehicle->removeWeapon(currSection, row, weaponNo);
					weaponsLocker->addWeaponToStock(weaponNo, 1);
					changed = true;
				}
				showMechTonnage();
			}
			break;
		case MechLab_SectionSelectionTable:
			SET_OP;
			currSection = sectionSelectionTable->getSelectedRow();
			break;
		case MechLab_TorsoTilt:
			if (slider != nullptr) {
				SET_OP;
				torsoTilt = slider->getValue();
				vehicle->setAim(torsoTwist, torsoTilt);
			}
			break;
		case MechLab_TorsoTwist:
			if (slider != nullptr) {
				SET_OP;
				torsoTwist = slider->getValue();
				vehicle->setAim(torsoTwist, torsoTilt);
			}
			break;
		case MechLab_Equipment_EngineRatingID:
			if (slider != NULL) {
				SET_OP;
				changed = true;
				int newVal = (int) slider->getValue();
				vehicle->setInteger (Vehicle::Info_EngineRating, newVal);
				updateEquipmentLabels();
			}
			break;
		case MechLab_Equipment_EngineTypeID:
			if (cb != NULL) {
				SET_OP;
				changed = true;
				if (cb->getText() == "XL") {
					// updateEquipmentLabels calls showMechTonnage, which checks
					// continually whether the needed crits can be added
					vehicle->setInteger(Vehicle::Info_EngineType, ENGINE_TYPE_XL);
				} else {
					vehicle->setInteger(Vehicle::Info_EngineType, ENGINE_TYPE_STD);
					vehicle->removeWeapon(Biped_Mech_Section_Left_Torso, 0, WeaponType::CE_Engine, 3);
					vehicle->removeWeapon(Biped_Mech_Section_Right_Torso, 0, WeaponType::CE_Engine, 3);
				}
				updateEquipmentLabels();
			}
			break;
		case MechLab_Equipment_HeatSinkCount:
			if (slider != NULL) {
				SET_OP;
				WeaponType hsType = vehicle->getHeatSinkType();
				const MWEquipment* stats = getWeaponStats(hsType);

				if (stats == nullptr) {
					RETURN;
				}

				SET_OP;
				int engineRating = vehicle->getInteger(Vehicle::Info_EngineRating);
				int IntegralHeatSinks = engineRating / 25;
				int currentlyUsedHSCrits = vehicle->countWeaponInAllSections (hsType);
				int heatSinksThatUseCrits = currentlyUsedHSCrits / stats->Criticals;
				int targetVal = (int) slider->getValue();
				int section = 0;

				if (targetVal > IntegralHeatSinks + heatSinksThatUseCrits) {
					// Increase number of heat sinks
					SET_OP;
					while (section < vehicle->getSectionCount() 
					&&     heatSinksThatUseCrits + IntegralHeatSinks < targetVal)
					{
						if (vehicle->addWeapon(section, hsType)) {
							++heatSinksThatUseCrits;
						} else {
							++section;
						}
					}
				} else {
					// Reduce number of heat sinks
					SET_OP;
					while (section < vehicle->getSectionCount() 
					&&     heatSinksThatUseCrits + IntegralHeatSinks > targetVal)
					{
						if (vehicle->removeWeapon(section, 0, hsType, stats->Criticals) > 0) {
							--heatSinksThatUseCrits;
						} else {
							++section;
						}
					}

					if (targetVal < IntegralHeatSinks)
						IntegralHeatSinks = targetVal;
				}

				SET_OP;
				int resultingHSCount = IntegralHeatSinks + heatSinksThatUseCrits;
				vehicle->setInteger (Vehicle::Info_Heat_Sinks, resultingHSCount);
				updateEquipmentLabels();
			}
			break;
		case MechLab_Equipment_HeatSinkType:
			if (cb != NULL) {
				SET_OP;
				changed = true;
				WeaponType oldHSType = vehicle->getHeatSinkType();
				WeaponType newHSType = WeaponType::IS_Heat_Sink;
				int techType = vehicle->getInteger(Vehicle::Info_TechType);
				if (cb->getText() != "Single") {
					if (techType != Tech_Type_Clan) {
						newHSType = WeaponType::IS_Double_Heat_Sink;
					} else {
						newHSType = WeaponType::Clan_Double_Heat_Sink;
					}
				}
				int hsCrits = getWeaponStats(newHSType)->Criticals;
				vehicle->removeAll(oldHSType);
				vehicle->setHeatSinkType(newHSType);
				int integralHS = vehicle->getInteger(Vehicle::Info_EngineRating) / 25;
				int HScount = vehicle->getInteger(Vehicle::Info_Heat_Sinks);
				if (HScount > integralHS) {
					vehicle->autoAddCriticals (newHSType, HScount-integralHS, hsCrits);
				}
				updateEquipmentLabels();
			}
			break;
		case MechLab_Equipment_InternalStructureType:
			if (cb != NULL) {
				SET_OP;
				changed = true;
				WeaponType oldISType = vehicle->getInternalStructureType();
				WeaponType isType = WeaponType::Unused_Critical_Slot; // Standard structure
				int techType = vehicle->getInteger(Vehicle::Info_TechType);

				if (cb->getText() != "Standard") {
					isType = WeaponType::IS_Endo_Steel;
					int isCrits = 14;
					if (techType == Tech_Type_Clan) {
						isType = WeaponType::Clan_Endo_Steel_Internals;
						isCrits = 7;
					}
					int autoAdded = vehicle->autoAddCriticals (isType, isCrits, 1);
					if (autoAdded < isCrits)
						weaponsLocker->addWeaponToStock(isType, isCrits-autoAdded);
				} else {
					vehicle->removeAll(oldISType);
					weaponsLocker->removeWeapon(oldISType, 50);
				}
				vehicle->setInternalStructureType(isType);
				updateEquipmentLabels();
			}
			break;
		case MechLab_Equipment_ArmorType:
			if (cb != NULL) {
				SET_OP;
				changed = true;
				int armorType = ARMOR_TYPE_STD;
				int armorCritCount = 0;
				int techType = vehicle->getInteger(Vehicle::Info_TechType);
				if (cb->getText() != "Standard") {
					armorType = ARMOR_TYPE_FERRO_FIBROUS;
					if (techType == Tech_Type_Clan)
						armorCritCount = 7;
					else 
						armorCritCount = 14;
				}

				WeaponType armorCritType = WeaponType::IS_Ferro_Fibrous;
				if (techType == Tech_Type_Clan)
					armorCritType = WeaponType::Clan_Ferro_Fibrous;

				if (armorType == ARMOR_TYPE_STD) {
					vehicle->removeAll (armorCritType);
					weaponsLocker->removeWeapon(armorCritType, 50);
				} else {
					int autoAdded = vehicle->autoAddCriticals (armorCritType, armorCritCount, 1);
					if (autoAdded < armorCritCount)
						weaponsLocker->addWeaponToStock(armorCritType, armorCritCount-autoAdded);
				}
				vehicle->setInteger(Vehicle::Info_Armor_Type, armorType);
				updateEquipmentLabels();
			}
			break;
		case MechLab_Equipment_LeftLowerArmActuator:
			SET_OP;
			changed = true;
			if (MessageType == CheckBoxChecked) {
				if (!vehicle->addWeapon(Biped_Mech_Section_Left_Arm, WeaponType::CE_Lower_Arm_Actuator)) {
					GLCheckBox* cb = dynamic_cast <GLCheckBox*> (sender);
					cb->setChecked(false);
					label->setTitle(tr(28));
				}
			} else {
				vehicle->removeWeapon(Biped_Mech_Section_Left_Arm, 0, WeaponType::CE_Lower_Arm_Actuator, 1);
			}
			break;
		case MechLab_Equipment_LeftHandActuator:
			SET_OP;
			changed = true;
			if (MessageType == CheckBoxChecked) {
				if (!vehicle->addWeapon(Biped_Mech_Section_Left_Arm, WeaponType::CE_Hand)) {
					GLCheckBox* cb = dynamic_cast <GLCheckBox*> (sender);
					cb->setChecked(false);
					label->setTitle(tr(28));
				}
			} else {
				vehicle->removeWeapon(Biped_Mech_Section_Left_Arm, 0, WeaponType::CE_Hand, 1);
			}
			break;
		case MechLab_Equipment_RightLowerArmActuator:
			SET_OP;
			changed = true;
			if (MessageType == CheckBoxChecked) {
				if (!vehicle->addWeapon(Biped_Mech_Section_Right_Arm, WeaponType::CE_Lower_Arm_Actuator)) {
					GLCheckBox* cb = dynamic_cast <GLCheckBox*> (sender);
					cb->setChecked(false);
					label->setTitle(tr(28));
				}
			} else {
				vehicle->removeWeapon(Biped_Mech_Section_Right_Arm, 0, WeaponType::CE_Lower_Arm_Actuator, 1);
			}
			break;
		case MechLab_Equipment_RightHandActuator:
			SET_OP;
			changed = true;
			if (MessageType == CheckBoxChecked) {
				if (!vehicle->addWeapon(Biped_Mech_Section_Right_Arm, WeaponType::CE_Hand)) {
					GLCheckBox* cb = dynamic_cast <GLCheckBox*> (sender);
					cb->setChecked(false);
					label->setTitle(tr(28));
				}
			} else {
				vehicle->removeWeapon(Biped_Mech_Section_Right_Arm, 0, WeaponType::CE_Hand, 1);
			}
			break;
		case MechLab_CommentID:
			SET_OP;
			if (edit != NULL) {
				changed = true;
				vehicle->setComment (edit->getText());
			}
			break;
		default:
			SET_OP;
			if (win->userData < MechLab_Equipment_ArmorRear) {
				changed = true;
				int section = (win->userData - MechLab_Equipment_ArmorFront) / 2;
				int newVal = (int) slider->getValue();
				vehicle->setArmor(section, newVal, Vehicle::Armor_Front);
				updateEquipmentLabels();
			} else if (win->userData < MechLab_Equipment_ArmorRear+8) {
				int section = (win->userData - MechLab_Equipment_ArmorRear) / 2;
				int newVal = (int) slider->getValue();
				vehicle->setArmor(section, newVal, Vehicle::Armor_Rear);
				changed = true;
				updateEquipmentLabels();
			} else if (win->userData >= WeaponGroupAssignerID) {
				int weaponNo = (win->userData - WeaponGroupAssignerID) / 5;
				int groupNo = win->userData % 5;
				vehicle->setGroup(weaponNo, groupNo);
				showWeaponGroups();
				changed = true;
			}
	}

	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Render

void MechLab::render(int x, int y, int w, int h)
{
	SET_SCOPE;
	Config* cfg = getConfig();
	Color clearCol = cfg->getViewerBackgroundColor();
	glClearColor((GLclampf)clearCol.r, (GLclampf)clearCol.g, (GLclampf)clearCol.b, 0.0f);
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	//glEnable(GL_NORMALIZE);

	glViewport(0, 0, w, h);

	if (mBackground.getWidth() > 0) {
		SET_OP;
		glDisable(GL_DEPTH_TEST);
		glMatrixMode (GL_PROJECTION);
		glLoadIdentity();
		glMatrixMode (GL_MODELVIEW);
		glLoadIdentity();
		mBackground.use();
		double u1 = 0.0;
		double v1 = (479.0 / 512);
		double u2 = 639.0 / 1024; // todo ... constants ...
		double v2 = 0.0;
		glColor3d(1.0, 1.0, 1.0);
		glEnable(GL_TEXTURE_2D);
		glBegin(GL_QUADS);
			glTexCoord2d(u1, v2);
			glVertex2d(-1, -1);

			glTexCoord2d(u2, v2);
			glVertex2d(1, -1);

			glTexCoord2d(u2, v1);
			glVertex2d(1, 1);

			glTexCoord2d(u1, v1);
			glVertex2d(-1, 1);
		glEnd();
	}

	DWORD ticks = GetTickCount();

	// Set up renderer
	if (vehicle != NULL && renderMech) {
		// Set viewing projection
		SET_OP;
		glMatrixMode (GL_PROJECTION);
		glLoadIdentity();
		gluPerspective (FOVY, ((double) w) / ((double) h),
			0.1*viewDist, 
			100*viewDist);
		gluLookAt (eye.x, eye.y, eye.z, center.x, center.y, center.z, 
			yDir.x, yDir.y, yDir.z);
		glMatrixMode (GL_MODELVIEW);
		glLoadIdentity();

		// Activate light source
		glColor3d(1, 1, 1);
		glEnable (GL_LIGHTING);
		glEnable (GL_COLOR_MATERIAL);

		SET_OP;
		//GLfloat lightPos[4] = {0.5f, 1.0f, 0.5f, 0.0f};
		GLfloat lightPos[4] = {(GLfloat) eye.x, (GLfloat) (eye.y+1.0), (GLfloat) (eye.z), 0.0f};
		GLfloat color[3] = {0.5f, 0.5f, 0.5f};
		GLfloat ambient[3] = {0.3f, 0.3f, 0.3f};
		GLfloat k0 = 1.0;
		GLfloat k1 = 0.0;
		GLfloat k2 = 0.0;

		SET_OP;
		glEnable (GL_LIGHT0);
		glLightfv (GL_LIGHT0, GL_POSITION, lightPos);
		glLightfv (GL_LIGHT0, GL_AMBIENT, ambient);
		glLightfv (GL_LIGHT0, GL_DIFFUSE, color);
		glLightfv (GL_LIGHT0, GL_SPECULAR, color);
		glLightfv (GL_LIGHT0, GL_CONSTANT_ATTENUATION, &k0);
		glLightfv (GL_LIGHT0, GL_LINEAR_ATTENUATION, &k1);
		glLightfv (GL_LIGHT0, GL_QUADRATIC_ATTENUATION, &k2);

		glColor3d (1, 1, 1);

		// Render Mech/vehicle
		SET_OP;
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_TEXTURE_2D);

		vehicle->setMatrices(10.0, SDL_GetTicks());
		Matrix m;
		vehicle->cacheTransformedVertices(m);
		vehicle->render();
		glDisable (GL_LIGHT0);
		glDisable (GL_LIGHTING);
	}

	SET_OP;
	GLWindowContainer::render (x, y, w, h);
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Keyboard event handler

void MechLab::keypressed(BGKey /*key*/, bool /*shiftHeld*/, bool /*ctrlHeld*/, bool /*down*/, bool /*repeat*/)
{
}

////////////////////////////////////////////////////////////////////////////////
// Button state changed over a window: returns child that was clicked

GLWindow* MechLab::buttonStateChanged (int button, bool down, int x, int y)
{ 
   lastMouseX = x;
   lastMouseY = y;
   GLWindow* win = GLWindowContainer::buttonStateChanged(button, down, x, y);
   if (win != NULL) {
      return win;
   } else {
      if (button == 1) {
         leftButtonDown = down;
      } else if (button == 3)
         rightButtonDown = down;
   }
   return NULL;
}

void MechLab::mouseMoved (int x, int y)
{
    if (focused != nullptr) {
        focused->mouseMoved(x, y);
        return;
    }
   
    if (leftButtonDown && rightButtonDown) {
      // Translate view
      //center.x += ((double)(lastMouseX-x)*meshSize)/width;
      //center.y += ((double)(y-lastMouseY)*meshSize)/height;

      // Zoom
      //viewDist += ((double)(y-lastMouseY)*meshSize)/height;
      eye = zDir*viewDist;
   } else if (leftButtonDown) {
   } else if (rightButtonDown) {
      // Rotate view
      double dx = ((double) (x-lastMouseX)) / ((double) width);
      double dy = ((double) (y-lastMouseY)) / ((double) height);
      Point3D axis = dx * yDir + dy * xDir;
      if (axis.length () != 0.0) {
         axis.unitLength ();
         double rotSpeed = 2.0 * M_PI * sqrt (dx*dx+dy*dy);
         Matrix m = RotationMatrix (rotSpeed, axis.x, axis.y, axis.z);
         //assert (axis.length () != 0.0);
         xDir = m.Transform (xDir);
         yDir = m.Transform (yDir);
         zDir = m.Transform (zDir);
         xDir.unitLength ();
         yDir.unitLength ();
         zDir.unitLength ();
         eye = zDir*viewDist;
      }
   }
   lastMouseX = x;
   lastMouseY = y;
}

////////////////////////////////////////////////////////////////////////////////
// Implementation of GLTableViewDataSource

bool MechLab::getString (size_t senderID, int row, int col, BGCPString& str)
{
	SET_SCOPE;
	switch (senderID) {
		case MechLab_selectChassisButtonID:
			if ((size_t)row < getMechCount() && row >= 0) {
				RETURN mechList.getMechName(row, str);
			}
		break;
		case MechLab_WeaponsTableID:
			if (row == 0) {
				switch (col) {
					case 0: str = "Name"; break;
					case 1: str = "Criticals"; break;
					case 2: str = "Tonnage"; break;
					case 3: str = "Heat"; break;
					case 4: str = "Damage"; break;
					case 5: str = "Range"; break;
					case 6: str = "In Stock"; break;
				}
			} else if (row < (int) WeaponsCount) {
				//int weaponNo = row;
				WeaponType weaponNo = weaponsLocker->getWeaponType(row-1);
				const MWEquipment* stats = getWeaponStats (weaponNo);
				if (stats != nullptr) {
					switch (col) {
						case 0: str = stats->longName; break;
						case 1: str = BGCPString::fromInt(stats->Criticals); break;
						case 2: str = BGCPString::fromDouble(stats->TonnageTimes100 * 0.01); break;
						case 3: str = BGCPString::fromDouble(stats->Heat); break;
						case 4: str = BGCPString::fromDouble(stats->MaxDamage); break;
						case 5: str = BGCPString::fromDouble(stats->MaxRangeInMeters); break;
						case 6: str = BGCPString::fromInt(weaponsLocker->getWeaponCount(row-1));
					}
				}
			}
			RETURN true;
		case MechLab_SectionID:
			{
				WeaponType weaponNum = vehicle->getCriticals(currSection, row);
	            const MWEquipment* weapon = getWeaponStats (weaponNum);
				if (weapon != NULL) {
	               str = weapon->shortName;
				}
			}
			RETURN true;
		case MechLab_SectionSelectionTable:
			str = vehicle->getSectionName (row);
		RETURN true;
	}
	RETURN false;
}

void MechLab::getDimensions (size_t senderID, size_t& rowCount, size_t& colCount)
{
	SET_SCOPE;
	switch (senderID) {
		case MechLab_selectChassisButtonID:
			rowCount = getMechCount(); 
			colCount = 1;
			break;
		case MechLab_WeaponsTableID:
			rowCount = weaponsLocker->getWeaponCount();
			if (LimitedStock) {
				colCount = 7;
			} else {
				colCount = 6;
			}
			break;
		case MechLab_SectionID:
			rowCount = vehicle->getCriticalCount (currSection);
			colCount = 1;
			break;
		case MechLab_SectionSelectionTable:
			rowCount = vehicle->getSectionCount(); 
			colCount = 1;
			break;
	}
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// WeaponsLocker implementation

// Find weapon - returns index if found, or -1 otherwise
int WeaponsLocker::findWeapon (WeaponType weaponNo)
{
   for (size_t i = 0; i < weaponsInStock.size(); i += 2) {
      if (weaponsInStock[i].type == weaponNo)
         return i;
   }

   return false;
}

// Add weapons to locker
void WeaponsLocker::addWeaponToStock (WeaponType weaponNo, int count)
{
   if (count == 0)
      return;

   // Check if weapon is already in list
   bool found = false;
   size_t i = 0;

   while (!found && i < weaponsInStock.size()) {
      if (weaponsInStock[i].type == weaponNo) {
         found = true;
         weaponsInStock[i].count += count;
      } else {
         ++i;
      }
   }

   if (!found) {
       WeaponInStock weaponInStock;
       weaponInStock.type = weaponNo;
       weaponInStock.count = count;
       weaponsInStock.push_back(weaponInStock);
   }
}

void WeaponsLocker::addWeaponsToStock (WeaponType weaponStartNo, WeaponType weaponEndNo, int count)
{
	if (count > 0)
		for (int i = weaponStartNo; i <= weaponEndNo; ++i)
			addWeaponToStock ((WeaponType)i, count);
}

// Remove weapons from locker
bool WeaponsLocker::removeWeapon (WeaponType weaponNo, int count)
{
	// Check if weapon is already in list
	size_t i = 0;

	while (i < weaponsInStock.size()) {
		if (weaponsInStock[i].type == weaponNo) {
			if (weaponsInStock[i].count > count) {
				// Remove some, but not all
				weaponsInStock[i].count -= count;
				return true;
			} else if (weaponsInStock[i].count == count) {
				// Remove rest
				weaponsInStock[i].count = 0;
				return true;
			} else {
				// Remove all from this entry, but look for further entries
				count -= weaponsInStock[i].count;
				weaponsInStock[i].count = 0;
				++i;
			}
		} else {
			++i;
		}
	}

	return false;
}

void WeaponsLocker::addISWeapons(size_t weaponCount, size_t ammoCount, int year)
{
	addWeaponsToStock(WeaponType::IS1_Flamer, WeaponType::IS1_PPC, weaponCount);
	addWeaponsToStock(WeaponType::IS1_Autocannon_20, WeaponType::IS1_Autocannon_2, weaponCount);
	addWeaponToStock(WeaponType::IS1_Autocannon_Ammo, ammoCount);
	addWeaponToStock(WeaponType::IS1_Machine_Gun, weaponCount);
	addWeaponToStock(WeaponType::IS1_Machine_Gun_Ammo, ammoCount);
	addWeaponsToStock(WeaponType::IS1_SRM_2, WeaponType::IS1_LRM_20, weaponCount);
	addWeaponsToStock(WeaponType::IS1_SRM_2_Ammo, WeaponType::IS1_SRM_6_Ammo, ammoCount);
	addWeaponsToStock(WeaponType::IS1_LRM_5_Ammo, WeaponType::IS1_LRM_20_Ammo, ammoCount);
	addWeaponToStock(WeaponType::IS_Heat_Sink, 100);
	addWeaponToStock(WeaponType::CE_Jump_Jet, 100);

	if (year >= 3048) {
		addWeaponToStock(IS2_Anti_Missile_System, weaponCount/2);
	}

	if (year >= 3049) {
		addWeaponToStock(IS2_ER_Large_Laser, weaponCount/2);
		addWeaponToStock(IS2_Small_Pulse_Laser, weaponCount/2);
		addWeaponToStock(IS2_Medium_Pulse_Laser, weaponCount/2);
		addWeaponToStock(IS1_Large_Pulse_Laser, weaponCount/2);

		addWeaponToStock(IS_Double_Heat_Sink, 50);
		addWeaponToStock(IS_CASE, 10);

		addWeaponToStock(IS2_Streak_SRM_2, weaponCount/2);
		addWeaponToStock(IS2_Streak_SRM_4, weaponCount/2);
		addWeaponToStock(IS2_Streak_SRM_6, weaponCount/2);
		addWeaponToStock(IS2_NARC, weaponCount/2);
		addWeaponToStock(IS2_Streak_SRM_Ammo, ammoCount/2);
		addWeaponToStock(IS2_NARC_AMMO, weaponCount/2);

		// No additional projectile weapons in 3049
	}

	if (year >= 3050) {
		addWeaponToStock(IS2_ER_PPC, 10);
		addWeaponToStock(IS2_ER_Medium_Laser, 10);
		addWeaponToStock(IS2_ER_Small_Laser, 10);
		addWeaponToStock(IS2_Ultra_Autocannon_5, 10);
		addWeaponToStock(IS2_LB_X_Autocannon_10, 10);
		addWeaponToStock(IS2_Gauss, 10);
		addWeaponToStock(IS2_Gauss_Ammo, 10);
	}
}

void WeaponsLocker::addClanWeapons(size_t energyWeaponCount, 
   size_t ballisticWeaponCount, size_t ballisticAmmoCount,
   size_t GaussAndMachineGuns,
   size_t missileWeaponCount, size_t missileAmmoCount,
   size_t equipmentCount, bool GBL)
{
	addWeaponsToStock (WeaponType::Clan_ER_Large_Laser, WeaponType::Clan_ER_Small_Laser, energyWeaponCount);
	addWeaponsToStock (WeaponType::Clan_ER_PPC, WeaponType::Clan_Small_Pulse_Laser, energyWeaponCount);
	addWeaponsToStock (WeaponType::Clan_LB_20_X_AC, WeaponType::Clan_LB_2_X_AC, ballisticWeaponCount);
	addWeaponsToStock (WeaponType::Clan_LB_20_X_AC_Ammo, WeaponType::Clan_LB_2_X_AC_Ammo, ballisticAmmoCount);
	addWeaponsToStock (WeaponType::Clan_UAC_20, WeaponType::Clan_UAC_2, ballisticWeaponCount);
	addWeaponsToStock (WeaponType::Clan_UAC_20_Ammo, WeaponType::Clan_UAC_2_Ammo, ballisticAmmoCount);

	if (GBL) {
		addWeaponToStock (WeaponType::Clan_Anti_Missile_System, ballisticWeaponCount);
		addWeaponToStock (WeaponType::Clan_Anti_Missile_System_Ammo, ballisticAmmoCount);
	}

	addWeaponToStock (WeaponType::Clan_Gauss, GaussAndMachineGuns);
	addWeaponToStock (WeaponType::Clan_Gauss_Ammo, GaussAndMachineGuns);
	addWeaponToStock (WeaponType::Clan_Machine_Gun, GaussAndMachineGuns);
	addWeaponToStock (WeaponType::Clan_Machine_Gun_Ammo, GaussAndMachineGuns);

	addWeaponsToStock (WeaponType::Clan_LRM_5, WeaponType::Clan_LRM_20, missileWeaponCount);
	addWeaponsToStock (WeaponType::Clan_LRM_5_Ammo, WeaponType::Clan_LRM_20_Ammo, missileAmmoCount);
	addWeaponsToStock (WeaponType::Clan_SRM_2, WeaponType::Clan_SRM_6, missileWeaponCount);
	addWeaponsToStock (WeaponType::Clan_SRM_2_Ammo, WeaponType::Clan_SRM_6_Ammo, missileAmmoCount);
	addWeaponsToStock (WeaponType::Clan_Streak_SRM_2, WeaponType::Clan_Streak_SRM_6, missileWeaponCount);
	addWeaponsToStock (WeaponType::Clan_Streak_SRM_2_Ammo, WeaponType::Clan_Streak_SRM_6_Ammo, missileAmmoCount);

	if (GBL) {
		addWeaponToStock (WeaponType::Clan_NARC_Missile_Beacon, missileWeaponCount);
		addWeaponToStock (WeaponType::Clan_NARC_Missile_Beacon_Ammo, missileAmmoCount);
		addWeaponToStock (WeaponType::Clan_Arrow_IV, missileWeaponCount);
		addWeaponToStock (WeaponType::Clan_Arrow_IV_Ammo, missileAmmoCount);
		addWeaponToStock (WeaponType::Clan_SRM_2_Inferno, missileWeaponCount);
		addWeaponToStock (WeaponType::Clan_SRM_Ammo_Inferno, missileAmmoCount);
		addWeaponToStock (WeaponType::Clan_Streak_SRM_2_Inferno, missileWeaponCount);
		addWeaponToStock (WeaponType::Clan_SSRM_Inferno_Ammo, missileAmmoCount);
	}

	addWeaponToStock (WeaponType::CE_Jump_Jet, equipmentCount);
	addWeaponToStock (WeaponType::Clan_Double_Heat_Sink, equipmentCount);
}

void WeaponsLocker::addGBLUnderwaterWeapons(size_t weaponCount, size_t ammoCount)
{
	addWeaponsToStock (WeaponType::Clan_ER_Large_Laser, WeaponType::Clan_ER_Small_Laser, weaponCount);
	addWeaponsToStock (WeaponType::Clan_ER_PPC, WeaponType::Clan_Small_Pulse_Laser, weaponCount);
	addWeaponToStock (WeaponType::Clan_SRT_4, weaponCount);
	addWeaponToStock (WeaponType::Clan_SRT_Ammo, ammoCount);
	addWeaponToStock (WeaponType::IS_Heat_Sink, 100);
	addWeaponToStock (WeaponType::Clan_Double_Heat_Sink, 100);
}

////////////////////////////////////////////////////////////////////////////////
// Select file name to store MEK file from given directory

BGCPString MechLab::selectMEKFNInDirectory (const PathChar* dir)
{
	int i = 0;
	BGCPString mekFN, mekFNWithExt;

	do {
		++i;
		mekFN = BGCPString(dir, CP_SYSTEM);
		mekFN.addPathSep("user-");

		if (vehicle != nullptr) {
			if (!vehicle->getComment().empty()) {
				mekFN += vehicle->getComment();
			}
		}

		if (i > 0) {
			mekFN += "-";
			mekFN += BGCPString::fromInt (i);
		}

		mekFNWithExt = mekFN + ".MEK";
	} while (fileExists(mekFNWithExt.getAsPath()));

	return mekFN; // filename is needed without extension for mechvm.cfg
}

////////////////////////////////////////////////////////////////////////////////

bool MechLab::showMech(size_t num)
{
	SET_SCOPE;
	Vehicle * newVehicle = mechList.loadMech(num);

	if (newVehicle == NULL) {
		RETURN false;
	}

	changed = false;
	BGCPString MVMKFN = mechList.getMVMKFN(num);
	if (!MVMKFN.empty()) {
		SET_OP;
		vehicleDir = getDirectory(MVMKFN);
	}

	SET_OP;
	newVehicle->MVMKFN = mechList.getMVMKFN(num);
	newVehicle->textureFN = mechList.getTextureFN(num);
	showMech(newVehicle);
	showMechTonnage();
	newVehicle->setStatus(VehicleStatus::engineRunning);

	RETURN true;
}

size_t MechLab::getMechCount() const
{
	return mechList.getMechCount();
}

// Set the window that will be displayed if the user clicks exit
void MechLab::setReturnVis (GLWindow* returnWin)
{
	mReturnWin = returnWin;
}

void MechLab::setCurrMechName (const BGCPString& name)
{ 
	if (vehicle != NULL) {
		vehicle->setName(name);
	}
}
