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


package engine.net.client;

import engine.Enum.DispatchChannel;
import engine.Enum.GameObjectType;
import engine.exception.MsgSendException;
import engine.gameManager.*;
import engine.job.JobScheduler;
import engine.jobs.RefreshGroupJob;
import engine.net.Dispatch;
import engine.net.DispatchMessage;
import engine.net.NetMsgHandler;
import engine.net.client.handlers.AbstractClientMsgHandler;
import engine.net.client.msg.*;
import engine.net.client.msg.chat.AbstractChatMsg;
import engine.objects.AbstractCharacter;
import engine.objects.Mob;
import engine.objects.NPC;
import engine.objects.PlayerCharacter;
import engine.server.MBServerStatics;
import engine.server.world.WorldServer;
import engine.session.Session;
import engine.util.StringUtils;
import org.pmw.tinylog.Logger;

/**
 * @author:
 * @summary: This class is the mainline router for application protocol
 * messages received by the client.
 */

public class ClientMessagePump implements NetMsgHandler {

    // Instance variable declaration

    private final WorldServer server;

    public ClientMessagePump(WorldServer server) {
        super();
        this.server = server;
    }

    // called when player clicks respawn button
    private static void respawn(RespawnMsg msg, ClientConnection origin) throws MsgSendException {

        PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter(origin);

        if (sourcePlayer == null)
            return;

        if (msg.getObjectType() != sourcePlayer.getObjectType().ordinal() || msg.getObjectID() != sourcePlayer.getObjectUUID()) {
            Logger.error("Player " + sourcePlayer.getObjectUUID() + " respawning character of id " + msg.getObjectType() + ' '
                    + msg.getObjectID());
            return;
        }

        if (sourcePlayer.isAlive()) {
            Logger.error("Player " + sourcePlayer.getObjectUUID() + " respawning while alive");
            return;
        }
        // ResetAfterDeath player
        sourcePlayer.respawnLock.writeLock().lock();
        try {
            sourcePlayer.respawn(true, false, true);

        } catch (Exception e) {
            Logger.error(e);
        } finally {
            sourcePlayer.respawnLock.writeLock().unlock();

        }
        // Echo ResetAfterDeath message back
        msg.setPlayerHealth(sourcePlayer.getHealth());
        // TODO calculate any experience loss before this point
        msg.setPlayerExp(sourcePlayer.getExp() + sourcePlayer.getOverFlowEXP());
        Dispatch dispatch = Dispatch.borrow(sourcePlayer, msg);
        DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);

        MoveToPointMsg moveMsg = new MoveToPointMsg();
        moveMsg.setPlayer(sourcePlayer);
        moveMsg.setStartCoord(sourcePlayer.getLoc());
        moveMsg.setEndCoord(sourcePlayer.getLoc());
        moveMsg.setInBuilding(-1);
        moveMsg.setInBuildingFloor(-1);

        dispatch = Dispatch.borrow(sourcePlayer, moveMsg);
        DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);

        MovementManager.sendRWSSMsg(sourcePlayer);

        // refresh the whole group with what just happened
        JobScheduler.getInstance().scheduleJob(new RefreshGroupJob(sourcePlayer), MBServerStatics.LOAD_OBJECT_DELAY);
    }

    // called when player types /show
    private static void show(ShowMsg msg, ClientConnection origin) throws MsgSendException {

        PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);

        if (pc == null)
            return;

        int targetType = msg.getTargetType();
        AbstractCharacter tar = null;

        if (targetType == GameObjectType.PlayerCharacter.ordinal())
            tar = PlayerCharacter.getFromCache(msg.getTargetID());
        else if (targetType == GameObjectType.NPC.ordinal())
            tar = NPC.getFromCache(msg.getTargetID());
        else if (targetType == GameObjectType.Mob.ordinal())
            tar = Mob.getFromCache(msg.getTargetID());

        if (tar == null || !tar.isAlive() || !tar.isActive())
            return;

        msg.setUnknown01(pc.getLoc());
        msg.setUnknown02(pc.getLoc());
        msg.setRange01(pc.getRange());
        msg.setUnknown03(tar.getLoc());
        msg.setUnknown04(tar.getLoc());
        msg.setRange01(tar.getRange());

        Dispatch dispatch = Dispatch.borrow(pc, msg);
        DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);

    }

    protected static void petAttack(PetAttackMsg msg, ClientConnection conn) throws MsgSendException {

        PlayerCharacter pc = SessionManager.getPlayerCharacter(conn);

        if (pc == null)
            return;

        Mob pet = pc.getPet();

        if (pet == null)
            return;

        if (!pet.isAlive())
            return;

        if ((pc.inSafeZone())
                && (msg.getTargetType() == GameObjectType.PlayerCharacter.ordinal()))
            return;

        //CombatManager.setAttackTarget(msg, conn);
        if (msg.getTargetType() == GameObjectType.Building.ordinal()) {
            conn.getPlayerCharacter().getPet().setCombatTarget(PlayerCharacter.getPlayerCharacter(msg.getTargetID()));
        }
        switch (msg.getTargetType()) {
            case 53: //player character
                conn.getPlayerCharacter().getPet().setCombatTarget(PlayerCharacter.getPlayerCharacter(msg.getTargetID()));
                break;
            case 37://mob
                conn.getPlayerCharacter().getPet().setCombatTarget(Mob.getMob(msg.getTargetID()));
                break;
            case 8://mob
                conn.getPlayerCharacter().getPet().setCombatTarget(BuildingManager.getBuilding(msg.getTargetID()));
                break;
        }

        if (pet.getCombatTarget() == null)
            return;
    }

    //Handle RepairObject Window and RepairObject Requests

    @Override
    public boolean handleClientMsg(ClientNetMsg msg) {

        if (msg == null) {
            Logger.error("handleClientMsg", "Recieved null msg. Returning.");
            return false;
        }

        ClientConnection origin;
        Protocol protocolMsg = Protocol.NONE;
        Session s;

        try {

            // Try registered opcodes first as we take a hatchet to this GodObject

            AbstractClientMsgHandler msgHandler = msg.getProtocolMsg().handler;

            if (msgHandler != null)
                return msgHandler.handleNetMsg(msg);

            // Any remaining opcodes fall through and are routed
            // through this ungodly switch of doom.

            origin = (ClientConnection) msg.getOrigin();
            s = SessionManager.getSession(origin);

            protocolMsg = msg.getProtocolMsg();

            switch (protocolMsg) {
                /*
                 * Chat
                 */

                // Simplify by fall through. Route in ChatManager
                case CHATSAY:
                case CHATSHOUT:
                case CHATTELL:
                case CHATGUILD:
                case CHATGROUP:
                case CHATPVP:
                case CHATIC:
                case CHATCITY:
                case CHATINFO:
                case SYSTEMBROADCASTCHANNEL:
                case CHATCSR:
                case SYSTEMCHANNEL:
                case GLOBALCHANNELMESSAGE:
                case LEADERCHANNELMESSAGE:
                    ChatManager.handleChatMsg(s, (AbstractChatMsg) msg);
                    break;
                case READYTOENTER:
                    break;
                case OPENVAULT:
                    break;
                case RESETAFTERDEATH:
                    respawn((RespawnMsg) msg, origin);
                    break;
                case SHOWCOMBATINFO:
                    show((ShowMsg) msg, origin);
                    break;
                case REQUESTTOTRADE:
                    TradeManager.tradeRequest((TradeRequestMsg) msg, origin);
                    break;
                case REQUESTTRADEOK:
                    TradeManager.acceptTradeRequest((AcceptTradeRequestMsg) msg, origin);
                    break;
                case REQUESTTRADECANCEL:
                    TradeManager.rejectTradeRequest((RejectTradeRequestMsg) msg, origin);
                    break;
                case TRADEADDOBJECT:
                    TradeManager.addItemToTradeWindow((AddItemToTradeWindowMsg) msg, origin);
                    break;
                case TRADEADDGOLD:
                    TradeManager.addGoldToTradeWindow((AddGoldToTradeWindowMsg) msg, origin);
                    break;
                case TRADECONFIRM:
                    TradeManager.commitToTrade((CommitToTradeMsg) msg, origin);
                    break;
                case TRADEUNCONFIRM:
                    TradeManager.uncommitToTrade((UncommitToTradeMsg) msg, origin);
                    break;
                case TRADECLOSE:
                    TradeManager.closeTradeWindow((CloseTradeWindowMsg) msg, origin);
                    break;
                case ARCREQUESTTRADEBUSY:
                    TradeManager.invalidTradeRequest((InvalidTradeRequestMsg) msg);
                    break;
                case TRAINERLIST:
                    WorldServer.trainerInfo((TrainerInfoMsg) msg, origin);
                    break;
                case ARCUNTRAINLIST:
                    WorldServer.refinerScreen((RefinerScreenMsg) msg, origin);
                    break;
                case ARCUNTRAINABILITY:
                    RefineMsg.refine((RefineMsg) msg, origin);
                    break;
                case POWERTARGNAME:
                    PowersManager.summon((SendSummonsRequestMsg) msg, origin);
                    break;
                case ARCSUMMON:
                    PowersManager.recvSummon((RecvSummonsRequestMsg) msg, origin);
                    break;
                case ARCTRACKINGLIST:
                    PowersManager.trackWindow((TrackWindowMsg) msg, origin);
                    break;
                case STUCK:
                    MovementManager.stuck(origin);
                    break;
                case ARCPETATTACK:
                    petAttack((PetAttackMsg) msg, origin);
                    break;
                case CHANNELMUTE:
                    break;
                case KEEPALIVESERVERCLIENT:
                    break;
                case UNKNOWN:
                    break;

                case CONFIRMPROMOTE:
                    break;

                default:
                    String ocHex = StringUtils.toHexString(protocolMsg.opcode);
                    Logger.error("Cannot handle Opcode: " + ocHex + " " + protocolMsg.name());
                    return false;

            }

        } catch (Exception e) {
            Logger.error("handler for " + protocolMsg + " failed:  " + e);
            return false;
        }

        return true;
    }



}