#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>
#include "util.h"
#include "error.h"
#include "heap.h"
#include "registry.h"
#include "tilemap.h"
#include "actor.h"
#include "game.h"
#include "json_helpers.h"
#include <jansson.h>

ErrorContext *test_tilemap_get_json_tilemap_property(void)
{
    PREPARE_ERROR(errctx);
    json_t *jsondoc = NULL;
    json_error_t jsonerr;
    string *tmpstr = NULL;
    int propnum;
    
    ATTEMPT {
	CATCH(errctx, heap_next_string(&tmpstr));
	snprintf(
	    (char *)&tmpstr->data,
	    MAX_STRING_LENGTH,
	    "%s%s",
	    SDL_GetBasePath(),
	    "assets/snippets/test_tilemap_get_json_tilemap_property.json"
	    );
	jsondoc = json_load_file((char *)&tmpstr->data, 0, (json_error_t *)&jsonerr);
	FAIL_ZERO_BREAK(errctx, jsondoc, ERR_NULLPOINTER, "Failure loading json fixture: %s", (char *)jsonerr.text);
	CATCH(
	    errctx,
	    get_json_properties_string(
		jsondoc,
		"character",
		&tmpstr
		)
	    );
	FAIL_NONZERO_BREAK(
	    errctx,
	    strcmp((char *)&tmpstr->data, "testcharacter"),
	    ERR_VALUE,
	    "Incorrect value loaded from property `character` (`testcharacter` vs `%s`)",
	    (char *)&tmpstr->data
	    );
	CATCH(
	    errctx,
	    get_json_properties_integer(
		jsondoc,
		"state",
		&propnum
		)
	    );
	FAIL_NONZERO_BREAK(
	    errctx,
	    (propnum != 6),
	    ERR_VALUE,
	    "Incorrect value loaded from property `state` (6 vs %d)",
	    propnum
	    );
    } CLEANUP {
	if ( tmpstr != NULL ) {
	    IGNORE(heap_release_string(tmpstr));
	}
	if ( jsondoc != NULL ) {
	    json_decref(jsondoc);
	}
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_tilemap_compute_tileset_offsets(void)
{
    int comparison_values[8] = {
	0, // Tile 0 X
	0, // Tile 0 Y
	10, // Tile 1 X
	0, // Tile 1 Y
	0, // Tile 2 X
	10, // Tile 2 Y
	10, // Tile 2 X
	10 // Tile 2 Y
    };
    int *comparison_ptrs[8] = {
	&gamemap.tilesets[0].tile_offsets[0][0], // Tile 0 X
	&gamemap.tilesets[0].tile_offsets[0][1], // Tile 0 Y
	&gamemap.tilesets[0].tile_offsets[1][0], // Tile 1 X
	&gamemap.tilesets[0].tile_offsets[1][1], // Tile 0 Y
	&gamemap.tilesets[0].tile_offsets[2][0], // Tile 2 X
	&gamemap.tilesets[0].tile_offsets[2][1], // Tile 2 Y
	&gamemap.tilesets[0].tile_offsets[3][0], // Tile 3 X
	&gamemap.tilesets[0].tile_offsets[3][0], // Tile 3 Y
    };
    int i = 0;
    
    memset((void *)&gamemap, 0x00, sizeof(tilemap));
    gamemap.tilesets[0].tilecount = 4;
    gamemap.tilesets[0].columns = 2;
    gamemap.tilesets[0].firstgid = 1;
    gamemap.tilesets[0].imageheight = 20;
    gamemap.tilesets[0].imagewidth = 20;
    gamemap.tilesets[0].tilecount = 4;
    gamemap.tilesets[0].tileheight = 10;
    gamemap.tilesets[0].tilewidth = 10;
    gamemap.tilesets[0].spacing = 0;
    gamemap.tilesets[0].margin = 0;

    PREPARE_ERROR(errctx);
    ATTEMPT {
	CATCH(errctx, tilemap_compute_tileset_offsets(&gamemap, 0));
	// Tile 0 X offset
	for ( i = 0; i < 8; i++ ) {
	    FAIL_NONZERO_BREAK(
		errctx,
		(*comparison_ptrs[i] != comparison_values[i]),
		ERR_VALUE,
		"Tile offset incorrectly calculated for index %d (%d vs %d)",
		i,
		*comparison_ptrs[i],
		comparison_values[i]
		);
	}
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_tilemap_load_layer_objects(void)
{
    string *pathstr;
    PREPARE_ERROR(errctx);
    json_t *doc = NULL;
    json_t *layers = NULL;
    json_t *objectlayer = NULL;
    json_error_t errdata;
    actor *testactor;

    memset((void *)&gamemap, 0x00, sizeof(tilemap));
    ATTEMPT {
	CATCH(errctx, heap_next_string(&pathstr));
	snprintf((char *)&pathstr->data, MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
	doc = json_load_file((char *)&pathstr->data, 0, &errdata);
	FAIL_ZERO_BREAK(errctx, doc, ERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text);
	CATCH(errctx, get_json_array_value(doc, "layers", &layers));
	CATCH(errctx, get_json_array_index_object(layers, 1, &objectlayer));
	CATCH(errctx, tilemap_load_layer_objects(&gamemap, objectlayer, 1));

	testactor = SDL_GetPointerProperty(REGISTRY_ACTOR, "testactor", NULL);
	FAIL_ZERO_BREAK(
	    errctx,
	    testactor,
	    ERR_NULLPOINTER,
	    "Test Actor was not loaded from the test map"
	    );
	if ( (testactor->basechar != SDL_GetPointerProperty(REGISTRY_CHARACTER, "testcharacter", NULL)) ||
	     (testactor->layer != 1) ||
	     (testactor->state != 6) ||
	     (testactor->visible != true) ||
	     (testactor->x != 16) ||
	     (testactor->y != 16) ) {
	    FAIL_BREAK(errctx, ERR_VALUE, "Test actor was loaded with incorrect values (check gdb)");
	}
    } CLEANUP {
	if ( pathstr != NULL ) {
	    IGNORE(heap_release_string(pathstr));
	}
	if ( doc != NULL ) {
	    json_decref(doc);
	}
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_tilemap_load_layer_tile(void)
{
    string *pathstr;
    PREPARE_ERROR(errctx);
    json_t *doc = NULL;
    json_t *layers = NULL;
    json_t *tilelayer = NULL;
    json_t *tiledata = NULL;
    json_error_t errdata;

    memset((void *)&gamemap, 0x00, sizeof(tilemap));
    
    ATTEMPT {
	CATCH(errctx, heap_next_string(&pathstr));
	snprintf((char *)&pathstr->data, MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
	doc = json_load_file((char *)&pathstr->data, 0, &errdata);
	FAIL_ZERO_BREAK(errctx, doc, ERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text);
	CATCH(errctx, get_json_array_value(doc, "layers", &layers));
	CATCH(errctx, get_json_array_index_object(layers, 0, &tilelayer));
	CATCH(errctx, get_json_array_value(tilelayer, "data", &tiledata));
	CATCH(errctx, tilemap_load_layer_tile(&gamemap, tilelayer, 0));
	if ( (gamemap.layers[0].data[0] != 1) ||
	     (gamemap.layers[0].data[1] != 2) ||
	     (gamemap.layers[0].data[2] != 3) ||
	     (gamemap.layers[0].data[3] != 4) ) {
	    FAIL_BREAK(errctx, ERR_VALUE, "Test tilemap layer 0 tiles loaded with incorrect values (check gdb)");
	}   
    } CLEANUP {
	if ( pathstr != NULL ) {
	    IGNORE(heap_release_string(pathstr));
	}
	if ( doc != NULL ) {
	    json_decref(doc);
	}
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_tilemap_load_layers(void)
{
    string *pathstr;
    PREPARE_ERROR(errctx);
    json_t *doc = NULL;
    json_error_t errdata;
    int i = 0;

    memset((void *)&gamemap, 0x00, sizeof(tilemap));
    
    ATTEMPT {
	CATCH(errctx, heap_next_string(&pathstr));
	snprintf((char *)&pathstr->data, MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
	doc = json_load_file((char *)&pathstr->data, 0, &errdata);
	FAIL_ZERO_BREAK(errctx, doc, ERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text);
	CATCH(errctx, tilemap_load_layers(&gamemap, doc));
	FAIL_NONZERO_BREAK(
	    errctx,
	    (gamemap.numlayers != 3),
	    ERR_VALUE,
	    "Map layer count incorrect"
	    );
	for ( i = 0; i < gamemap.numlayers; i++ ) {
	    if ( (gamemap.layers[i].opacity != 1) ||
		 (gamemap.layers[i].visible != true) ||
		 (gamemap.layers[i].id != (i + 1)) ||
		 (gamemap.layers[i].x != 0) ||
		 (gamemap.layers[i].y != 0) ) {
		FAIL(errctx, ERR_VALUE, "Map layer data loaded incorrectly (see gdb)");
		goto _test_tilemap_load_layers_cleanup;
	    }
	}
	// Layer 2 should have 1 object loaded
	if ( (gamemap.layers[1].objects[0].actorptr != SDL_GetPointerProperty(REGISTRY_ACTOR, "testactor", NULL)) ||
	     (gamemap.layers[1].objects[1].name[0] != '\0' ) ||
	     (gamemap.layers[1].objects[1].id != 0) ) {
	    FAIL_BREAK(errctx, ERR_VALUE, "Map layer 2 should have 1 loaded object (testactor) and nothing else (see gdb)");
	}
	// Layer 1 and 3 should have no objects
 	for ( i = 0; i < TILEMAP_MAX_OBJECTS_PER_LAYER ; i++ ) {
	    if ( gamemap.layers[0].objects[i].id != 0 ) {
		FAIL(errctx, ERR_VALUE, "Map layers 1 and 3 should have no objects loaded but found objects");
		goto _test_tilemap_load_layers_cleanup;
	    }
	}
 	for ( i = 0; i < TILEMAP_MAX_OBJECTS_PER_LAYER ; i++ ) {
	    if ( gamemap.layers[2].objects[i].id != 0 ) {
		FAIL(errctx, ERR_VALUE, "Map layers 1 and 3 should have no objects loaded but found objects");
		goto _test_tilemap_load_layers_cleanup;
	    }
	}
	// Layers 1 and 3 should have tile data
	if ( (gamemap.layers[0].data[0] != 1) ||
	     (gamemap.layers[0].data[1] != 2) ||
	     (gamemap.layers[0].data[2] != 3) ||
	     (gamemap.layers[0].data[3] != 4) ||
	     (gamemap.layers[2].data[0] != 0) ||
	     (gamemap.layers[2].data[1] != 5) ||
	     (gamemap.layers[2].data[2] != 0) ||
	     (gamemap.layers[2].data[3] != 6)
	    ) {
	    FAIL_BREAK(errctx, ERR_VALUE, "Map layers 1 and 3 should have tile data but it is incorrect");
	}
_test_tilemap_load_layers_cleanup:
    } CLEANUP {
	if ( pathstr != NULL ) {
	    IGNORE(heap_release_string(pathstr));
	}
	if ( doc != NULL ) {
	    json_decref(doc);
	}
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_tilemap_load_tilesets(void)
{
    string *pathstr = NULL;
    PREPARE_ERROR(errctx);
    json_t *doc = NULL;
    json_error_t errdata;
    SDL_Texture *image = NULL;

    memset((void *)&gamemap, 0x00, sizeof(tilemap));
    
    ATTEMPT {
	CATCH(errctx, heap_next_string(&pathstr));
	snprintf((char *)&pathstr->data, MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
	doc = json_load_file((char *)&pathstr->data, 0, &errdata);
	FAIL_ZERO_BREAK(errctx, doc, ERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text);
	CATCH(errctx, tilemap_load_tilesets(&gamemap, doc));
	FAIL_NONZERO_BREAK(errctx, (gamemap.numtilesets != 1), ERR_VALUE, "Incorrect number of tilesets loaded for map");
	if ( (gamemap.tilesets[0].columns != 48 ) ||
	     (gamemap.tilesets[0].firstgid != 1) ||
	     (gamemap.tilesets[0].imageheight != 576) ||
	     (gamemap.tilesets[0].imagewidth != 768) ||
	     (gamemap.tilesets[0].margin != 0) ||
	     (gamemap.tilesets[0].spacing != 0) ||
	     (gamemap.tilesets[0].tilecount != 1728) ||
	     (gamemap.tilesets[0].tileheight != 16) ||
	     (gamemap.tilesets[0].tilewidth != 16) ) {
	    FAIL_BREAK(errctx, ERR_VALUE, "Tileset loaded with incorrect values");
	}
	FAIL_NONZERO_BREAK(
	    errctx,
	    strcmp((char *)&gamemap.tilesets[0].name, "World_A1"),
	    ERR_VALUE,
	    "Tileset loaded with incorrect name");

	snprintf((char *)&pathstr->data, MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/World_A1.png");
	image = IMG_LoadTexture(renderer, (char *)&pathstr->data);
	FAIL_ZERO_BREAK(errctx, image, ERR_SDL, "Failed to load comparison image");

	CATCH(
	    errctx,
	    render_and_compare(
		gamemap.tilesets[0].texture,
		image,
		0, 0, 768, 576,
		"test_tilemap_loaded_tileset.png")
	    );

    } CLEANUP {
	if ( pathstr != NULL ) {
	    IGNORE(heap_release_string(pathstr));
	}
	if ( doc != NULL ) {
	    json_decref(doc);
	}
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_tilemap_load(void)
{
    PREPARE_ERROR(errctx);

    memset((void *)&gamemap, 0x00, sizeof(tilemap));

    ATTEMPT {
	CATCH(errctx, tilemap_load("assets/testmap.tmj", &gamemap));
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_tilemap_draw(void)
{
    PREPARE_ERROR(errctx);
    
    ATTEMPT {
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}


ErrorContext *test_tilemap_draw_tileset(void)
{
    PREPARE_ERROR(errctx);
    
    ATTEMPT {
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

int main(void)
{
    PREPARE_ERROR(errctx);
    
    ATTEMPT {
	SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
      
	if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) {
	    FAIL_BREAK(errctx, ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
	}
      
	if (!SDL_CreateWindowAndRenderer("net/aklabs/libsdl3game/test_sprite", 768, 576, 0, &window, &renderer)) {
	    FAIL_BREAK(errctx, ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
	}

	CATCH(errctx, registry_init());
	CATCH(errctx, heap_init());
	CATCH(errctx, sprite_load_json("assets/testsprite.json"));
	CATCH(errctx, sprite_load_json("assets/testsprite2.json"));
	CATCH(errctx, character_load_json("assets/testcharacter.json"));

	CATCH(errctx, test_tilemap_get_json_tilemap_property());
	CATCH(errctx, test_tilemap_compute_tileset_offsets());
	CATCH(errctx, test_tilemap_load_layer_objects());
	CATCH(errctx, test_tilemap_load_layer_tile());
	CATCH(errctx, test_tilemap_load_layers());
	CATCH(errctx, test_tilemap_load_tilesets());
	//CATCH(errctx, test_tilemap_load());
	//CATCH(errctx, test_tilemap_draw_tileset());
	//CATCH(errctx, test_tilemap_draw());
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH_NORETURN(errctx);
    
}

