package engine.net.client.handlers;

import engine.Enum;
import engine.Enum.*;
import engine.InterestManagement.InterestManager;
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);

    }

    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);
    }

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

    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 (ZoneManager.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 static boolean validateBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo placementInfo) {

        if (serverZone.isGuildZone == 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 (ZoneManager.isLocUnderwater(placementInfo.getLoc())) {
            PlaceAssetMsg.sendPlaceAssetError(origin, 6, ""); // Cannot place underwater
            return false;
        }

        // Players cannot place buildings in mob zones.

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

        Realm serverRealm = RealmMap.getRealmAtLocation(city.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.isGuildZone)
                && (City.getCity(serverZone.playerCityUUID).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.isGuildZone == 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.playerCityUUID);

        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;
    }

    @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();
        }

        // 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;
    }

    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.playerCityUUID);

        // 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().isGuildZone == 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.halfExtents))
                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,
                0,
                treeInfo.getLoc().z);

        cityObjects = DbManager.CityQueries.CREATE_CITY(playerCharacter.getObjectUUID(), serverZone.getObjectUUID(),
                serverRealm.getRealmID(),
                plantLoc.x - serverZone.absX, plantLoc.y,
                plantLoc.z - serverZone.absZ, 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.isGuildZone = true;

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

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

        zoneObject.worldAltitude = ZoneManager.caclulateWorldAltitude(zoneObject);

        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.halfExtents, Enum.CityBoundsType.ZONE.halfExtents);
        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();
        treeObject.setLoc(treeObject.getLoc());
        InterestManager.setObjectDirty(treeObject);

        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;
    }

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

    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.playerCityUUID);

        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.playerCityUUID);

        // 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

        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.playerCityUUID);

        // 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.playerCityUUID);

        // 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().parent.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);

        }

        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));

        newMesh.setLoc(newMesh.getLoc());
        InterestManager.setObjectDirty(newMesh);
        return newMesh;

    }

    // Validates that player is able to place buildings

    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.playerCityUUID);

        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));
                    newMesh.setLoc(newMesh.getLoc());
                    InterestManager.setObjectDirty(newMesh);
                    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.playerCityUUID);

        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));
        newMesh.setLoc(newMesh.getLoc());
        InterestManager.setObjectDirty(newMesh);

        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));
                newMesh.setLoc(newMesh.getLoc());
                InterestManager.setObjectDirty(newMesh);
                newMesh.runAfterLoad();
            } else if (ago.getObjectType() == GameObjectType.Warehouse) {
                Warehouse warehouse = (Warehouse) ago;
                City city = City.getCity(currentZone.playerCityUUID);

                if (city == null)
                    return true;

                city.setWarehouseBuildingID(newMesh.getObjectUUID());
                Warehouse.warehouseByBuildingUUID.put(newMesh.getObjectUUID(), warehouse);
            }
        }

        return true;
    }
}