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

package engine.InterestManagement;

import engine.Enum.GridObjectType;
import engine.math.FastMath;
import engine.math.Vector3f;
import engine.math.Vector3fImmutable;
import engine.net.DispatchMessage;
import engine.net.client.ClientConnection;
import engine.net.client.msg.LoadCharacterMsg;
import engine.net.client.msg.LoadStructureMsg;
import engine.net.client.msg.UnloadObjectsMsg;
import engine.objects.*;
import engine.server.MBServerStatics;

import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;


public class WorldGrid {

    public static ConcurrentHashMap<Integer, AbstractWorldObject>[][] DynamicGridMap;
    public static ConcurrentHashMap<Integer, AbstractWorldObject>[][] StaticGridMap;
    private static float dynamicBucketScale = 0.00390625f; // 256 bucket size, 1/256
    private static float staticBucketScale = 0.00390625f;

    public static void startLoadJob() {

        Thread loadJobThread;


        loadJobThread = new Thread(InterestManager.INTERESTMANAGER);
        loadJobThread.setName("InterestManager");
        loadJobThread.start();
    }

    public static boolean moveWorldObject(AbstractWorldObject awo, Vector3fImmutable location) {
        awo.setLoc(location);
        return true;
    }

    public static HashSet<AbstractWorldObject> getInRange(Vector3f loc, double r) {
        HashSet<AbstractWorldObject> outbound = new HashSet<>();
        return outbound;
    }

    public static HashSet<AbstractWorldObject> getObjectsInRangePartial(Vector3fImmutable loc, double r, int mask) {
        HashSet<AbstractWorldObject> outbound = new HashSet<>();
        float scale;

        if ((mask & MBServerStatics.MASK_STATIC) != 0)
            scale = WorldGrid.staticBucketScale;
        else
            scale = WorldGrid.dynamicBucketScale;
        int gridX = (int) Math.abs(loc.x * scale);
        int gridZ = (int) Math.abs(loc.z * scale);
        int bucketSize = (int) (r * scale) + 1;
        //start at top left most corner to scan.
        int startingX = gridX - bucketSize;
        int startingZ = gridZ + bucketSize;


        int limitX = Math.abs((int) (MBServerStatics.MAX_WORLD_WIDTH * scale));
        int limitZ = Math.abs((int) (MBServerStatics.MAX_WORLD_HEIGHT * scale)); //LimitZ is negative, remember to flip sign.

        if (startingX < 0)
            startingX = 0;

        if (startingZ < 0)
            startingZ = 0;

        if (startingX > limitX)
            startingX = limitX;

        if (startingZ > limitZ)
            startingZ = limitZ;

        int endX = startingX + (bucketSize * 2);
        int endZ = startingZ - (bucketSize * 2);

        if (endX < 0)
            endX = 0;

        if (endZ < 0)
            endZ = 0;

        if (endX > limitX)
            endX = limitX;

        if (endZ > limitZ)
            endZ = limitZ;

        int auditMob = 0;
        for (int x = startingX; x <= endX; x++) {
            for (int z = startingZ; z >= endZ; z--) {

                ConcurrentHashMap<Integer, AbstractWorldObject> gridMap;

                if ((MBServerStatics.MASK_STATIC & mask) != 0)
                    gridMap = WorldGrid.StaticGridMap[x][z];
                else
                    gridMap = WorldGrid.DynamicGridMap[x][z];
                for (AbstractWorldObject gridObject : gridMap.values()) {
                    if ((gridObject.getObjectTypeMask() & mask) == 0)
                        continue;
                    if (gridObject.getLoc().distanceSquared2D(loc) <= FastMath.sqr(r))
                        outbound.add(gridObject);
                }
            }
        }
        return outbound;
    }

    public static HashSet<AbstractWorldObject> getObjectsInRangePartialNecroPets(Vector3fImmutable loc, double r) {
        HashSet<AbstractWorldObject> outbound = new HashSet<>();
        return outbound;
    }

    public static HashSet<AbstractWorldObject> getObjectsInRangeContains(Vector3fImmutable loc, double r, int mask) {
        HashSet<AbstractWorldObject> outbound = getObjectsInRangePartial(loc, r, mask);
        return outbound;
    }

    public static HashSet<AbstractWorldObject> getObjectsInRangePartial(AbstractWorldObject awo, double range, int mask) {
        return getObjectsInRangePartial(awo.getLoc(), range, mask);
    }


    public static void InitializeGridObjects() {

        int dynamicWidth = (int) Math.abs(MBServerStatics.MAX_WORLD_WIDTH * WorldGrid.dynamicBucketScale);
        int dynamicHeight = (int) Math.abs(MBServerStatics.MAX_WORLD_HEIGHT * WorldGrid.dynamicBucketScale);

        int staticWidth = (int) Math.abs(MBServerStatics.MAX_WORLD_WIDTH * WorldGrid.staticBucketScale);
        int staticHeight = (int) Math.abs(MBServerStatics.MAX_WORLD_HEIGHT * WorldGrid.staticBucketScale);
        WorldGrid.DynamicGridMap = new ConcurrentHashMap[dynamicWidth + 1][dynamicHeight + 1];
        WorldGrid.StaticGridMap = new ConcurrentHashMap[staticWidth + 1][staticHeight + 1];
        //create new hash maps for each bucket
        for (int x = 0; x <= staticWidth; x++)
            for (int y = 0; y <= staticHeight; y++) {
                WorldGrid.StaticGridMap[x][y] = new ConcurrentHashMap<Integer, AbstractWorldObject>();
            }

        for (int x = 0; x <= dynamicWidth; x++)
            for (int y = 0; y <= dynamicHeight; y++) {
                WorldGrid.DynamicGridMap[x][y] = new ConcurrentHashMap<Integer, AbstractWorldObject>();
            }

    }

    public static void RemoveWorldObject(AbstractWorldObject gridObject) {

        if (gridObject == null)
            return;
        AbstractWorldObject.RemoveFromWorldGrid(gridObject);
    }

    public static boolean addObject(AbstractWorldObject gridObject, float x, float z) {

        if (gridObject == null)
            return false;

        if (x > MBServerStatics.MAX_WORLD_WIDTH)
            return false;

        if (z < MBServerStatics.MAX_WORLD_HEIGHT)
            return false;

        if (x < 0)
            return false;
        if (z > 0)
            return false;

        int gridX;
        int gridZ;

        if (gridObject.getGridObjectType().equals(GridObjectType.STATIC)) {
            gridX = Math.abs((int) (x * WorldGrid.staticBucketScale));
            gridZ = Math.abs((int) (z * WorldGrid.staticBucketScale));
        } else {
            gridX = Math.abs((int) (x * WorldGrid.dynamicBucketScale));
            gridZ = Math.abs((int) (z * WorldGrid.dynamicBucketScale));
        }


        WorldGrid.RemoveWorldObject(gridObject);

        return AbstractWorldObject.AddToWorldGrid(gridObject, gridX, gridZ);


    }

    public static void unloadObject(AbstractWorldObject awo) {

        UnloadObjectsMsg uom = new UnloadObjectsMsg();
        uom.addObject(awo);
        DispatchMessage.sendToAllInRange(awo, uom);
    }

    public static void loadObject(AbstractWorldObject awo) {

        LoadStructureMsg lsm;
        LoadCharacterMsg lcm;

        switch (awo.getObjectType()) {
            case Building:
                lsm = new LoadStructureMsg();
                lsm.addObject((Building) awo);
                DispatchMessage.sendToAllInRange(awo, lsm);
                break;
            case NPC:
                lcm = new LoadCharacterMsg((NPC) awo, false);
                DispatchMessage.sendToAllInRange(awo, lcm);
                break;
            case Mob:
                lcm = new LoadCharacterMsg((Mob) awo, false);
                DispatchMessage.sendToAllInRange(awo, lcm);
                break;
            default:
                // *** Refactor: Log error?
                break;
        }
    }

    public static void loadObject(AbstractWorldObject awo, ClientConnection origin) {

        LoadStructureMsg lsm;
        LoadCharacterMsg lcm;

        switch (awo.getObjectType()) {

            case Building:
                lsm = new LoadStructureMsg();
                lsm.addObject((Building) awo);
                DispatchMessage.sendToAllInRange(awo, lsm);
                break;
            case NPC:
                lcm = new LoadCharacterMsg((NPC) awo, false);
                DispatchMessage.sendToAllInRange(awo, lcm);
                break;
            case Mob:
                lcm = new LoadCharacterMsg((Mob) awo, false);
                DispatchMessage.sendToAllInRange(awo, lcm);
                break;
            case PlayerCharacter:
                lcm = new LoadCharacterMsg((PlayerCharacter) awo, false);
                DispatchMessage.sendToAllInRange(awo, lcm);
                break;
            default:
                // *** Refactor: Log error?
                break;
        }
    }

    public static void unloadObject(AbstractWorldObject awo,
                                    ClientConnection origin) {
        UnloadObjectsMsg uom = new UnloadObjectsMsg();
        uom.addObject(awo);
        DispatchMessage.sendToAllInRange(awo, uom);
    }

    public static void addObject(AbstractWorldObject awo, PlayerCharacter pc) {
        if (pc == null || awo == null)
            return;
        ClientConnection origin = pc.getClientConnection();
        if (origin == null)
            return;
        loadObject(awo, origin);
    }

    public static void removeObject(AbstractWorldObject awo, PlayerCharacter pc) {
        if (pc == null || awo == null)
            return;
        ClientConnection origin = pc.getClientConnection();
        if (origin == null)
            return;
        unloadObject(awo, origin);
    }

    public static void updateObject(AbstractWorldObject awo, PlayerCharacter pc) {
        if (pc == null || awo == null)
            return;
        ClientConnection origin = pc.getClientConnection();
        if (origin == null)
            return;
        unloadObject(awo, origin);
        loadObject(awo, origin);
    }

    public static void updateObject(AbstractWorldObject awo) {
        if (awo == null)
            return;
        unloadObject(awo);
        loadObject(awo);
    }

    /*
     *
     */
    public static void removeObject(AbstractWorldObject awo) {
        if (awo == null)
            return;
        unloadObject(awo);
    }
}
