package engine.net.client.handlers;

import engine.Enum;
import engine.Enum.*;
import engine.InterestManagement.HeightMap;
import engine.InterestManagement.RealmMap;
import engine.InterestManagement.WorldGrid;
import engine.db.archive.CityRecord;
import engine.db.archive.DataWarehouse;
import engine.exception.MsgSendException;
import engine.gameManager.*;
import engine.math.Bounds;
import engine.math.Vector3fImmutable;
import engine.net.Dispatch;
import engine.net.DispatchMessage;
import engine.net.client.ClientConnection;
import engine.net.client.msg.CityZoneMsg;
import engine.net.client.msg.ClientNetMsg;
import engine.net.client.msg.ErrorPopupMsg;
import engine.net.client.msg.PlaceAssetMsg;
import engine.net.client.msg.PlaceAssetMsg.PlacementInfo;
import engine.objects.*;
import engine.server.MBServerStatics;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/*
 * @Summary: Processes application protocol message which requests
 *  creation of new city / buildings from seeds/deeds in inventory.
 */
public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {

	// Useful constants
	// ActionType 1 = client request
	//            2 = Server confirms open window
	//            3 = Request to place asset
	//            4 = Server confirms/close window
	private static final int CLIENTREQ_UNKNOWN = 1;
	private static final int SERVER_OPENWINDOW = 2;
	private static final int CLIENTREQ_NEWBUILDING = 3;  // Request to place asset
	private static final int SERVER_CLOSEWINDOW = 4;

	private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

	public PlaceAssetMsgHandler() {

		super(PlaceAssetMsg.class);

	}

	@Override
	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {

		// Member variable declaration

		PlaceAssetMsg msg;
		Boolean buildingCreated;

		// Character location and session

		PlayerCharacter playerCharacter;
		PlacementInfo buildingList;
		Blueprint buildingBlueprint;

		// Tell compiler it's ok to trust us and parse
		// what we need from the message structure

		msg = (PlaceAssetMsg) baseMsg;

		// Action type 3 is a client requesting to place an object
		// For all other action types let's just early exit

		if (msg.getActionType() != CLIENTREQ_NEWBUILDING)
			return true;

		// assign our character

		playerCharacter = SessionManager.getPlayerCharacter(origin);

		// We need to figure out what exactly the player is attempting
		// to place, as some objects like tol/bane/walls are edge cases.
		// So let's get the first item in their list.

		buildingList = msg.getFirstPlacementInfo();

		// Early exit if null building list.

		if (buildingList == null) {
			Logger.error("Player " + playerCharacter.getCombinedName()
					+ " null building list on deed use");
			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
			closePlaceAssetWindow(origin);
			return true;
		}

		Item contract = null;

		for (Item inventoryItem : playerCharacter.getInventory()) {
			if (inventoryItem.getItemBase().getUseID() == buildingList.getBlueprintUUID()) {
				contract = inventoryItem;
				break;
			}
		}

		// Grab the blueprint from the uuid in the message

		buildingBlueprint = Blueprint.getBlueprint(buildingList.getBlueprintUUID());

		// Early exit if blueprint can't be retrieved for the object.

		if (buildingBlueprint == null) {
			Logger.error("Player " + playerCharacter.getCombinedName()
					+ " null blueprint UUID: " + buildingList.getBlueprintUUID() + " on deed use");
			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
			closePlaceAssetWindow(origin);
			return true;
		}

		// Let's now attempt to place the building
		buildingCreated = false;

		// Many buildings have particular validation and
		// post-creation cleanup requirements.

		boolean close = true;
		lock.writeLock().lock();
		boolean isSiege = false;
		try {
			switch (buildingBlueprint.getBuildingGroup()) {

				case TOL:
					if (contract == null)
						break;
					buildingCreated = placeTreeOfLife(playerCharacter, origin, msg);
					break;
				case WAREHOUSE:
					if (contract == null)
						break;
					if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID()))
						break;
					buildingCreated = placeWarehouse(playerCharacter, origin, msg);
					break;
				case SIEGETENT:
				case BULWARK:
					if (contract == null)
						break;
					if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID()))
						break;
					buildingCreated = placeSiegeEquip(playerCharacter, origin, msg);
					break;
				case SPIRE:
					if (contract == null)
						break;
					if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID()))
						break;
					buildingCreated = placeSpire(playerCharacter, origin, msg);
					break;
				case SHRINE:
					if (contract == null)
						break;
					if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID()))
						break;
					buildingCreated = placeShrine(playerCharacter, origin, msg);
					break;
				case BARRACK:
					if (contract == null)
						break;
					if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID()))
						break;
					buildingCreated = placeBarrack(playerCharacter, origin, msg);
					break;
				case WALLSTRAIGHT:
				case WALLCORNER:
				case SMALLGATE:
				case ARTYTOWER:
				case WALLSTAIRS:
				case WALLSTRAIGHTTOWER:
					buildingCreated = placeCityWalls(playerCharacter, origin, msg);
					close = false;
					break;
				default:
					if (contract == null)
						break;
					if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID()))
						break;
					buildingCreated = placeSingleBuilding(playerCharacter, origin, msg);
					break;
			}
		} catch (Exception e) {
			Logger.error("PlaceAssetHandler", e.getMessage());
			e.printStackTrace();
		} finally {
			lock.writeLock().unlock();
		}

		// Update the player's last contract (What is this used for?)

		playerCharacter.setLastContract(msg.getContractID());

		// Remove the appropriate deed.
		if (buildingCreated == true)
			if (contract != null) {
				playerCharacter.getCharItemManager().delete(contract);
				playerCharacter.getCharItemManager().updateInventory();
			}

		// Close the window.  We're done!
		//DONT CLOSE THE WINDOW IF WALL KTHANX

		if (close)
			closePlaceAssetWindow(origin);
		return true;
	}

	// Default method: Validates and places all buildings that do not
	//  require special treatment in some fashion.

	private boolean placeSingleBuilding(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) {

		PlacementInfo buildingList;
		Zone serverZone;

		// Retrieve the building details we're placing

		buildingList = msg.getFirstPlacementInfo();

		serverZone = ZoneManager.findSmallestZone(buildingList.getLoc());
		// Early exit if something went horribly wrong
		// with locating the current or zone

		if (serverZone == null) {
			Logger.error("Null zone in placeSingleBuilding");
			return false;
		}

		// Method checks validation conditions arising when placing
		// buildings.  Player must be on a city grid, must be
		// inner council of the city's guild, etc.

		if (validateBuildingPlacement(serverZone, msg, origin, playerCharacter, buildingList) == false)
			return false; // Close window here?

		// Place the building
		if (createStructure(playerCharacter, buildingList, serverZone) == null) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
			return false;
		}

		return true;
	}

	private boolean placeWarehouse(PlayerCharacter player, ClientConnection origin, PlaceAssetMsg msg) {

		Zone serverZone;
		City cityObject;
		PlacementInfo buildingList;

		// Retrieve the building details we're placing

		buildingList = msg.getFirstPlacementInfo();

		// Setup working variables we'll need

		serverZone = ZoneManager.findSmallestZone(buildingList.getLoc());

		// Early exit if something went horribly wrong

		if (serverZone == null)
			return false;

		cityObject = City.getCity(serverZone.getPlayerCityUUID());

		// Early exit if something went horribly wrong

		if (cityObject == null){
			PlaceAssetMsg.sendPlaceAssetError(origin, 52, "");
			return false;}

		// Method checks validation conditions arising when placing
		// buildings.  Player must be on a city grid, must be
		// inner council of the city's guild, etc.

		if (validateCityBuildingPlacement(serverZone, msg, origin, player, buildingList) == false)
			return false;

		if (cityObject.getWarehouse() != null) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 50, "");  //"You can only have one warehouse"
			return false;
		}

		// Create the warehouse object and it's entry in the database

		if (createWarehouse(player, msg.getFirstPlacementInfo(), serverZone) == false) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
			return false;
		}

		return true;
	}

	private boolean placeSiegeEquip(PlayerCharacter player, ClientConnection origin, PlaceAssetMsg msg) {

		Zone serverZone;
		Building siegeBuilding;
		PlacementInfo buildingList;
		City serverCity;
		Bane bane;

		// Retrieve the building details we're placing

		buildingList = msg.getFirstPlacementInfo();

		// Setup working variables we'll need

		serverZone = ZoneManager.findSmallestZone(buildingList.getLoc());

		// Early exit if something went horribly wrong
		// with locating the current city and/or zone

		if (serverZone == null) {
			Logger.error("Error obtaining reference to zone");
			return false;
		}

		serverCity = ZoneManager.getCityAtLocation(buildingList.getLoc());

		// No valid player city found

		if (serverCity == null || serverCity.getParent().isPlayerCity() == false) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 52, ""); // Cannot place outisde a guild zone
			return false;
		}

		// No bane no bow

		bane = serverCity.getBane();

		if (bane == null) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 66, ""); // There is no bane circle to support this building of war
			return false;
		}

		// Must belong to either attacker or defenders.

		if ((player.getGuild().equals(serverCity.getBane().getOwner().getGuild()) == false)
					&& (player.getGuild().equals(serverCity.getGuild()) == false)) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 54, ""); // Must belong to attacker or defender
			return false;
			}

		// Player must be  GL or IC of the bane guild to place bow.

		if(GuildStatusController.isGuildLeader(player.getGuildStatus()) == false
				&& GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false)
		{
			PlaceAssetMsg.sendPlaceAssetError(origin, 10, player.getName());  // You must be a guild leader to place this asset
			return false;
		}

		// Attackers cannot place on grid until bane is live

		if (bane.getSiegePhase() != SiegePhase.WAR &&
				player.getGuild().equals(serverCity.getBane().getOwner().getGuild()) &&
				serverCity.isLocationOnCityGrid(buildingList.getLoc())) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 53, player.getName()); // Buildings of war cannot be placed around a city grid unless there is an active bane
			return false;
		}

		// If there is a bane placed, we limit bow placement to 2x the stone rank's worth of attacker assets
		// and 1x the tree rank for defenders

		if (validateSiegeLimits(player, origin, serverCity.getBane()) == false)
			return false;

		// Collision check (Removes rubble side effect)

		if (placementCollisionCheck(serverZone, origin, buildingList)){
			PlaceAssetMsg.sendPlaceAssetError(origin, 3, ""); // Conflict between proposed assets
			return false;
		}

		// Create the siege Building

		siegeBuilding = createStructure(player, msg.getFirstPlacementInfo(), serverZone);
		
		// Oops something went really wrong

		if (siegeBuilding == null)
			return false;
		
		// passes validation: can assign auto-protection to war asset

		siegeBuilding.setProtectionState(ProtectionState.PROTECTED);
		
		return true;
	}

	private  boolean validateSiegeLimits(PlayerCharacter playerCharacter, ClientConnection origin, Bane bane) {

		City serverCity = bane.getCity();
		HashSet<AbstractWorldObject> awoList;
		HashSet<AbstractWorldObject> attackerBuildings = new HashSet<>();
		HashSet<AbstractWorldObject> defenderBuildings = new HashSet<>();
		;
		int maxAttackerAssets = serverCity.getBane().getStone().getRank() * 2;
		int maxDefenderAssets = serverCity.getRank();
		
		// Count bow for attackers and defenders

		awoList =  WorldGrid.getObjectsInRangePartial(serverCity, 1000, MBServerStatics.MASK_BUILDING);

		for (AbstractWorldObject awo : awoList) {
			Building building = (Building) awo;

			if (building.getBlueprint() != null)
				if (!building.getBlueprint().isSiegeEquip())
					continue;

			if (!building.getLoc().isInsideCircle(serverCity.getLoc(), CityBoundsType.ZONE.extents))
				continue;

			if (building.getGuild() == null)
				continue;

			if (building.getGuild().isEmptyGuild())
				continue;

			if (!building.getGuild().equals(serverCity.getGuild()) && !building.getGuild().equals(serverCity.getBane().getOwner().getGuild()))
				continue;
			if (building.getRank() < 0) {
				continue;
			}
			if (building.getGuild().equals(serverCity.getGuild()))
				defenderBuildings.add(building);

			if (building.getGuild().equals(serverCity.getBane().getOwner().getGuild()))
				attackerBuildings.add(building);

		}
			// Validate bane limits on siege assets

			if (playerCharacter.getGuild().equals(serverCity.getGuild())) {
				//defender attempting to place asset
				if (defenderBuildings.size() >= maxDefenderAssets) {
					PlaceAssetMsg.sendPlaceAssetError(origin, 62, "");
					return false;
				}
			}
			
			if (playerCharacter.getGuild().equals(serverCity.getBane().getStone().getGuild())) {
				//attacker attempting to place asset
				if (attackerBuildings.size() >= maxAttackerAssets) {
					PlaceAssetMsg.sendPlaceAssetError(origin, 61, "");
					return false;
				}
			}

		// Passed validation
		
		return  true;
	}
	
			
	private boolean placeTreeOfLife(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) {

		Realm serverRealm;
		Zone serverZone;
		ArrayList<AbstractGameObject> cityObjects; // MySql result set
		HashMap<GameObjectType, AbstractGameObject> cityObjectMap = new HashMap<>();
		PlacementInfo treeInfo;
		Guild playerNation;
		PlacementInfo treePlacement = msg.getFirstPlacementInfo();
		Building treeObject;
		City cityObject;
		Zone zoneObject;

		// Setup working variables we'll need

		serverRealm = RealmMap.getRealmAtLocation(treePlacement.getLoc());
		serverZone = ZoneManager.findSmallestZone(treePlacement.getLoc());

		// Early exit if something went horribly wrong
		// with locating the current realm and/or zone

		if (serverRealm == null || serverZone == null)
			return false;

		// Method checks validation conditions arising when placing
		// trees

		if (validateTreeOfLifePlacement(playerCharacter, serverRealm, serverZone, origin, msg) == false)
			return false;

		// Retrieve tree info for the w value it's passing.

		treeInfo = msg.getFirstPlacementInfo();

		if (treeInfo == null) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
			return false;
		}

		Vector3fImmutable plantLoc = new Vector3fImmutable(treeInfo.getLoc().x,
				serverZone.getHeightMap().getInterpolatedTerrainHeight(treeInfo.getLoc()),
				treeInfo.getLoc().z);

		cityObjects = DbManager.CityQueries.CREATE_CITY(playerCharacter.getObjectUUID(), serverZone.getObjectUUID(),
				serverRealm.getRealmID(),
				plantLoc.x - serverZone.getAbsX(), plantLoc.y,
				plantLoc.z - serverZone.getAbsZ(), treeInfo.getRot().y, treeInfo.getW(), playerCharacter.getGuild().getName(), LocalDateTime.now());

		// Uh oh!
		if (cityObjects == null || cityObjects.isEmpty()) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
			return false;
		}

		// Assign our worker variables after figuring out what
		// is what in the result set.

		for (AbstractGameObject gameObject : cityObjects)
			cityObjectMap.put(gameObject.getObjectType(), gameObject);

		treeObject = (Building) cityObjectMap.get(GameObjectType.Building);
		treeObject.runAfterLoad();;
		cityObject = (City) cityObjectMap.get(GameObjectType.City);
		zoneObject = (Zone) cityObjectMap.get(GameObjectType.Zone);

		// not allowed to plant a tree if ur not an errant guild.
		// Desub from any previous nation.
		// This should be done automatically in a method inside Guild *** Refactor
		// Player is now a Sovereign guild, configure them as such.

		playerCharacter.getGuild().setNation(playerCharacter.getGuild());
		playerNation = playerCharacter.getGuild();
		playerNation.setGuildState(GuildState.Sovereign);

		// Link the zone with the city and then add
		// to the appropriate hash tables and cache

		zoneObject.setPlayerCity(true);

		if (zoneObject.getParent() != null)
			zoneObject.getParent().addNode(zoneObject); //add as child to parent

		ZoneManager.addZone(zoneObject.getObjectUUID(), zoneObject);
		ZoneManager.addPlayerCityZone(zoneObject);
		serverZone.addNode(zoneObject);

		zoneObject.generateWorldAltitude();

		cityObject.setParent(zoneObject);
		cityObject.setObjectTypeMask(MBServerStatics.MASK_CITY); // *** Refactor : should have it already
		//Link the tree of life with the new zone

		treeObject.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
		treeObject.setParentZone(zoneObject);
		MaintenanceManager.setMaintDateTime(treeObject, LocalDateTime.now().plusDays(7));

		// Update guild binds and tags
		//load the new city on the clients

		CityZoneMsg czm = new CityZoneMsg(1, treeObject.getLoc().x, treeObject.getLoc().y, treeObject.getLoc().z, cityObject.getCityName(), zoneObject, Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents);
		DispatchMessage.dispatchMsgToAll(czm);

		GuildManager.updateAllGuildBinds(playerNation, cityObject);
		GuildManager.updateAllGuildTags(playerNation);

		// Send all the cities to the clients?
		// *** Refactor : figure out how to send like, one?

		City.lastCityUpdate = System.currentTimeMillis();
		WorldGrid.addObject(treeObject, playerCharacter);

		serverRealm.addCity(cityObject.getObjectUUID());
		playerNation.setCityUUID(cityObject.getObjectUUID());

		// Bypass warehouse entry if we're an admin

		if (playerCharacter.getAccount().status.equals(AccountStatus.ADMIN))
			return true;

		// Push this event to the data warehouse

		CityRecord cityRecord = CityRecord.borrow(cityObject, RecordEventType.CREATE);
		DataWarehouse.pushToWarehouse(cityRecord);

		return true;
	}

	private boolean placeSpire(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) {

		Zone serverZone;
		Building spireBuilding;
		Blueprint blueprint;
		City cityObject;
		PlacementInfo buildingList;

		// Setup working variables we'll need

		buildingList = msg.getFirstPlacementInfo();

		serverZone = ZoneManager.findSmallestZone(buildingList.getLoc());

		// Early exit if something went horribly wrong
		// with locating the current realm and/or city

		if (serverZone == null)
			return false;

		cityObject = City.getCity(serverZone.getPlayerCityUUID());

		if (cityObject == null)
			return false;

		// Method checks validation conditions arising when placing
		// buildings.  Player must be on a city grid, must be
		// inner council of the city's guild, etc.

		if (validateCityBuildingPlacement(serverZone, msg, origin, playerCharacter, buildingList) == false)
			return false;

		// Loop through all buildings in this city looking for a spire of the.
		// same type we are placing.  There can be only one of each type

		int spireCount = 0;

		blueprint = Blueprint.getBlueprint(msg.getFirstPlacementInfo().getBlueprintUUID());

		for (Building building : serverZone.zoneBuildingSet) {

			if (building.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE) {

				if (building.getBlueprintUUID() == blueprint.getMeshForRank(0)) {
					PlaceAssetMsg.sendPlaceAssetError(origin, 46, "");  // "Spire of that type exists"
					return false;
				}
				spireCount++;
			}
		}

		// Too many spires for this tree's rank?

		if (spireCount >= Blueprint.getMaxShrines(cityObject.getTOL().getRank())) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 45, "");  //Tree cannot support anymore spires
			return false;
		}

		// Create the spire

		spireBuilding = createStructure(playerCharacter, msg.getFirstPlacementInfo(), serverZone);
		return spireBuilding != null;
	}

	private boolean placeShrine(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) {

		Zone serverZone;
		Blueprint blueprint;
		City cityObject;
		PlacementInfo buildingList;

		// Setup working variables we'll need
		buildingList = msg.getFirstPlacementInfo();

		serverZone = ZoneManager.findSmallestZone(buildingList.getLoc());

		// Early exit if something went horribly wrong
		// with locating the current realm and/or zone
		if (serverZone == null)
			return false;

		// Method checks validation conditions arising when placing
		// buildings.  Player must be on a city grid, must be
		// inner council of the city's guild, etc.

		if (validateCityBuildingPlacement(serverZone, msg, origin, playerCharacter, buildingList) == false)
			return false;

		// Loop through all buildings in this city looking for a shrine of the.
		// same type we are placing.  There can be only one of each type

		int shrineCount = 0;

		cityObject = City.getCity(serverZone.getPlayerCityUUID());

		// Cannot place shrine in abandoned city.  Shrines must be owned
		// by the tol owner not the person placing them.

		if (cityObject.getTOL().getOwnerUUID() == 0) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 42, "");  //Tree cannot support anymore shrines
			return false;
		}

		blueprint = Blueprint.getBlueprint(msg.getFirstPlacementInfo().getBlueprintUUID());

		if (blueprint == null){
			return false;
		}

		for (Building building : serverZone.zoneBuildingSet) {
			if (building.getBlueprint() == null)
				continue;

			if (building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE) {
				if (building.getBlueprintUUID() == blueprint.getMeshForRank(0)) {
					PlaceAssetMsg.sendPlaceAssetError(origin, 43, "");  // "shrine of that type exists"
					return false;
				}
				shrineCount++;
			}
		}

		// Too many shrines for this tree's rank?

		if (shrineCount >= Blueprint.getMaxShrines(cityObject.getTOL().getRank())) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 42, "");  //Tree cannot support anymore shrines
			return false;
		}

		// Create the shrine

		return createShrine((PlayerCharacter)cityObject.getTOL().getOwner(), msg.getFirstPlacementInfo(), serverZone);
	}

	private boolean placeBarrack(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) {

		Zone serverZone;
		City cityObject;
		PlacementInfo buildingList;

		// Setup working variables we'll need
		buildingList = msg.getFirstPlacementInfo();

		serverZone = ZoneManager.findSmallestZone(buildingList.getLoc());

		// Early exit if something went horribly wrong
		// with locating the current realm and/or zone

		if (serverZone == null)
			return false;

		// Method checks validation conditions arising when placing
		// buildings.  Player must be on a city grid, must be
		// inner council of the city's guild, etc.

		if (validateCityBuildingPlacement(serverZone, msg, origin, playerCharacter, buildingList) == false)
			return false;

		// Loop through all buildings in this city counting barracks .

		int barracksCount = 0;

		cityObject = City.getCity(serverZone.getPlayerCityUUID());

		// Cannot place barracks in abandoned city.

		if (cityObject.getTOL().getOwnerUUID() == 0) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 42, "");  //Tree cannot support anymore shrines
			return false;
		}

		for (Building building : serverZone.zoneBuildingSet) {
			if (building.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK)
				barracksCount++;
		}

		// Too many shrines for this tree's rank?

		if (barracksCount >= cityObject.getTOL().getRank()) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 47, "");  //Tree cannot support anymore shrines
			return false;
		}

		// Create the shrine

		return createBarracks((PlayerCharacter)cityObject.getTOL().getOwner(), msg.getFirstPlacementInfo(), serverZone);
	}

	private boolean placeCityWalls(PlayerCharacter player, ClientConnection origin, PlaceAssetMsg msg) {

		// Member variables

		Zone serverZone;
		City cityObject;
		int placementCost = 0;
		CharacterItemManager itemMan;
		Item goldItem;
		Building wallPiece;

		// Setup working variables we'll need

		serverZone = ZoneManager.findSmallestZone(player.getLoc());

		// Early exit if something went horribly wrong

		if (serverZone == null)
			return false;


		if (player.getCharItemManager().getGoldTrading() > 0){
			ErrorPopupMsg.sendErrorPopup(player, 195);
			return false;
		}


		// Method checks validation conditions arising when placing
		// buildings.  Player must be on a city grid, must be
		// inner council of the city's guild, etc.

		if (validateCityBuildingPlacement(serverZone, msg, origin, player, msg.getFirstPlacementInfo()) == false)
			return false;

		cityObject = City.getCity(serverZone.getPlayerCityUUID());

		// We need to be able to access how much gold a character is carrying

		itemMan = player.getCharItemManager();

		if (itemMan == null)

			return false;

		goldItem = itemMan.getGoldInventory();

		// Grab list of walls we're placing

		ArrayList<PlacementInfo> walls = msg.getPlacementInfo();

		// Character must be able to afford walls

		for (PlacementInfo wall : walls) {
			placementCost += PlaceAssetMsg.getWallCost(wall.getBlueprintUUID());
		}

		// Early exit if not enough gold in character's inventory to place walls

		if (placementCost > goldItem.getNumOfItems()) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 28, ""); // Not enough gold
			return false;
		}

		placementCost = 0; // reset placement cost for fix bug with wall pieces somethings not taking gold out if forced an error.


		// Overlap check and wall deed verifications
		for (PlacementInfo wall : walls) {

			if (Blueprint.isMeshWallPiece(wall.getBlueprintUUID()) == false) {
				PlaceAssetMsg.sendPlaceAssetError(origin, 48, "");  //"Assets (except walls) must be placed one at a time"
				continue;
			}

			// Ignore wall pieces not on the city grid
			if (cityObject.isLocationOnCityGrid(wall.getLoc()) == false) {
				PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Asset " + cityObject.getName() + " not on citygrid");
				continue;
			}

			// Does this wall collide with any other building?

			for (Building building : serverZone.zoneBuildingSet) {


				//TODO Clean up collision with placementInfo. don't need to create the same placementinfo bounds for collision checks on each building.
				if ((building.getBlueprintUUID() != 0) && (Bounds.collide(wall, building) == true)) {

					if (building.getRank() == -1) {
						building.removeFromCache();
						WorldGrid.RemoveWorldObject(building);
						WorldGrid.removeObject(building);
						building.getParentZone().getParent().zoneBuildingSet.remove(building);
						if(building.getBlueprint() != null && building.getBlueprint().getBuildingGroup().equals(BuildingGroup.BARRACK)){
							building.RemoveFromBarracksList();
						}
						continue;
					}
					// remove gold from walls already placed before returning.

					PlaceAssetMsg.sendPlaceAssetError(origin, 3, building.getName());  //"Conflict between assets"
					return false;
				}
			}
			placementCost = PlaceAssetMsg.getWallCost(wall.getBlueprintUUID());

			if (!itemMan.modifyInventoryGold(-placementCost)){
				ChatManager.chatSystemInfo(player, player.getFirstName() + " can't has free moneys! no for real.. Thor.. seriously... I didnt fix it because you getting laid isnt important enough for me.");
				return false;
			}
			// Attempt to place wall piece

			wallPiece = createStructure(player, wall, serverZone);

			if (wallPiece == null) {
				PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
				continue;
			}

			// walls are auto protected
			wallPiece.setProtectionState(ProtectionState.PROTECTED);
			PlaceAssetMsg.sendPlaceAssetConfirmWall(origin,serverZone);

		}

		// Deduct gold from character's inventory


		return true;
	}

	private static void closePlaceAssetWindow(ClientConnection origin) {

		// Action type 4 is the server telling the client to
		// close the asset placement window.
		// This is believed to be a confirmation message to the client
		PlaceAssetMsg pam = new PlaceAssetMsg();
		pam.setActionType(4);
		Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), pam);
		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
	}

	// Method validates the location we have selected for our new city

	private static boolean validateTreeOfLifePlacement(PlayerCharacter playerCharacter, Realm serverRealm, Zone serverZone,
													   ClientConnection origin, PlaceAssetMsg msg) {

		PlacementInfo placementInfo = msg.getFirstPlacementInfo();

		// Your guild already owns a tree

		if (playerCharacter.getGuild().getOwnedCity() != null) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Your guild already owns a tree!");
			return false;
		}

		// Validate that the player is the leader of a guild

		if (GuildStatusController.isGuildLeader(playerCharacter.getGuildStatus()) == false) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 10, ""); // Must be a guild leader
			return false;
		}

		// Validate that the player is the leader of a guild
		// that is not currently Sovereign  *** BUG? Doesn't look right.  isGuildLeader()?

		if ((playerCharacter.getGuild().getGuildState() != GuildState.Sworn
				|| playerCharacter.getGuild().getGuildState() != GuildState.Errant) == false) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 17, ""); // Your is not an errant or soverign guild
			return false;
		}

		// All trees must be placed within a continent.

		if (!serverZone.isContinent()) {

			PlaceAssetMsg.sendPlaceAssetError(origin, 69, ""); // Tree must be within a territory
			return false;
		}

		if (serverRealm == null || serverRealm.getCanPlaceCities() == false) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 57, playerCharacter.getName()); // No building may be placed within this territory
			return false;
		}

		// Cannot place a tree underwater

		if (HeightMap.isLocUnderwater(placementInfo.getLoc())) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 6, ""); // Cannot place underwater
			return false;
		}

		//Test city not too close to any other zone

		if (!ZoneManager.validTreePlacementLoc(serverZone, placementInfo.getLoc().x, placementInfo.getLoc().z)) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 39, ""); // Too close to another tree
			return false;
		}

		// Validate that Realm is not at it's city limit

		if (serverRealm.isRealmFull() == true) {
			int numCities;
			numCities = serverRealm.getNumCities();
			PlaceAssetMsg.sendPlaceAssetError(origin, 58, Integer.toString(numCities)); // This territory is full
			return false;
		}

		return true;
	}

	private Building createStructure(PlayerCharacter playerCharacter, PlacementInfo buildingInfo, Zone currentZone) {

		Blueprint blueprint;
		Building newMesh;
		DateTime completionDate;
		float vendorRotation;
		float buildingRotation;

		blueprint = Blueprint.getBlueprint(buildingInfo.getBlueprintUUID());

		if (blueprint == null) {
			Logger.error("CreateStructure: DB returned null blueprint.");
			return null;
		}

		// All siege buildings build in 15 minutes
		if ((blueprint.getBuildingGroup().equals(BuildingGroup.SIEGETENT))
				|| (blueprint.getBuildingGroup().equals(BuildingGroup.BULWARK)))
			completionDate = DateTime.now().plusMinutes(15);
		else
			completionDate = DateTime.now().plusHours(blueprint.getRankTime(1));

		Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone));

		buildingRotation = buildingInfo.getRot().y;
		vendorRotation = buildingInfo.getW();

		// if W return is negative, this is a -90 rotation not a 90?

		newMesh = DbManager.BuildingQueries.CREATE_BUILDING(
				currentZone.getObjectUUID(), playerCharacter.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
				localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0,
				completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation);

		// Make sure we have a valid mesh
		if (newMesh == null) {
			Logger.error("CreateStructure: DB returned null object.");
			return null;
		}

		newMesh.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
		MaintenanceManager.setMaintDateTime(newMesh, LocalDateTime.now().plusDays(7));

		WorldGrid.addObject(newMesh, playerCharacter);
		return newMesh;

	}

	private boolean createShrine(PlayerCharacter player, PlacementInfo buildingInfo, Zone currentZone) {

		Blueprint blueprint;
		Building newMesh;
		Shrine newShrine;
		City city;
		ShrineType shrineType;

		if (player == null)
			return false;

		blueprint = Blueprint.getBlueprint(buildingInfo.getBlueprintUUID());

		if (blueprint == null) {
			Logger.error("CreateShrine: DB returned null blueprint.");
			return false;
		}

		shrineType = Shrine.getShrineTypeByBlueprintUUID(blueprint.getBlueprintUUID());

		city = City.getCity(currentZone.getPlayerCityUUID());

		if (city == null)
			return false;

		if (!city.isLocationOnCityGrid(buildingInfo.getLoc()))
			return false;

		Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone));

		float buildingRotation = buildingInfo.getRot().y;
		float vendorRotation = buildingInfo.getW();


		ArrayList<AbstractGameObject> shrineObjects = DbManager.ShrineQueries.CREATE_SHRINE(
				currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
				localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.PROTECTED, 0, 0,
				DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation, shrineType.name());

		if (shrineObjects == null) {
			PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
			return false;
		}

		for (AbstractGameObject ago : shrineObjects) {

			switch (ago.getObjectType()) {
				case Building:
					newMesh = (Building) ago;
					newMesh.runAfterLoad();
					newMesh.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
					MaintenanceManager.setMaintDateTime(newMesh, LocalDateTime.now().plusDays(7));
					WorldGrid.addObject(newMesh, player);
					break;
				case Shrine:
					newShrine = (Shrine) ago;
					newShrine.getShrineType().addShrineToServerList(newShrine);
					break;
				default:
					PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
					break;
			}
		}

		return true;
	}

	private boolean createBarracks(PlayerCharacter player, PlacementInfo buildingInfo, Zone currentZone) {

		Blueprint blueprint;
		Building newMesh;
		Shrine newShrine;
		City city;

		if (player == null)
			return false;

		blueprint = Blueprint.getBlueprint(buildingInfo.getBlueprintUUID());

		if (blueprint == null) {
			Logger.error("CreateShrine: DB returned null blueprint.");
			return false;
		}

		city = City.getCity(currentZone.getPlayerCityUUID());

		if (city == null)
			return false;

		if (!city.isLocationOnCityGrid(buildingInfo.getLoc()))
			return false;

		Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone));

		float buildingRotation = buildingInfo.getRot().y;
		float vendorRotation = buildingInfo.getW();
		DateTime completionDate = DateTime.now().plusHours(blueprint.getRankTime(1));


		newMesh = DbManager.BuildingQueries.CREATE_BUILDING(
				currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
				localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.PROTECTED, 0, 0,
				completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation);

		// Make sure we have a valid mesh
		if (newMesh == null) {
			Logger.error("CreateStructure: DB returned null object.");
			return false;
		}

		newMesh.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
		MaintenanceManager.setMaintDateTime(newMesh, LocalDateTime.now().plusDays(7));
		WorldGrid.addObject(newMesh, player);

		return true;
	}

	private boolean createWarehouse(PlayerCharacter player, PlacementInfo buildingInfo, Zone currentZone) {

		Blueprint blueprint;
		Building newMesh = null;
		ArrayList<AbstractGameObject> warehouseObjects;

		blueprint = Blueprint.getBlueprint(buildingInfo.getBlueprintUUID());

		if (blueprint == null) {
			Logger.error("CreateWarehouse: DB returned null blueprint.");
			return false;
		}

		Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone));

		float buildingRotation = buildingInfo.getRot().y;
		float vendorRotation = buildingInfo.getW();

		warehouseObjects = DbManager.WarehouseQueries.CREATE_WAREHOUSE(
				currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
				localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0,
				DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation);

		if (warehouseObjects == null) {
			PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
			return false;
		}

		// Load the building into the simulation

		for (AbstractGameObject ago : warehouseObjects) {

			if (ago.getObjectType() == GameObjectType.Building) {
				newMesh = (Building) ago;
				newMesh.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
				MaintenanceManager.setMaintDateTime(newMesh, LocalDateTime.now().plusDays(7));
				WorldGrid.addObject(newMesh, player);
				newMesh.runAfterLoad();
			}
			else if (ago.getObjectType() == GameObjectType.Warehouse) {
				Warehouse warehouse = (Warehouse) ago;
				City city = City.getCity(currentZone.getPlayerCityUUID());
				if (city == null)
					return true;
				city.setWarehouseBuildingID(newMesh.getObjectUUID());
				Warehouse.warehouseByBuildingUUID.put(newMesh.getObjectUUID(), warehouse);
			}
		}

		return true;
	}

	// Validates that player is able to place buildings

	private static boolean validateBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo placementInfo) {

			if (serverZone.isPlayerCity() == false) {
				PlaceAssetMsg.sendPlaceAssetError(origin, 52, player.getName());
				return false;
			}

			City city = ZoneManager.getCityAtLocation(placementInfo.getLoc());

			if (player.getGuild().equals(city.getGuild()) == false) {
				PlaceAssetMsg.sendPlaceAssetError(origin, 40, player.getName());
				return false;
			}

			if (city.isLocationOnCityGrid(placementInfo.getLoc()) == false) {
				PlaceAssetMsg.sendPlaceAssetError(origin, 41, player.getName());
				return false;
			}

		// Retrieve the building details we're placing

		if (serverZone.isNPCCity() == true) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 15, ""); // Cannot place in a peace zone
			return false;
		}

		// Errant guilds cannot place assets

		if (player.getGuild().getGuildState() == GuildState.Errant) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Only sovereign or sworn guilds may place assets.");
			return false;
		}

		// Player must be GL or IC of a guild to place buildings.

		if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false && GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 10, ""); // You must be a guild leader
			return false;
		}

		// Cannot place a building underwater

		if (HeightMap.isLocUnderwater(placementInfo.getLoc())) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 6, ""); // Cannot place underwater
			return false;
		}

		// Players cannot place buildings in mob zones.

		if ((serverZone.isMacroZone() == true)
				|| (serverZone.getParent().isMacroZone() == true)) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 57, player.getName()); // No building may be placed within this territory
			return false;
		}

		Realm serverRealm = RealmMap.getRealmAtLocation(player.getLoc());

		// Cannot place buildings on seafloor or other restricted realms

		if (serverRealm == null || serverRealm.getCanPlaceCities() == false) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 57, player.getName()); // No building may be placed within this territory
			return false;
		}

		// Cannot place assets on a dead tree

		if ((serverZone.isPlayerCity())
				&& (City.getCity(serverZone.getPlayerCityUUID()).getTOL().getRank() == -1)){
			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Cannot place asset on dead tree until world heals");
			return false;
		}

		if (placementCollisionCheck(serverZone, origin, placementInfo)){
			PlaceAssetMsg.sendPlaceAssetError(origin, 3, ""); // Conflict between proposed assets
			return false;
		}

		return true;
	}

	private static boolean placementCollisionCheck(Zone serverZone, ClientConnection origin, PlacementInfo placementInfo) {
		// Overlap check

		for (Building building : serverZone.zoneBuildingSet) {

			if ((building.getBlueprintUUID() != 0) && (Bounds.collide(placementInfo, building) == true)) {

				// Ignore and remove from simulation if we are placing over rubble

				if (building.getRank() == -1) {

					if ((building.getBlueprintUUID() != 0)
							&& (building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)){
						Shrine.RemoveShrineFromCacheByBuilding(building);
						if (building.getCity() != null){

						}
					}

					building.removeFromCache();
					WorldGrid.RemoveWorldObject(building);
					WorldGrid.removeObject(building);
					building.getParentZone().zoneBuildingSet.remove(building);
					if(building.getBlueprint() != null && building.getBlueprint().getBuildingGroup().equals(BuildingGroup.BARRACK)){
						building.RemoveFromBarracksList();
					}
					continue;
				}


				PlaceAssetMsg.sendPlaceAssetError(origin, 3, "");  // Conflict between proposed assets
				return true;
			}
		}
		return false;
	}

	private static boolean validateCityBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo buildingInfo) {

		// Perform shared common validation first

		if (validateBuildingPlacement(serverZone, msg, origin, player, buildingInfo) == false)
			return false;

		// Must be a player city

		if (serverZone.isPlayerCity() == false) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 41, player.getName()); // Cannot place outside a guild zone
			return false;
		}

		//Test zone has a city object

		City city = City.getCity(serverZone.getPlayerCityUUID());

		if (city == null) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 52, ""); //"no city to associate asset with"
			return false;
		}

		// City assets must be placed on the city grid

		if (!city.isLocationOnCityGrid(buildingInfo.getLoc())) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 52, "");
			return false;
		}

		// Make sure it's not an errant tree

		if ( (city.getGuild() == null || city.getGuild().isEmptyGuild() == true)) {
			PlaceAssetMsg.sendPlaceAssetError(origin, 18, ""); //"There are no guild trees to be found"
			return false;
		}

		//Test player is in correct guild to place buildings

		if (!player.isCSR)
			if (player.getGuild().getObjectUUID() != city.getGuild().getObjectUUID()) {
				PlaceAssetMsg.sendPlaceAssetError(origin, 9, "");  //You must be a guild member to place this asset
				return false;
			}
		return true;
	}
}