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


package engine.net.client.handlers;

import engine.gameManager.*;
import engine.loot.WorkOrder;
import engine.mbEnums;
import engine.mbEnums.ItemType;
import engine.net.Dispatch;
import engine.net.client.ClientConnection;
import engine.net.client.msg.ClientNetMsg;
import engine.net.client.msg.ErrorPopupMsg;
import engine.net.client.msg.ItemProductionMsg;
import engine.net.client.msg.ManageNPCMsg;
import engine.objects.*;
import org.pmw.tinylog.Logger;

import java.util.HashMap;

/*
 * @Summary: Processes application protocol message which modifies
 * hireling inventory through rolling, junking or depositing.
 */

public class ItemProductionMsgHandler extends AbstractClientMsgHandler {

    public ItemProductionMsgHandler() {
        super();
    }

    @Override
    protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) {

        // Member variable declaration

        PlayerCharacter player;
        NPC vendor;
        ItemProductionMsg msg;
        Dispatch dispatch;

        // Member variable assignment

        msg = (ItemProductionMsg) baseMsg;
        player = origin.getPlayerCharacter();

        if (player == null)
            return true;

        // Grab reference to vendor we are interacting with

        vendor = (NPC) DbManager.getObject(mbEnums.GameObjectType.NPC, msg.npcUUID);

        // Oops?

        if (vendor == null)
            return true;

        // Process Request

        switch (msg.actionType) {

            case PRODUCE:

                // Create new work order

                WorkOrder workOrder = new WorkOrder();
                workOrder.total_to_produce = msg.total_to_produce;
                workOrder.vendor = vendor;
                workOrder.templateID = msg.itemUUID;
                workOrder.prefixToken = msg.pToken;
                workOrder.suffixToken = msg.sToken;
                workOrder.item_name_override = msg.name;
                workOrder.multiple_slot_request = (msg.size != 0);

                // Submit workOder to begin rolling items

                int validation_result = ForgeManager.submit(workOrder);

                // workOrder could not be completed
                // Report back to the user the reason why

                if (validation_result != 0) {
                    ErrorPopupMsg.sendErrorPopup(player, validation_result);
                    return true;
                }

                break;
            case JUNK:
                junkItem(msg.itemUUID, vendor, origin);
                break;
            case RECYCLE:
                recycleItem(msg.items, vendor, origin);
                msg.actionType = mbEnums.ProductionActionType.TAKE;
                dispatch = Dispatch.borrow(player, msg);
                DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
                break;
            case COMPLETE:
                completeItem(msg.itemUUID, vendor);
                break;
            case DEPOSIT:
                depositItem(msg.itemUUID, vendor, origin);
                break;
            case SETPRICE:
                setItemPrice(msg.itemUUID, msg.itemPrice, vendor, origin);
                break;
            case TAKE:
                takeItem(msg.items, vendor, origin);
                dispatch = Dispatch.borrow(player, msg);
                DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
                break;
        }
        return true;
    }

    private static void completeItem(int itemUUID, NPC vendor) {

        Item virtualItem = Item.getFromCache(itemUUID);
        WorkOrder workOrder = ForgeManager.itemWorkOrderLookup.get(virtualItem);

        City city = workOrder.vendor.building.getCity();

        if (city == null)
            return;

        city.transactionLock.writeLock().lock();

        try {

            // Remove virtual item from the vendor rolling window
            // (Add / Completed / Remove)

            ItemProductionMsg outMsg = new ItemProductionMsg(workOrder.vendor.building, workOrder.vendor, virtualItem, mbEnums.ProductionActionType.CONFIRM_SETPRICE, true);
            DispatchManager.dispatchMsgToInterestArea(workOrder.vendor, outMsg, mbEnums.DispatchChannel.SECONDARY, 700, false, false);

            // Remove virtualItem from collections

            workOrder.cooking.remove(virtualItem);
            ForgeManager.itemWorkOrderLookup.remove(virtualItem);
            DbManager.removeFromCache(virtualItem);

            workOrder.slots_used.decrementAndGet();

            // Update workOrder on disk

            if (workOrder.cooking.isEmpty()) {
                ForgeManager.vendorWorkOrderLookup.get(vendor).remove(workOrder);
                DbManager.WarehouseQueries.DELETE_WORKORDER(workOrder);
            } else
                DbManager.WarehouseQueries.WRITE_WORKORDER(workOrder);

            // Persist item and add to vendor inventory

            virtualItem.containerType = mbEnums.ItemContainerType.INVENTORY;
            Item completedItem = DbManager.ItemQueries.PERSIST(virtualItem);

            // Apply Item effects for Prefix and Suffix tokens

            completedItem.prefixToken = virtualItem.prefixToken;
            completedItem.suffixToken = virtualItem.suffixToken;

            ItemManager.applyItemEffects(completedItem);

            vendor.charItemManager.addItemToInventory(completedItem);

            // Add persisted items to the vendor inventory window

            ItemProductionMsg outMsg1 = new ItemProductionMsg(vendor.building, vendor, completedItem, mbEnums.ProductionActionType.DEPOSIT, true);
            DispatchManager.dispatchMsgToInterestArea(vendor, outMsg1, mbEnums.DispatchChannel.SECONDARY, 700, false, false);
            ItemProductionMsg outMsg2 = new ItemProductionMsg(vendor.building, vendor, completedItem, mbEnums.ProductionActionType.CONFIRM_DEPOSIT, true);
            DispatchManager.dispatchMsgToInterestArea(vendor, outMsg2, mbEnums.DispatchChannel.SECONDARY, 700, false, false);

        } catch (Exception e) {
            Logger.error(e);
        } finally {
            city.transactionLock.writeLock().unlock();
        }
    }

    private static void setItemPrice(int itemUUID, int itemPrice, NPC vendor, ClientConnection origin) {

        Item targetItem;
        ItemProductionMsg outMsg;
        Dispatch dispatch;

        PlayerCharacter player = origin.getPlayerCharacter();

        if (player == null)
            return;

        targetItem = Item.getFromCache(itemUUID);

        if (targetItem == null)
            return;

        if (!DbManager.ItemQueries.UPDATE_VALUE(targetItem, itemPrice)) {
            ChatManager.chatInfoError(origin.getPlayerCharacter(), "Failed to set price! Contact CCR For help.");
            return;
        }

        targetItem.setValue(itemPrice);
        targetItem.magicValue = targetItem.value;

        outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, mbEnums.ProductionActionType.DEPOSIT, true);
        dispatch = Dispatch.borrow(player, outMsg);
        DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);

        outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, mbEnums.ProductionActionType.SETPRICE, true);
        dispatch = Dispatch.borrow(player, outMsg);
        DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);

    }

    // Method sets the price on an item in the vendor inventory

    private static void depositItem(int itemUUID, NPC vendor, ClientConnection origin) {

        Item targetItem;
        ItemProductionMsg outMsg;
        CharacterItemManager itemMan;
        Dispatch dispatch;

        PlayerCharacter player = origin.getPlayerCharacter();

        if (player == null)
            return;

        if (origin.sellLock.tryLock()) {
            try {
                targetItem = Item.getFromCache(itemUUID);

                if (targetItem == null)
                    return;

                if (targetItem.template.item_type.equals(ItemType.GOLD))
                    return;

                if (!vendor.charItemManager.hasRoomInventory(targetItem.template.item_wt)) {
                    ErrorPopupMsg.sendErrorPopup(player, 21);
                    return;
                }

                itemMan = origin.getPlayerCharacter().charItemManager;

                if (itemMan == null)
                    return;

                if (vendor.charItemManager.getInventoryWeight() > 500) {
                    ErrorPopupMsg.sendErrorPopup(player, 21);
                    return;
                }

                if (!targetItem.validForInventory(origin, player, itemMan)) {
                    ErrorPopupMsg.sendErrorPopup(player, 19);
                    return;
                }

                // Transfer item from player to vendor's inventory

                if (!itemMan.sellToNPC(targetItem, vendor)) {
                    ErrorPopupMsg.sendErrorPopup(player, 109);
                    return;
                }

                outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, mbEnums.ProductionActionType.DEPOSIT, true);
                dispatch = Dispatch.borrow(player, outMsg);
                DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);

                outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, mbEnums.ProductionActionType.CONFIRM_DEPOSIT, true);
                dispatch = Dispatch.borrow(player, outMsg);
                DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);

                origin.getPlayerCharacter().charItemManager.updateInventory();
            } catch (Exception e) {
                Logger.error(e);
            } finally {
                origin.sellLock.unlock();
            }
        }

    }

    // Method completes an item that has been previously rolled
    // adding it to the Vendor inventory

    private static void recycleItem(HashMap<Integer, Integer> itemList, NPC vendor, ClientConnection origin) {

        Item targetItem;
        ItemProductionMsg outMsg;
        int totalValue = 0;
        Dispatch dispatch;

        if (vendor.getBuilding() == null)
            return;

        PlayerCharacter player = origin.getPlayerCharacter();

        if (player == null)
            return;

        if (itemList == null)
            return;

        if (origin.sellLock.tryLock()) {
            try {


                for (int itemUUID : itemList.keySet()) {
                    int itemValue = 0;

                    targetItem = Item.getFromCache(itemUUID);

                    if (targetItem == null)
                        continue;

                    if (targetItem.template.item_type.equals(ItemType.GOLD))
                        return;

                    if (!vendor.charItemManager.doesCharOwnThisItem(targetItem.getObjectUUID()))
                        continue;

                    if (!vendor.charItemManager.inventoryContains(targetItem))
                        continue;

                    itemValue = targetItem.template.item_value;

                    if (vendor.getBuilding().getStrongboxValue() + itemValue > vendor.getBuilding().getMaxGold()) {
                        ErrorPopupMsg.sendErrorPopup(player, 201);
                        break;
                    }

                    switch (targetItem.template.item_type) {
                        case EMPLOYMENTCONTRACT:
                        case CHARTER:
                        case DEED:
                        case REALMCHARTER:
                        case SCROLL:
                        case POTION:
                            continue;
                    }

                    totalValue += itemValue;
                    vendor.charItemManager.recycle(targetItem);

                    outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, mbEnums.ProductionActionType.TAKE, true);

                    dispatch = Dispatch.borrow(origin.getPlayerCharacter(), outMsg);
                    DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
                }

                // Refund a portion of the gold

                vendor.getBuilding().transferGold(totalValue, false);

            } catch (Exception e) {
                Logger.error(e);
            } finally {
                origin.sellLock.unlock();
            }
        }
    }

    // Method handles recycling of an item

    private static void junkItem(int itemUUID, NPC vendor, ClientConnection origin) {

        Item virtualItem;
        ManageNPCMsg outMsg;
        Dispatch dispatch;

        PlayerCharacter player = origin.getPlayerCharacter();

        if (player == null)
            return;

        // Cannot junk items without a forge!

        if (vendor.getBuilding() == null)
            return;

        //  junk nothing?

        virtualItem = Item.getFromCache(itemUUID);

        if (virtualItem == null)
            return;

        WorkOrder workOrder = ForgeManager.itemWorkOrderLookup.get(virtualItem);

        // If this virtual item was already processed then
        // it will have been removed from the workOrder.

        if (workOrder == null)
            return;

        if (!workOrder.cooking.contains(virtualItem))
            return;

        City city = workOrder.vendor.building.getCity();

        if (city == null)
            return;

        city.transactionLock.writeLock().lock();

        try {

            workOrder.cooking.remove(virtualItem);
            DbManager.removeFromCache(virtualItem);
            ForgeManager.itemWorkOrderLookup.remove(virtualItem);

            // Update total_to_produce accounting for the slot being
            // removed while workOrder is not completed.

            if (!workOrder.runCompleted.get())
                if (workOrder.multiple_slot_request && workOrder.slots_used.get() > 1) {
                    int itemsPerSlot = workOrder.total_to_produce / workOrder.slots_used.get();
                    workOrder.total_to_produce = workOrder.total_to_produce - itemsPerSlot;
                }

            // Slot is no longer allocated to this workOrder.

            workOrder.slots_used.decrementAndGet();

            // Update workOrder on disk

            if (workOrder.cooking.isEmpty()) {
                ForgeManager.vendorWorkOrderLookup.get(vendor).remove(workOrder);
                DbManager.WarehouseQueries.DELETE_WORKORDER(workOrder);
            } else
                DbManager.WarehouseQueries.WRITE_WORKORDER(workOrder);

            // Refresh vendor's inventory to client

            outMsg = new ManageNPCMsg(vendor);
            outMsg.setMessageType(1);
            dispatch = Dispatch.borrow(player, outMsg);
            DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
        } catch (Exception e) {
            Logger.error(e);
        } finally {
            city.transactionLock.writeLock().unlock();
        }

    }

    private static void takeItem(HashMap<Integer, Integer> itemList, NPC vendor, ClientConnection origin) {

        Item targetItem;
        PlayerCharacter player = origin.getPlayerCharacter();

        if (player == null)
            return;

        for (int itemUUID : itemList.keySet()) {

            targetItem = Item.getFromCache(itemUUID);

            if (targetItem == null)
                return;

            if (targetItem.template.item_type.equals(ItemType.GOLD))
                return;

            if (!vendor.charItemManager.inventoryContains(targetItem))
                return;

            if (!player.charItemManager.hasRoomInventory(targetItem.template.item_wt))
                return;

            player.charItemManager.buyFromNPC(targetItem, vendor);
        }
        player.charItemManager.updateInventory();
    }

}
