import AABB2 from "../../../geom/2d/aabb/AABB2.js";
import Vector2 from "../../../geom/Vector2.js";
import { EPSILON } from "../../../math/EPSILON.js";
import { forceIntoBox } from "./forceIntoBox.js";
import { pullBoxTowardsPoint } from "./pullBoxTowardsPoint.js";
import { resolveAABB2Overlap } from "./resolveAABB2Overlap.js";

/**
 *
 * @param {AABB2} box
 * @param {AABB2} target
 * @param {AABB2} bounds
 * @returns {Vector2}
 */
function compute_initial_placement(
    box,
    target,
    bounds
) {

    /**
     * list of preferred directions, ordered. First direction is higher priority than the next, last is lowest priority
     * @type {Vector2[]}
     */
    const preferredDirections = [
        Vector2.up,
        Vector2.right,
        Vector2.down,
        Vector2.left
    ];

    const targetCenter = target.getCenter();

    const targetH = target.height;
    const targetW = target.width;

    const targetDiagonal = Math.sqrt(targetH * targetH + targetW * targetW);

    const boxWidth = box.width;
    const boxHeight = box.height;

    const boxWidth_2 = boxWidth / 2;
    const boxHeight_2 = boxHeight / 2;

    const intersectionPoint = new Vector2();

    const placements = preferredDirections.map(function (direction) {
        //invert Y axis, screen-space direction is Down(+) and Up(-)
        const viewportDirection = direction.clone()._multiply(1, -1);

        const pointOutside = viewportDirection.clone();
        //scale vector to be larger than the box diagonal so it would intersect the boundary when placed in the middle
        pointOutside.multiplyScalar(targetDiagonal + 1);
        //move vector to the middle od the box
        pointOutside.add(targetCenter);

        target.lineIntersectionPoint(targetCenter, pointOutside, intersectionPoint);

        //align placement to the opposite edge of the box
        const offset = new Vector2(boxWidth_2, boxHeight_2);

        offset.multiply(viewportDirection);

        offset._sub(boxWidth_2, boxHeight_2);

        offset.add(intersectionPoint);

        return offset;
    });

    /**
     *
     * @param {Vector2} placement
     * @returns {number}
     */
    function computePlacementScore(placement) {
        //find space for the tip between bounds and the target
        const availableSpace = new AABB2();

        const px0 = placement.x;
        const px1 = px0 + boxWidth;
        const py0 = placement.y;
        const py1 = py0 + boxHeight;

        if (px1 <= target.x0) {
            //left
            availableSpace.set(
                bounds.x0,
                bounds.y0,
                target.x0,
                bounds.y1
            );
        } else if (px0 >= target.x1) {
            //right
            availableSpace.set(
                target.x1,
                bounds.y0,
                bounds.x1,
                bounds.y1
            );
        } else if (py1 <= target.y0) {
            //up
            availableSpace.set(
                bounds.x0,
                bounds.y0,
                bounds.x1,
                target.y0
            );
        } else if (py0 >= target.y1) {
            //bottom
            availableSpace.set(
                bounds.x0,
                target.y1,
                bounds.x1,
                bounds.y1
            );
        } else {
            //tip is inside, no arbitration
        }

        let score = 0;

        const availableWidth = availableSpace.width;
        const availableHeight = availableSpace.height;

        const spareWidth = availableWidth - boxWidth;
        const spareHeight = availableHeight - boxHeight;

        if (spareHeight < 0) {
            score--;
        }

        if (spareWidth < 0) {
            score--;
        }

        return score;
    }

    //score placements
    const scores = placements.map(computePlacementScore);

    //find best placement
    let bestIndex = 0;
    let bestScore = scores[0];

    for (let i = 1; i < scores.length; i++) {
        const score = scores[i];

        if (score > bestScore + EPSILON) {
            bestScore = score;
            bestIndex = i;
        }
    }

    return placements[bestIndex];
}

/**
 *
 * @param {AABB2} box
 * @param {AABB2} target
 * @param {AABB2} bounds
 * @returns {AABB2} input {@link box} parameter
 */
export function position_box_next_to_box(
    box,
    target,
    bounds
) {

    const b = box.clone();
    const t = target.clone();

    //center box inside the bounds
    const y0 = (bounds.height + b.height) / 2;
    const x0 = (bounds.width + b.width) / 2;

    b.set(x0, y0, x0 + b.width, y0 + b.height);

    t.locked = true;

    const initialPosition = compute_initial_placement(b, t, bounds);

    //set bounds to initial position
    b.set(initialPosition.x, initialPosition.y, initialPosition.x + b.width, initialPosition.y + b.height);

    // touch();

    for (let i = 0; i < 5; i++) {
        //pull
        pullBoxTowardsPoint(b, initialPosition.x + b.width / 2, initialPosition.y + b.height / 2, 0.1);

        resolveAABB2Overlap([b, t], 10);

        forceIntoBox(bounds, [b]);
    }

    box.copy(b);

    return box;
}