Source: reference/common/TemplateLister.js

/**
 * This class compiles and stores lists of which templates can be built/trained/researched by other templates.
 */
class TemplateLister
{
	constructor(TemplateLoader)
	{
		this.TemplateLoader = TemplateLoader;
		this.templateLists = new Map();
	}

	/**
	 * Compile lists of templates buildable/trainable/researchable of a given civ.
	 *
	 * @param {Object} civCode
	 * @param {Object} civData - Data defining every civ in the game.
	 */
	compileTemplateLists(civCode, civData)
	{
		if (this.hasTemplateLists(civCode))
			return this.templateLists.get(civCode);

		let templatesToParse = civData[civCode].StartEntities.map(entity => entity.Template);

		let templateLists = {
			"units": new Map(),
			"structures": new Map(),
			"techs": new Map(),
			"wallsetPieces": new Map()
		};

		do
		{
			const templatesThisIteration = templatesToParse;
			templatesToParse = [];

			for (let templateBeingParsed of templatesThisIteration)
			{
				let baseOfTemplateBeingParsed = this.TemplateLoader.getVariantBaseAndType(templateBeingParsed, civCode)[0];
				let list = this.deriveTemplateListsFromTemplate(templateBeingParsed, civCode);
				for (let type in list)
					for (let templateName of list[type])
					{
						if (type != "techs")
						{
							let templateVariance = this.TemplateLoader.getVariantBaseAndType(templateName, civCode);
							if (templateVariance[1].passthru)
								templateName = templateVariance[0];
						}

						if (!templateLists[type].has(templateName))
						{
							templateLists[type].set(templateName, [baseOfTemplateBeingParsed]);
							if (type != "techs")
								templatesToParse.push(templateName);
						}
						else if (templateLists[type].get(templateName).indexOf(baseOfTemplateBeingParsed) == -1)
							templateLists[type].get(templateName).push(baseOfTemplateBeingParsed);
					}
			}
		} while (templatesToParse.length);

		// Expand/filter tech pairs
		for (let [techCode, researcherList] of templateLists.techs)
		{
			if (!this.TemplateLoader.isPairTech(techCode))
				continue;

			for (let subTech of this.TemplateLoader.loadTechnologyPairTemplate(techCode, civCode).techs)
				if (!templateLists.techs.has(subTech))
					templateLists.techs.set(subTech, researcherList);
				else
					for (let researcher of researcherList)
						if (templateLists.techs.get(subTech).indexOf(researcher) == -1)
							templateLists.techs.get(subTech).push(researcher);

			templateLists.techs.delete(techCode);
		}

		// Remove wallset pieces, as they've served their purpose.
		delete templateLists.wallsetPieces;

		this.templateLists.set(civCode, templateLists);
		return this.templateLists.get(civCode);
	}

	/**
	 * Returns a civ's template list.
	 *
	 * Note: this civ must have gone through the compilation process above!
	 *
	 * @param {string} civCode
	 * @return {Object} containing lists of template names, grouped by type.
	 */
	getTemplateLists(civCode)
	{
		if (this.hasTemplateLists(civCode))
			return this.templateLists.get(civCode);

		error("Template lists of \"" + civCode + "\" requested, but this civ has not been loaded.");
		return {};
	}

	/**
	 * Returns whether the civ of the given civCode has been loaded into cache.
	 *
	 * @param {string} civCode
	 * @return {boolean}
	 */
	hasTemplateLists(civCode)
	{
		return this.templateLists.has(civCode);
	}

	/**
	 * Compiles lists of buildable, trainable, or researchable entities from
	 * a named template.
	 */
	deriveTemplateListsFromTemplate(templateName, civCode)
	{
		if (!templateName || !Engine.TemplateExists(templateName))
			return {};

		let template = this.TemplateLoader.loadEntityTemplate(templateName, civCode);

		const templateLists = this.TemplateLoader.deriveProduction(template, civCode);
		templateLists.structures = this.TemplateLoader.deriveBuildQueue(template, civCode);

		if (template.WallSet)
		{
			templateLists.wallsetPieces = [];
			for (let segment in template.WallSet.Templates)
			{
				segment = template.WallSet.Templates[segment].replace(/\{(civ|native)\}/g, civCode);
				if (Engine.TemplateExists(segment))
					templateLists.wallsetPieces.push(segment);
			}
		}

		return templateLists;
	}
}