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


package engine.mobileAI.utilities;

import engine.Enum;
import engine.Enum.GameObjectType;
import engine.Enum.ModType;
import engine.Enum.SourceType;
import engine.exception.MsgSendException;
import engine.gameManager.MovementManager;
import engine.math.Vector3fImmutable;
import engine.mobileAI.Threads.MobAIThread;
import engine.net.client.msg.MoveToPointMsg;
import engine.objects.*;
import org.pmw.tinylog.Logger;

import java.util.concurrent.ThreadLocalRandom;

import static engine.math.FastMath.sqr;
import static engine.math.FastMath.sqrt;

public class MovementUtilities {


    public static boolean inRangeOfBindLocation(Mob agent) {


        if (agent.isPlayerGuard()) {

            Mob guardCaptain = null;
            if (agent.getContract() != null)
                guardCaptain = agent;
            else
                guardCaptain = (Mob) agent.guardCaptain;

            if (guardCaptain != null) {
                Building barracks = guardCaptain.building;

                if (barracks != null) {
                    City city = barracks.getCity();

                    if (city != null) {
                        Building tol = city.getTOL();

                        //Guards recall distance = 814.
                        if (tol != null) {
                            return !(agent.getLoc().distanceSquared2D(tol.getLoc()) > sqr(Enum.CityBoundsType.ZONE.extents));
                        }

                    }
                }
            }

            return true;
        }

        Vector3fImmutable sl = new Vector3fImmutable(agent.getLoc().getX(), 0, agent.getLoc().getZ());
        Vector3fImmutable tl = new Vector3fImmutable(agent.getTrueBindLoc().x, 0, agent.getTrueBindLoc().z);

        float distanceSquaredToTarget = sl.distanceSquared2D(tl); //distance to center of target
        float zoneRange = 250;

        if (agent.getParentZone() != null) {
            if (agent.getParentZone().getBounds() != null)
                zoneRange = agent.getParentZone().getBounds().getHalfExtents().x * 2;
        }

        if (zoneRange > 300)
            zoneRange = 300;

        if (agent.getSpawnRadius() > zoneRange)
            zoneRange = agent.getSpawnRadius();


        return distanceSquaredToTarget < sqr(MobAIThread.AI_DROP_AGGRO_RANGE + zoneRange);

    }

    public static boolean inRangeToAggro(Mob agent, PlayerCharacter target) {

        Vector3fImmutable sl = agent.getLoc();
        Vector3fImmutable tl = target.getLoc();

        float distanceSquaredToTarget = sl.distanceSquared2D(tl) - sqr(agent.calcHitBox() + target.calcHitBox()); //distance to center of target
        float range = MobAIThread.AI_BASE_AGGRO_RANGE;

        if (agent.isPlayerGuard())
            range = 150;

        return distanceSquaredToTarget < sqr(range);

    }

    public static boolean inRangeDropAggro(Mob agent, AbstractCharacter target) {

        Vector3fImmutable sl = agent.getLoc();
        Vector3fImmutable tl = target.getLoc();

        float distanceSquaredToTarget = sl.distanceSquared2D(tl) - sqr(agent.calcHitBox() + target.calcHitBox()); //distance to center of target

        float range = agent.getRange() + 150;

        if (range > 200)
            range = 200;


        return distanceSquaredToTarget < sqr(range);

    }

    public static Vector3fImmutable GetMoveLocation(Mob aiAgent, AbstractCharacter aggroTarget) {

        // Player isnt moving and neither is mob.  Just return
        // the mobile's current location.  Ain't goin nowhere!
        // *** Refactor: Check to ensure methods calling us
        // all don't sent move messages when not moving.

        if ((aggroTarget.isMoving() == false))
            return aggroTarget.getLoc();

        if (aggroTarget.getEndLoc().x != 0) {

            float aggroTargetDistanceSquared = aggroTarget.getLoc().distanceSquared2D(aggroTarget.getEndLoc());
            float aiAgentDistanceSquared = aiAgent.getLoc().distanceSquared2D(aggroTarget.getEndLoc());

            if (aiAgentDistanceSquared >= aggroTargetDistanceSquared)
                return aggroTarget.getEndLoc();
            else {
                float distanceToMove = sqrt(aggroTargetDistanceSquared + aiAgentDistanceSquared) * .5f;

                return aggroTarget.getFaceDir().scaleAdd(distanceToMove, aggroTarget.getLoc());

            }
        }

        // One of us is moving so let's calculate our destination loc for this
        // simulation frame.  We will simply project our position onto the
        // character's movement vector and return the closest point.

        return aiAgent.getLoc().ClosestPointOnLine(aggroTarget.getLoc(), aggroTarget.getEndLoc());
    }

    public static void moveToLocation(Mob agent, Vector3fImmutable newLocation, float offset) {
        try {

            //don't move farther than 30 units from player.
            if (offset > 30)
                offset = 30;
            Vector3fImmutable newLoc = Vector3fImmutable.getRandomPointInCircle(newLocation, offset);


            agent.setFaceDir(newLoc.subtract2D(agent.getLoc()).normalize());

            aiMove(agent, newLoc, false);
        } catch (Exception e) {
            Logger.error(e.toString());
        }
    }


    public static boolean canMove(Mob agent) {
        if (agent.getMobBase() != null && Enum.MobFlagType.SENTINEL.elementOf(agent.getMobBase().getFlags()))
            return false;

        return (agent.isAlive() && !agent.getBonuses().getBool(ModType.Stunned, SourceType.None) && !agent.getBonuses().getBool(ModType.CannotMove, SourceType.None));
    }

    public static Vector3fImmutable randomPatrolLocation(Mob agent, Vector3fImmutable center, float radius) {

        //Determing where I want to move.
        return new Vector3fImmutable((center.x - radius) + ((ThreadLocalRandom.current().nextFloat() + .1f * 2) * radius),
                center.y,
                (center.z - radius) + ((ThreadLocalRandom.current().nextFloat() + .1f * 2) * radius));
    }

    public static Long estimateMovementTime(Mob agent) {
        if (agent.getEndLoc().x == 0 && agent.getEndLoc().y == 0)
            return 0L;

        return (long) ((agent.getLoc().distance2D(agent.getEndLoc()) * 1000) / agent.getSpeed());
    }

    public static void aiMove(Mob agent, Vector3fImmutable vect, boolean isWalking) {

        //update our walk/run state.
        if (isWalking && !agent.isWalk()) {
            agent.setWalkMode(true);
            MovementManager.sendRWSSMsg(agent);
        } else if (!isWalking && agent.isWalk()) {
            agent.setWalkMode(false);
            MovementManager.sendRWSSMsg(agent);
        }

        MoveToPointMsg msg = new MoveToPointMsg();


//		Regions currentRegion = Mob.InsideBuildingRegion(agent);
//
//		if (currentRegion != null){
//
//
//			if (currentRegion.isGroundLevel()){
//				agent.setInBuilding(0);
//				agent.setInFloorID(-1);
//			}else{
//				agent.setInBuilding(currentRegion.getLevel());
//				agent.setInFloorID(currentRegion.getRoom());
//			}
//		}else{
//			agent.setInBuilding(-1);
//			agent.setInFloorID(-1);
//			agent.setInBuildingID(0);
//		}
//		agent.setLastRegion(currentRegion);


        Vector3fImmutable startLoc = null;
        Vector3fImmutable endLoc = null;

//		if (agent.getLastRegion() != null){
//			Building inBuilding = Building.getBuildingFromCache(agent.getInBuildingID());
//			if (inBuilding != null){
//				startLoc = ZoneManager.convertWorldToLocal(inBuilding, agent.getLoc());
//				endLoc = ZoneManager.convertWorldToLocal(inBuilding, vect);
//			}
//		}else{
//			agent.setBuildingID(0);
//			agent.setInBuildingID(0);
//			startLoc = agent.getLoc();
//			endLoc = vect;
//		}

        startLoc = agent.getLoc();
        endLoc = vect;

        msg.setSourceType(GameObjectType.Mob.ordinal());
        msg.setSourceID(agent.getObjectUUID());
        msg.setStartCoord(startLoc);
        msg.setEndCoord(endLoc);
        msg.setUnknown01(-1);
        msg.setInBuilding(-1);
        msg.setTargetType(0);
        msg.setTargetID(0);


        try {
            MovementManager.movement(msg, agent);
        } catch (MsgSendException e) {
            // TODO Figure out how we want to handle the msg send exception
            e.printStackTrace();
        }
    }

    public static Vector3fImmutable GetDestinationToCharacter(Mob aiAgent, AbstractCharacter character) {

        if (!character.isMoving())
            return character.getLoc();


        float agentDistanceEndLoc = aiAgent.getLoc().distanceSquared2D(character.getEndLoc());
        float characterDistanceEndLoc = character.getLoc().distanceSquared2D(character.getEndLoc());

        if (agentDistanceEndLoc > characterDistanceEndLoc)
            return character.getEndLoc();

        return character.getLoc();
    }

    public static boolean updateMovementToCharacter(Mob aiAgent, AbstractCharacter aggroTarget) {

        if (aiAgent.destination.equals(Vector3fImmutable.ZERO))
            return true;

        if (!aiAgent.isMoving())
            return true;


        if (aggroTarget.isMoving()) {
            return !aiAgent.destination.equals(aggroTarget.getEndLoc()) && !aiAgent.destination.equals(aggroTarget.getLoc());
        } else {
            if (aiAgent.destination.equals(aggroTarget.getLoc()))
                return false;
        }

        return false;
    }

}
