//  • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
//  ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
//  ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
//  ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
//  ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
//      Magicbane Emulator Project © 2013 - 2026
//                https://www.magicbane.com


package engine.objects;

import engine.InterestManagement.RealmMap;
import engine.InterestManagement.WorldGrid;
import engine.db.archive.DataWarehouse;
import engine.db.archive.RealmRecord;
import engine.gameManager.BuildingManager;
import engine.gameManager.DbManager;
import engine.gameManager.GuildManager;
import engine.gameManager.PowersManager;
import engine.mbEnums;
import engine.net.ByteBufferWriter;
import engine.net.client.msg.ErrorPopupMsg;
import engine.powers.PowersBase;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;

import java.awt.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;

import static engine.mbEnums.CharterType;


public class Realm {

    // Internal class cache

    public static ConcurrentHashMap<Integer, Realm> _realms = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
    public final int realmID;
    private final float mapR; //Red color
    private final float mapG; //Green color
    private final float mapB; //Blue color
    private final float mapA; //Alpha color
    private final boolean canBeClaimed;
    private final boolean canPlaceCities;
    private final int numCities;
    private final String realmName;
    private final float mapY1;
    private final float mapX1;
    private final float mapY2;
    private final float mapX2;
    private final int stretchX;
    private final int stretchY;
    private final int locX;
    private final int locY;
    private final HashSet<Integer> cities = new HashSet<>();
    public Color mapColor;
    public LocalDateTime ruledSince;
    private int rulingCityUUID;
    private int rulingCharacterUUID;
    private int rulingCharacterOrdinal;
    private String rulingCharacterName;
    private int rulingNationUUID;
    private GuildTag rulingNationTags;
    private String rulingNationName;
    private int charterType;
    private String hash;

    /**
     * ResultSet Constructor
     */
    public Realm(ResultSet rs) throws SQLException {

        this.mapColor = new Color(Integer.parseInt(rs.getString("realmColor"), 16));
        this.mapR = (float) (mapColor.getRed() * 0.00392156863);
        this.mapG = (float) (mapColor.getGreen() * 0.00392156863);
        this.mapB = (float) (mapColor.getBlue() * 0.00392156863);
        this.mapA = 1;
        this.canBeClaimed = rs.getBoolean("canBeClaimed");
        this.canPlaceCities = rs.getBoolean("canPlaceCities");
        this.numCities = rs.getInt("numCities");
        this.realmName = rs.getString("realmName");
        this.rulingCityUUID = rs.getInt("rulingCityUID");
        this.charterType = rs.getInt("charterType");

        java.sql.Timestamp ruledTimeStamp = rs.getTimestamp("ruledSince");

        if (ruledTimeStamp != null)
            this.ruledSince = LocalDateTime.ofInstant(ruledTimeStamp.toInstant(), ZoneId.systemDefault());

        this.mapY1 = rs.getFloat("mapY1");
        this.mapX1 = rs.getFloat("mapX1");
        this.mapY2 = rs.getFloat("mapY2");
        this.mapX2 = rs.getFloat("mapX2");
        this.stretchX = rs.getInt("stretchX");
        this.stretchY = rs.getInt("stretchY");
        this.locX = rs.getInt("locX");
        this.locY = rs.getInt("locY");
        this.realmID = rs.getInt("realmID");
        this.hash = rs.getString("hash");
    }

    public static void configureAllRealms() {

        for (Realm realm : Realm._realms.values()) {
            realm.configure();
        }
    }

    public static Realm getRealm(int realmID) {
        return _realms.get(realmID);
    }

    public static boolean HasAllBlessings(PlayerCharacter claimer) {

        if (claimer == null)
            return false;

        PowersBase powerBlessing = PowersManager.getPowerByIDString("BLS-POWER");
        if (!claimer.effects.containsKey(Integer.toString(powerBlessing.getActions().get(0).getUUID())))
            return false;
        PowersBase wisdomBlessing = PowersManager.getPowerByIDString("BLS-POWER");
        if (!claimer.effects.containsKey(Integer.toString(wisdomBlessing.getActions().get(0).getUUID())))
            return false;
        PowersBase fortuneBlessing = PowersManager.getPowerByIDString("BLS-FORTUNE");
        return claimer.effects.containsKey(Integer.toString(fortuneBlessing.getActions().get(0).getUUID()));
    }

    public static float getRealmHealthMod(City city) {
        Realm serverRealm;
        int charterType;
        float returnBonus = 0.0f;

        serverRealm = RealmMap.getRealmForCity(city);
        charterType = serverRealm.charterType;

        switch (charterType) {

            case 762228431:
                returnBonus = 0.0f;
                break;
            case -15978914:
                returnBonus = -.15f;
                break;
            case -600065291:
                returnBonus = .15f;
                break;
            default:
                break;
        }

        return returnBonus;
    }

    public static int getRealmMesh(City city) {
        Realm serverRealm;
        CharterType charterType;

        serverRealm = city.realm;
        charterType = CharterType.getCharterTypeByID(serverRealm.charterType);

        return charterType.getMeshID();
    }

    public static void loadAllRealms() {

        _realms = DbManager.RealmQueries.LOAD_ALL_REALMS();

    }

    public static boolean claimRealm(PlayerCharacter player, int charterUUID) {

        Guild guild;
        Realm realm;
        City city;
        Building tol;
        float hPMod;
        Warehouse warehouse;
        boolean hasResources = true;
        int resourceValue;

        if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) {
            ErrorPopupMsg.sendErrorPopup(player, 176); // Only guild leaders can claim a territory
            return false;
        }

        guild = player.getGuild();
        city = guild.getOwnedCity();

        if (city == null) {
            ErrorPopupMsg.sendErrorPopup(player, 179); // Only landed guilds may claim a territory
            return false;
        }

        if (city.isLocationOnCityGrid(player.getLoc()) == false) {
            ErrorPopupMsg.sendErrorPopup(player, 186); // Your tree is not inside a territory!
            return false;
        }

        tol = city.getTOL();

        if (tol.getRank() != 7) {
            ErrorPopupMsg.sendErrorPopup(player, 181); // Your tree must be rank 7 before claiming a territory
            return false;
        }

        realm = RealmMap.getRealmForCity(city);

        if (realm.getCanBeClaimed() == false) {
            ErrorPopupMsg.sendErrorPopup(player, 180); // This territory cannot be ruled by anyone
            return false;
        }

        if (realm.isRuled() == true) {
            ErrorPopupMsg.sendErrorPopup(player, 178); // This territory is already claimed
            return false;
        }

        if (!HasAllBlessings(player)) {
            ErrorPopupMsg.sendErrorPopup(player, 185); // You must seek the blessing of the three sages before you can rule
            return false;
        }

        // Must have the required resources in warehouse to claim realm

        warehouse = city.warehouse;

        if (warehouse == null) {
            ErrorPopupMsg.sendErrorPopup(player, 188);  // You must have a warehouse to become a capital
            return false;
        }

        resourceValue = warehouse.resources.get(mbEnums.ResourceType.GOLD);

        if (resourceValue < 5000000)
            hasResources = false;

        resourceValue = warehouse.resources.get(mbEnums.ResourceType.STONE);

        if (resourceValue < 8000)
            hasResources = false;

        resourceValue = warehouse.resources.get(mbEnums.ResourceType.LUMBER);

        if (resourceValue < 8000)
            hasResources = false;

        resourceValue = warehouse.resources.get(mbEnums.ResourceType.GALVOR);

        if (resourceValue < 15)
            hasResources = false;

        resourceValue = warehouse.resources.get(mbEnums.ResourceType.WORMWOOD);

        if (resourceValue < 15)
            hasResources = false;

        if (hasResources == false) {
            ErrorPopupMsg.sendErrorPopup(player, 184);  // Insufficient gold or resources to upgrade to capital
            return false;
        }

        // Remove resources from warehouse before claiming realm

        resourceValue = warehouse.resources.get(mbEnums.ResourceType.GOLD);
        warehouse.resources.put(mbEnums.ResourceType.GOLD, resourceValue - 5000000);

        if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
            Logger.error("gold update failed for warehouse of city:" + warehouse.city.getName());
            warehouse.resources.put(mbEnums.ResourceType.GOLD, resourceValue);
            return false;
        }

        Warehouse.AddTransactionToWarehouse(warehouse, mbEnums.GameObjectType.Building, tol.getObjectUUID(), mbEnums.TransactionType.WITHDRAWL, mbEnums.ResourceType.GOLD, 5000000);

        resourceValue = warehouse.resources.get(mbEnums.ResourceType.STONE);
        warehouse.resources.put(mbEnums.ResourceType.STONE, resourceValue - 8000);

        if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
            Logger.error("stone update failed for warehouse of city:" + warehouse.city.getName());
            warehouse.resources.put(mbEnums.ResourceType.STONE, resourceValue);
            return false;
        }

        resourceValue = warehouse.resources.get(mbEnums.ResourceType.LUMBER);
        warehouse.resources.put(mbEnums.ResourceType.LUMBER, resourceValue - 8000);

        if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
            Logger.error("lumber update failed for warehouse of city:" + warehouse.city.getName());
            warehouse.resources.put(mbEnums.ResourceType.LUMBER, resourceValue);
            return false;
        }

        resourceValue = warehouse.resources.get(mbEnums.ResourceType.GALVOR);
        warehouse.resources.put(mbEnums.ResourceType.GALVOR, resourceValue - 15);

        if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
            Logger.error("GALVOR update failed for warehouse of city:" + warehouse.city.getName());
            warehouse.resources.put(mbEnums.ResourceType.GALVOR, resourceValue);
            return false;
        }

        resourceValue = warehouse.resources.get(mbEnums.ResourceType.WORMWOOD);
        warehouse.resources.put(mbEnums.ResourceType.WORMWOOD, resourceValue - 15);

        if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
            Logger.error("WORMWOOD update failed for warehouse of city:" + warehouse.city.getName());
            warehouse.resources.put(mbEnums.ResourceType.WORMWOOD, resourceValue);
            return false;
        }

        realm.claimRealmForCity(city, charterUUID);

        BuildingManager.setRank(tol, 8);
        WorldGrid.updateObject(tol);

        for (Building building : city.getParent().zoneBuildingSet) {

            if (building.getBlueprintUUID() != 0) {

                // TOL Health set through regular linear equation
                if (building.getBlueprint().getBuildingGroup() == mbEnums.BuildingGroup.TOL) {
                    continue;
                }

                hPMod = (building.getMaxHitPoints() * getRealmHealthMod(city));
                building.setMaxHitPoints(building.getMaxHitPoints() + hPMod);
            }
        }

        if (!guild.getNation().equals(guild)) {
            Guild guild1 = guild.getNation();
            guild1.realmsOwned = guild.getNation().realmsOwned + 1;
            GuildManager.updateAllGuildTags(guild.getNation());
        }

        guild.realmsOwned = guild.realmsOwned + 1;
        GuildManager.updateAllGuildTags(guild);

        AbstractCharacter.removeAllBlessings(player);

        return true;

    }

    /*
     * Getters
     */
    public boolean isRuled() {
        return (this.rulingCityUUID != 0);
    }

    public boolean getCanBeClaimed() {
        return this.canBeClaimed;
    }

    public boolean getCanPlaceCities() {
        return this.canPlaceCities;
    }

    public int getNumCities() {
        return this.numCities;
    }

    public String getRealmName() {
        return this.realmName;
    }

    public City getRulingCity() {
        return City.getCity(this.rulingCityUUID);
    }


    // Call this after changing ownership before you serialize a realm

    public int getRealmID() {
        return this.realmID;
    }

    public void addCity(int cityUUID) {
        if (!this.cities.add(cityUUID))
            this.cities.add(cityUUID);
    }

    public void removeCity(int cityUUID) {
        this.cities.remove(cityUUID);
    }

    public boolean isRealmFull() {

        return this.cities.size() >= this.numCities;
    }

    public boolean isRealmFullAfterBane() {

        return this.cities.size() > this.numCities;
    }

    public void configure() {

        PlayerCharacter rulingCharacter;

        // Configure what exactly?  We won't send any of it.

        if (this.rulingCityUUID == 0)
            return;
        if (this.getRulingCity() == null)
            return;
        if (this.getRulingCity().getTOL() == null)
            return;

        rulingCharacter = PlayerCharacter.getPlayerCharacter(this.getRulingCity().getTOL().getOwnerUUID());
        if (rulingCharacter == null) {
            Logger.info(this.realmName + " failed to load " + this.getRulingCity().getCityName() + " ID : " + this.rulingCityUUID);
            return;
        }

        this.rulingCharacterUUID = rulingCharacter.getObjectUUID();
        this.rulingCharacterOrdinal = rulingCharacter.getObjectType().ordinal();
        this.rulingCharacterName = rulingCharacter.getFirstName() + ' ' + rulingCharacter.getLastName();
        this.rulingNationUUID = rulingCharacter.getGuild().getNation().getObjectUUID();
        this.rulingNationName = rulingCharacter.getGuild().getNation().getName();
        this.rulingNationTags = rulingCharacter.getGuild().getNation().getGuildTag();
    }

    public void serializeForClientMsg(ByteBufferWriter writer) {

        writer.putFloat(this.mapR);
        writer.putFloat(this.mapG);
        writer.putFloat(this.mapB);
        writer.putFloat(this.mapA);
        writer.put((byte) (this.canBeClaimed ? 0x1 : 0x0));
        writer.put((byte) (this.canPlaceCities ? 0x1 : 0x0));
        writer.putInt(this.numCities);
        writer.putFloat(this.mapR);
        writer.putFloat(this.mapG);
        writer.putFloat(this.mapB);
        writer.putFloat(this.mapA);
        writer.putString(this.realmName);

        if (isRuled() == true) {
            writer.putInt(mbEnums.GameObjectType.Guild.ordinal());
            writer.putInt(rulingNationUUID);

            writer.putInt(rulingCharacterOrdinal);
            writer.putInt(rulingCharacterUUID);

            writer.putInt(mbEnums.GameObjectType.City.ordinal());
            writer.putInt(rulingCityUUID);

            writer.putLocalDateTime(this.ruledSince);

            writer.putString(rulingNationName);
            GuildTag._serializeForDisplay(rulingNationTags, writer);
            writer.putString(rulingCharacterName);
            writer.putInt(0xB); // Display Title: enum index starts at 10.
        } else {
            if (this.rulingCityUUID != 0)
                Logger.error("Failed to Load realm info for city" + this.rulingCityUUID);
            writer.putLong(0);
            writer.putLong(0);
            writer.putLong(0);
            writer.put((byte) 1);
            writer.put((byte) 0);
            writer.putInt(0x64);
            writer.put((byte) 0);
            writer.put((byte) 0);
            writer.put((byte) 0);
            writer.putInt(0);
            writer.putInt(0x10);
            writer.putInt(0x10);
            writer.putInt(0x10);
            writer.putInt(0x0);
            writer.putInt(0x0);
            writer.putInt(0);
            writer.putInt(0);
        }
        writer.putInt(0); // Male/Female
        writer.putInt(this.charterType); // Charter Type
        writer.putFloat(this.mapY1);
        writer.putFloat(this.mapX1);
        writer.putFloat(this.mapY2);
        writer.putFloat(this.mapX2);
        writer.putInt(this.stretchX);
        writer.putInt(this.stretchY);
        writer.putInt(this.locX);
        writer.putInt(this.locY);
    }

    public void updateDatabase() {
        DbManager.RealmQueries.REALM_UPDATE(this);
    }

    /**
     * @return the charterType
     */
    public int getCharterType() {
        return charterType;
    }

    public void abandonRealm() {

        // Push event to warehouse

        RealmRecord realmRecord = RealmRecord.borrow(this, mbEnums.RecordEventType.LOST);
        DataWarehouse.pushToWarehouse(realmRecord);

        // No longer own a realm
        Guild guild1 = this.getRulingCity().getGuild();
        guild1.realmsOwned = this.getRulingCity().getGuild().realmsOwned - 1;

        if (!guild1.getNation().equals(guild1))
            guild1.getNation().realmsOwned -= 1;

        // Configure realm
        this.charterType = 0;
        this.rulingCityUUID = 0;
        this.ruledSince = null;

        this.updateDatabase();
    }

    public void claimRealmForCity(City city, int charterType) {

        // Configure realm
        this.charterType = charterType;
        this.rulingCityUUID = city.getObjectUUID();
        this.ruledSince = LocalDateTime.now();
        this.configure();
        this.updateDatabase();

        // Push event to warehouse

        RealmRecord realmRecord = RealmRecord.borrow(this, mbEnums.RecordEventType.CAPTURE);
        DataWarehouse.pushToWarehouse(realmRecord);

    }

    public String getHash() {
        return hash;
    }

}
