package engine.net.client.handlers;

import engine.exception.MsgSendException;
import engine.gameManager.BuildingManager;
import engine.gameManager.DbManager;
import engine.gameManager.DispatchManager;
import engine.gameManager.SessionManager;
import engine.math.FastMath;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import engine.mbEnums.DispatchChannel;
import engine.mbEnums.GameObjectType;
import engine.mbEnums.ProfitType;
import engine.net.Dispatch;
import engine.net.client.ClientConnection;
import engine.net.client.msg.*;
import engine.objects.*;
import org.pmw.tinylog.Logger;

import java.util.ArrayList;

/*
 * @Author:
 * @Summary: Processes application protocol message which keeps
 * client's tcp connection open.
 */
public class OrderNPCMsgHandler extends AbstractClientMsgHandler {

    // Constants used for incoming message type

    private static final int CLIENT_UPGRADE_REQUEST = 3;
    private static final int CLIENT_REDEED_REQUEST = 6;
    private static final int SVR_CLOSE_WINDOW = 4;

    public OrderNPCMsgHandler() {
        super();
    }

    public static void processRedeedHireling(AbstractCharacter hireling, Building building, ClientConnection origin) {

        PlayerCharacter player;
        Contract contract;
        CharacterItemManager itemMan;
        Item item;

        player = SessionManager.getPlayerCharacter(origin);
        itemMan = player.charItemManager;

        contract = hireling.contract;

        if (!player.charItemManager.hasRoomInventory((short) 1)) {
            ErrorPopupMsg.sendErrorPopup(player, 21);
            return;
        }

        if (!building.getHirelings().containsKey(hireling))
            return;

        BuildingManager.removeHireling(building, hireling);

        boolean itemWorked = false;

        item = new Item(contract.getContractID());
        item.ownerID = player.getObjectUUID();
        item.ownerType = mbEnums.OwnerType.PlayerCharacter;
        item.chargesRemaining = (byte) hireling.getRank() - 1;
        item.containerType = mbEnums.ItemContainerType.INVENTORY;

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

        if (itemWorked) {
            itemMan.addItemToInventory(item);
            itemMan.updateInventory();
        }

        ManageCityAssetsMsg mca = new ManageCityAssetsMsg();
        mca.actionType = NPC.SVR_CLOSE_WINDOW;
        mca.setTargetType(building.getObjectType().ordinal());
        mca.setTargetID(building.getObjectUUID());
        origin.sendMsg(mca);

    }

    private static void modifyBuyProfit(OrderNPCMsg msg, ClientConnection origin) {
        NPC npc;
        PlayerCharacter player;
        Building building;
        float percent;

        ProfitType profitType = null;
        player = origin.getPlayerCharacter();

        if (player == null)
            return;

        npc = NPC.getFromCache(msg.getNpcUUID());

        if (npc == null)
            return;

        building = npc.getBuilding();

        if (building == null)
            return;

        NPCProfits profit = NPC.GetNPCProfits(npc);

        if (profit == null)
            return;

        switch (msg.getActionType()) {
            case 10:
                profitType = ProfitType.BuyNormal;
                break;
            case 11:
                profitType = ProfitType.BuyGuild;
                break;
            case 12:
                profitType = ProfitType.BuyNation;
        }

        percent = msg.getBuySellPercent();
        percent = FastMath.clamp(percent, 0.0f, 1.0f);

        NPCProfits.UpdateProfits(npc, profit, profitType, percent);
    }

    private static void modifySellProfit(OrderNPCMsg orderNPCMsg, ClientConnection origin) {
        NPC npc;
        PlayerCharacter player;
        Building building;
        float percent;

        ProfitType profitType = null;

        player = origin.getPlayerCharacter();

        if (player == null)
            return;

        npc = NPC.getFromCache(orderNPCMsg.getNpcUUID());

        if (npc == null)
            return;

        building = npc.getBuilding();

        if (building == null)
            return;

        NPCProfits profit = NPC.GetNPCProfits(npc);

        if (profit == null)
            return;

        switch (orderNPCMsg.getActionType()) {
            case 7:
                profitType = ProfitType.SellNormal;
                break;
            case 8:
                profitType = ProfitType.SellGuild;
                break;
            case 9:
                profitType = ProfitType.SellNation;
        }

        percent = orderNPCMsg.getBuySellPercent();

        percent -= 1f;
        percent = FastMath.clamp(percent, 0.0f, 3.0f);

        NPCProfits.UpdateProfits(npc, profit, profitType, percent);
    }

    private static void handleCityCommand(OrderNPCMsg orderNpcMsg, PlayerCharacter player) {

        Building building = BuildingManager.getBuildingFromCache(orderNpcMsg.getBuildingUUID());

        if (building == null)
            return;

        if (ManageCityAssetMsgHandler.playerCanManageNotFriends(player, building) == false)
            return;

        if (orderNpcMsg.getPatrolSize() >= 20)
            Logger.info(player.getName() + " is attempting to add patrol points amount " + orderNpcMsg.getPatrolSize());

        if (orderNpcMsg.getSentrySize() >= 20)
            Logger.info(player.getName() + " is attempting to add patrol points amount " + orderNpcMsg.getSentryPoints());

        if (orderNpcMsg.getPatrolPoints() != null) {

            if (!AddPatrolPoints(building.getObjectUUID(), orderNpcMsg.getPatrolPoints())) {
                ErrorPopupMsg.sendErrorMsg(player, "Patrol Points must be placed on city zone.");
                return;
            }

        } else if (building.getPatrolPoints() != null)
            ClearPatrolPoints(building.getObjectUUID());

        if (orderNpcMsg.getSentryPoints() != null) {
            AddSentryPoints(building.getObjectUUID(), orderNpcMsg.getSentryPoints());
        } else if (building.getSentryPoints() != null)
            ClearSentryPoints(building.getObjectUUID());
    }

    private static void processUpgradeNPC(PlayerCharacter player, AbstractCharacter abstractCharacter) {

        Building building;

        switch (abstractCharacter.getObjectType()) {

            case NPC:
                NPC npc = (NPC) abstractCharacter;
                building = npc.getBuilding();

                // Cannot upgrade an npc not within a building

                if (building == null)
                    return;

                City buildingCity = building.getCity();

                if (buildingCity == null) {
                    npc.processUpgradeNPC(player);
                    return;
                }

                buildingCity.transactionLock.writeLock().lock();

                try {
                    npc.processUpgradeNPC(player);
                } catch (Exception e) {
                    Logger.error(e);
                } finally {
                    buildingCity.transactionLock.writeLock().unlock();
                }
                break;
            case Mob:

                Mob mob = (Mob) abstractCharacter;
                building = mob.building;

                if (mob.building == null)
                    return;

                City mobCity = building.getCity();

                if (mobCity == null) {
                    mob.processUpgradeMob(player);
                    return;
                }

                mobCity.transactionLock.writeLock().lock();

                try {
                    mob.processUpgradeMob(player);
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    Logger.error(e);
                } finally {
                    mobCity.transactionLock.writeLock().unlock();
                }
                break;
        }
    }

    private static boolean AddPatrolPoints(int buildingID, ArrayList<Vector3fImmutable> patrolPoints) {

        Building building = BuildingManager.getBuildingFromCache(buildingID);

        if (building == null)
            return false;

        Zone zone = building.getParentZone();

        if (zone == null)
            return false;

        if (zone.playerCityUUID == 0)
            return false;

        City city = building.getCity();

        if (city == null)
            return false;


        //clear first.

        for (Vector3fImmutable point : patrolPoints) {

            if (city.isLocationOnCityZone(point) == false) {
                return false;
            }

        }

        DbManager.BuildingQueries.CLEAR_PATROL(buildingID);

        for (Vector3fImmutable point : patrolPoints) {

            if (!DbManager.BuildingQueries.ADD_TO_PATROL(buildingID, point))
                return false;
        }
        building.patrolPoints = patrolPoints;
        return true;
    }

    private static boolean AddSentryPoints(int buildingID, ArrayList<Vector3fImmutable> sentryPoints) {

        Building building = BuildingManager.getBuildingFromCache(buildingID);

        if (building == null)
            return false;

        building.sentryPoints = sentryPoints;
        return true;
    }

    private static boolean ClearPatrolPoints(int buildingID) {

        Building building = BuildingManager.getBuildingFromCache(buildingID);

        if (building == null)
            return false;

        if (building.patrolPoints == null)
            return true;

        if (DbManager.BuildingQueries.CLEAR_PATROL(buildingID) == false)
            return false;

        building.patrolPoints.clear();
        return true;
    }

    private static boolean ClearSentryPoints(int buildingID) {

        Building building = BuildingManager.getBuildingFromCache(buildingID);

        if (building == null)
            return false;

        if (building.sentryPoints == null)
            return true;

        building.sentryPoints.clear();
        return true;
    }

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

        // Member variable declarations

        PlayerCharacter player;
        NPC npc;
        Mob mob;
        Building building;
        OrderNPCMsg orderNPCMsg;
        ManageCityAssetsMsg outMsg;

        // Member variable assignment
        orderNPCMsg = (OrderNPCMsg) baseMsg;

        if (origin.ordernpcspam > System.currentTimeMillis())
            return true;

        origin.ordernpcspam = System.currentTimeMillis() + 500;

        player = SessionManager.getPlayerCharacter(origin);

        if (player == null)
            return true;

        if (orderNPCMsg.getActionType() == 28) {
            OrderNPCMsgHandler.handleCityCommand(orderNPCMsg, player);
            return true;
        }

        if (orderNPCMsg.getObjectType() == GameObjectType.NPC.ordinal()) {

            npc = NPC.getFromCache(orderNPCMsg.getNpcUUID());

            if (npc == null)
                return true;

            building = BuildingManager.getBuildingFromCache(orderNPCMsg.getBuildingUUID());

            if (building == null)
                return true;

            if (building.getHirelings().containsKey(npc) == false)
                return true;


            if (player.charItemManager.getTradingWith() != null) {
                ErrorPopupMsg.sendErrorMsg(player, "Cannot barter and trade with same timings.");
                return true;
            }

            player.lastBuildingAccessed = building.getObjectUUID();

            switch (orderNPCMsg.getActionType()) {

                case 2:
                    player = SessionManager.getPlayerCharacter(origin);

                    if (ManageCityAssetMsgHandler.playerCanManageNotFriends(player, building) == false)
                        return true;

                    if (building.getHirelings().containsKey(npc) == false)
                        return true;

                    if (npc.remove() == false) {
                        PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
                        return true;
                    }

                    ManageCityAssetsMsg manageCityAssetsMsg = new ManageCityAssetsMsg();
                    manageCityAssetsMsg.actionType = SVR_CLOSE_WINDOW;
                    manageCityAssetsMsg.setTargetType(building.getObjectType().ordinal());
                    manageCityAssetsMsg.setTargetID(building.getObjectUUID());

                    Dispatch dispatch = Dispatch.borrow(player, manageCityAssetsMsg);
                    DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);

                    return true;

                case CLIENT_UPGRADE_REQUEST:

                    if (BuildingManager.playerCanManage(player, building) == false)
                        return true;

                    processUpgradeNPC(player, npc);

                    outMsg = new ManageCityAssetsMsg(player, building);

                    // Action TYPE
                    outMsg.actionType = 3;
                    outMsg.setTargetType(building.getObjectType().ordinal());
                    outMsg.setTargetID(building.getObjectUUID());
                    outMsg.setTargetType3(building.getObjectType().ordinal());
                    outMsg.setTargetID3(building.getObjectUUID());
                    outMsg.setAssetName1(building.getName());
                    outMsg.setUnknown54(1);

                    dispatch = Dispatch.borrow(player, outMsg);
                    DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);

                    break;
                case CLIENT_REDEED_REQUEST:

                    if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
                        return true;

                    processRedeedHireling(npc, building, origin);
                    return true;
                //MB TODO HANDLE all profits.
                case 7:
                case 8:
                case 9:

                    if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
                        return true;

                    modifySellProfit(orderNPCMsg, origin);
                    dispatch = Dispatch.borrow(player, orderNPCMsg);
                    DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
                    return true;
                case 10:
                case 11:
                case 12:

                    if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
                        return true;

                    modifyBuyProfit(orderNPCMsg, origin);
                    dispatch = Dispatch.borrow(player, orderNPCMsg);
                    DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
                    return true;
            }

            // Validation check Owner or IC or friends
            if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
                if (BuildingManager.playerCanManage(player, building) == false)
                    return true;

            ManageNPCMsg manageNPCMsg = new ManageNPCMsg(npc);
            Dispatch dispatch = Dispatch.borrow(player, manageNPCMsg);
            DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
            return true;

        } else if (orderNPCMsg.getObjectType() == GameObjectType.Mob.ordinal()) {

            mob = Mob.getMob(orderNPCMsg.getNpcUUID());

            if (mob == null)
                return true;

            building = BuildingManager.getBuildingFromCache(orderNPCMsg.getBuildingUUID());

            if (building == null)
                return true;

            if (!building.getHirelings().containsKey(mob))
                return true;

            if (player.charItemManager.getTradingWith() != null) {
                ErrorPopupMsg.sendErrorMsg(player, "Cannot barter and trade with same timings.");
                return true;
            }

            player.lastBuildingAccessed = building.getObjectUUID();

            switch (orderNPCMsg.getActionType()) {
                case 2:

                    if (BuildingManager.playerCanManage(player, building) == false)
                        return true;

                    if (building.getHirelings().containsKey(mob) == false)
                        return true;

                    BuildingManager.removeHireling(building, mob);

                    ManageCityAssetsMsg manageCityAssetsMsg = new ManageCityAssetsMsg();
                    manageCityAssetsMsg.actionType = SVR_CLOSE_WINDOW;
                    manageCityAssetsMsg.setTargetType(building.getObjectType().ordinal());
                    manageCityAssetsMsg.setTargetID(building.getObjectUUID());
                    Dispatch dispatch = Dispatch.borrow(player, manageCityAssetsMsg);
                    DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
                    break;
                case 3:

                    if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
                        return true;

                    processUpgradeNPC(player, mob);

                    outMsg = new ManageCityAssetsMsg(player, building);

                    // Action TYPE
                    outMsg.actionType = 3;
                    outMsg.setTargetType(building.getObjectType().ordinal());
                    outMsg.setTargetID(building.getObjectUUID());
                    outMsg.setTargetType3(building.getObjectType().ordinal());
                    outMsg.setTargetID3(building.getObjectUUID());
                    outMsg.setAssetName1(building.getName());
                    outMsg.setUnknown54(1);

                    dispatch = Dispatch.borrow(player, outMsg);
                    DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
                    break;
                case 6:

                    if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
                        return true;

                    processRedeedHireling(mob, building, origin);
                    return true;
                //MB TODO HANDLE all profits.
                case 7:
                case 8:
                case 9:
                    break;
                case 10:
                case 11:
                case 12:
                    break;
            }

            // Validation check Owner or IC
            if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
                if (BuildingManager.playerCanManage(player, building) == false)
                    return true;

            ManageNPCMsg manageNPCMsg = new ManageNPCMsg(mob);
            Dispatch dispatch = Dispatch.borrow(player, manageNPCMsg);
            DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
            return true;
        }
        return true;
    }

}