/*
 * Copyright 2010-2016 OpenXcom Developers.
 *
 * This file is part of OpenXcom.
 *
 * OpenXcom is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * OpenXcom is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OpenXcom.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "SaveConverter.h"
#include <yaml-cpp/yaml.h>
#include <SDL_endian.h>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <bitset>
#include "../Engine/Options.h"
#include "../Engine/CrossPlatform.h"
#include "../Engine/Exception.h"
#include "../Engine/Language.h"
#include "../Mod/Mod.h"
#include "../Mod/RuleItem.h"
#include "SavedGame.h"
#include "GameTime.h"
#include "Country.h"
#include "Region.h"
#include "Base.h"
#include "BaseFacility.h"
#include "ItemContainer.h"
#include "Ufo.h"
#include "Craft.h"
#include "AlienBase.h"
#include "Waypoint.h"
#include "MissionSite.h"
#include "CraftWeapon.h"
#include "Transfer.h"
#include "Vehicle.h"
#include "AlienStrategy.h"
#include "AlienMission.h"
#include "../Mod/RuleResearch.h"
#include "../Mod/RuleRegion.h"
#include "../Mod/ArticleDefinition.h"
#include "ResearchProject.h"
#include "../Mod/RuleManufacture.h"
#include "Production.h"
#include "../Mod/UfoTrajectory.h"
#include "../Engine/RNG.h"
#include "../Mod/RuleConverter.h"
#include "../Ufopaedia/Ufopaedia.h"
#include "../fmath.h"
 
namespace OpenXcom
{
 
enum TargetType { TARGET_NONE, TARGET_UFO, TARGET_CRAFT, TARGET_XBASE, TARGET_ABASE, TARGET_CRASH, TARGET_LANDED, TARGET_WAYPOINT, TARGET_TERROR, TARGET_PORT = 0x51, TARGET_ISLAND = 0x52, TARGET_SHIP = 0x53, TARGET_ARTEFACT = 0x54 };
const char *xcomAltitudes[] = { "STR_GROUND", "STR_VERY_LOW", "STR_LOW_UC", "STR_HIGH_UC", "STR_VERY_HIGH" };
const char *xcomStatus[] = { "STR_READY", "STR_OUT", "STR_REPAIRS", "STR_REFUELLING", "STR_REARMING" };
 
// Helper functions
template <typename T> T load(char* data) { return *(T*)data; }
template <> Uint16 load(char* data) { return SDL_SwapLE16(*(Uint16*)data); }
template <> Sint16 load(char* data) { return SDL_SwapLE16(*(Sint16*)data); }
template <> int load(char* data) { return SDL_SwapLE32(*(int*)data); }
template <> unsigned int load(char* data) { return SDL_SwapLE32(*(unsigned int*)data); }
template <> std::string load(char* data) { return data; }
 
char *SaveConverter::binaryBuffer(const std::string &filename, std::vector<char> &buffer) const
{
	std::string s = _savePath + CrossPlatform::PATH_SEPARATOR + filename;
	std::ifstream datFile(s.c_str(), std::ios::in | std::ios::binary);
	if (!datFile)
	{
		throw Exception(filename + " not found");
	}
	buffer = std::vector<char>((std::istreambuf_iterator<char>(datFile)), (std::istreambuf_iterator<char>()));
	datFile.close();
	return &buffer[0];
}
 
/**
 * Creates a new converter for the given save folder.
 * @param save Number of the save folder GAME_#
 * @param mod Mod to associate with this save.
 */
SaveConverter::SaveConverter(int save, Mod *mod) : _save(0), _mod(mod), _rules(mod->getConverter()), _year(0), _funds(0)
{
	std::ostringstream ssFolder, ssPath;
	ssFolder << "GAME_" << save;
	ssPath << Options::getMasterUserFolder() << ssFolder.str();
	_saveName = ssFolder.str();
	_savePath = ssPath.str();
	ssPath << "/SAVEINFO.DAT";
	if (!CrossPlatform::fileExists(ssPath.str()))
	{
		throw Exception(_saveName + " is not a valid save folder");
	}
}
 
SaveConverter::~SaveConverter()
{
}
 
/**
 * Gets all the info of the saves found in the user folder.
 * @param lang Loaded language.
 * @param info Returned list of saves info.
 */
void SaveConverter::getList(Language *lang, SaveOriginal info[NUM_SAVES])
{
	for (int i = 0; i < NUM_SAVES; ++i)
	{
		SaveOriginal save;
		save.id = 0;
 
		int id = i + 1;
		std::ostringstream ss;
		ss << Options::getMasterUserFolder() << "GAME_" << id << CrossPlatform::PATH_SEPARATOR << "SAVEINFO.DAT";
		std::ifstream datFile(ss.str().c_str(), std::ios::in | std::ios::binary);
		if (datFile)
		{
			std::vector<char> buffer((std::istreambuf_iterator<char>(datFile)), (std::istreambuf_iterator<char>()));
			char *data = &buffer[0];
 
			std::string name = load<std::string>(data + 0x02);
			int year = load<Uint16>(data + 0x1C);
			int month = load<Uint16>(data + 0x1E);
			int day = load<Uint16>(data + 0x20);
			int hour = load<Uint16>(data + 0x22);
			int minute = load<Uint16>(data + 0x24);
			bool tactical = load<char>(data + 0x26) != 0;
 
			GameTime time = GameTime(0, day, month + 1, year, hour, minute, 0);
 
			std::ostringstream ssDate, ssTime;
			ssDate << time.getDayString(lang) << "  " << lang->getString(time.getMonthString()) << "  " << time.getYear();
			ssTime << time.getHour() << ":" << std::setfill('0') << std::setw(2) << time.getMinute();
 
			save.id = id;
			save.name = name;
			save.date = ssDate.str();
			save.time = ssTime.str();
			save.tactical = tactical;
 
			datFile.close();
		}
		info[i] = save;
	}
}
 
/**
 * Converts an original X-COM save into an OpenXcom save.
 * @return New OpenXcom save.
 */
SavedGame *SaveConverter::loadOriginal()
{
	_save = new SavedGame();
 
	// Load globe data
	_save->getIncomes().clear();
	for (size_t i = 0; i < _rules->getCountries().size(); ++i)
	{
		Country *country = new Country(_mod->getCountry(_rules->getCountries()[i], true));
		country->getActivityAlien().clear();
		country->getActivityXcom().clear();
		country->getFunding().clear();
		_save->getCountries()->push_back(country);
	}
	for (size_t i = 0; i < _rules->getRegions().size(); ++i)
	{
		Region *region = new Region(_mod->getRegion(_rules->getRegions()[i], true));
		region->getActivityAlien().clear();
		region->getActivityXcom().clear();
		_save->getRegions()->push_back(region);
	}
	loadDatXcom();
	loadDatAlien();
	loadDatDiplom();
	loadDatLease();
 
	// Load graph data
	_save->getExpenditures().clear();
	_save->getMaintenances().clear();
	_save->getFundsList().clear();
	_save->getResearchScores().clear();
	loadDatLIGlob();
	loadDatUIGlob();
	loadDatIGlob();
 
	// Load alien data
	loadDatZonal();
	loadDatActs();
	loadDatMissions();
 
	// Load player data
	loadDatLoc();
	loadDatBase();
	loadDatAStore();
	loadDatCraft();
	loadDatSoldier();
	loadDatTransfer();
	loadDatResearch();
	loadDatUp();
	loadDatProject();
	loadDatBProd();
	loadDatXBases();
 
	return _save;
}
 
/**
 * Corrects vectors of graph data.
 * Original X-COM uses months as array indexes,
 * while OpenXcom stores month data in a linear fashion.
 * @param vector Vector of graph data.
 * @param month Current month.
 * @param year Has game gone longer than a year?
 */
template <typename T>
void SaveConverter::graphVector(std::vector<T> &vector, int month, bool year)
{
	if (year)
	{
		std::vector<T> newVector;
		int i = month;
		do
		{
			newVector.push_back(vector[i]);
			i = (i + 1) % vector.size();
		}
		while (i != month);
		vector = newVector;
	}
	else
	{
		vector.resize(month);
	}
}
 
/**
 * Loads the IGLOB.DAT file.
 * Contains game date, time and difficulty.
 */
void SaveConverter::loadDatIGlob()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("IGLOB.DAT", buffer);
 
	int month = load<int>(data + 0x00) + 1;
	int weekday = load<int>(data + 0x04) + 1;
	int day = load<int>(data + 0x08);
	int hour = load<int>(data + 0x0C);
	int minute = load<int>(data + 0x10);
	int second = load<int>(data + 0x14);
	_save->setTime(GameTime(weekday, day, month, _year, hour, minute, second));
 
	// account for difficulty bug
	if (buffer.size() > 0x3C)
	{
		int coefficient = load<int>(data + 0x3C);
		for (size_t i = DIFF_BEGINNER; i <= DIFF_SUPERHUMAN; ++i)
		{
			if (coefficient == Mod::DIFFICULTY_COEFFICIENT[i])
			{
				_save->setDifficulty((GameDifficulty)i);
				break;
			}
		}
	}
 
	// Fix up the months
	size_t monthsPassed = month + (_year - _mod->getStartingTime().getYear()) * 12;
	for (size_t i = 0; i < monthsPassed; ++i)
	{
		_save->addMonth();
	}
	graphVector(_save->getIncomes(), month, _year != _mod->getStartingTime().getYear());
	graphVector(_save->getExpenditures(), month, _year != _mod->getStartingTime().getYear());
	graphVector(_save->getMaintenances(), month, _year != _mod->getStartingTime().getYear());
	graphVector(_save->getFundsList(), month, _year != _mod->getStartingTime().getYear());
	graphVector(_save->getResearchScores(), month, _year != _mod->getStartingTime().getYear());
	for (size_t i = 0; i < _rules->getCountries().size(); ++i)
	{
		Country *country = _save->getCountries()->at(i);
		graphVector(country->getActivityAlien(), month, _year != _mod->getStartingTime().getYear());
		graphVector(country->getActivityXcom(), month, _year != _mod->getStartingTime().getYear());
		graphVector(country->getFunding(), month, _year != _mod->getStartingTime().getYear());
	}
	for (size_t i = 0; i < _rules->getRegions().size(); ++i)
	{
		Region *region = _save->getRegions()->at(i);
		graphVector(region->getActivityAlien(), month, _year != _mod->getStartingTime().getYear());
		graphVector(region->getActivityXcom(), month, _year != _mod->getStartingTime().getYear());
	}
	_save->getFundsList().back() = _funds;
}
 
/**
 * Loads the LIGLOB.DAT file.
 * Contains financial data.
 */
void SaveConverter::loadDatLIGlob()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("LIGLOB.DAT", buffer);
 
	const size_t MONTHS = 12;
	for (size_t i = 0; i < MONTHS; ++i)
	{
		int expenditure = load<int>(data + 0x04 + i * sizeof(int));
		int maintenance = load<int>(data + 0x34 + i * sizeof(int));
		int balance = load<int>(data + 0x64 + i * sizeof(int));
		_save->getExpenditures().push_back(expenditure);
		_save->getMaintenances().push_back(maintenance);
		_save->getFundsList().push_back(balance);
	}
 
	_funds = load<int>(data);
}
 
/**
 * Loads the UIGLOB.DAT file.
 * Contains Geoscape number IDs and scores.
 */
void SaveConverter::loadDatUIGlob()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("UIGLOB.DAT", buffer);
 
	std::map<std::string, int> ids;
	for (size_t i = 0; i < _rules->getMarkers().size(); ++i)
	{
		ids[_rules->getMarkers()[i]] = load<Uint16>(data + i * sizeof(Uint16));
	}
	ids["STR_CRASH_SITE"] = ids["STR_LANDING_SITE"] = ids["STR_UFO"];
 
	_year = load<Uint16>(data + 0x16);
 
	const size_t MONTHS = 12;
	for (size_t i = 0; i < MONTHS; ++i)
	{
		int score = load<Sint16>(data + 0x18 + i * sizeof(Sint16));
		_save->getResearchScores().push_back(score);
	}
 
	// Loads the SITE.DAT file (TFTD only).
	std::string s = _savePath + CrossPlatform::PATH_SEPARATOR + "SITE.DAT";
	if (CrossPlatform::fileExists(s))
	{
		std::vector<char> sitebuffer;
		char* sitedata = binaryBuffer("SITE.DAT", sitebuffer);
		int generatedArtifactSiteMissions = load<Uint16>(sitedata + 0x24);
		if (generatedArtifactSiteMissions > 0)
		{
			_save->getAlienStrategy().addMissionRun("artifacts", generatedArtifactSiteMissions);
 
			int spawnedArtifactSites = generatedArtifactSiteMissions;
			char siteTypeToBeSpawned = load<char>(sitedata + 0x26);
			if (siteTypeToBeSpawned == 'T')
			{
				// before the first hour of the month, the mission was generated already, but the site has not spawned yet
				spawnedArtifactSites--;
			}
			else
			{
				// after the first hour of the month
				// or not an artifact site type
			}
			ids["STR_ARTIFACT_SITE"] = spawnedArtifactSites + 1; // OXC stores the ID of the next site, thus +1
		}
	}
 
	_save->setAllIds(ids);
}
 
/**
 * Loads the LEASE.DAT file.
 * Contains globe camera settings.
 */
void SaveConverter::loadDatLease()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("LEASE.DAT", buffer);
 
	double lat = -Xcom2Rad(load<Sint16>(data + 0x00));
	double lon = -Xcom2Rad(load<Sint16>(data + 0x06));
	_save->setGlobeLongitude(lon);
	_save->setGlobeLatitude(lat);
 
	int zoom = load<Sint16>(data + 0x0C);
	const int DISTANCE[] = { 90, 120, 180, 360, 450, 720 };
	for (size_t i = 0; i < 6; ++i)
	{
		if (zoom == DISTANCE[i])
		{
			_save->setGlobeZoom(i);
			break;
		}
	}
}
 
/**
 * Loads the XCOM.DAT file.
 * Contains X-COM graph info.
 */
void SaveConverter::loadDatXcom()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("XCOM.DAT", buffer);
 
	const size_t ENTRIES = _rules->getCountries().size() + _rules->getRegions().size();
	const size_t MONTHS = 12;
	for (size_t i = 0; i < ENTRIES * MONTHS; ++i)
	{
		int score = load<int>(data + i * sizeof(int));
		size_t j = i % ENTRIES;
		// country
		if (j < _rules->getCountries().size())
		{
			_save->getCountries()->at(j)->getActivityXcom().push_back(score);
		}
		// region
		else
		{
			j -= _rules->getCountries().size();
			_save->getRegions()->at(j)->getActivityXcom().push_back(score);
		}
	}
}
 
/**
 * Loads the ALIEN.DAT file.
 * Contains Alien graph info.
 */
void SaveConverter::loadDatAlien()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("ALIEN.DAT", buffer);
 
	const size_t ENTRIES = _rules->getCountries().size() + _rules->getRegions().size();
	const size_t MONTHS = 12;
	for (size_t i = 0; i < ENTRIES * MONTHS; ++i)
	{
		int score = load<int>(data + i * sizeof(int));
		size_t j = i % ENTRIES;
		// country
		if (j < _rules->getCountries().size())
		{
			_save->getCountries()->at(j)->getActivityAlien().push_back(score);
		}
		// region
		else
		{
			j -= _rules->getCountries().size();
			_save->getRegions()->at(j)->getActivityAlien().push_back(score);
		}
	}
}
 
/**
 * Loads the DIPLOM.DAT file.
 * Contains country status.
 */
void SaveConverter::loadDatDiplom()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("DIPLOM.DAT", buffer);
 
	const size_t MONTHS = 12;
	std::vector<int64_t> income;
	for (size_t i = 0; i < MONTHS; ++i)
	{
		income.push_back(0);
	}
 
	const size_t ENTRY_SIZE = 36;
	for (size_t i = 0; i < _rules->getCountries().size(); ++i)
	{
		char *cdata = (data + i * ENTRY_SIZE);
		Country *country = _save->getCountries()->at(i);
 
		int satisfaction = load<Sint16>(cdata + 0x02);
		for (size_t j = 0; j < MONTHS; ++j)
		{
			int funding = load<Sint16>(cdata + 0x04 + j * sizeof(Sint16));
			funding *= 1000;
			income[j] += funding;
			country->getFunding().push_back(funding);
		}
		bool pact = satisfaction == 0;
		bool newPact = load<Sint16>(cdata + 0x1E) != 0;
 
		if (pact)
			country->setPact();
		if (newPact)
			country->setNewPact();
	}
	_save->getIncomes() = income;
}
 
/**
 * Loads the ZONAL.DAT file.
 * Contains alien region chances.
 */
void SaveConverter::loadDatZonal()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("ZONAL.DAT", buffer);
 
	std::map<std::string, int> chances;
	const size_t REGIONS = 12;
	for (size_t i = 0; i < REGIONS; ++i)
	{
		chances[_rules->getRegions()[i]] = load<Uint8>(data + i);
	}
	YAML::Node node;
	node["regions"] = chances;
	_save->getAlienStrategy().load(node);
}
 
/**
 * Loads the ACTS.DAT file.
 * Contains alien mission chances.
 */
void SaveConverter::loadDatActs()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("ACTS.DAT", buffer);
 
	std::map< std::string, std::map<std::string, int> > chances;
	const size_t REGIONS = 12;
	const size_t MISSIONS = 7;
	for (size_t i = 0; i < REGIONS * MISSIONS; ++i)
	{
		size_t mission = i % MISSIONS;
		size_t region = i / MISSIONS;
 
		chances[_rules->getRegions()[region]][_rules->getMissions()[mission]] = load<Uint8>(data + i);
	}
 
	YAML::Node node;
	for (std::map< std::string, std::map<std::string, int> >::iterator i = chances.begin(); i != chances.end(); ++i)
	{
		YAML::Node subnode;
		subnode["region"] = i->first;
		subnode["missions"] = i->second;
		node["possibleMissions"].push_back(subnode);
	}
	_save->getAlienStrategy().load(node);
}
 
/**
 * Loads the MISSIONS.DAT file.
 * Contains ongoing alien missions.
 */
void SaveConverter::loadDatMissions()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("MISSIONS.DAT", buffer);
 
	const size_t REGIONS = 12;
	const size_t MISSIONS = 7;
	const size_t ENTRY_SIZE = 8;
	for (size_t i = 0; i < REGIONS * MISSIONS; ++i)
	{
		char *mdata = (data + i * ENTRY_SIZE);
		int wave = load<Uint16>(mdata + 0x00);
		if (wave != 0xFFFF)
		{
			int ufoCounter = load<Uint16>(mdata + 0x02);
			int spawn = load<Uint16>(mdata + 0x04);
			int race = load<Uint16>(mdata + 0x06);
			int mission = i % MISSIONS;
			int region = i / MISSIONS;
 
			YAML::Node node;
			AlienMission *m = new AlienMission(*_mod->getAlienMission(_rules->getMissions()[mission], true));
			node["region"] = _rules->getRegions()[region];
			node["race"] = _rules->getCrews()[race];
			node["nextWave"] = wave;
			node["nextUfoCounter"] = ufoCounter;
			node["spawnCountdown"] = spawn * 30;
			node["uniqueID"] = _save->getId("ALIEN_MISSIONS");
			if (m->getRules().getObjective() == OBJECTIVE_SITE)
			{
				int missionZone = 3; // pick a city for terror missions
				RuleRegion *rule = _mod->getRegion(_rules->getRegions()[region], true);
				if (rule->getMissionZones().size() <= 3)
				{
					// try to account for TFTD's artefacts and such
					missionZone = 0;
				}
				node["missionSiteZone"] = RNG::generate(0, rule->getMissionZones().at(missionZone).areas.size() - 1);
			}
			m->load(node, *_save);
			_save->getAlienMissions().push_back(m);
			_missions[std::make_pair(mission, region)] = m;
		}
	}
}
 
/**
 * Loads the LOC.DAT file.
 * Contains globe markers.
 */
void SaveConverter::loadDatLoc()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("LOC.DAT", buffer);
 
	const size_t ENTRIES = 50;
	const size_t ENTRY_SIZE = buffer.size() / ENTRIES;
	for (size_t i = 0; i < ENTRIES; ++i)
	{
		char *tdata = (data + i * ENTRY_SIZE);
		TargetType type = (TargetType)load<Uint8>(tdata);
 
		int dat = load<Uint8>(tdata + 0x01);
		double lon = Xcom2Rad(load<Sint16>(tdata + 0x02));
		double lat = Xcom2Rad(load<Sint16>(tdata + 0x04));
		int timer = load<Sint16>(tdata + 0x06);
		int id = load<Sint16>(tdata + 0x0A);
		std::bitset<3> visibility(load<int>(tdata + 0x10));
		bool detected = !visibility.test(0);
 
		// can't declare variables in switches :(
		Target *target = 0;
		Ufo *ufo = 0;
		Craft *craft = 0;
		Base *xbase = 0;
		AlienBase *abase = 0;
		Waypoint *waypoint = 0;
		MissionSite *mission = 0;
		switch (type)
		{
		case TARGET_NONE:
			target = 0;
			break;
		case TARGET_UFO:
		case TARGET_CRASH:
		case TARGET_LANDED:
			ufo = new Ufo(_mod->getUfo(_rules->getUfos()[0], true));
			ufo->setId(id);
			ufo->setCrashId(id);
			ufo->setLandId(id);
			ufo->setSecondsRemaining(timer);
			ufo->setDetected(detected);
			target = ufo;
			break;
		case TARGET_CRAFT:
			craft = new Craft(_mod->getCraft(_rules->getCrafts()[0], true), 0, id);
			target = craft;
			break;
		case TARGET_XBASE:
			xbase = new Base(_mod);
			target = xbase;
			break;
		case TARGET_ABASE:
			abase = new AlienBase(_mod->getDeployment("STR_ALIEN_BASE_ASSAULT", true));
			abase->setId(id);
			abase->setAlienRace(_rules->getCrews()[dat]);
			abase->setDiscovered(detected);
			_save->getAlienBases()->push_back(abase);
			target = abase;
			break;
		case TARGET_WAYPOINT:
			waypoint = new Waypoint();
			waypoint->setId(id);
			_save->getWaypoints()->push_back(waypoint);
			target = waypoint;
			break;
		case TARGET_TERROR:
			mission = new MissionSite(_mod->getAlienMission("STR_ALIEN_TERROR", true), _mod->getDeployment("STR_TERROR_MISSION", true));
			break;
		case TARGET_PORT:
			mission = new MissionSite(_mod->getAlienMission("STR_ALIEN_SURFACE_ATTACK", true), _mod->getDeployment("STR_PORT_TERROR", true));
			break;
		case TARGET_ISLAND:
			mission = new MissionSite(_mod->getAlienMission("STR_ALIEN_SURFACE_ATTACK", true), _mod->getDeployment("STR_ISLAND_TERROR", true));
			break;
		case TARGET_SHIP:
			mission = new MissionSite(_mod->getAlienMission("STR_ALIEN_SHIP_ATTACK", true), _mod->getDeployment("STR_CARGO_SHIP_P1", true));
			break;
		case TARGET_ARTEFACT:
			mission = new MissionSite(_mod->getAlienMission("STR_ALIEN_ARTIFACT", true), _mod->getDeployment("STR_ARTIFACT_SITE_P1", true));
			break;
		}
		if (mission != 0)
		{
			mission->setId(id);
			mission->setAlienRace(_rules->getCrews()[dat]);
			mission->setSecondsRemaining(timer * 3600);
			mission->setDetected(detected);
			_save->getMissionSites()->push_back(mission);
			target = mission;
		}
		if (target != 0)
		{
			target->setLongitude(lon);
			target->setLatitude(lat);
		}
		_targets.push_back(target);
		_targetDat.push_back(dat);
	}
}
 
/**
 * Loads the BASE.DAT file.
 * Contains X-COM base contents.
 */
void SaveConverter::loadDatBase()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("BASE.DAT", buffer);
 
	const size_t BASES = 8;
	const size_t BASE_SIZE = 6;
	const size_t FACILITIES = BASE_SIZE * BASE_SIZE;
	const size_t ENTRY_SIZE = buffer.size() / BASES;
	std::vector<Base*> bases(BASES, 0);
	for (size_t i = 0; i < _targets.size(); ++i)
	{
		Base *base = dynamic_cast<Base*>(_targets[i]);
		if (base != 0)
		{
			int j = _targetDat[i];
			char *bdata = (data + j * ENTRY_SIZE);
			std::string name = load<std::string>(bdata);
			// facilities
			for (size_t k = 0; k < FACILITIES; ++k)
			{
				size_t facilityType = load<Uint8>(bdata + _rules->getOffset("BASE.DAT_FACILITIES") + k);
				if (facilityType < _rules->getFacilities().size())
				{
					BaseFacility *facility = new BaseFacility(_mod->getBaseFacility(_rules->getFacilities()[facilityType], true), base);
					int x = k % BASE_SIZE;
					int y = k / BASE_SIZE;
					int days = load<Uint8>(bdata + _rules->getOffset("BASE.DAT_FACILITIES") + FACILITIES + k);
					facility->setX(x);
					facility->setY(y);
					facility->setBuildTime(days);
					base->getFacilities()->push_back(facility);
				}
			}
			int engineers = load<Uint8>(bdata + _rules->getOffset("BASE.DAT_ENGINEERS"));
			int scientists = load<Uint8>(bdata + _rules->getOffset("BASE.DAT_SCIENTISTS"));
			// items
			for (size_t k = 0; k < _rules->getItems().size(); ++k)
			{
				int qty = load<Uint16>(bdata + _rules->getOffset("BASE.DAT_ITEMS") + k * 2);
				if (qty != 0 && !_rules->getItems()[k].empty())
				{
					base->getStorageItems()->addItem(_rules->getItems()[k], qty);
				}
			}
			base->setEngineers(engineers);
			base->setScientists(scientists);
			base->setName(name);
			bases[j] = base;
		}
	}
 
	for (std::vector<Base*>::iterator i = bases.begin(); i != bases.end(); ++i)
	{
		if (*i != 0)
		{
			_save->getBases()->push_back(*i);
		}
	}
}
 
/**
 * Loads the ASTORE.DAT file.
 * Contains X-COM alien containment.
 */
void SaveConverter::loadDatAStore()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("ASTORE.DAT", buffer);
 
	const size_t ENTRY_SIZE = 12;
	const size_t ENTRIES = buffer.size() / ENTRY_SIZE;
	for (size_t i = 0; i < ENTRIES; ++i)
	{
		char *adata = (data + i * ENTRY_SIZE);
		int race = load<Uint8>(adata + 0x00);
		std::string liveAlien;
		if (race != 0)
		{
			int rank = load<Uint8>(adata + 0x01);
			int base = load<Uint8>(adata + 0x02);
			liveAlien = _rules->getAlienRaces()[race];
			liveAlien += _rules->getAlienRanks()[rank];
			if (base != 0xFF)
			{
				Base *b = dynamic_cast<Base*>(_targets[base]);
				b->getStorageItems()->addItem(liveAlien);
			}
		}
		_aliens.push_back(liveAlien);
	}
}
 
/**
 * Loads the TRANSFER.DAT file.
 * Contains transfers to X-COM bases.
 */
void SaveConverter::loadDatTransfer()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("TRANSFER.DAT", buffer);
 
	const size_t ENTRY_SIZE = 8;
	const size_t ENTRIES = buffer.size() / ENTRY_SIZE;
	for (size_t i = 0; i < ENTRIES; ++i)
	{
		char *tdata = (data + i * ENTRY_SIZE);
		int qty = load<Uint8>(tdata + 0x06);
		if (qty != 0)
		{
			int baseSrc = load<Uint8>(tdata + 0x00);
			int baseDest = load<Uint8>(tdata + 0x01);
			Base *b = dynamic_cast<Base*>(_targets[baseDest]);
			int hours = load<Uint8>(tdata + 0x02);
			TransferType type = (TransferType)load<Uint8>(tdata + 0x03);
			int dat = load<Uint8>(tdata + 0x04);
 
			Transfer *transfer = new Transfer(hours);
			switch (type)
			{
			case TRANSFER_CRAFT:
				if (baseSrc == 255)
				{
					std::string newCraft = _rules->getCrafts()[dat];
					transfer->setCraft(new Craft(_mod->getCraft(newCraft, true), b, _save->getId(newCraft)));
				}
				else
				{
					transfer->setCraft(dynamic_cast<Craft*>(_targets[dat]));
				}
				break;
			case TRANSFER_SOLDIER:
				transfer->setSoldier(_soldiers[dat]);
				break;
			case TRANSFER_SCIENTIST:
				transfer->setScientists(qty);
				break;
			case TRANSFER_ENGINEER:
				transfer->setEngineers(qty);
				break;
			default:
				if (type == TRANSFER_ITEM)
					transfer->setItems(_rules->getItems()[dat], qty);
				else
					transfer->setItems(_aliens[dat]);
				break;
			}
 
			b->getTransfers()->push_back(transfer);
		}
	}
}
 
 
/**
 * Loads the CRAFT.DAT file.
 * Contains X-COM craft and Alien UFOs.
 */
void SaveConverter::loadDatCraft()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("CRAFT.DAT", buffer);
 
	const size_t ENTRY_SIZE = buffer.size() / _targets.size();
	for (size_t i = 0; i < _targets.size(); ++i)
	{
		int j = _targetDat[i];
		char *cdata = (data + j * ENTRY_SIZE);
		int type = load<Uint8>(cdata);
		if (type != 0xFF)
		{
			YAML::Node node;
			Craft *craft = dynamic_cast<Craft*>(_targets[i]);
			if (craft != 0)
			{
				craft->changeRules(_mod->getCraft(_rules->getCrafts()[type], true));
 
				int lweapon = load<Uint8>(cdata + _rules->getOffset("CRAFT.DAT_LEFT_WEAPON"));
				int lammo = load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_LEFT_AMMO"));
				if (lweapon != 0xFF)
				{
					CraftWeapon *cw = new CraftWeapon(_mod->getCraftWeapon(_rules->getCraftWeapons()[lweapon], true), lammo);
					craft->getWeapons()->at(0) = cw;
				}
				int flight = load<Uint8>(cdata + _rules->getOffset("CRAFT.DAT_FLIGHT"));
				int rweapon = load<Uint8>(cdata + _rules->getOffset("CRAFT.DAT_RIGHT_WEAPON"));
				int rammo = load<Uint8>(cdata + _rules->getOffset("CRAFT.DAT_RIGHT_AMMO"));
				if (rweapon != 0xFF)
				{
					CraftWeapon *cw = new CraftWeapon(_mod->getCraftWeapon(_rules->getCraftWeapons()[rweapon], true), rammo);
					craft->getWeapons()->at(1) = cw;
				}
 
				node["damage"] = (int)load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_DAMAGE"));
				node["speed"] = (int)load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_SPEED"));
				int dest = load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_DESTINATION"));
				node["fuel"] = (int)load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_FUEL"));
				int base = load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_BASE"));
				node["status"] = xcomStatus[load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_STATUS"))];
 
				// vehicles
				const size_t VEHICLES = 5;
				for (size_t k = 0; k < VEHICLES; ++k)
				{
					int qty = load<Uint8>(cdata + _rules->getOffset("CRAFT.DAT_ITEMS") + k);
					for (int v = 0; v < qty; ++v)
					{
						RuleItem *rule = _mod->getItem(_rules->getItems()[k + 10], true);
						craft->getVehicles()->push_back(new Vehicle(rule, rule->getClipSize(), 4));
					}
				}
				// items
				const size_t ITEMS = 50;
				for (size_t k = VEHICLES; k < VEHICLES + ITEMS; ++k)
				{
					int qty = load<Uint8>(cdata + _rules->getOffset("CRAFT.DAT_ITEMS") + k);
					if (qty != 0 && !_rules->getItems()[k + 10].empty())
					{
						craft->getItems()->addItem(_rules->getItems()[k + 10], qty);
					}
				}
 
				std::bitset<7> state(load<int>(cdata + _rules->getOffset("CRAFT.DAT_STATE")));
				node["lowFuel"] = state.test(1);
 
				craft->load(node, _mod, _save);
 
				if (flight != 0 && dest != 0xFFFF)
				{
					Target *t = _targets[dest];
					craft->setDestination(t);
				}
				if (base != 0xFFFF)
				{
					Base *b = dynamic_cast<Base*>(_targets[base]);
					craft->setBase(b, false);
					b->getCrafts()->push_back(craft);
				}
			}
			Ufo *ufo = dynamic_cast<Ufo*>(_targets[i]);
			if (ufo != 0)
			{
				ufo->changeRules(_mod->getUfo(_rules->getUfos()[type - 5], true));
				node["damage"] = (int)load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_DAMAGE"));
				node["altitude"] = xcomAltitudes[load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_ALTITUDE"))];
				node["speed"] = (int)load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_SPEED"));
				node["dest"]["lon"] = Xcom2Rad(load<Sint16>(cdata + _rules->getOffset("CRAFT.DAT_DEST_LON")));
				node["dest"]["lat"] = Xcom2Rad(load<Sint16>(cdata + _rules->getOffset("CRAFT.DAT_DEST_LAT")));
 
				int mission = load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_MISSION"));
				int region = load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_REGION"));
				std::ostringstream trajectory;
				AlienMission *m = _missions[std::make_pair(mission, region)];
				if (m == 0)
				{
					YAML::Node subnode;
					m = new AlienMission(*_mod->getAlienMission(_rules->getMissions()[mission], true));
					subnode["region"] = _rules->getRegions()[region];
					subnode["race"] = _rules->getCrews()[load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_RACE"))];
					subnode["nextWave"] = 1;
					subnode["nextUfoCounter"] = 0;
					subnode["spawnCountdown"] = 1000;
					subnode["uniqueID"] = _save->getId("ALIEN_MISSIONS");
					m->load(subnode, *_save);
					_save->getAlienMissions().push_back(m);
					_missions[std::make_pair(mission, region)] = m;
					if (mission == 6)
					{
						trajectory << UfoTrajectory::RETALIATION_ASSAULT_RUN;
					}
				}
				node["mission"] = m->getId();
				m->increaseLiveUfos();
				if (trajectory.str().empty())
				{
					trajectory << "P" << load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_TRAJECTORY"));
				}
				node["trajectory"] = trajectory.str();
				node["trajectoryPoint"] = (int)load<Uint16>(cdata + _rules->getOffset("CRAFT.DAT_TRAJECTORY_POINT"));
				std::bitset<7> state(load<int>(cdata + _rules->getOffset("CRAFT.DAT_STATE")));
				node["hyperDetected"] = state.test(6);
 
				ufo->load(node, *_mod, *_save);
				ufo->setSpeed(ufo->getSpeed());
				if (ufo->getStatus() == Ufo::CRASHED)
				{
					ufo->setSecondsRemaining(ufo->getSecondsRemaining() * 3600);
				}
				else if (ufo->getStatus() == Ufo::LANDED)
				{
					ufo->setSecondsRemaining(ufo->getSecondsRemaining() * 5);
				}
				else
				{
					ufo->setSecondsRemaining(0);
				}
 
				_save->getUfos()->push_back(ufo);
			}
		}
	}
}
 
/**
 * Loads the SOLDIER.DAT file.
 * Contains X-COM soldiers.
 */
void SaveConverter::loadDatSoldier()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("SOLDIER.DAT", buffer);
 
	const size_t SOLDIERS = 250;
	const size_t ENTRY_SIZE = buffer.size() / SOLDIERS;
	for (size_t i = 0; i < SOLDIERS; ++i)
	{
		char *sdata = (data + i * ENTRY_SIZE);
		int rank = load<Uint16>(sdata + _rules->getOffset("SOLDIER.DAT_RANK"));
		if (rank != 0xFFFF)
		{
			YAML::Node node;
			int base = load<Uint16>(sdata + _rules->getOffset("SOLDIER.DAT_BASE"));
			int craft = load<Uint16>(sdata + _rules->getOffset("SOLDIER.DAT_CRAFT"));
			node["missions"] = (int)load<Sint16>(sdata + _rules->getOffset("SOLDIER.DAT_MISSIONS"));
			node["kills"] = (int)load<Sint16>(sdata + _rules->getOffset("SOLDIER.DAT_KILLS"));
			node["recovery"] = (int)load<Sint16>(sdata + _rules->getOffset("SOLDIER.DAT_RECOVERY"));
			node["name"] = load<std::string>(sdata + _rules->getOffset("SOLDIER.DAT_NAME"));
			node["rank"] = rank;
 
			UnitStats initial;
			initial.tu = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_INITIAL_TU"));
			initial.health = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_INITIAL_HE"));
			initial.stamina = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_INITIAL_STA"));
			initial.reactions = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_INITIAL_RE"));
			initial.strength = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_INITIAL_STR"));
			initial.firing = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_INITIAL_FA"));
			initial.throwing = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_INITIAL_TA"));
			initial.melee = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_INITIAL_ME"));
			initial.psiStrength = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_INITIAL_PST"));
			initial.psiSkill = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_INITIAL_PSK"));
			initial.bravery = 110 - (10 * load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_INITIAL_BR")));
			node["initialStats"] = initial;
 
			UnitStats current;
			current.tu = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_IMPROVED_TU"));
			current.health = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_IMPROVED_HE"));
			current.stamina = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_IMPROVED_STA"));
			current.reactions = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_IMPROVED_RE"));
			current.strength = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_IMPROVED_STR"));
			current.firing = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_IMPROVED_FA"));
			current.throwing = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_IMPROVED_TA"));
			current.melee = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_IMPROVED_ME"));
			current.psiStrength = 0;
			current.psiSkill = 0;
			current.bravery = 10 * load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_IMPROVED_BR"));
			current += initial;
			node["currentStats"] = current;
 
			int armor = load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_ARMOR"));
			node["armor"] = _rules->getArmor()[armor];
			node["improvement"] = (int)load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_PSI"));
			node["psiTraining"] = (int)load<char>(sdata + _rules->getOffset("SOLDIER.DAT_PSILAB")) != 0;
			node["gender"] = (int)load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_GENDER"));
			node["look"] = (int)load<Uint8>(sdata + _rules->getOffset("SOLDIER.DAT_LOOK"));
			node["id"] = _save->getId("STR_SOLDIER");
 
			Soldier *soldier = new Soldier(_mod->getSoldier(_mod->getSoldiersList().front(), true), 0);
			soldier->load(node, _mod, _save);
			if (base != 0xFFFF)
			{
				Base *b = dynamic_cast<Base*>(_targets[base]);
				b->getSoldiers()->push_back(soldier);
			}
			if (craft != 0xFFFF)
			{
				Craft *c = dynamic_cast<Craft*>(_targets[craft]);
				soldier->setCraft(c);
			}
			_soldiers.push_back(soldier);
		}
		else
		{
			_soldiers.push_back(0);
		}
	}
}
 
/**
 * Loads the RESEARCH.DAT file.
 * Contains X-COM research progress.
 */
void SaveConverter::loadDatResearch()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("RESEARCH.DAT", buffer);
 
	const size_t ENTRY_SIZE = buffer.size() / _rules->getResearch().size();
	for (size_t i = 0; i < _rules->getResearch().size(); ++i)
	{
		char *rdata = (data + i * ENTRY_SIZE);
		if (!_rules->getResearch()[i].empty())
		{
			RuleResearch *research = _mod->getResearch(_rules->getResearch()[i]);
			if (research != 0 && research->getCost() != 0)
			{
				bool discovered = load<Uint8>(rdata + 0x0A) != 0;
				bool popped = load<Uint8>(rdata + 0x12) != 0;
				if (discovered)
				{
					_save->addFinishedResearch(research, _mod, 0, false);
				}
				else if (popped)
				{
					_save->addPoppedResearch(research);
				}
			}
		}
	}
}
 
/**
 * Loads the UP.DAT file.
 * Contains X-COM Ufopaedia progress.
 */
void SaveConverter::loadDatUp()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("UP.DAT", buffer);
 
	const size_t ENTRY_SIZE = buffer.size() / _rules->getUfopaedia().size();
	for (size_t i = 0; i < _rules->getUfopaedia().size(); ++i)
	{
		char *rdata = (data + i * ENTRY_SIZE);
		ArticleDefinition *article = _mod->getUfopaediaArticle(_rules->getUfopaedia()[i]);
		if (article != 0 && article->section != UFOPAEDIA_NOT_AVAILABLE)
		{
			bool discovered = load<Uint8>(rdata + 0x08) == 2;
			if (discovered)
			{
				std::vector<std::string> requires = article->requires;
				for (std::vector<std::string>::const_iterator r = requires.begin(); r != requires.end(); ++r)
				{
					RuleResearch *research = _mod->getResearch(*r);
					if (research && research->getCost() == 0)
					{
						_save->addFinishedResearch(research, _mod, 0, false);
					}
				}
			}
		}
	}
}
 
/**
 * Loads the PROJECT.DAT file.
 * Contains X-COM research projects.
 */
void SaveConverter::loadDatProject()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("PROJECT.DAT", buffer);
 
	const size_t ENTRIES = _rules->getResearch().size();
	// days (Uint16) | scientists (Uint8)
	const size_t ENTRY_SIZE = ENTRIES * (sizeof(Uint16) + sizeof(Uint8));
	for (size_t i = 0; i < _save->getBases()->size(); ++i)
	{
		Base *base = _save->getBases()->at(i);
		char *pdata = (data + i * ENTRY_SIZE);
		Uint16 *arrRemaining = (Uint16*)pdata;
		Uint8 *arrScientists = (Uint8*)(&arrRemaining[ENTRIES]);
		for (size_t j = 0; j < _rules->getResearch().size(); ++j)
		{
			int remaining = load<Uint16>((char*)&arrRemaining[j]);
			int scientists = load<Uint8>((char*)&arrScientists[j]);
			if (remaining != 0 && !_rules->getResearch()[j].empty())
			{
				RuleResearch *research = _mod->getResearch(_rules->getResearch()[j]);
				if (research != 0 && research->getCost() != 0)
				{
					ResearchProject *project = new ResearchProject(research, research->getCost());
					project->setAssigned(scientists);
					project->setSpent(project->getCost() - remaining);
					base->addResearch(project);
					base->setScientists(base->getScientists() - scientists);
				}
			}
		}
	}
}
 
/**
 * Loads the BPROD.DAT file.
 * Contains X-COM manufacture projects.
 */
void SaveConverter::loadDatBProd()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("BPROD.DAT", buffer);
 
	const size_t ENTRIES = _rules->getManufacture().size();
	// hours (int) | engineers (Uint16) | quantity (Uint16)| produced (Uint16)
	const size_t ENTRY_SIZE = ENTRIES * (sizeof(int) + 3 * sizeof(Uint16));
	for (size_t i = 0; i < _save->getBases()->size(); ++i)
	{
		Base *base = _save->getBases()->at(i);
		char *pdata = (data + i * ENTRY_SIZE);
		int *arrRemaining = (int*)pdata;
		Uint16 *arrEngineers = (Uint16*)(&arrRemaining[ENTRIES]);
		Uint16 *arrTotal = (Uint16*)(&arrEngineers[ENTRIES]);
		Uint16 *arrProduced = (Uint16*)(&arrTotal[ENTRIES]);
		for (size_t j = 0; j < _rules->getManufacture().size(); ++j)
		{
			int remaining = load<int>((char*)&arrRemaining[j]);
			int engineers = load<Uint16>((char*)&arrEngineers[j]);
			int total = load<Uint16>((char*)&arrTotal[j]);
			int produced = load<Uint16>((char*)&arrProduced[j]);
			if (remaining != 0 && !_rules->getManufacture()[j].empty())
			{
				RuleManufacture *manufacture = _mod->getManufacture(_rules->getManufacture()[j]);
				if (manufacture != 0)
				{
					Production *project = new Production(manufacture, total);
					project->setAssignedEngineers(engineers);
					project->setTimeSpent(produced * manufacture->getManufactureTime() + manufacture->getManufactureTime() - remaining);
					base->addProduction(project);
					base->setEngineers(base->getEngineers() - engineers);
				}
			}
		}
	}
}
 
/**
 * Loads the XBASES.DAT file.
 * Contains targeted X-COM bases.
 */
void SaveConverter::loadDatXBases()
{
	std::vector<char> buffer;
	char *data = binaryBuffer("XBASES.DAT", buffer);
	const size_t REGIONS = 12;
	for (size_t i = 0; i < REGIONS; ++i)
	{
		char *bdata = (data + i * 4);
		bool detected = load<Uint16>(bdata + 0x00) != 0;
		if (detected)
		{
			int loc = load<Uint16>(bdata + 0x02);
			Base *base = dynamic_cast<Base*>(_targets[loc]);
			if (base != 0)
			{
				base->setRetaliationTarget(true);
			}
		}
	}
 
}
 
}

V807 Decreased performance. Consider creating a reference to avoid using the '_mod->getStartingTime()' expression repeatedly.

V820 The 'name' variable is not used after copying. Copying can be replaced with move/swap for optimization.

V832 It's better to use '= default;' syntax instead of empty destructor body.

V807 Decreased performance. Consider creating a reference to avoid using the '_rules->getCountries()' expression repeatedly.

V807 Decreased performance. Consider creating a reference to avoid using the '_rules->getCountries()' expression repeatedly.