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

package engine.gameManager;

import engine.loot.WorkOrder;
import engine.mbEnums;
import engine.objects.Item;
import engine.objects.ItemTemplate;
import engine.objects.MobLoot;
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.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public enum ForgeManager implements Runnable {

    FORGE_MANAGER;

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

    @Override
    public void run() {

        while (true) {

            WorkOrder workOrder = null;

            try {
                workOrder = forge.take();
            } catch (Exception e) {
                Logger.error(e);
            }

            if (workOrder == null) {
                Logger.error("NULL workOrder in queue");
                continue;
            }

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

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

                // Create in memory items to add to collections
                forgeItems(workOrder);

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

                    // Persist current items that are currently building for this
                    // worker after first removing the negative id item from all
                    // collections.

                    // Add new item to the vendors inventory

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


                // enQueue this workOrder again; back into the oven
                // until all items for this workOrder are completed.

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

    public static void start() {

        Thread forgeManager;
        forgeManager = new Thread(FORGE_MANAGER);

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

    public static int submit(WorkOrder workOrder) {

        int validation_result = ForgeManager.validate(workOrder);

        if (validation_result != 0)
            return validation_result;

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

        workOrder.total_to_produce *= workOrder.slots_used;

        // Create in-memory items and add to collections

        forgeItems(workOrder);

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

        return validation_result;
    }

    public static int validate(WorkOrder workOrder) {

        int validation_result = 0;

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

        if (!workOrder.vendor.charItemManager.hasRoomInventory(template.item_wt))
            return 30;  //30: That person cannot carry that item

        if (!workOrder.vendor.getItemModTable().contains(((byte) template.modTable)))
            return 59;   //59: This hireling does not have this formula


        if (!calcCostOverrun(workOrder).isEmpty())
            return 10;     //18: You can't really afford that

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

        if (calcProductionCost(workOrder).size() > 1)
            if (!workOrder.vendor.building.protectionState.equals(mbEnums.ProtectionState.PROTECTED))
                return 193;     //193: Production denied: This building must be protected to gain access to warehouse

        return validation_result;
    }

    public static long calcRollingDuration(WorkOrder workOrder) {

        float rollingDuration;

        rollingDuration = workOrder.vendor.getBuilding().getRank() * -5L + 40;
        rollingDuration = TimeUnit.MINUTES.toMillis((long) rollingDuration);
        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;

        // Single item rolls are msg_size of 0;

        if (availableSlots > 0 && workOrder.multiple_slot_request == 0)
            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;
    }

    public static Item forgeItem(WorkOrder workOrder) {

        Item forgedItem = new Item(workOrder.templateID);
        forgedItem.objectUUID = MobLoot.lastNegativeID.getAndDecrement();
        forgedItem.containerType = mbEnums.ItemContainerType.FORGE;
        forgedItem.ownerID = workOrder.vendor.getObjectUUID();
        forgedItem.setDateToUpgrade(workOrder.completionTime);

        // Forged random rolled items are unidentified until completed

        if (workOrder.prefixToken == 0 && workOrder.suffixToken == 0)
            forgedItem.flags.remove(mbEnums.ItemFlags.Identified);

        return forgedItem;
    }

    public static void forgeItems(WorkOrder workOrder) {

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

            Item forged_item = forgeItem(workOrder);
            ForgeManager.workOrderMap.put(forged_item, workOrder);

            workOrder.cooking.add(forged_item);

            workOrder.total_produced = workOrder.total_produced + 1;
            Logger.info("Forging item: " + forged_item.objectUUID + " (" + forged_item.templateID + ") " + forged_item.template.item_base_name);
        }
    }
}