/**
 * Checks whether building placement is valid
 *	1. Visibility is not hidden (may be fogged or visible)
 *	2. Check foundation
 *		a. Doesn't obstruct foundation-blocking entities
 *		b. On valid terrain, based on passability class
 *	3. Territory type is allowed (see note below)
 *	4. Dock is on shoreline and facing into water
 *	5. Distance constraints satisfied
 *
 * Returns result object:
 * 	{
 *		"success":             true iff the placement is valid, else false
 *		"message":             message to display in UI for invalid placement, else ""
 *		"parameters":          parameters to use in the GUI message
 *		"translateMessage":    always true
 *		"translateParameters": list of parameters to translate
 *		"pluralMessage":       we might return a plural translation instead (optional)
 *		"pluralCount":         plural translation argument (optional)
 *  }
 *
 * Note: The entity which is used to check this should be a preview entity
 *  (template name should be "preview|"+templateName), as otherwise territory
 *  checks for buildings with territory influence will not work as expected.
 */
 BuildRestrictions.prototype.CheckPlacement = function()
 {
	 var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
	 var name = cmpIdentity ? cmpIdentity.GetGenericName() : "Building";

	 var result = {
		 "success": false,
		 "message": markForTranslation("%(name)s cannot be built due to unknown error"),
		 "parameters": {
			 "name": name,
		 },
		 "translateMessage": true,
		 "translateParameters": ["name"],
	 };

	 var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
	 if (!cmpPlayer)
		 return result; // Fail

	 // TODO: AI has no visibility info
	 if (!cmpPlayer.IsAI())
	 {
		 // Check whether it's in a visible or fogged region
		 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
		 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
		 if (!cmpRangeManager || !cmpOwnership)
			 return result; // Fail

		 var explored = (cmpRangeManager.GetLosVisibility(this.entity, cmpOwnership.GetOwner()) != "hidden");
		 if (!explored)
		 {
			 result.message = markForTranslation("%(name)s cannot be built in unexplored area");
			 return result; // Fail
		 }
	 }

	 // Check obstructions and terrain passability
	 var passClassName = "";
	 switch (this.template.PlacementType)
	 {
	 case "shore":
		 passClassName = "building-shore";
		 break;

	 case "land-shore":
		 // 'default-terrain-only' is everywhere a normal unit can go, ignoring
		 // obstructions (i.e. on passable land, and not too deep in the water)
		 passClassName = "default-terrain-only";
		 break;

	 case "land":
	 default:
		 passClassName = "building-land";
	 }

	 var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
	 if (!cmpObstruction)
		 return result; // Fail


	 if (this.template.Category == "Wall")
	 {
		 // for walls, only test the center point
		 var ret = cmpObstruction.CheckFoundation(passClassName, true);
	 }
	 else
	 {
		 var ret = cmpObstruction.CheckFoundation(passClassName, false);
	 }

	 if (this.template.Category == "Wall")
	 {
		 // allow walls to be built on top of trees
		 let collisions = cmpObstruction.GetEntitiesBlockingConstruction();
		 let areWoodEnts = collisions.length > 0;
		 for (let ent of collisions)
		 {
			 // don't let entities with unitmotion prevent us from laying a foundation
			 let cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
			 let cmpResource = Engine.QueryInterface(ent, IID_ResourceSupply);
			 if ((!cmpResource || cmpResource.GetType().generic !== "wood") && !cmpUnitMotion)
			 {
				 areWoodEnts = false;
				 break;
			 }
		 }
		 // for walls, only test the center point
		 var ret = areWoodEnts ? "success" : cmpObstruction.CheckFoundation(passClassName, true);
	 }
	 else
	 {
		 var ret = cmpObstruction.CheckFoundation(passClassName, false);
	 }

	 if (ret != "success")
	 {
		 switch (ret)
		 {
		 case "fail_error":
		 case "fail_no_obstruction":
			 error("CheckPlacement: Error returned from CheckFoundation");
			 break;
		 case "fail_obstructs_foundation":
			 result.message = markForTranslation("%(name)s cannot be built on another building or resource");
			 break;
		 case "fail_terrain_class":
			 // TODO: be more specific and/or list valid terrain?
			 result.message = markForTranslation("%(name)s cannot be built on invalid terrain");
		 }
		 return result; // Fail
	 }

	 // Check territory restrictions
	 var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
	 var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
	 if (!cmpTerritoryManager || !cmpPosition || !cmpPosition.IsInWorld())
		 return result;	// Fail

	 const playerID = cmpPlayer.GetPlayerID();
	 const cmpDiplomacy = QueryPlayerIDInterface(playerID, IID_Diplomacy);
	 if (!cmpDiplomacy)
		 return result;

	 var pos = cmpPosition.GetPosition2D();
	 var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
	 var isConnected = !cmpTerritoryManager.IsTerritoryBlinking(pos.x, pos.y);
	 var isOwn = tileOwner == playerID;
	 var isMutualAlly = cmpDiplomacy.IsExclusiveMutualAlly(tileOwner);
	 var isNeutral = tileOwner == 0;

	 var invalidTerritory = "";
	 if (isOwn)
	 {
		 if (!this.HasTerritory("own"))
			 // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
			 invalidTerritory = markForTranslationWithContext("Territory type", "own");
		 else if (!isConnected && !this.HasTerritory("neutral"))
			 // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
			 invalidTerritory = markForTranslationWithContext("Territory type", "unconnected own");
	 }
	 else if (isMutualAlly)
	 {
		 if (!this.HasTerritory("ally"))
			 // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
			 invalidTerritory = markForTranslationWithContext("Territory type", "allied");
		 else if (!isConnected && !this.HasTerritory("neutral"))
			 // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
			 invalidTerritory = markForTranslationWithContext("Territory type", "unconnected allied");
	 }
	 else if (isNeutral)
	 {
		 if (!this.HasTerritory("neutral"))
			 // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
			 invalidTerritory = markForTranslationWithContext("Territory type", "neutral");
	 }
	 else
	 {
		 // consider everything else enemy territory
		 if (!this.HasTerritory("enemy"))
			 // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
			 invalidTerritory = markForTranslationWithContext("Territory type", "enemy");
	 }

	 if (invalidTerritory)
	 {
		 result.message = markForTranslation("%(name)s cannot be built in %(territoryType)s territory. Valid territories: %(validTerritories)s");
		 result.translateParameters.push("territoryType");
		 result.translateParameters.push("validTerritories");
		 result.parameters.territoryType = { "context": "Territory type", "_string": invalidTerritory };
		 // gui code will join this array to a string
		 result.parameters.validTerritories = { "context": "Territory type list", "list": this.GetTerritories() };
		 return result;	// Fail
	 }

	 // Check special requirements
	 if (this.template.PlacementType == "shore")
	 {
		 if (!cmpObstruction.CheckShorePlacement())
		 {
			 result.message = markForTranslation("%(name)s must be built on a valid shoreline");
			 return result;	// Fail
		 }
	 }

	 let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);

	 let templateName = cmpTemplateManager.GetCurrentTemplateName(this.entity);
	 let template = cmpTemplateManager.GetTemplate(removeFiltersFromTemplateName(templateName));

	 // Check distance restriction
	 if (this.template.Distance)
	 {
		 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
		 var cat = this.template.Distance.FromClass;

		 var filter = function(id)
		 {
			 var cmpIdentity = Engine.QueryInterface(id, IID_Identity);
			 return cmpIdentity.GetClassesList().indexOf(cat) > -1;
		 };

		 if (this.template.Distance.MinDistance !== undefined)
		 {
			 let minDistance = ApplyValueModificationsToTemplate("BuildRestrictions/Distance/MinDistance", +this.template.Distance.MinDistance, cmpPlayer.GetPlayerID(), template);
			 if (cmpRangeManager.ExecuteQuery(this.entity, 0, minDistance, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions, false).some(filter))
			 {
				 let result = markForPluralTranslation(
					 "%(name)s too close to a %(category)s, must be at least %(distance)s meter away",
					 "%(name)s too close to a %(category)s, must be at least %(distance)s meters away",
					 minDistance);

				 result.success = false;
				 result.translateMessage = true;
				 result.parameters = {
					 "name": name,
					 "category": cat,
					 "distance": minDistance
				 };
				 result.translateParameters = ["name", "category"];
				 return result;  // Fail
			 }
		 }
		 if (this.template.Distance.MaxDistance !== undefined)
		 {
			 let maxDistance = ApplyValueModificationsToTemplate("BuildRestrictions/Distance/MaxDistance", +this.template.Distance.MaxDistance, cmpPlayer.GetPlayerID(), template);
			 if (!cmpRangeManager.ExecuteQuery(this.entity, 0, maxDistance, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions, false).some(filter))
			 {
				 let result = markForPluralTranslation(
					 "%(name)s too far from a %(category)s, must be within %(distance)s meter",
					 "%(name)s too far from a %(category)s, must be within %(distance)s meters",
					 maxDistance);

				 result.success = false;
				 result.translateMessage = true;
				 result.parameters = {
					 "name": name,
					 "category": cat,
					 "distance": maxDistance
				 };
				 result.translateParameters = ["name", "category"];
				 return result;	// Fail
			 }
		 }
	 }

	 // Success
	 result.success = true;
	 result.message = "";
	 return result;
 };

BuildRestrictions.prototype.IsWoodEntity = function(entity)
{
    let cmpResource = Engine.QueryInterface(entity, IID_ResourceSupply);
    return cmpResource && cmpResource.GetType().generic === "wood";
}

Engine.ReRegisterComponentType(IID_BuildRestrictions, "BuildRestrictions", BuildRestrictions);
