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

package engine.gameManager;

import engine.loot.WorkOrder;
import engine.mbEnums;
import engine.net.client.msg.ErrorPopupMsg;
import engine.objects.Item;
import engine.objects.ItemTemplate;
import engine.objects.PlayerCharacter;
import engine.objects.Warehouse;
import engine.powers.EffectsBase;
import org.pmw.tinylog.Logger;

import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.atomic.AtomicInteger;

public enum ForgeManager implements Runnable {

    FORGE_MANAGER;

    private static final BlockingQueue<WorkOrder> workOrders = new DelayQueue();
    public static final AtomicInteger wordOrderCounter = new AtomicInteger(0);
    public static HashMap<Item, WorkOrder> oven = new HashMap<Item, WorkOrder>();

    @Override
    public void run() {

        while (true) {

            try {

                WorkOrder workOrder = workOrders.take();

                // Completed or canceled work orders are not re-enqueued

                if (workOrder.runCanceled || workOrder.runCompleted)
                    continue;

                // Create negative ID items to add to collections

                for (int i = 0; i < workOrder.slots_used; ++i) {

                    // Create workOrder items; one for each slot
                    // assigned to this workOrder.

                    // if Prefix and suffix are null random roll item
                    // otherwise roll what was asked for

                    workOrder.total_produced = workOrder.total_produced + 1;
                }

                Logger.info("item forged:" + workOrder.workOrderID + " (" + workOrder.total_produced + "/" + workOrder.total_to_produce + ")");

                if (workOrder.total_produced >= workOrder.total_to_produce) {

                    Logger.info("workOrder has completed: " + workOrder.workOrderID);

                    workOrder.runCompleted = true;
                    workOrder.vendor.workOrders.remove(workOrder);
                    continue;
                }

                // Persist current items that are cooking
                // after removing the negative id item from all collections.

                // Add new item to the vendors inventory

                // Resubmit workOrder

                workOrder.completionTime = System.currentTimeMillis() + workOrder.rollingDuration;
                workOrders.add(workOrder);
                Logger.info(workOrder.toString());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void start() {

        Thread forgeManager;
        forgeManager = new Thread(FORGE_MANAGER);

        forgeManager.setName("Forge Manager");
        forgeManager.start();
    }

    public static void submit(WorkOrder workOrder) {

        workOrder.workOrderID = wordOrderCounter.incrementAndGet();
        workOrder.rollingDuration = ForgeManager.calcRollingDuration(workOrder);
        workOrder.completionTime = System.currentTimeMillis() + workOrder.rollingDuration;
        workOrder.slots_used = calcAvailableSlots(workOrder);

        // Cost to execute this workOrder

        workOrder.production_cost = calcProductionCost(workOrder);

        // Set total cost for this production run

        workOrder.production_cost_total.putAll(workOrder.production_cost);
        workOrder.production_cost_total.forEach((key, value) -> workOrder.production_cost_total.compute(key, (k, v) -> v * workOrder.total_to_produce));

        // Single item rolls are total_to_produce of 0;

        if (workOrder.slots_used > 0 && workOrder.total_to_produce == 0)
            workOrder.slots_used = 1;

        // Create negative ID items to add to collections

        for (int i = 0; i < workOrder.slots_used; ++i) {

            // Create workOrder items; one for each slot
            // assigned to this workOrder.

            // if Prefix and suffix are null random roll item
            // otherwise roll what was asked for

            workOrder.total_produced = workOrder.total_produced + 1;
        }

        Logger.info(workOrder.toString());
        workOrder.vendor.workOrders.add(workOrder);
        workOrders.add(workOrder);
    }

    public static boolean validate(PlayerCharacter playerCharacter, WorkOrder workOrder) {

        ItemTemplate template = ItemTemplate.templates.get(workOrder.templateID);

        if (!workOrder.vendor.charItemManager.hasRoomInventory(template.item_wt)) {
            if (playerCharacter != null)
                ErrorPopupMsg.sendErrorPopup(playerCharacter, 30);  //30: That person cannot carry that item
            return false;
        }

        if (!workOrder.vendor.getItemModTable().contains(((byte) template.modTable))) {
            if (playerCharacter != null)
                ErrorPopupMsg.sendErrorPopup(playerCharacter, 59);  //59: This hireling does not have this formula
            return false;
        }

        if (!calcCostOverrun(workOrder).isEmpty()) {
            if (playerCharacter != null)
                ErrorPopupMsg.sendErrorPopup(playerCharacter, 18);      //18: You can't really afford that
            return false;
        }

        // Forge must be protected in order to access warehouse.

        if (workOrder.production_cost_total.size() > 1)
            if (!workOrder.vendor.building.protectionState.equals(mbEnums.ProtectionState.PROTECTED)) {
                if (playerCharacter != null)
                    ErrorPopupMsg.sendErrorPopup(playerCharacter, 193);     //193: Production denied: This building must be protected to gain access to warehouse
            }

        return true;
    }

    public static long calcRollingDuration(WorkOrder workOrder) {

        float rollingDuration;

        rollingDuration = workOrder.vendor.getBuilding().getRank() * -5L + 40;
        rollingDuration *= 60000;
        rollingDuration *= Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue());

        ItemTemplate template = ItemTemplate.templates.get(workOrder.templateID);

        // Bane circles

        if (template.item_bane_rank > 0)
            rollingDuration = (long) template.item_bane_rank * 60 * 60 * 3 * 1000 * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue());

        return (long) rollingDuration;
    }

    public static int calcAvailableSlots(WorkOrder workOrder) {

        // Slots available in a forge are based on the npc rank

        int availableSlots = workOrder.vendor.getRank();

        // Slots currently used up by the npc workOrders

        for (WorkOrder npcWorkOrder : workOrder.vendor.workOrders)
            availableSlots = availableSlots - npcWorkOrder.slots_used;

        // Slot count override for single item production

        if (workOrder.total_to_produce == 0 && availableSlots > 1)
            availableSlots = 1;

        return availableSlots;
    }

    public static HashMap<mbEnums.ResourceType, Integer> calcProductionCost(WorkOrder workOrder) {

        // Calculate the production cost for a single run of this workOrder

        HashMap<mbEnums.ResourceType, Integer> production_cost = new HashMap<>();
        ItemTemplate template = ItemTemplate.templates.get(workOrder.templateID);

        // Add gold and resource costs from template

        production_cost.put(mbEnums.ResourceType.GOLD, template.item_value);
        production_cost.putAll(template.item_resource_cost);

        // Calculate cost of prefix and suffix

        if (workOrder.prefixToken != 0) {
            EffectsBase prefix = PowersManager.getEffectByToken(workOrder.prefixToken);
            EffectsBase prefixValue = PowersManager.getEffectByIDString(prefix.getIDString() + 'A');
            production_cost.putAll(prefixValue.getResourcesForEffect());
        }

        if (workOrder.suffixToken != 0) {
            EffectsBase suffix = PowersManager.getEffectByToken(workOrder.suffixToken);
            EffectsBase suffixValue = PowersManager.getEffectByIDString(suffix.getIDString() + 'A');
            production_cost.putAll(suffixValue.getResourcesForEffect());
        }

        return production_cost;
    }

    public static HashMap<mbEnums.ResourceType, Integer> calcCostOverrun(WorkOrder workOrder) {

        HashMap<mbEnums.ResourceType, Integer> costMap = new HashMap<>();
        Warehouse warehouse;

        // See if we can meet gold only requirements and early exit

        if (workOrder.production_cost_total.size() == 1) {
            if (workOrder.vendor.building.getStrongboxValue() > workOrder.production_cost_total.get(mbEnums.ResourceType.GOLD))
                return costMap;
        }

        // Gold deficit exists so a warehouse is required

        warehouse = workOrder.vendor.building.getCity() == null ? null : workOrder.vendor.building.getCity().warehouse;

        if (warehouse == null)
            return workOrder.production_cost_total;

        // Method returns a map of resourceType that a transaction overdrafts.

        HashMap<mbEnums.ResourceType, Integer> overflowMap = new HashMap<>();

        for (mbEnums.ResourceType resourceType : workOrder.production_cost_total.keySet()) {

            int debit = warehouse.resources.get(resourceType) - workOrder.production_cost_total.get(resourceType);

            // Locked resources are always unavailable

            if (debit < 0 || warehouse.locked.contains(resourceType))
                overflowMap.put(resourceType, debit);
        }

        return overflowMap;
    }

}