/**
 * @file world.c
 * @brief The content pipeline: sprites, characters, the town map, and what the
 *        map's actors do once they exist.
 *
 * Almost nothing here is game logic. It is the four steps between a directory
 * of JSON and a world with people standing in it, in the one order that works,
 * plus the two hooks -- a `movementlogicfunc` and a `renderfunc` -- that a game
 * has to supply because the library does not, and the collision world the
 * library resolves through.
 */

#include <limits.h>
#include <math.h>
#include <stdbool.h>

#include <SDL3/SDL.h>
#include <akerror.h>
#include <akstdlib.h>

#include <akgl/actor.h>
#include <akgl/character.h>
#include <akgl/collision.h>
#include <akgl/error.h>
#include <akgl/game.h>
#include <akgl/heap.h>
#include <akgl/physics.h>
#include <akgl/registry.h>
#include <akgl/sprite.h>
#include <akgl/tilemap.h>

#include "jrpg.h"

/*
 * The three character templates and the twenty-four sprites that dress them.
 * The file names are a product of these three tables rather than a list of
 * twenty-four strings, because that is what the naming convention *is*: one
 * sprite per character, per motion, per facing. A missing combination shows up
 * as a load failure naming the file, not as art that silently never appears.
 */

/*                          Character template   Sprite name prefix */
static char *CAST[] = {
    "player",       /*      jrpg_player          jrpg_player_*      */
    "elder",        /*      jrpg_elder           jrpg_elder_*       */
    "shopkeeper"    /*      jrpg_shopkeeper      jrpg_shopkeeper_*  */
};

static char *MOTIONS[] = { "idle", "walk" };
static char *FACINGS[] = { "up", "down", "left", "right" };

#define CAST_COUNT      (sizeof(CAST) / sizeof(CAST[0]))
#define MOTION_COUNT    (sizeof(MOTIONS) / sizeof(MOTIONS[0]))
#define FACING_COUNT    (sizeof(FACINGS) / sizeof(FACINGS[0]))

/** @brief One person in the town who has something to say. */
typedef struct {
    char *actor;    /**< Registry key, which is the `name` of the map object that spawned them. */
    char *line;     /**< What they say. */
} jrpg_Townsfolk;

/*
 *  Actor (the map object's name)   What they say
 */
static jrpg_Townsfolk TOWNSFOLK[] = {
    { "elder",      "ELDER: The old road north is closed. Nothing to be done about it,\nand nothing beyond it worth the walk." },
    { "shopkeeper", "SHOPKEEPER: Nothing in stock but tiles and good intentions.\nCome back when I have inventory." }
};

#define TOWNSFOLK_COUNT (sizeof(TOWNSFOLK) / sizeof(TOWNSFOLK[0]))

/** @brief How close the player has to stand, in pixels between sprite centres. */
#define TALK_RANGE      64.0f

/*
 * The player's footprint, in pixels from the top-left of a 32x32 frame. A
 * character stands on the bottom third of their sprite, so that is the part
 * that has to fit through a doorway -- testing the whole frame would make every
 * corridor two tiles wide.
 *
 *   0                              32
 *   +------------------------------+  0
 *   |                              |
 *   |            (art)             |
 *   |                              |
 *   |      +----------------+      | 20   FEET_TOP
 *   |      |     footprint  |      |
 *   +------+----------------+------+ 30   FEET_TOP + FEET_H
 *          6               26
 *        FEET_X       FEET_X + FEET_W
 */
#define FEET_X          6.0f
#define FEET_W          20.0f
#define FEET_TOP        20.0f
#define FEET_H          10.0f

/**
 * @brief How far past the edge of the map the boundary walls extend, in pixels.
 *
 * The walls cover the map's outer ring of cells *and* this much beyond it. The
 * ring is what the old hand-rolled rule made solid and what the art is drawn to
 * expect; the overhang is what stops an actor arriving fast enough from being
 * resolved out the far side of a wall exactly one tile thick.
 */
#define EDGE_OVERHANG   16.0f

/** @brief Where the party member walks, in pixels relative to the player. */
#define FOLLOWER_OFFSET_X  (-14.0f)
#define FOLLOWER_OFFSET_Y  (10.0f)

akgl_CollisionWorld jrpg_collision;

/** @brief The four map-edge walls, and the shapes they were built from. */
static akgl_CollisionProxy *jrpg_edges[4];
static akgl_CollisionShape jrpg_edge_shapes[4];

/**
 * @brief Load one sprite definition by its three naming components.
 *
 * @param cast   Character stem, e.g. `"player"`.
 * @param motion `"idle"` or `"walk"`.
 * @param facing `"up"`, `"down"`, `"left"` or `"right"`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 */
static akerr_ErrorContext *sprite_load(char *cast, char *motion, char *facing)
{
    PREPARE_ERROR(errctx);
    char path[PATH_MAX];
    int written = 0;

    FAIL_ZERO_RETURN(errctx, cast, AKERR_NULLPOINTER, "cast");
    FAIL_ZERO_RETURN(errctx, motion, AKERR_NULLPOINTER, "motion");
    FAIL_ZERO_RETURN(errctx, facing, AKERR_NULLPOINTER, "facing");

    PASS(errctx,
	 aksl_snprintf(
	     &written,
	     path,
	     sizeof(path),
	     "%s/sprite_jrpg_%s_%s_%s.json",
	     JRPG_ASSET_DIR,
	     cast,
	     motion,
	     facing
	     ));
    PASS(errctx, akgl_sprite_load_json(path));
    SUCCEED_RETURN(errctx);
}

/**
 * @brief Load one character definition by its stem.
 * @param cast Character stem, e.g. `"player"`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 */
static akerr_ErrorContext *character_load(char *cast)
{
    PREPARE_ERROR(errctx);
    char path[PATH_MAX];
    int written = 0;

    FAIL_ZERO_RETURN(errctx, cast, AKERR_NULLPOINTER, "cast");
    PASS(errctx,
	 aksl_snprintf(
	     &written,
	     path,
	     sizeof(path),
	     "%s/character_jrpg_%s.json",
	     JRPG_ASSET_DIR,
	     cast
	     ));
    PASS(errctx, akgl_character_load_json(path));
    SUCCEED_RETURN(errctx);
}

/**
 * @brief Wall off the edges of the map with four static proxies.
 *
 * The town's decoration layer says what is solid -- a building, a tree, a lamp
 * post -- and the map's edge is not on it. Nothing else stops an actor walking
 * off the edge of the world.
 *
 * Four proxies rather than a hundred solid tiles, because static geometry that
 * is not tile-shaped is exactly what a proxy is for: a long thin box costs one
 * slot whatever its length. `layermask` is set by hand because
 * akgl_collision_shape_box defaults a shape to LAYER_ACTOR, which is right for
 * an actor and wrong for scenery -- an actor responds to LAYER_STATIC and would
 * walk straight through these.
 *
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKGL_ERR_HEAP If the proxy pool is exhausted.
 */
static akerr_ErrorContext *wall_off_the_edges(void)
{
    SDL_FRect sides[4];
    float32_t ow = 0.0f;        /* Map width, plus the overhang at each end. */
    float32_t oh = 0.0f;        /* Map height, the same. */
    float32_t tw = 0.0f;        /* One tile of ring, plus one overhang. */
    float32_t th = 0.0f;
    int i = 0;
    PREPARE_ERROR(errctx);

    ow = (float32_t)(akgl_gamemap->width * akgl_gamemap->tilewidth) + (2.0f * EDGE_OVERHANG);
    oh = (float32_t)(akgl_gamemap->height * akgl_gamemap->tileheight) + (2.0f * EDGE_OVERHANG);
    tw = (float32_t)akgl_gamemap->tilewidth + EDGE_OVERHANG;
    th = (float32_t)akgl_gamemap->tileheight + EDGE_OVERHANG;

    /*                              x                 y                 w    h  */
    sides[0] = (SDL_FRect){ -EDGE_OVERHANG,   -EDGE_OVERHANG,           tw,  oh };   /* west  */
    sides[1] = (SDL_FRect){ (ow - tw - EDGE_OVERHANG), -EDGE_OVERHANG,  tw,  oh };   /* east  */
    sides[2] = (SDL_FRect){ -EDGE_OVERHANG,   -EDGE_OVERHANG,           ow,  th };   /* north */
    sides[3] = (SDL_FRect){ -EDGE_OVERHANG,   (oh - th - EDGE_OVERHANG), ow, th };   /* south */

    for ( i = 0; i < 4; i++ ) {
	/*
	 * Acquire and initialize adjacent, with nothing between them: until
	 * akgl_collision_proxy_initialize takes the reference the slot is still
	 * free, and the next acquire hands out the same pointer.
	 */
	PASS(errctx, akgl_heap_next_collision_proxy(&jrpg_edges[i]));
	PASS(errctx, akgl_collision_shape_box(&jrpg_edge_shapes[i], &sides[i], 0.0f));
	jrpg_edge_shapes[i].layermask = AKGL_COLLISION_LAYER_STATIC;
	jrpg_edge_shapes[i].collidemask = AKGL_COLLISION_LAYER_NONE;
	jrpg_edge_shapes[i].flags |= AKGL_COLLISION_FLAG_STATIC;
	PASS(errctx, akgl_collision_proxy_initialize(jrpg_edges[i], NULL, &jrpg_edge_shapes[i],
						     0.0f, 0.0f, 0.0f));
	PASS(errctx, jrpg_collision.partitioner.insert(&jrpg_collision.partitioner, jrpg_edges[i]));
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *jrpg_world_load(void)
{
    PREPARE_ERROR(errctx);
    char path[PATH_MAX];
    int written = 0;
    size_t c = 0;
    size_t m = 0;
    size_t f = 0;

    // Sprites first. A character JSON names its sprites by registry key and
    // akgl_character_load_json refuses one it cannot find, so loading the two
    // the other way round fails on every mapping in the file.
    for ( c = 0; c < CAST_COUNT; c++ ) {
	for ( m = 0; m < MOTION_COUNT; m++ ) {
	    for ( f = 0; f < FACING_COUNT; f++ ) {
		PASS(errctx, sprite_load(CAST[c], MOTIONS[m], FACINGS[f]));
	    }
	}
    }
    for ( c = 0; c < CAST_COUNT; c++ ) {
	PASS(errctx, character_load(CAST[c]));
    }

    // And the map last: loading it creates an actor for every `actor` object in
    // its object layer, and each of those resolves a character by name.
    PASS(errctx,
	 aksl_snprintf(&written, path, sizeof(path), "%s/town.tmj", JRPG_ASSET_DIR));
    PASS(errctx, akgl_tilemap_load(path, akgl_gamemap));

    // The map declares its own physics -- `physics.model` and zero gravity on
    // both axes -- and akgl_tilemap_load builds a backend for it and sets
    // use_own_physics. It does not *switch* to it: akgl_game_update simulates
    // through the global akgl_physics, whatever that points at. Honouring the
    // map is the caller's job, and this is it.
    if ( akgl_gamemap->use_own_physics ) {
	akgl_physics = &akgl_gamemap->physics;
    }

    // Collision is opt-in, and this is the whole of turning it on. The cell
    // arguments are placeholders: akgl_collision_bind_tilemap overwrites them
    // from the map's own tile size, and reads which layers are solid off each
    // layer's `collidable` property rather than an index the game and the map
    // had to agree on out of band.
    PASS(errctx, akgl_collision_world_init(&jrpg_collision, NULL, 16.0f, 16.0f));
    PASS(errctx, akgl_collision_bind_tilemap(&jrpg_collision, akgl_gamemap));
    PASS(errctx, wall_off_the_edges());
    akgl_physics->collision = &jrpg_collision;

    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *jrpg_world_populate(void)
{
    PREPARE_ERROR(errctx);
    akgl_Actor *player = NULL;
    akgl_Actor *follower = NULL;
    SDL_FRect body;
    int i = 0;

    // Every actor the map spawned, including the player.
    //
    // akgl_actor_initialize sets movement_controls_face, and the default
    // facefunc it installs clears every facing bit and then sets the one
    // matching a *movement* bit. An NPC has no movement bits, so on the first
    // frame its state falls from ALIVE|FACE_DOWN (17) to ALIVE (16), which is
    // a state combination no character JSON maps a sprite to -- and an actor
    // with no sprite for its state is skipped rather than reported. Every NPC
    // in the town disappears on frame one, silently, and so does the player as
    // soon as they stop walking.
    for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
	if ( akgl_heap_actors[i].refcount == 0 ) {
	    continue;
	}
	akgl_heap_actors[i].movement_controls_face = false;
    }

    player = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "player", NULL);
    FAIL_ZERO_RETURN(
	errctx,
	player,
	AKGL_ERR_REGISTRY,
	"town.tmj spawned no actor named \"player\""
	);
    player->movementlogicfunc = &jrpg_actor_logic_walk;

    // The footprint, and only the player gets one. The NPCs stand still and are
    // spoken to, not walked into: giving them shapes would make the town a
    // pinball table and would change what the proximity test in jrpg.c means.
    // The follower is a child, snapped to the player's position every step, so
    // a shape on it would fight the snap rather than stop anything.
    body = (SDL_FRect){ FEET_X, FEET_TOP, FEET_W, FEET_H };
    PASS(errctx, akgl_collision_shape_box(&player->shape, &body, 0.0f));
    player->shape_override = true;

    // The party member. Nothing in the map creates it, and nothing has to: an
    // actor is a pool object with a name, and the character it instantiates is
    // already registered. This one borrows the elder's template, which is the
    // whole point of splitting instance from template.
    PASS(errctx, akgl_heap_next_actor(&follower));
    PASS(errctx, akgl_actor_initialize(follower, JRPG_FOLLOWER_NAME));
    PASS(errctx, akgl_actor_set_character(follower, "jrpg_elder"));
    follower->state = (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_DOWN);
    follower->movement_controls_face = false;
    follower->visible = true;
    follower->layer = player->layer;
    follower->renderfunc = &jrpg_follower_render;
    PASS(errctx, player->addchild(player, follower));

    // A child's velocity fields are read as a fixed offset from the parent
    // rather than as a velocity: akgl_physics_simulate snaps a child to
    // `parent->x + vx` and never simulates it. Set after addchild, because
    // addchild is what makes them mean an offset.
    follower->vx = FOLLOWER_OFFSET_X;
    follower->vy = FOLLOWER_OFFSET_Y;

    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *jrpg_actor_logic_walk(akgl_Actor *obj, float32_t dt)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "obj");
    FAIL_ZERO_RETURN(errctx, obj->basechar, AKERR_NULLPOINTER, "obj->basechar");

    // A conversation freezes the world. AKGL_ERR_LOGICINTERRUPT raised from a
    // movementlogicfunc means "skip the rest of this tick for this actor", and
    // akgl_physics_simulate swallows it in a HANDLE block -- so gravity, drag,
    // the velocity recompute and the move are all skipped and the frame carries
    // on. Thrust has already been integrated by the time this runs, so it is
    // zeroed here; leaving it would let it accumulate while frozen and lurch on
    // the first step after the box closes. The movement bits go too, so the
    // walk animation stops rather than marching on the spot -- which does mean
    // a direction held across the close has to be pressed again.
    if ( jrpg_textbox_showing() ) {
	obj->tx = 0.0f;
	obj->ty = 0.0f;
	AKGL_BITMASK_DEL(obj->state, AKGL_ACTOR_STATE_MOVING_ALL);
	FAIL_RETURN(
	    errctx,
	    AKGL_ERR_LOGICINTERRUPT,
	    "%s does not move while a conversation is open",
	    (char *)obj->name
	    );
    }

    // Everything the default hook does -- re-copy the character's speed limits,
    // turn the movement bits into signed acceleration -- is still wanted. This
    // hook adds to it rather than replacing it.
    //
    // Walls used to be here too: a prediction of where this step would land,
    // tested axis at a time. It was only ever correct because the town has zero
    // gravity and zero drag, so `v` is `t` and the prediction is exact. The
    // library resolves after the move instead, on a position that already
    // happened, and that qualifier goes away with it.
    PASS(errctx, akgl_actor_logic_movement(obj, dt));

    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *jrpg_follower_render(akgl_Actor *obj)
{
    PREPARE_ERROR(errctx);
    akgl_Actor *parent = NULL;

    FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "obj");

    // The guard. akgl_physics_simulate has already written this actor's x and y
    // as `parent->x + vx` and `parent->y + vy`, which is an absolute world
    // position; akgl_actor_render adds the parent's position to it a second
    // time. Detaching the parent for the duration of the draw takes the branch
    // that does not. CLEANUP puts it back on every path, including the failing
    // one -- an actor left with a NULL parent would be simulated as a free
    // agent on the very next step.
    parent = obj->parent;
    obj->parent = NULL;
    ATTEMPT {
	CATCH(errctx, akgl_actor_render(obj));
    } CLEANUP {
	obj->parent = parent;
    } PROCESS(errctx) {
    } FINISH(errctx, true);

    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *jrpg_camera_follow(void)
{
    PREPARE_ERROR(errctx);
    akgl_Actor *player = NULL;
    float32_t mapw = 0.0f;
    float32_t maph = 0.0f;

    player = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "player", NULL);
    FAIL_ZERO_RETURN(errctx, player, AKGL_ERR_REGISTRY, "No actor named \"player\"");

    mapw = (float32_t)(akgl_gamemap->width * akgl_gamemap->tilewidth);
    maph = (float32_t)(akgl_gamemap->height * akgl_gamemap->tileheight);

    akgl_camera->x = (player->x + 16.0f) - (akgl_camera->w / 2.0f);
    akgl_camera->y = (player->y + 16.0f) - (akgl_camera->h / 2.0f);

    if ( akgl_camera->x < 0.0f ) {
	akgl_camera->x = 0.0f;
    }
    if ( akgl_camera->y < 0.0f ) {
	akgl_camera->y = 0.0f;
    }
    if ( akgl_camera->x > (mapw - akgl_camera->w) ) {
	akgl_camera->x = mapw - akgl_camera->w;
    }
    if ( akgl_camera->y > (maph - akgl_camera->h) ) {
	akgl_camera->y = maph - akgl_camera->h;
    }

    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *jrpg_cmhf_talk(akgl_Actor *obj, SDL_Event *event)
{
    PREPARE_ERROR(errctx);
    akgl_Actor *npc = NULL;
    size_t i = 0;
    float32_t dx = 0.0f;
    float32_t dy = 0.0f;

    FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "obj");
    FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "event");

    if ( jrpg_textbox_showing() ) {
	jrpg_textbox_close();
	SUCCEED_RETURN(errctx);
    }

    for ( i = 0; i < TOWNSFOLK_COUNT; i++ ) {
	npc = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, TOWNSFOLK[i].actor, NULL);
	if ( npc == NULL ) {
	    continue;
	}
	dx = npc->x - obj->x;
	dy = npc->y - obj->y;
	if ( sqrtf((dx * dx) + (dy * dy)) > TALK_RANGE ) {
	    continue;
	}
	PASS(errctx, jrpg_textbox_open(TOWNSFOLK[i].line));
	SUCCEED_RETURN(errctx);
    }

    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *jrpg_cmhf_ignore(akgl_Actor *obj, SDL_Event *event)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "obj");
    FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "event");
    SUCCEED_RETURN(errctx);
}
