package oving1;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class RectangleTest {

    // Used in RectangleExtraTest as well
    /**
     * Compares all values in a given {@link Rectangle} to a set of expected values.
     *
     * @param rect The rectangle to check
     * @param minX The expected minimum x value of rect
     * @param minY The expected minimum y value of rect
     * @param maxX The expected maximum x value of rect
     * @param maxY The expected maximum y value of rect
     * @param width The expected width of rect
     * @param height The expected height of rect
     */
    public static void assertValues(Rectangle rect, int minX, int minY, int maxX, int maxY,
            int width, int height, String suffix) {
        assertEquals(minX, rect.getMinX(), "Wrong minX " + suffix);
        assertEquals(minY, rect.getMinY(), "Wrong minY " + suffix);
        assertEquals(maxX, rect.getMaxX(), "Wrong maxX " + suffix);
        assertEquals(maxY, rect.getMaxY(), "Wrong maxY " + suffix);
        assertEquals(width, rect.getWidth(), "Wrong width " + suffix);
        assertEquals(height, rect.getHeight(), "Wrong height " + suffix);
    }

    /**
     * Check that a rectangle is empty.
     *
     * @param rect The rectangle to check
     */
    private static void assertEmpty(Rectangle rect) {
        assertTrue(rect.isEmpty(), "Expected rectangle to be empty!");
        assertTrue(rect.getWidth() == 0 || rect.getHeight() == 0,
                "Empty rectangle should either have a width or height of 0!");
    }

    @Test
    @DisplayName("Empty rectangle")
    public void testEmpty() {
        // Test creating empty rectangle
        Rectangle rect1 = new Rectangle(0, 0, 0, 0);
        assertEmpty(rect1);

        // Test creating empty rectangle with only 0 height
        Rectangle rect2 = new Rectangle(3, 2, 1, 2);
        assertEmpty(rect2);

        // Test creating empty rectangle with only 0 width
        Rectangle rect3 = new Rectangle(3, 1, 3, 2);
        assertEmpty(rect3);
    }

    @Test
    @DisplayName("Rectangle constructor")
    public void testConstructor() {
        // Simple test
        Rectangle rect1 = new Rectangle(0, 0, 1, 2);
        RectangleTest.assertValues(rect1, 0, 0, 1, 2, 1, 2, "when testing constructor");

        // Test providing points in opposite order
        Rectangle rect2 = new Rectangle(1, 2, 0, 0);
        RectangleTest.assertValues(rect2, 0, 0, 1, 2, 1, 2, "when testing constructor");

        // Test negative values
        Rectangle rect3 = new Rectangle(3, 3, -1, 5);
        RectangleTest.assertValues(rect3, -1, 3, 3, 5, 4, 2, "when testing constructor");
    }

    private void testAdd(Rectangle rect, int x, int y, boolean expected) {
        assertEquals(expected, rect.add(x, y),
                "Wrong value returned when adding (" + x + ", " + y + ") to " + rect);
        assertFalse(rect.isEmpty(), "Rectangle should not be empty after adding a point!");
        assertTrue(rect.contains(x, y),
                "Rectangle should contain the point that has been just added!");
    }

    @Test
    @DisplayName("Adding point to rectangle")
    public void testAddXY() {
        int x1 = 13;
        int y1 = -27;
        int x2 = -11;
        int y2 = 23;
        int x3 = 15;
        int y3 = 33;

        Rectangle rect = new Rectangle(x1, y1, x2, y2);

        // Add (x3, y3) and check that rect is updated accordingly
        this.testAdd(rect, x3, y3, true);

        int minX1X2 = Math.min(x1, x2), minY1Y2 = Math.min(y1, y2);
        int maxX1X2 = Math.max(x1, x2), maxY1Y2 = Math.max(y1, y2);
        int minX1X2X3 = Math.min(minX1X2, x3), minY1Y2Y3 = Math.min(minY1Y2, y3);
        int maxX1X2X3 = Math.max(maxX1X2, x3), maxY1Y2Y3 = Math.max(maxY1Y2, y3);

        RectangleTest.assertValues(rect, minX1X2X3, minY1Y2Y3, maxX1X2X3, maxY1Y2Y3,
                maxX1X2X3 - minX1X2X3, maxY1Y2Y3 - minY1Y2Y3,
                "when adding point that is not in rectangle");
    }

    @Test
    @DisplayName("Adding point already in rectangle")
    public void testAddSameXY() {
        int x1 = 13;
        int y1 = -27;
        int x2 = -11;
        int y2 = 23;
        int x3 = 15;
        int y3 = 33;

        int minX1X2 = Math.min(x1, x2);
        int minY1Y2 = Math.min(y1, y2);
        int maxX1X2 = Math.max(x1, x2);
        int maxY1Y2 = Math.max(y1, y2);
        int minX1X2X3 = Math.min(minX1X2, x3);
        int minY1Y2Y3 = Math.min(minY1Y2, y3);
        int maxX1X2X3 = Math.max(maxX1X2, x3);
        int maxY1Y2Y3 = Math.max(maxY1Y2, y3);

        Rectangle rect = new Rectangle(x1, y1, x2, y2);

        // Add (x3, y3) and check that rect is updated accordingly
        this.testAdd(rect, x3, y3, true);
        RectangleTest.assertValues(rect, minX1X2X3, minY1Y2Y3, maxX1X2X3, maxY1Y2Y3,
                maxX1X2X3 - minX1X2X3, maxY1Y2Y3 - minY1Y2Y3,
                "when adding point that is not in rectangle");

        // Add (x3, y3) again and check that all state is the same, with false response
        this.testAdd(rect, x3, y3, false);
        RectangleTest.assertValues(rect, minX1X2X3, minY1Y2Y3, maxX1X2X3, maxY1Y2Y3,
                maxX1X2X3 - minX1X2X3, maxY1Y2Y3 - minY1Y2Y3,
                "when adding point that is in rectangle");
    }

    @Test
    @DisplayName("Adding other rectangle")
    public void testAddRectangle() {
        // Add a point to this.rect
        int x1 = 13;
        int y1 = -27;
        int x2 = -11;
        int y2 = 23;
        int x3 = 15;
        int y3 = 33;
        int minX1X2 = Math.min(x1, x2);
        int minY1Y2 = Math.min(y1, y2);
        int maxX1X2 = Math.max(x1, x2);
        int maxY1Y2 = Math.max(y1, y2);
        int minX1X3 = Math.min(x1, x3);
        int minY1Y3 = Math.min(y1, y3);
        int maxX1X3 = Math.max(x1, x3);
        int maxY1Y3 = Math.max(y1, y3);

        int minX1X2X3 = Math.min(minX1X2, x3);
        int minY1Y2Y3 = Math.min(minY1Y2, y3);
        int maxX1X2X3 = Math.max(maxX1X2, x3);
        int maxY1Y2Y3 = Math.max(maxY1Y2, y3);
        int widthX1X2X3 = maxX1X2X3 - minX1X2X3;
        int heightX1X2X3 = maxY1Y2Y3 - minY1Y2Y3;

        // Create a rectangle and fill it with some points. Assert that this rect is correct
        Rectangle rect = new Rectangle(x1, y1, x2, y2);
        RectangleTest.assertValues(rect, minX1X2, minY1Y2, maxX1X2, maxY1Y2, maxX1X2 - minX1X2,
                maxY1Y2 - minY1Y2, "when creating new rectangle");

        // Create another rectangle and fill it with some points. Assert that this rect is correct
        Rectangle rect2 = new Rectangle(x1, y1, x3, y3);
        RectangleTest.assertValues(rect2, minX1X3, minY1Y3, maxX1X3, maxY1Y3, maxX1X3 - minX1X3,
                maxY1Y3 - minY1Y3, "when creating new rectangle");

        // Add rect to this.rect, and check correctness
        assertTrue(rect.add(rect2));
        RectangleTest.assertValues(rect, minX1X2X3, minY1Y2Y3, maxX1X2X3, maxY1Y2Y3, widthX1X2X3,
                heightX1X2X3, "when adding another rectangle");
    }

    @Test
    @DisplayName("Adding rectangle to itself")
    public void testAddSameRectangle() {
        int x1 = 13;
        int y1 = -27;
        int x2 = -11;
        int y2 = 23;
        int width = Math.abs(x1 - x2);
        int height = Math.abs(y1 - y2);
        int minX1X2 = Math.min(x1, x2);
        int minY1Y2 = Math.min(y1, y2);
        int maxX1X2 = Math.max(x1, x2);
        int maxY1Y2 = Math.max(y1, y2);

        // Create a rectangle and fill it with some points. Assert that this rect is correct
        Rectangle rect = new Rectangle(x1, y1, x2, y2);
        RectangleTest.assertValues(rect, minX1X2, minY1Y2, maxX1X2, maxY1Y2, width, height,
                "when creating new rectangle");

        // Add rectangle to itself and check that state stays the same
        assertFalse(rect.add(rect),
                "Expected no change when adding equal rectangle using #add(Rectangle)");
        RectangleTest.assertValues(rect, minX1X2, minY1Y2, maxX1X2, maxY1Y2, width, height,
                "when adding the same rectangle to itself");
    }

    @Test
    @DisplayName("Rectangle union")
    public void testUnion() {
        int x1 = 13;
        int y1 = -27;
        int x2 = -11;
        int y2 = 23;
        int x3 = 15;
        int y3 = 33;
        int x4 = 17;
        int y4 = -33;

        int minX1X2 = Math.min(x1, x2);
        int minY1Y2 = Math.min(y1, y2);
        int maxX1X2 = Math.max(x1, x2);
        int maxY1Y2 = Math.max(y1, y2);
        int minX3X4 = Math.min(x3, x4);
        int minY3Y4 = Math.min(y3, y4);
        int maxX3X4 = Math.max(x3, x4);
        int maxY3Y4 = Math.max(y3, y4);

        // Create two rectangles and check correctness
        Rectangle rect1 = new Rectangle(x1, y1, x2, y2);
        RectangleTest.assertValues(rect1, minX1X2, minY1Y2, maxX1X2, maxY1Y2, maxX1X2 - minX1X2,
                maxY1Y2 - minY1Y2, "when creating new rectangle");

        Rectangle rect2 = new Rectangle(x3, y3, x4, y4);
        RectangleTest.assertValues(rect2, minX3X4, minY3Y4, maxX3X4, maxY3Y4, maxX3X4 - minX3X4,
                maxY3Y4 - minY3Y4, "when creating new rectangle");

        // Take the union (both ways), and check that both are the same
        int minX = Math.min(minX1X2, minX3X4);
        int minY = Math.min(minY1Y2, minY3Y4);
        int maxX = Math.max(maxX1X2, maxX3X4);
        int maxY = Math.max(maxY1Y2, maxY3Y4);

        Rectangle union1 = rect1.union(rect2);
        RectangleTest.assertValues(union1, minX, minY, maxX, maxY, maxX - minX, maxY - minY,
                "when calling #union with another rectangle");
        RectangleTest.assertValues(rect1, minX1X2, minY1Y2, maxX1X2, maxY1Y2, maxX1X2 - minX1X2,
                maxY1Y2 - minY1Y2,
                "Values changed when calling #union with another rectangle! Make sure to not "
                        + "create a new Rectangle and not modify the current ones!");
        RectangleTest.assertValues(rect2, minX3X4, minY3Y4, maxX3X4, maxY3Y4, maxX3X4 - minX3X4,
                maxY3Y4 - minY3Y4,
                "Values changed when calling #union with another rectangle! Make sure to not "
                        + "create a new Rectangle and not modify the current ones!");

        Rectangle union2 = rect2.union(rect1);
        RectangleTest.assertValues(union2, minX, minY, maxX, maxY, maxX - minX, maxY - minY,
                "when calling #union with another rectangle");
        RectangleTest.assertValues(rect1, minX1X2, minY1Y2, maxX1X2, maxY1Y2, maxX1X2 - minX1X2,
                maxY1Y2 - minY1Y2,
                "Values changed when calling #union with another rectangle! Make sure to not "
                        + "create a new Rectangle and not modify the current ones!");
        RectangleTest.assertValues(rect2, minX3X4, minY3Y4, maxX3X4, maxY3Y4, maxX3X4 - minX3X4,
                maxY3Y4 - minY3Y4,
                "Values changed when calling #union with another rectangle! Make sure to not "
                        + "create a new Rectangle and not modify the current ones!");
    }
}
