#include "tileset/TileAdjacencyBehavior.h"
#include "unittest/TestSuite.h"

static void testInit(void) {
	TileAdjacencyRule rule = {TILE_CONDITION_MATCH_SINGLE, {.single = {{1, 2}, 3}}};
	TileAdjacencyOverlay overlay = {4, {5, 6}, {7, 8, 9, 10}, 11, 0};
	TileAdjacencyBehavior behavior = TileAdjacencyBehavior_init(0, false, rule, RECT4i(1, 2, 3, 4), 1, &overlay, true);
	TestCase_assertUIntEqual(behavior.ownTileID, 0);
	TestCase_assertBoolFalse(behavior.exclusive);
	TestCase_assertIntEqual(behavior.rule.type, TILE_CONDITION_MATCH_SINGLE);
	TestCase_assertIntEqual(behavior.rule.rule.single.offset.x, 1);
	TestCase_assertIntEqual(behavior.rule.rule.single.offset.y, 2);
	TestCase_assertUIntEqual(behavior.rule.rule.single.tileID, 3);
	TestCase_assertIntEqual(behavior.sliceInsets.xMin, 1);
	TestCase_assertIntEqual(behavior.sliceInsets.xMax, 2);
	TestCase_assertIntEqual(behavior.sliceInsets.yMin, 3);
	TestCase_assertIntEqual(behavior.sliceInsets.yMax, 4);
	TestCase_assertUIntEqual(behavior.overlayCount, 1);
	TestCase_assertPointerNonNULL(behavior.overlays);
	TestCase_assertPointerUnequal(behavior.overlays, &overlay);
	TestCase_assertUIntEqual(behavior.overlays[0].imageID, 4);
	TestCase_assertIntEqual(behavior.overlays[0].offset.x, 5);
	TestCase_assertIntEqual(behavior.overlays[0].offset.y, 6);
	TestCase_assertIntEqual(behavior.overlays[0].sliceInsets.xMin, 7);
	TestCase_assertIntEqual(behavior.overlays[0].sliceInsets.xMax, 8);
	TestCase_assertIntEqual(behavior.overlays[0].sliceInsets.yMin, 9);
	TestCase_assertIntEqual(behavior.overlays[0].sliceInsets.yMax, 10);
	TestCase_assertIntEqual(behavior.overlays[0].drawPriority, 11);
	TileAdjacencyBehavior_dispose(&behavior);
}

struct queryContextStructExact {
	Vector2i targetPosition;
	TileID tileIDEqual;
	TileID tileIDUnequal;
};

static TileID queryCallbackExact(Vector2i position, void * context) {
	struct queryContextStructExact * contextStruct = context;
	if (Vector2i_isEqual(position, contextStruct->targetPosition)) {
		return contextStruct->tileIDEqual;
	}
	return contextStruct->tileIDUnequal;
}

static void testMatchRules(void) {
	TileAdjacencyRule rule = {TILE_CONDITION_MATCH_SINGLE, {.single = {{1, 2}, 3}}};
	TileAdjacencyBehavior behavior = TileAdjacencyBehavior_init(0, false, rule, RECT4i_EMPTY, 0, NULL, true);
	struct queryContextStructExact contextStruct = {{4, 4}, 3, 1};
	bool applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(4, 5), queryCallbackExact, &contextStruct);
	TestCase_assertBoolFalse(applicable);
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(3, 2), queryCallbackExact, &contextStruct);
	TestCase_assertBoolTrue(applicable);
	
	behavior.rule.rule.single.tileID = TILE_ID_SELF;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(3, 2), queryCallbackExact, &contextStruct);
	TestCase_assertBoolFalse(applicable);
	behavior.ownTileID = 3;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(3, 2), queryCallbackExact, &contextStruct);
	TestCase_assertBoolTrue(applicable);
	behavior.ownTileID = 0;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(0, 0), queryCallbackExact, &contextStruct);
	TestCase_assertBoolFalse(applicable);
	
	behavior.rule.type = TILE_CONDITION_NONMATCH_SINGLE;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(3, 2), queryCallbackExact, &contextStruct);
	TestCase_assertBoolTrue(applicable);
	behavior.ownTileID = 3;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(3, 2), queryCallbackExact, &contextStruct);
	TestCase_assertBoolFalse(applicable);
	behavior.ownTileID = 0;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(0, 0), queryCallbackExact, &contextStruct);
	TestCase_assertBoolTrue(applicable);
	
	behavior.rule.type = TILE_CONDITION_MATCH_MULTIPLE;
	behavior.rule.rule.multiple.offset = V2i(2, 1);
	behavior.rule.rule.multiple.tileIDCount = 2;
	behavior.rule.rule.multiple.tileIDs = malloc(behavior.rule.rule.multiple.tileIDCount * sizeof(*behavior.rule.rule.multiple.tileIDs));
	behavior.rule.rule.multiple.tileIDs[0] = 3;
	behavior.rule.rule.multiple.tileIDs[1] = TILE_ID_SELF;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(2, 3), queryCallbackExact, &contextStruct);
	TestCase_assertBoolTrue(applicable);
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(3, 2), queryCallbackExact, &contextStruct);
	TestCase_assertBoolFalse(applicable);
	contextStruct.tileIDEqual = 2;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(2, 3), queryCallbackExact, &contextStruct);
	TestCase_assertBoolFalse(applicable);
	behavior.ownTileID = 2;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(2, 3), queryCallbackExact, &contextStruct);
	TestCase_assertBoolTrue(applicable);
	TileAdjacencyBehavior_dispose(&behavior);
}

struct queryContextStructList {
	unsigned int targetPositionCount;
	Vector2i * targetPositions;
	TileID tileIDEqual;
	TileID tileIDUnequal;
};

static TileID queryCallbackList(Vector2i position, void * context) {
	struct queryContextStructList * contextStruct = context;
	for (unsigned int positionIndex = 0; positionIndex < contextStruct->targetPositionCount; positionIndex++) {
		if (Vector2i_isEqual(position, contextStruct->targetPositions[positionIndex])) {
			return contextStruct->tileIDEqual;
		}
	}
	return contextStruct->tileIDUnequal;
}

static void testConditionals(void) {
	TileAdjacencyRule conditions[2] = {
		{TILE_CONDITION_MATCH_SINGLE, {.single = {{0, 1}, 3}}},
		{TILE_CONDITION_MATCH_SINGLE, {.single = {{1, 0}, 3}}}
	};
	TileAdjacencyRule rule = {TILE_CONDITION_AND, {.conditionList = {2, conditions}}};
	TileAdjacencyBehavior behavior = TileAdjacencyBehavior_init(0, false, rule, RECT4i_EMPTY, 0, NULL, true);
	Vector2i targetPositions[2] = {{0, 1}, {1, 0}};
	struct queryContextStructList contextStruct = {2, targetPositions, 3, 1};
	bool applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(0, 0), queryCallbackList, &contextStruct);
	TestCase_assertBoolTrue(applicable);
	targetPositions[0].y = 0;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(0, 0), queryCallbackList, &contextStruct);
	TestCase_assertBoolFalse(applicable);
	targetPositions[0].y = 1;
	targetPositions[1].x = 0;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(0, 0), queryCallbackList, &contextStruct);
	TestCase_assertBoolFalse(applicable);
	targetPositions[1].x = 1;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(1, 0), queryCallbackList, &contextStruct);
	TestCase_assertBoolFalse(applicable);
	
	behavior.rule.type = TILE_CONDITION_OR;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(0, 0), queryCallbackList, &contextStruct);
	TestCase_assertBoolTrue(applicable);
	targetPositions[0].y = 0;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(0, 0), queryCallbackList, &contextStruct);
	TestCase_assertBoolTrue(applicable);
	targetPositions[1].x = 0;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(0, 0), queryCallbackList, &contextStruct);
	TestCase_assertBoolFalse(applicable);
	targetPositions[0].y = 1;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(0, 0), queryCallbackList, &contextStruct);
	TestCase_assertBoolTrue(applicable);
	targetPositions[1].x = 1;
	applicable = TileAdjacencyBehavior_isApplicable(&behavior, V2i(1, 0), queryCallbackList, &contextStruct);
	TestCase_assertBoolFalse(applicable);
	TileAdjacencyBehavior_dispose(&behavior);
}

TEST_SUITE(TileAdjacencyBehaviorTest,
           testInit,
           testMatchRules,
           testConditionals)
