package engine.objects;

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



import engine.Enum.BuildingGroup;
import engine.gameManager.DbManager;
import engine.math.Vector2f;
import org.pmw.tinylog.Logger;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;

/*  @Summary - Blueprint class is used for determining
 characteristics of instanced player owned
 structures such as available slots, upgrade
 cost/time and the target window symbol icon.
 */
public class Blueprint {

	public final static Vector2f IrikieForgeExtents = new Vector2f(32, 32);
	public final static Vector2f IrikieBarracksExtents = new Vector2f(32, 32);

	private static HashMap<Integer, Blueprint> _blueprints = new HashMap<>();
	private static HashMap<Integer, Integer> _doorNumbers = new HashMap<>();
	public static HashMap<Integer, Blueprint> _meshLookup = new HashMap<>();

	private final int blueprintUUID;
	private final String name;
	private final BuildingGroup buildingGroup;
	private final int icon;
	private final int maxRank;
	private final int maxSlots;
	private final int rank1UUID;
	private final int rank3UUID;
	private final int rank7UUID;
	private final int destroyedUUID;

	private Blueprint() {
		this.blueprintUUID = 0;
		this.name = "";
		this.icon = 0;
		this.buildingGroup = BuildingGroup.BANESTONE;
		this.maxRank = 0;
		this.maxSlots = 0;
		this.rank1UUID = 0;
		this.rank3UUID = 0;
		this.rank7UUID = 0;
		this.destroyedUUID = 0;
	}

	public Blueprint(ResultSet rs) throws SQLException {

		this.blueprintUUID = rs.getInt("Rank0UUID");
		this.name = rs.getString("MeshName");
		this.icon = rs.getInt("Icon");
		this.buildingGroup = BuildingGroup.valueOf(rs.getString("BuildingGroup"));
		this.maxRank = rs.getInt("MaxRank");
		this.maxSlots = rs.getInt("MaxSlots");
		this.rank1UUID = rs.getInt("Rank1UUID");
		this.rank3UUID = rs.getInt("Rank3UUID");
		this.rank7UUID = rs.getInt("Rank7UUID");
		this.destroyedUUID = rs.getInt("DestroyedUUID");

	}

	// Accessors

	public static Blueprint getBlueprint(int blueprintUUID) {

		return _blueprints.get(blueprintUUID);

	}

	public static BuildingGroup getBuildingGroup(int blueprintUUID) {

		Blueprint blueprint;

		blueprint = _blueprints.get(blueprintUUID);

        return blueprint.buildingGroup;
    }

	public static int getMaxShrines(int treeRank) {

		// Returns the number of allowed spires/shrines
		// for a given rank.

		int maxShrines;

		switch (treeRank) {
			case 0:
			case 1:
			case 2:
				maxShrines = 0;
				break;
			case 3:
			case 4:
				maxShrines = 1;
				break;
			case 5:
			case 6:
				maxShrines = 2;
				break;
			case 7:
			case 8:
				maxShrines = 3;
				break;
			default:
				maxShrines = 0;

		}

		return maxShrines;
	}

	public static void loadAllBlueprints() {

		_blueprints = DbManager.BlueprintQueries.LOAD_ALL_BLUEPRINTS();

	}

	// Method returns a blueprint based on a blueprintUUID

	public static void loadAllDoorNumbers() {

		_doorNumbers = DbManager.BlueprintQueries.LOAD_ALL_DOOR_NUMBERS();

	}

	public static int getDoorNumberbyMesh(int doorMeshUUID) {

		if (_doorNumbers.containsKey(doorMeshUUID))
			return _doorNumbers.get(doorMeshUUID);

		return 0;
	}

	public static boolean isMeshWallPiece(int meshUUID) {

		Blueprint buildingBlueprint = Blueprint.getBlueprint(meshUUID);

		if (buildingBlueprint == null)
			return false;

        switch (buildingBlueprint.buildingGroup) {
		case WALLSTRAIGHT:
		case ARTYTOWER:
		case WALLCORNER:
		case SMALLGATE:
		case WALLSTAIRS:
			return true;
		default:
			break;
		}
		return false;

	}

	// Method calculates available vendor slots
	// based upon the building's current rank

	public static int getNpcMaintCost(int rank) {
		int maintCost = Integer.MAX_VALUE;

		maintCost = (9730 * rank) + 1890;

		return maintCost;
	}

	public int getMaxRank() {
		return maxRank;
	}

	public int getMaxSlots() {
        if (this.buildingGroup != null && this.buildingGroup.equals(BuildingGroup.BARRACK))
			return 1;
		return maxSlots;
	}

	// Method returns a mesh UUID for this blueprint
	// based upon a given rank.

	public BuildingGroup getBuildingGroup() {
		return this.buildingGroup;
	}

	// Method returns a cost to upgrade a building to a given rank
	// based upon this blueprint's maintenance group

	public int getMaxHealth(int currentRank) {

		int maxHealth;

		// Return 0 health for a destroyed building
		// or 1 for a destroyed mine (cleint looting restriction)

		if (currentRank == -1) {

            return this.buildingGroup == BuildingGroup.MINE ? 1 : 0;
		}

		// Return 15k for a constructing mesh

		if (currentRank == 0)
			return 15000;

		switch (this.buildingGroup) {

		case TOL:
			maxHealth = (70000 * currentRank) + 10000;
			break;
		case BARRACK:
			maxHealth = (35000 * currentRank) + 5000;
			break;
		case BANESTONE:
			maxHealth = (170000 * currentRank) - 120000;
			break;
		case CHURCH:
			maxHealth = (28000 * currentRank) + 4000;
			break;
		case MAGICSHOP:
		case FORGE:
		case INN:
		case TAILOR:
			maxHealth = (17500 * currentRank) + 2500;
			break;
		case VILLA:
		case ESTATE:
		case FORTRESS:
			maxHealth = 300000;
			break;
		case CITADEL:
			maxHealth = 500000;
			break;
		case SPIRE:
			maxHealth = (37000 * currentRank) - 9000;
			break;
		case GENERICNOUPGRADE:
		case SHACK:
		case SIEGETENT:
			maxHealth = 40000;
			break;
		case BULWARK:
			if (currentRank == 1)
				maxHealth = 110000;
			else
				maxHealth = 40000;
			break;
		case WALLSTRAIGHT:
		case WALLSTRAIGHTTOWER:
		case WALLSTAIRS:
			maxHealth = 1000000;
			break;
		case WALLCORNER:
		case ARTYTOWER:
			maxHealth = 900000;
			break;
		case SMALLGATE:
			maxHealth = 1100000;
			break;
		case AMAZONHALL:
		case CATHEDRAL:
		case GREATHALL:
		case KEEP:
		case THIEFHALL:
		case TEMPLEHALL:
		case WIZARDHALL:
		case ELVENHALL:
		case ELVENSANCTUM:
		case IREKEIHALL:
		case FORESTHALL:
			maxHealth = (28000 * currentRank) + 4000;
			break;
		case MINE:
			maxHealth = 125000;
			break;
		case RUNEGATE:
			maxHealth = 100000;
			break;
		case SHRINE:
			maxHealth = 100000;
			break;
		case WAREHOUSE:
			maxHealth = 40000;
			break;

		default:
			maxHealth = 40000;
			break;

		}
		return maxHealth;
	}

	// Returns number of vendor slots available
	// for the building's current rank.

	public int getSlotsForRank(int currentRank) {

		int availableSlots;

		// Early exit for buildings not yet constructed

		if (currentRank == 0)
			return 0;

		// Early exit for buildings with single or no slots

		if (this.maxSlots <= 1)
			return maxSlots;

		if (this.maxRank == 1 && currentRank == 1)
			return getMaxSlots();

		switch (currentRank) {

		case 1:
		case 2:
			availableSlots = 1;
			break;
		case 3:
			case 4:
			case 5:
			case 6:
			availableSlots = 2;
			break;
		case 7:
			availableSlots = 3;
			break;
		case 8:
			availableSlots = 1;
			break;
		default:
			availableSlots = 0;
			break;
		}

		return availableSlots;
	}

	// Returns the half extents of this blueprint's
	// bounding box, based upon it's buildinggroup

	public int getIcon() {
		return this.icon;
	}

	public String getName() {
		return this.name;
	}

	public int getMeshForRank(int targetRank) {

		int targetMesh = this.blueprintUUID;

		// The Blueprint UUID is the 'constructing' mesh so
		// we return that value if the rank passed is 0.

        if ((maxRank == 1) && (this.rank1UUID == 0)) {
            return blueprintUUID;
        }

		// Set the return value to the proper mesh UID for rank

		switch (targetRank) {

		case -1:
			targetMesh = this.destroyedUUID; // -1 Rank is a destroyed mesh
			break;
		case 0:
			targetMesh = this.blueprintUUID; // Rank 0 is the 'constructing' mesh
			break;
		case 1:
		case 2:
			targetMesh = this.rank1UUID;
			break;
		case 3:
		case 4:
		case 5:
		case 6:
			targetMesh = this.rank3UUID;
			break;
		case 7:
		case 8:
			targetMesh = this.rank7UUID;
			break;
		default:
			break;
		}

		return targetMesh;
	}

	public int getRankCost(int targetRank) {

		// Set a MAXINT rankcost in case something goes wrong

		int rankCost = Integer.MAX_VALUE;

		// Sanity chack for retrieving a rankcost outside proper range

        if ((targetRank > maxRank) || (targetRank < 0)) {
			Logger.error( "Attempt to retrieve rankcost for rank of" + targetRank);
			return rankCost;
		}

		// Select linear equation for rank cost based upon the
		// buildings current Maintenance BuildingGroup.

		switch (this.buildingGroup) {

		case GENERICNOUPGRADE:
		case WALLSTRAIGHT:
		case WALLSTAIRS:
		case WALLCORNER:
		case SMALLGATE:
		case ARTYTOWER:
		case SIEGETENT:
		case BULWARK:
		case BANESTONE:
		case SHACK:
			break; // This set cannot be upgraded.  Returns max integer.

		case TOL:
			rankCost = (880000 * targetRank) - 440000;
			break;
		case BARRACK:
		case VILLA:
		case ESTATE:
		case FORTRESS:
		case CITADEL:
			rankCost = (451000 * targetRank) - 308000;
			break;
		case CHURCH:
			rankCost = (682000 * targetRank) - 110000;
			break;
		case FORGE:
		case INN:
		case TAILOR:
		case MAGICSHOP:
			rankCost = (440000 * targetRank) - 550000;
			break;
		case SPIRE:
			rankCost = (176000 * targetRank) - 88000;
			break;
		case AMAZONHALL:
		case CATHEDRAL:
		case GREATHALL:
		case KEEP:
		case THIEFHALL:
		case TEMPLEHALL:
		case WIZARDHALL:
		case ELVENHALL:
		case ELVENSANCTUM:
		case IREKEIHALL:
		case FORESTHALL:
			rankCost = (682000 * targetRank) - 110000;
			break;
		default:
            Logger.error("Attempt to retrieve rankcost without MaintGroup for " + this.buildingGroup.name());
			break;
		}

		return rankCost;
	}

	public int getRankTime(int targetRank) {

		// Set a very long rankTime in case something goes wrong

		int rankTime = (Integer.MAX_VALUE / 2);

		// Set all initial construction to a default of 4 hours.

		if (targetRank == 1)
			return 4;

		// Sanity chack for retrieving a ranktime outside proper range

        if ((targetRank > maxRank) || (targetRank < 1)) {
			Logger.error( "Attempt to retrieve ranktime for rank of" + targetRank);
			return rankTime;
		}

		// Select equation for rank time based upon the
		// buildings current Maintenance BuildingGroup.  These values
		// are expressed in hours

		switch (this.buildingGroup) {

		case GENERICNOUPGRADE:
			break; // Cannot be upgraded
		case VILLA:
		case ESTATE:
		case FORTRESS:
		case CITADEL:
			rankTime = (7 * targetRank) - 7;
			break;
		case TOL:
			rankTime = (7 * targetRank) - 7;
			break;
		case BARRACK:
			rankTime = (7 * targetRank) - 7;
			break;
		case CHURCH:
			rankTime = (7 * targetRank) - 7;
			break;
		case FORGE:
		case INN:
		case TAILOR:
		case MAGICSHOP:
			rankTime = (7 * targetRank) - 7;
			break;
		case SPIRE:
			rankTime = (4 * targetRank) + 4;
			break;
		case AMAZONHALL:
		case CATHEDRAL:
		case GREATHALL:
		case KEEP:
		case THIEFHALL:
		case TEMPLEHALL:
		case WIZARDHALL:
		case ELVENHALL:
		case ELVENSANCTUM:
		case IREKEIHALL:
		case FORESTHALL:
			rankTime = (7 * targetRank) - 7;
			break;
		default:
			Logger.error("Attempt to retrieve ranktime without MaintGroup");
			break;
		}

		return rankTime;
	}

	public Vector2f getExtents() {

        if (blueprintUUID == 1302600)
			return Blueprint.IrikieForgeExtents;
		else if (blueprintUUID == 1300600)
			return Blueprint.IrikieBarracksExtents;

		return this.buildingGroup.getExtents();

	}

	public boolean isWallPiece() {

        switch (this.buildingGroup) {
		case WALLSTRAIGHT:
		case WALLSTAIRS:
		case ARTYTOWER:
		case WALLCORNER:
		case SMALLGATE:
			return true;
		default:
			break;
		}
		return false;
	}

	public boolean isSiegeEquip() {

        switch (this.buildingGroup) {
		case BULWARK:
		case SIEGETENT:
			return true;
		default:
			break;
		}
		return false;

	}

	public int getBlueprintUUID() {
		return blueprintUUID;
	}


	@Override
	public boolean equals(Object object) {

		if ((object instanceof Blueprint) == false)
			return false;

		Blueprint blueprint = (Blueprint) object;

        return this.blueprintUUID == blueprint.blueprintUUID;
	}

	@Override
	public int hashCode() {

		return this.blueprintUUID ;
	}

	public int getMaintCost(int rank) {

		int maintCost = Integer.MAX_VALUE;

        switch (this.buildingGroup) {
		case TOL:
		case BARRACK:
			maintCost = (61500 * rank) + 19500;
			break;
		case SPIRE:
			maintCost = (4800 * rank) + 1200;
			break;
		default:
            if (maxRank == 1)
				maintCost = 22500;
			else
				maintCost = (15900 * rank) + 3300;
			break;
		}

		return maintCost;
	}
}