package se.urmo.game.collision;


import lombok.extern.slf4j.Slf4j;
import se.urmo.game.main.GamePanel;
import se.urmo.game.map.GameMap;
import se.urmo.game.util.Direction;
import se.urmo.game.util.MyPoint;

import java.util.Collections;
import java.util.List;

@Slf4j
public class CollisionChecker {
    private final GameMap map;

    public CollisionChecker(GameMap map) {
        this.map = map;
    }

    public MyPoint getValidDestination(Direction direction, MyPoint position, int agentWidth, int agentHeight) {
        List<MyPoint> boundaries = getBoundariesForDirection(direction, position, agentWidth / 2, agentHeight / 2);

        return canMoveInDirection(agentWidth, boundaries)
                ? normalizePosition(position, agentWidth)
                : null; // Blocked
    }

    /**
     * Determines whether boundaries (corners) are in a passible i.e., not blocked, position
     *
     * @param agentWidth The width of the entity attempting to move.
     * @param boundaries The boundary points representing the edges of the area occupied by the entity.
     * @return {@code true} if movement in the specified direction is possible; {@code false} otherwise.
     */
    private boolean canMoveInDirection(int agentWidth, List<MyPoint> boundaries) {
        return boundaries.stream()
                .map(boundary -> normalizePosition(boundary, agentWidth))
                .allMatch(myPoint -> map.isPassable((int) myPoint.x, (int) myPoint.y));
    }

    /**
     * Calculates the boundary points of a given position based on a specified direction
     * and horizontal and vertical offsets. The resulting boundaries represent the edges
     * of the area occupied by the entity moving in the specified direction.
     *
     * @param direction        The direction of movement (e.g., UP, DOWN, LEFT, RIGHT, or NONE).
     * @param position         The current position represented as a {@code MyPoint}.
     * @param horizontalOffset The horizontal offset to determine the boundary width.
     * @param verticalOffset   The vertical offset to determine the boundary height.
     * @return A list of boundary points in the form of {@code MyPoint} objects. If the
     * direction is {@code NONE}, an empty list is returned.
     */
    private static List<MyPoint> getBoundariesForDirection(Direction direction, MyPoint position, int horizontalOffset, int verticalOffset) {
        return switch (direction) {
            case NONE -> Collections.emptyList();
            case RIGHT, LEFT -> List.of(
                    new MyPoint(position.x + ((double) (direction.dx * horizontalOffset) / 2), position.y - (double) verticalOffset / 2),
                    new MyPoint(position.x + ((double) (direction.dx * horizontalOffset) / 2), position.y + (double) verticalOffset / 2)
            );
            case UP, DOWN -> List.of(
                    new MyPoint(position.x - (double) horizontalOffset / 2, position.y + ((double) (direction.dy * verticalOffset) / 2)),
                    new MyPoint(position.x + (double) horizontalOffset / 2, position.y + ((double) (direction.dy * verticalOffset) / 2))
            );
        };
    }

    /**
     * Normalizes the position of an agent when it reaches screen boundaries,
     * implementing tunnel-like behavior when crossing horizontal boundaries.
     *
     * @param position   The current position to normalize
     * @param agentWidth The width of the agent
     * @return Normalized position as MyPoint
     */
    private MyPoint normalizePosition(MyPoint position, int agentWidth) {
        if (isLeftBoundary(position.x)) {
            return new MyPoint(GamePanel.SCREEN_WIDTH - (double) agentWidth / 2 - GameMap.OFFSET_X, position.y);
        }
        if (isRightBoundary(position.x)) {
            return new MyPoint(GameMap.OFFSET_X, position.y);
        }
        return position;
    }

    private boolean isLeftBoundary(double x) {
        return x < GameMap.OFFSET_X;
    }

    private boolean isRightBoundary(double x) {
        return x >= (GamePanel.SCREEN_WIDTH - GameMap.OFFSET_X);
    }

}
