#include "error.h"
#include "util.h"
#include <SDL3/SDL.h>

ErrorContext *test_rectangle_points_nullpointers(void)
{
    RectanglePoints points;
    SDL_FRect testrect;
    PREPARE_ERROR(errctx);

    ATTEMPT {
	CATCH(errctx, rectangle_points(NULL, NULL));
	FAIL_BREAK(errctx, ERR_BEHAVIOR, "rectangle_points fails to FAIL with all NULL pointers");
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
	// noop
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, rectangle_points(NULL, &testrect));
	FAIL_BREAK(errctx, ERR_BEHAVIOR, "rectangle_points fails to FAIL with NULL SDL_FRect pointer");
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
	// noop
    } FINISH(errctx, true);
  
    ATTEMPT {
	CATCH(errctx, rectangle_points(&points, NULL));
	FAIL_BREAK(errctx, ERR_BEHAVIOR, "rectangle_points fails to FAIL with NULL RectanglePoints pointer");
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
	// noop
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, rectangle_points(&points, &testrect));
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);

    SUCCEED_RETURN(errctx);
}

ErrorContext *test_rectangle_points_math(void)
{
    RectanglePoints points;
    SDL_FRect testrect = {.x = 0, .y = 0, .w = 32, .h = 32};
    memset((void *)&points, 0x00, sizeof(RectanglePoints));
  
    PREPARE_ERROR(errctx);
    ATTEMPT {
	CATCH(errctx, rectangle_points(&points, &testrect));
	if ( points.topleft.x != 0 ||
	     points.topleft.y != 0 ||
	     points.topright.x != 32 ||
	     points.topright.y != 0 ||
	     points.bottomleft.x != 0 ||
	     points.bottomleft.y != 32 ||
	     points.bottomright.x != 32 ||
	     points.bottomright.y != 32 ) {
	    FAIL_BREAK(
		errctx,
		ERR_BEHAVIOR,
		"rectangle_points incorrectly calculated points for {x=0, y=0, w=32, h=32} to {topleft={%d, %d}, topright={%d, %d}, bottomleft={%d, %d}, bottomright={%d, %d}}",
		points.topleft.x, points.topleft.y,
		points.topright.x, points.topright.y,
		points.bottomleft.x, points.bottomleft.y,
		points.bottomright.x, points.bottomright.y
		);
	}
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_collide_point_rectangle_nullpointers(void)
{
    point testpoint;
    RectanglePoints testrectpoints;
    bool testcollide;
    
    PREPARE_ERROR(errctx);

    ATTEMPT {
	CATCH(errctx, collide_point_rectangle(&testpoint, &testrectpoints, NULL));
	FAIL_BREAK(errctx, ERR_BEHAVIOR, "collide_point_rectangle(*, *, NULL) failed");
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
	// noop
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, collide_point_rectangle(&testpoint, NULL, &testcollide));
	FAIL_BREAK(errctx, ERR_BEHAVIOR, "collide_point_rectangle(*, NULL, *) failed");
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
	// noop
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, collide_point_rectangle(NULL, &testrectpoints, &testcollide));
	FAIL_BREAK(errctx, ERR_BEHAVIOR, "collide_point_rectangle(NULL, *, *) failed");
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
	// noop
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, collide_point_rectangle(NULL, NULL, NULL));
	FAIL_BREAK(errctx, ERR_BEHAVIOR, "collide_point_rectangle(NULL, NULL, NULL) failed");
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
	// noop
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, collide_point_rectangle(&testpoint, &testrectpoints, &testcollide));
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);

    SUCCEED_RETURN(errctx);
}

ErrorContext *test_collide_point_rectangle_logic(void)
{
    point testpoint = {.x = 16, .y = 16};
    SDL_FRect testrect = { .x = 0, .y = 0, .w = 32, .h = 32};
    RectanglePoints testrectpoints;
    bool testcollide = false;
    PREPARE_ERROR(errctx);
    
    ATTEMPT {
	CATCH(errctx, rectangle_points(&testrectpoints, &testrect));
	CATCH(errctx, collide_point_rectangle(&testpoint, &testrectpoints, &testcollide));
	if ( testcollide == false ) {
	    FAIL_BREAK(errctx, ERR_BEHAVIOR, "Valid collision missed");
	}

	testpoint.x = 48;
	testpoint.y = 48;
	CATCH(errctx, collide_point_rectangle(&testpoint, &testrectpoints, &testcollide));
	if ( testcollide == true ) {
	    FAIL_BREAK(errctx, ERR_BEHAVIOR, "Invalid collision reported");
	}
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_collide_rectangles_nullpointers(void)
{
    SDL_FRect testrect1;
    SDL_FRect testrect2;
    bool testcollide;
    
    PREPARE_ERROR(errctx);

    ATTEMPT {
	CATCH(errctx, collide_rectangles(&testrect1, &testrect2, NULL));
	FAIL_BREAK(errctx, ERR_BEHAVIOR, "collide_rectangles(*, *, NULL) failed");
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
	// noop
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, collide_rectangles(&testrect1, NULL, &testcollide));
	FAIL_BREAK(errctx, ERR_BEHAVIOR, "collide_rectangles(*, NULL, *) failed");
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
	// noop
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, collide_rectangles(NULL, &testrect2, &testcollide));
	FAIL_BREAK(errctx, ERR_BEHAVIOR, "collide_rectangles(NULL, *, *) failed");
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
	// noop
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, collide_rectangles(NULL, NULL, NULL));
	FAIL_BREAK(errctx, ERR_BEHAVIOR, "collide_rectangles(NULL, NULL, NULL) failed");
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
	// noop
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, collide_rectangles(&testrect1, &testrect2, &testcollide));
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);

    SUCCEED_RETURN(errctx);
}

ErrorContext *test_collide_rectangles_logic(void)
{
    SDL_FRect testrect1 = { .x = 0, .y = 0, .w = 32, .h = 32};
    SDL_FRect testrect2 = { .x = 30, .y = 30, .w = 40, .h = 40};
    bool testcollide = false;
    
    PREPARE_ERROR(errctx);
    
    ATTEMPT {
	// Collision overlapping on the top left
	CATCH(errctx, collide_rectangles(&testrect1, &testrect2, &testcollide));
	if ( testcollide == false ) {
	    FAIL_BREAK(errctx, ERR_BEHAVIOR, "Valid collision missed");
	}

	// Collision overlapping on the top right
	testrect1.x = 64;
	CATCH(errctx, collide_rectangles(&testrect1, &testrect2, &testcollide));
	if ( testcollide == false ) {
	    FAIL_BREAK(errctx, ERR_BEHAVIOR, "Valid collision missed");
	}

	// Collision overlapping on the bottom left
	testrect1.x = 0;
	testrect1.y = 32;
	CATCH(errctx, collide_rectangles(&testrect1, &testrect2, &testcollide));
	if ( testcollide == false ) {
	    FAIL_BREAK(errctx, ERR_BEHAVIOR, "Valid collision missed");
	}

	// Collision overlapping on the bottom right
	testrect1.x = 32;
	testrect1.y = 32;
	CATCH(errctx, collide_rectangles(&testrect1, &testrect2, &testcollide));
	if ( testcollide == false ) {
	    FAIL_BREAK(errctx, ERR_BEHAVIOR, "Valid collision missed");
	}

	// Collision overlapping the top edge
	testrect1.x = 0;
	testrect1.y = 0;
	testrect1.w = 60;
	testrect1.h = 32;
	CATCH(errctx, collide_rectangles(&testrect1, &testrect2, &testcollide));
	if ( testcollide == false ) {
	    FAIL_BREAK(errctx, ERR_BEHAVIOR, "Valid collision missed");
	}

	// Collision overlapping the left edge
	testrect1.x = 0;
	testrect1.y = 0;
	testrect1.w = 35;
	testrect1.h = 80;
	CATCH(errctx, collide_rectangles(&testrect1, &testrect2, &testcollide));
	if ( testcollide == false ) {
	    FAIL_BREAK(errctx, ERR_BEHAVIOR, "Valid collision missed");
	}

	// Collision overlapping the right edge
	testrect1.x = 65;
	testrect1.y = 0;
	testrect1.w = 60;
	testrect1.h = 80;
	CATCH(errctx, collide_rectangles(&testrect1, &testrect2, &testcollide));
	if ( testcollide == false ) {
	    FAIL_BREAK(errctx, ERR_BEHAVIOR, "Valid collision missed");
	}

	// Collision overlapping the bottom edge
	testrect1.x = 0;
	testrect1.y = 65;
	testrect1.w = 80;
	testrect1.h = 32;
	CATCH(errctx, collide_rectangles(&testrect1, &testrect2, &testcollide));
	if ( testcollide == false ) {
	    FAIL_BREAK(errctx, ERR_BEHAVIOR, "Valid collision missed");
	}

	// Not colliding
	testrect1.x = 0;
	testrect1.y = 0;
	testrect1.w = 16;
	testrect1.h = 16;
	CATCH(errctx, collide_rectangles(&testrect1, &testrect2, &testcollide));
	if ( testcollide == true ) {
	    FAIL_BREAK(errctx, ERR_BEHAVIOR, "Invalid collision reported");
	}
	
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}


int main(void)
{
    PREPARE_ERROR(errctx);
    ATTEMPT {
	CATCH(errctx, test_rectangle_points_nullpointers());
	CATCH(errctx, test_rectangle_points_math());
	CATCH(errctx, test_collide_point_rectangle_nullpointers());
	CATCH(errctx, test_collide_rectangles_nullpointers());
	CATCH(errctx, test_collide_rectangles_logic());
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH_NORETURN(errctx);
}
