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


package engine.objects;

import engine.gameManager.ChatManager;
import engine.gameManager.DbManager;
import engine.gameManager.DispatchManager;
import engine.loot.WorkOrder;
import engine.mbEnums;
import engine.net.Dispatch;
import engine.net.client.ClientConnection;
import engine.net.client.msg.*;
import engine.server.MBServerStatics;
import org.joda.time.DateTime;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

public class Warehouse {

    public EnumSet<mbEnums.ResourceType> locked = EnumSet.noneOf(mbEnums.ResourceType.class);
    public Building building;
    public City city;
    public ArrayList<Transaction> transactions = new ArrayList<>();
    public ConcurrentHashMap<mbEnums.ResourceType, Integer> resources = new ConcurrentHashMap<>();

    public Warehouse(Building building) {

        this.building = building;
        this.city = building.getCity();

        // New empty warehouse

        for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class))
            this.resources.put(resourceType, 0);

        // With no locks

        this.locked = EnumSet.noneOf(mbEnums.ResourceType.class);
    }

    public Warehouse(JSONObject warehouse) throws SQLException {

        JSONObject resources = (JSONObject) warehouse.get("resources");

        for (String key : resources.keySet()) {
            mbEnums.ResourceType resourceType = mbEnums.ResourceType.valueOf(key);
            int value = resources.getInt(key);
            this.resources.put(resourceType, value);
        }

        JSONArray lockedResources = (JSONArray) warehouse.get("locked");

        if (lockedResources.isEmpty() == false)
            for (Object o : lockedResources)
                this.locked.add(mbEnums.ResourceType.valueOf((String) o));

    }

    public static void warehouseDeposit(MerchantMsg msg, PlayerCharacter playerCharacter, NPC npc) {

        Building warehouseBuilding;
        Warehouse warehouse;
        int depositAmount;
        Dispatch dispatch;

        Item resource = Item.getFromCache(msg.getItemID());

        if (resource == null)
            return;

        depositAmount = msg.getAmount();
        CharacterItemManager itemMan = playerCharacter.charItemManager;

        if (!itemMan.doesCharOwnThisItem(resource.getObjectUUID()))
            return;

        warehouseBuilding = npc.getBuilding();

        if (warehouseBuilding == null)
            return;

        City city = warehouseBuilding.getCity();

        if (city == null)
            return;

        warehouse = city.warehouse;

        if (warehouse == null)
            return;

        if (!deposit(playerCharacter, resource, depositAmount, true, true, warehouse))
            return;

        ViewResourcesMsg vrm = new ViewResourcesMsg(playerCharacter);
        vrm.setGuild(playerCharacter.getGuild());
        vrm.setWarehouseBuilding(warehouseBuilding);
        vrm.configure();
        dispatch = Dispatch.borrow(playerCharacter, vrm);
        DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
    }

    public static void warehouseWithdraw(MerchantMsg msg, PlayerCharacter playerCharacter, NPC npc) {

        int withdrawAmount;
        Building warehouseBuilding;
        Warehouse warehouse;
        Dispatch dispatch;

        withdrawAmount = msg.getAmount();
        warehouseBuilding = npc.getBuilding();

        if (warehouseBuilding == null)
            return;

        if (playerCharacter.getGuild() != warehouseBuilding.getGuild() || !GuildStatusController.isInnerCouncil(playerCharacter.getGuildStatus()))
            return;

        City city = warehouseBuilding.getCity();

        if (city == null)
            return;

        warehouse = city.warehouse;

        if (warehouse == null)
            return;

        mbEnums.ResourceType resourceType = mbEnums.ResourceType.templateHashLookup.get(msg.getHashID());

        if (isResourceLocked(warehouse, resourceType)) {
            ChatManager.chatSystemInfo(playerCharacter, "You cannot withdrawl a locked resource.");
            return;
        }
        if (!withdraw(warehouse, playerCharacter, resourceType, withdrawAmount, true, true)) {
            ChatManager.chatGuildError(playerCharacter, "Failed to withdrawl " + resourceType.name() + '.');
            Logger.debug(playerCharacter.getName() + " Failed to withdrawl  =" + resourceType.name() + " from Warehouse With ID = " + warehouseBuilding.getObjectUUID());
            return;
        }

        ViewResourcesMsg vrm = new ViewResourcesMsg(playerCharacter);
        vrm.setGuild(playerCharacter.getGuild());
        vrm.setWarehouseBuilding(warehouseBuilding);
        vrm.configure();
        dispatch = Dispatch.borrow(playerCharacter, vrm);
        DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
    }

    public static void warehouseLock(MerchantMsg msg, PlayerCharacter playerCharacter, NPC npc) {
        Building warehouseBuilding;
        Warehouse warehouse;
        int hashID;
        Dispatch dispatch;

        hashID = msg.getHashID();
        warehouseBuilding = npc.getBuilding();

        if (warehouseBuilding == null)
            return;

        if (playerCharacter.getGuild() != warehouseBuilding.getGuild() || !GuildStatusController.isInnerCouncil(playerCharacter.getGuildStatus()))
            return;

        City city = warehouseBuilding.getCity();

        if (city == null)
            return;

        warehouse = city.warehouse;

        mbEnums.ResourceType resourceType = mbEnums.ResourceType.templateHashLookup.get(hashID);

        // toggle lock

        if (warehouse.locked.contains(resourceType)) {

            boolean worked;
            warehouse.locked.remove(resourceType);
            worked = DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse);

            if (worked) {
                ViewResourcesMsg vrm = new ViewResourcesMsg(playerCharacter);
                vrm.setGuild(playerCharacter.getGuild());
                vrm.setWarehouseBuilding(warehouseBuilding);
                vrm.configure();
                dispatch = Dispatch.borrow(playerCharacter, vrm);
                DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
            } else
                warehouse.locked.add(resourceType);

            return;
        }

        boolean worked;
        warehouse.locked.add(resourceType);
        worked = DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse);

        if (worked) {
            ViewResourcesMsg vrm = new ViewResourcesMsg(playerCharacter);
            vrm.setGuild(playerCharacter.getGuild());
            vrm.setWarehouseBuilding(warehouseBuilding);
            vrm.configure();
            dispatch = Dispatch.borrow(playerCharacter, vrm);
            DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
        } else
            warehouse.locked.remove(resourceType);

    }

    public static synchronized boolean deposit(PlayerCharacter playerCharacter, Item resource, int amount, boolean removeFromInventory, boolean transaction, Warehouse warehouse) {

        ClientConnection origin = playerCharacter.getClientConnection();

        if (origin == null)
            return false;

        if (amount < 0) {
            Logger.info(playerCharacter.getFirstName() + " Attempting to Dupe!!!!!!");
            return false;
        }

        mbEnums.ResourceType resourceType = mbEnums.ResourceType.templateLookup.get(resource.templateID);

        if (warehouse.resources.get(resourceType) == null)
            return false;

        CharacterItemManager itemMan = playerCharacter.charItemManager;

        if (itemMan == null)
            return false;

        if (itemMan.getGoldTrading() > 0) {
            ErrorPopupMsg.sendErrorPopup(playerCharacter, 195);
            return false;
        }

        if (!itemMan.doesCharOwnThisItem(resource.getObjectUUID()))
            return false;

        if (!resource.validForInventory(origin, playerCharacter, itemMan))
            return false;

        if (resource.getNumOfItems() < amount)
            return false;

        int oldAmount = warehouse.resources.get(resourceType);

        int newAmount = oldAmount + amount;

        if (newAmount > mbEnums.ResourceType.templateLookup.get(resource.templateID).deposit_limit)
            return false;

        if (removeFromInventory) {
            if (resourceType.equals(mbEnums.ResourceType.GOLD)) {

                if (itemMan.getGoldInventory().getNumOfItems() - amount < 0)
                    return false;

                if (itemMan.getGoldInventory().getNumOfItems() - amount > MBServerStatics.PLAYER_GOLD_LIMIT)
                    return false;

                if (!itemMan.modifyInventoryGold(-amount))
                    return false;

                UpdateGoldMsg ugm = new UpdateGoldMsg(playerCharacter);
                ugm.configure();
                Dispatch dispatch = Dispatch.borrow(playerCharacter, ugm);
                DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);

                itemMan.updateInventory();

            } else {
                itemMan.delete(resource);
                itemMan.updateInventory();
            }
        }

        itemMan.updateInventory();

        if (newAmount > resourceType.deposit_limit)
            return false;

        warehouse.resources.put(resourceType, newAmount);

        if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
            warehouse.resources.put(resourceType, oldAmount);
            return false;
        }

        if (resource.template.item_type.equals(mbEnums.ItemType.GOLD))
            resourceType = mbEnums.ResourceType.GOLD;
        else
            resourceType = mbEnums.ResourceType.valueOf(ItemTemplate.templates.get(resource.templateID).item_base_name.toUpperCase());

        if (transaction)
            AddTransactionToWarehouse(warehouse, playerCharacter.getObjectType(), playerCharacter.getObjectUUID(), mbEnums.TransactionType.DEPOSIT, resourceType, amount);

        return true;
    }

    public static synchronized boolean depositFromMine(Mine mine, mbEnums.ResourceType resourceType, int amount, Warehouse warehouse) {

        int oldAmount = warehouse.resources.get(resourceType);
        int newAmount = oldAmount + amount;

        if (newAmount > resourceType.deposit_limit)
            return false;

        warehouse.resources.put(resourceType, newAmount);

        if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
            warehouse.resources.put(resourceType, oldAmount);
            return false;
        }

        if (mine != null)
            AddTransactionToWarehouse(warehouse, mbEnums.GameObjectType.Building, mine.getBuildingID(), mbEnums.TransactionType.MINE, resourceType, amount);

        return true;
    }

    public static synchronized void depositRealmTaxes(PlayerCharacter taxer, mbEnums.ResourceType resourceType, int amount, Warehouse warehouse) {

        int oldAmount = warehouse.resources.get(resourceType);
        int newAmount = oldAmount + amount;
        warehouse.resources.put(resourceType, newAmount);

        if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
            warehouse.resources.put(resourceType, oldAmount);
            return;
        }

        AddTransactionToWarehouse(warehouse, taxer.getObjectType(), taxer.getObjectUUID(), mbEnums.TransactionType.TAXRESOURCEDEPOSIT, resourceType, amount);

    }

    public static synchronized void depositProfitTax(mbEnums.ResourceType resourceType, int amount, Building building, Warehouse warehouse) {

        if (warehouse.resources.get(resourceType) == null)
            return;

        int oldAmount = warehouse.resources.get(resourceType);
        int newAmount = oldAmount + amount;

        if (newAmount > resourceType.deposit_limit)
            return;

        warehouse.resources.put(resourceType, newAmount);

        if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
            warehouse.resources.put(resourceType, oldAmount);
            return;
        }

        if (building != null)
            AddTransactionToWarehouse(warehouse, mbEnums.GameObjectType.Building, building.getObjectUUID(), mbEnums.TransactionType.DEPOSIT, resourceType, amount);

    }

    public static synchronized void transferResources(Warehouse warehouse, PlayerCharacter taxer, TaxResourcesMsg msg, ArrayList<mbEnums.ResourceType> realmResources, float taxPercent) {

        for (mbEnums.ResourceType resourceType : realmResources) {

            if (warehouse.resources.get(resourceType) == null)
                return;

            int amount = (int) (warehouse.resources.get(resourceType) * taxPercent);

            if (amount <= 0) {
                msg.getResources().put(resourceType.resourceHash, 0);
                continue;
            }

            int oldAmount = warehouse.resources.get(resourceType);

            if (oldAmount < amount)
                amount = oldAmount;

            int newAmount = oldAmount - amount;

            if (newAmount < amount)
                continue;

            msg.getResources().put(resourceType.resourceHash, amount);

            if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
                msg.getResources().put(resourceType.resourceHash, 0);
                warehouse.resources.put(resourceType, oldAmount);
                continue;
            }

            warehouse.resources.put(resourceType, newAmount);
            depositRealmTaxes(taxer, resourceType, amount, warehouse);

            AddTransactionToWarehouse(warehouse, taxer.getObjectType(), taxer.getObjectUUID(), mbEnums.TransactionType.TAXRESOURCE, resourceType, amount);
        }
    }

    public static synchronized boolean withdraw(Warehouse warehouse, PlayerCharacter playerCharacter, mbEnums.ResourceType resourceType, int amount, boolean addToInventory, boolean transaction) {

        if (playerCharacter == null)
            return false;

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

        if (warehouse.resources.get(resourceType) == null)
            return false;

        if (amount <= 0)
            return false;

        CharacterItemManager itemMan = playerCharacter.charItemManager;

        if (itemMan == null)
            return false;

        if (addToInventory)
            if (!itemMan.hasRoomInventory(template.item_wt)) {
                ChatManager.chatSystemInfo(playerCharacter, "You can not carry any more of that item.");
                return false;
            }

        if (addToInventory && resourceType.equals(mbEnums.ResourceType.GOLD)) {
            if (playerCharacter.charItemManager.getGoldInventory().getNumOfItems() + amount > MBServerStatics.PLAYER_GOLD_LIMIT)
                return false;

            if (playerCharacter.charItemManager.getGoldInventory().getNumOfItems() + amount < 0)
                return false;
        }
        int oldAmount = warehouse.resources.get(resourceType);

        if (oldAmount < amount)
            return false;

        int newAmount = oldAmount - amount;

        warehouse.resources.put(resourceType, newAmount);

        if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
            warehouse.resources.put(resourceType, oldAmount);
            return false;
        }

        if (addToInventory) {
            if (resourceType.equals(mbEnums.ResourceType.GOLD)) {

                itemMan.addGoldToInventory(amount, false);
                UpdateGoldMsg ugm = new UpdateGoldMsg(playerCharacter);
                ugm.configure();
                Dispatch dispatch = Dispatch.borrow(playerCharacter, ugm);
                DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);

                itemMan.updateInventory();
            } else {
                boolean itemWorked = false;

                Item item = new Item(resourceType.templateID);
                item.ownerID = playerCharacter.getObjectUUID();
                item.ownerType = mbEnums.OwnerType.PlayerCharacter;
                item.containerType = mbEnums.ItemContainerType.INVENTORY;
                item.numberOfItems = amount;

                try {
                    item = DbManager.ItemQueries.PERSIST(item);
                    itemWorked = true;
                } catch (Exception e) {
                    Logger.error(e);
                }
                if (itemWorked) {
                    itemMan.addItemToInventory(item);
                    itemMan.updateInventory();
                }
            }
        }

        if (transaction)
            AddTransactionToWarehouse(warehouse, playerCharacter.getObjectType(), playerCharacter.getObjectUUID(), mbEnums.TransactionType.WITHDRAWL, resourceType, amount);

        return true;
    }

    public static synchronized boolean loot(Warehouse warehouse, PlayerCharacter playerCharacter, mbEnums.ResourceType resourceType, int amount, boolean addToInventory) {

        if (playerCharacter == null)
            return false;

        ItemTemplate template = ItemTemplate.templates.get(resourceType);

        if (template == null)
            return false;

        if (warehouse.resources.get(resourceType) == null)
            return false;

        if (amount <= 0)
            return false;

        CharacterItemManager itemMan = playerCharacter.charItemManager;

        if (itemMan == null)
            return false;

        if (!itemMan.hasRoomInventory(template.item_wt)) {
            ChatManager.chatSystemInfo(playerCharacter, "You can not carry any more of that item.");
            return false;
        }

        int oldAmount = warehouse.resources.get(resourceType);

        if (oldAmount < amount)
            return false;

        int newAmount = oldAmount - amount;

        warehouse.resources.put(resourceType, newAmount);

        if (addToInventory) {
            if (resourceType.equals(mbEnums.ResourceType.GOLD)) {

                itemMan.addGoldToInventory(amount, false);
                UpdateGoldMsg ugm = new UpdateGoldMsg(playerCharacter);
                ugm.configure();
                Dispatch dispatch = Dispatch.borrow(playerCharacter, ugm);
                DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);

                itemMan.updateInventory();
            } else {
                boolean itemWorked = false;
                Item item = new Item(resourceType.templateID);
                item.ownerID = playerCharacter.getObjectUUID();
                item.ownerType = mbEnums.OwnerType.PlayerCharacter;
                item.containerType = mbEnums.ItemContainerType.INVENTORY;
                item.numberOfItems = amount;

                try {
                    item = DbManager.ItemQueries.PERSIST(item);
                    itemWorked = true;
                } catch (Exception e) {
                    Logger.error(e);
                }
                if (itemWorked) {
                    itemMan.addItemToInventory(item);
                    itemMan.updateInventory();
                }
            }
        }

        return true;
    }

    public static boolean isEmpty(Warehouse warehouse) {
        int amount = 0;

        for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class)) {
            amount += warehouse.resources.get(resourceType);

            if (amount > 0)
                return false;
        }
        return true;
    }

    public static void AddTransactionToWarehouse(Warehouse warehouse, mbEnums.GameObjectType targetType, int targetUUID, mbEnums.TransactionType transactionType, mbEnums.ResourceType resource, int amount) {

        if (!DbManager.WarehouseQueries.CREATE_TRANSACTION(warehouse.building.getObjectUUID(), targetType, targetUUID, transactionType, resource, amount, DateTime.now()))
            return;

        Transaction transaction = new Transaction(warehouse.building.getObjectUUID(), targetType, targetUUID, transactionType, resource, amount, DateTime.now());
        warehouse.transactions.add(transaction);
    }

    public static boolean isAboveCap(Warehouse warehouse, mbEnums.ResourceType resourceType, int deposit) {
        int newAmount = warehouse.resources.get(resourceType) + deposit;
        return newAmount > resourceType.deposit_limit;

    }

    public static boolean isResourceLocked(Warehouse warehouse, mbEnums.ResourceType resourceType) {
        return warehouse.locked.contains(resourceType);
    }

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