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


package engine.objects;

import engine.InterestManagement.WorldGrid;
import engine.gameManager.BuildingManager;
import engine.math.Bounds;
import engine.math.FastMath;
import engine.math.Vector3f;
import engine.math.Vector3fImmutable;
import engine.server.MBServerStatics;

import java.util.ArrayList;
import java.util.HashMap;


public class Regions {

    public static HashMap<Integer, Regions> FurnitureRegionMap = new HashMap<>();
    public final boolean stairs;
    public final boolean exit;
    public int room;
    public boolean outside;
    public int level;
    public Vector3fImmutable lowLerp;
    public Vector3fImmutable highLerp;
    public Vector3f center;
    public float regionDistanceSquared;

    public ArrayList<Vector3f> regionPoints;

    public int parentBuildingID;


    public Regions(ArrayList<Vector3f> regionPoints, int level, int room, boolean outside, boolean exit, boolean stairs, Vector3f center, int parentBuildingID) {
        super();

        this.level = level;
        this.room = room;

        this.outside = (outside);
        this.regionPoints = regionPoints;
        this.exit = exit;
        this.stairs = stairs;
        this.center = center;
        this.parentBuildingID = parentBuildingID;
        //order regionpoints clockwise starting from top left, and ending bottom left.

        ArrayList<Vector3f> top = new ArrayList<>();
        ArrayList<Vector3f> bottom = new ArrayList<>();

        for (Vector3f point : this.regionPoints) {
            if (point.y > center.y)
                top.add(point);
            else if (point.y < center.y)
                bottom.add(point);
        }


        if (top.size() == 2 && bottom.size() == 2) {
            Vector3f topLeft = Vector3f.min(top.get(0), top.get(1));
            Vector3f topRight = Vector3f.max(top.get(0), top.get(1));

            Vector3f topCenter = topLeft.lerp(topRight, .5f);

            Vector3f bottomLeft = Vector3f.min(bottom.get(0), bottom.get(1));
            Vector3f bottomRight = Vector3f.max(bottom.get(0), bottom.get(1));

            Vector3f bottomCenter = bottomLeft.lerp(bottomRight, .5f);

            this.lowLerp = new Vector3fImmutable(bottomCenter);
            this.highLerp = new Vector3fImmutable(topCenter);

        } else if (top.size() == 2 && bottom.size() == 1) {
            Vector3f topLeft = Vector3f.min(top.get(0), top.get(1));
            Vector3f topRight = Vector3f.max(top.get(0), top.get(1));

            Vector3f topCenter = topLeft.lerp(topRight, .5f);

            Vector3f topCopy = topRight.subtract2D(topLeft);
            topCopy.normalize();

            float topMagnitude = topRight.subtract2D(topLeft).length();

            topCopy.multLocal(topMagnitude);

            Vector3f bottomLeft = null;
            Vector3f bottomRight = null;
            if (bottom.get(0).distance2D(topLeft) <= bottom.get(0).distance2D(topRight))
                bottomLeft = bottom.get(0);
            else if (bottom.get(0).distance2D(topRight) <= bottom.get(0).distance2D(topLeft))
                bottomRight = bottom.get(0);
            //find bottom right point

            if (bottomLeft != null) {
                bottomRight = bottomLeft.add(topCopy);
            } else if (bottomRight != null) {
                bottomLeft = bottomRight.subtract(topCopy);
            }


            Vector3f bottomCenter = bottomLeft.lerp(bottomRight, .5f);

            this.lowLerp = new Vector3fImmutable(bottomCenter);
            this.highLerp = new Vector3fImmutable(topCenter);

        } else if (bottom.size() == 2 && top.size() == 1) {
            Vector3f topLeft = Vector3f.min(bottom.get(0), bottom.get(1));
            Vector3f topRight = Vector3f.max(bottom.get(0), bottom.get(1));

            Vector3f topCenter = topLeft.lerp(topRight, .5f);

            Vector3f topCopy = topRight.subtract2D(topLeft);
            topCopy.normalize();

            float topMagnitude = topRight.subtract2D(topLeft).length();

            topCopy.multLocal(topMagnitude);

            Vector3f bottomLeft = null;
            Vector3f bottomRight = null;
            if (top.get(0).distance2D(topLeft) < top.get(0).distance2D(topRight))
                bottomLeft = bottom.get(0);
            else if (top.get(0).distance2D(topRight) < top.get(0).distance2D(topLeft))
                bottomRight = bottom.get(0);
            //find bottom right point

            if (bottomLeft != null) {
                bottomRight = bottomLeft.add(topCopy);
            } else if (bottomRight != null) {
                bottomLeft = bottomRight.subtract(topCopy);
            }


            Vector3f bottomCenter = bottomLeft.lerp(bottomRight, .5f);

            this.lowLerp = new Vector3fImmutable(bottomCenter);
            this.highLerp = new Vector3fImmutable(topCenter);
        }

        if (this.lowLerp == null)
            this.lowLerp = new Vector3fImmutable(this.regionPoints.get(0));

        if (this.highLerp == null) {
            this.highLerp = new Vector3fImmutable(this.regionPoints.get(2));
        }

        this.regionDistanceSquared = this.lowLerp.distanceSquared2D(this.highLerp);
    }

    public static boolean CanEnterRegion(AbstractWorldObject worldObject, Regions toEnter) {

        if (worldObject.region == null)
            if (toEnter.level == 0 || toEnter.room == -1 || toEnter.exit)
                return true;
            else
                return false;

        if (worldObject.region.equals(toEnter))
            return true;

        if (worldObject.region.level == toEnter.level)
            return true;

        //next region is stairs, if they are on the same level as stairs or 1 up, world object can enter.
        if (toEnter.isStairs())
            if (worldObject.region.level == toEnter.level || toEnter.level - 1 == worldObject.region.level)
                return true;
        if (worldObject.region.isStairs()) {

            boolean movingUp = false;

            boolean movingDown = false;
            float yLerp = worldObject.region.lerpY(worldObject);

            if (yLerp == (worldObject.region.highLerp.y))
                movingUp = true;
            else if (yLerp == (worldObject.region.lowLerp.y))
                movingDown = true;
            //Stairs are always considered on the bottom floor.


            if (movingUp) {
                if (toEnter.level == worldObject.region.level + 1)
                    return true;
            } else if (movingDown)
                if (toEnter.level == worldObject.region.level)
                    return true;

        }

        return false;
    }

    public static float GetMagnitudeOfRegionSlope(Regions region) {
        Vector3fImmutable lengthVector = region.highLerp.subtract2D(region.lowLerp);
        return lengthVector.magnitude();
    }

    public static float GetMagnitudeOfPlayerOnRegionSlope(Regions region, PlayerCharacter player) {
        Vector3fImmutable characterVector = player.getLoc().subtract2D(region.lowLerp);
        return characterVector.magnitude();
    }

    public static float SlopeLerpPercent(PlayerCharacter player, Regions region) {

        float lengthVectorMagnitude = Regions.GetMagnitudeOfRegionSlope(region);
        float characterVectorMagnitude = Regions.GetMagnitudeOfPlayerOnRegionSlope(region, player);
        float percentDistance = characterVectorMagnitude / lengthVectorMagnitude * 2;
        return percentDistance;
    }

    public static boolean CanEnterFromOutsideBuilding(Building building, Regions region) {
        if (!region.outside)
            return false;
        if (region.lowLerp.y - building.getLoc().y > 1)
            return false;

        return true;
    }

    public static boolean CanEnterNextLevel(Regions fromRegion, Regions toRegion, AbstractWorldObject worldObject) {

        if (fromRegion == null)
            return false;

        if (toRegion == null)
            return false;

        // regions are the same, no need to go any further.
        if (fromRegion.equals(toRegion))
            return true;

        //cant move up a level without stairs.
        if (!fromRegion.isStairs())
            return false;

        boolean movingUp = false;

        Vector3fImmutable closestPoint = Vector3fImmutable.ClosestPointOnLine(fromRegion.lowLerp, fromRegion.highLerp, worldObject.getLoc());

        //Closest point of a region higher than current region will always return highlerp.
        if (closestPoint.equals(fromRegion.highLerp))
            movingUp = true;
        //Stairs are always considered on the bottom floor.

        if (movingUp) {
            if (toRegion.level != fromRegion.level + 1)
                return false;
        } else if (toRegion.level != fromRegion.level)
            return false;
        return true;
    }

    public static boolean IsGroundLevel(Regions region, Building building) {

        if (region.lowLerp.y - building.getLoc().y > 1)
            return false;
        return true;
    }

    public static Building GetBuildingForRegion(Regions region) {
        return BuildingManager.getBuildingFromCache(region.parentBuildingID);
    }

    public static Regions GetRegionForTeleport(Vector3fImmutable location) {
        Regions region = null;


        //Find building
        for (AbstractWorldObject awo : WorldGrid.getObjectsInRangePartial(location, 128, MBServerStatics.MASK_BUILDING)) {
            Building building = (Building) awo;
            if (!Bounds.collide(location, building.getBounds()))
                continue;
            if(building != null) {
                region = BuildingManager.GetRegion(building, location.x, location.y, location.z);
            }
            //find regions that intersect x and z, check if object can enter.
            //for (Regions toEnter : building.getBounds().getRegions()) {
            //    if (toEnter.isPointInPolygon(location)) {

            //        if (region == null)
            //            region = toEnter;
            //        else // we're using a low level to high level tree structure, database not always in order low to high.
                        //check for highest level index.
            //            if (region != null && toEnter.highLerp.y > region.highLerp.y)
            //                region = toEnter;


            //    }
           // }
        }
        return region;
    }

    public int getRoom() {
        return room;
    }

    public boolean isOutside() {
        return outside;
    }

    public int getLevel() {
        return level;
    }

    public boolean collides(Vector3fImmutable collisionPoint) {

        //test if inside triangle // Regions either have 3 or 4 points
        if (this.regionPoints.size() == 3) {
            float regionArea = FastMath.area(regionPoints.get(0).x, regionPoints.get(0).z, regionPoints.get(1).x, regionPoints.get(1).z, regionPoints.get(2).x, regionPoints.get(2).z);
            float collisionArea1 = FastMath.area(collisionPoint.x, collisionPoint.z, regionPoints.get(0).x, regionPoints.get(0).z, regionPoints.get(1).x, regionPoints.get(1).z);
            float collisionArea2 = FastMath.area(collisionPoint.x, collisionPoint.z, regionPoints.get(1).x, regionPoints.get(1).z, regionPoints.get(2).x, regionPoints.get(2).z);
            float collisionArea3 = FastMath.area(collisionPoint.x, collisionPoint.z, regionPoints.get(0).x, regionPoints.get(0).z, regionPoints.get(2).x, regionPoints.get(2).z);

            if ((collisionArea1 + collisionArea2 + collisionArea3) == regionArea)
                return true;

        } else {

            int i;
            int j;
            for (i = 0, j = this.regionPoints.size() - 1; i < this.regionPoints.size(); j = i++) {
                if ((regionPoints.get(i).z > collisionPoint.z) != (regionPoints.get(j).z > collisionPoint.z) &&
                        (collisionPoint.x < (regionPoints.get(j).x - regionPoints.get(i).x) * (collisionPoint.z - regionPoints.get(i).z) / (regionPoints.get(j).z - regionPoints.get(i).z) + regionPoints.get(i).x)) {
                    return true;
                }
            }

        }

        return false;
    }

    public boolean isPointInPolygon(Vector3fImmutable point) {
        boolean inside = false;
        for (int i = 0, j = regionPoints.size() - 1; i < regionPoints.size(); j = i++) {
            if (((regionPoints.get(i).z > point.z) != (regionPoints.get(j).z > point.z)) &&
                    (point.x < (regionPoints.get(j).x - regionPoints.get(i).x) * (point.z - regionPoints.get(i).z) / (regionPoints.get(j).z - regionPoints.get(i).z) + regionPoints.get(i).x))
                inside = !inside;
        }
        return inside;
    }

    public float lerpY(AbstractWorldObject lerper) {

        Vector3fImmutable lengthVector = this.highLerp.subtract2D(this.lowLerp);
        Vector3fImmutable characterVector = lerper.getLoc().subtract2D(this.lowLerp);
        float lengthVectorMagnitude = lengthVector.magnitude();
        float characterVectorMagnitude = characterVector.magnitude();
        float percentDistance = characterVectorMagnitude / lengthVectorMagnitude;
        float interpolatedY = this.lowLerp.interpolate(this.highLerp, percentDistance).y;

        if (interpolatedY > this.highLerp.y)
            interpolatedY = this.highLerp.y;
        else if (interpolatedY < this.lowLerp.y)
            interpolatedY = this.lowLerp.y;
        return interpolatedY;
    }

    public boolean isStairs() {

        return this.highLerp.y - this.lowLerp.y > 0.25f;
    }

    public boolean isExit() {
        return exit;
    }
}
