/**
 * MIT License
 *
 * Copyright (c) 2024 Michael H. Mackus
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *
 * roguelike.h
 *
 *
 * A single header library for tile-based games. Most features have a
 * "rl_*_create" and a "rl_*_destroy" function. The create function allocates
 * memory and returns a pointer that is assumed to be freed with rl_*_destroy.
 * You can avoid using malloc & free by defining RL_MALLOC (and optionally
 * RL_CALLOC and RL_REALLOC) and RL_FREE, or simply manage the memory yourself.
 *
 * Make sure to define RL_IMPLEMENTATION once and only once before including
 * "roguelike.h" to compile the library.
 *
 * The primary feature of this library deals with the tile-based maps and map
 * generation. The functions are prefixed with rl_map and rl_mapgen.
 *
 * To generate a map, create the map via rl_map_create then call the function
 * with the algorithm you wish to use for mapgen. For example:
 *
 *   RL_Map *map = rl_map_create(80, 25);
 *   if (rl_mapgen_bsp(map, RL_MAPGEN_BSP_DEFAULTS) != RL_OK) {
 *     printf("Error occurred during mapgen!\n");
 *   }
 *   ....
 *   rl_map_destroy(map); // frees the map pointer & internal data
 *
 * The rl_bsp methods correspond to a BSP graph containing data for rectangles.
 * Note that the rl_bsp_split function does allocate memory for the split and
 * assigns the new left & right nodes to the BSP tree (this data is freed with
 * rl_bsp_destroy).
 *
 * There is also functionality for a simple min heap (or priority queue).
 * These functions are prefixed with rl_heap. To use these you need to create
 * a heap with rl_heap_create then insert items with rl_heap_insert. The heap
 * does not free or allocate memory for items you insert into the heap.
 *
 *  RL_Heap *q = rl_heap_create(1, NULL); // NULL comparison function acts as dynamic array
 *  int val = 5;
 *  rl_heap_insert(q, &val);
 *  ....
 *  int r;
 *  while ((r = rl_heap_pop(eq))) { ... }
 *  rl_heap_destroy(q);
 *
 * There is also a set of FOV functions - these functions use a simple
 * shadowcasting algorithm to implement FOV. Create the RL_FOV struct with
 * rl_fov_create (making sure to free it with rl_fov_destroy), and each time
 * you want to "update" the FOV you should call "rl_fov_calculate" or
 * "rl_fov_calculate_ex".
 *
 *  RL_FOV *fov = rl_fov_create(80, 25);
 *  for (;;) { // gameloop
 *      rl_fov_calculate(fov, map, player_x, player_y, 8); // last arg is FOV radius
 *      ... // draw map, handle input, etc.
 *  }
 *  rl_fov_destroy(fov);
 *
 * There is also a set of pathfinding functions - these functions primarily
 * create and manage Dijkstra graphs for pathfinding. These functions are
 * prefixed with rl_path, rl_dijkstra, and rl_graph. Paths should be "walked"
 * with "rl_path_walk" which frees each part of the path passed, returning the
 * next part of the path; or you can alternatively call "rl_path_destroy".
 *
 *  RL_Path *path = rl_path_create(map, RL_XY(0,0), RL_XY(20,20), rl_distance_euclidian, rl_map_is_passable);
 *  while ((path = rl_path_walk(path))) { ...  } // frees the path
 *
 * Dijkstra graphs can be created & scored with rl_dijkstra_create or manually
 * scored via rl_dijkstra_score* functions. After the graph is scored the
 * graph is can be walked by finding a "start" node in the graph, and
 * recursively walking the graph by choosing the lowest scored neighbor. If a
 * RL_GraphNode has a score of FLT_MAX it has not been scored.
 *
 *  // Typically you provide destination for the initial Dijkstra graph
 *  RL_Graph *graph = rl_dijkstra_create(map, dest, rl_distance_manhattan, rl_map_is_passable);
 *  // Then find start point in graph
 *  RL_GraphNode *node = rl_graph_node(graph, start);
 *  // Then, you "roll downhill" from the start point
 *  if (node != NULL) {
 *    RL_GraphNode *lowest_neighbor = rl_graph_node_lowest_neighbor(node);
 *    // The next point in the path is lowest_neighbor->point
 *    if (lowest_neighbor) move_player(lowest_neighbor->point);
 *    ...
 *  }
 *  rl_graph_destroy(graph);
 *
 *
 * Preprocessor definitions (define these before including roguelike.h to customize internals):
 *
 *
 *  RL_IMPLEMENTATION                 Define this to compile the library - should only be defined once in one file
 *  RL_MAX_NEIGHBOR_COUNT             Maximum neighbor count for Dijkstra graphs (defaults to 8). Note this is needed in the function definitions - if you override this, you'll have to define it everywhere you include roguelike.h (just make a wrapper).
 *  RL_FOV_SYMMETRIC                  Set this to 0 to disable symmetric FOV (defaults to 1)
 *  RL_MAX_RECURSION                  Maximum recursion (defaults to 100). This is used in FOV to limit recursion when fov_radius is large or -1 (unlimited).
 *  RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC  Set this to 0 to disable randomizing room locations within bsp (used in rl_mapgen_bsp - defaults to 1)
 *  RL_ENABLE_PATHFINDING             Set this to 0 to disable pathfinding functionality (defaults to 1)
 *  RL_ENABLE_FOV                     Set this to 0 to disable field of view functionality (defaults to 1)
 *  RL_ENABLE_FILE                    Set this to 0 to disable save & load helper functions.
 *  RL_IS_PASSABLE                    Passable tile logic. Macro function - first argument to the macro is the tile, second is x, third is y.
 *  RL_IS_OPAQUE                      Opaque tile logic. Macro function - first argument to the macro is the tile, second is x, third is y.
 *  RL_IS_WALL_TILE                   Wall tile logic, for checking if this tile can connect to other walls. Macro function - first argument to the macro is the tile, second is x, third is y.
 *  RL_PASSABLE_F                     Set this to your default passable function (defaults to rl_map_is_passable).
 *  RL_OPAQUE_F                       Set this to your default opaque function (defaults to rl_map_is_opaque).
 *  RL_WALL_F                         Set this to your default is_wall function (defaults to rl_map_is_wall).
 *  RL_FOV_DISTANCE_F                 Set this to your default FOV distance function (defaults to rl_distance_euclidian).
 *  RL_RNG_F                          Set this to your default RNG generation function (defaults to rl_rng_generate).
 *  RL_ASSERT                         Define this to override the assert function used by the library (defaults to "assert")
 *  RL_MALLOC                         Define this to override the malloc function used by the library (defaults to "malloc")
 *  RL_CALLOC                         Define this to override the calloc function used by the library (defaults to "calloc")
 *  RL_REALLOC                        Define this to override the realloc function used by the library, used in rl_heap_* (defaults to "realloc")
 *  RL_FREE                           Define this to override the free function used by the library (defaults to "free")
 */

#ifdef __cplusplus
extern "C" {
#endif

#ifndef RL_ROGUELIKE_H
#define RL_ROGUELIKE_H

#include <stddef.h>

/* This is a helper since MSVC & c89 don't support compound literals */
#ifndef RL_CLITERAL
#if _MSVC_LANG || __cplusplus
#define RL_CLITERAL(type) type
#elif __STDC_VERSION__ < 199409L
#define RL_CLITERAL(type)
#else
#define RL_CLITERAL(type) (type)
#endif
#endif

/* Bool type in c89 */
#if __STDC_VERSION__ < 199409L && !__cplusplus
typedef int bool;
#define true 1
#define false 0
#else
#include <stdbool.h>
#endif

/**
 * Generic structs for library.
 */

/* each tile is the size of 1 byte, so it can be casted back & forth from char <-> RL_Tile */
typedef unsigned char RL_Byte;

/* Generic dungeon map structure, supporting hex & square 2d maps, along with the associated tile enum. */
typedef enum {
    RL_TileRock = ' ',
    RL_TileRoom = '.',
    RL_TileCorridor = '#',
    RL_TileDoor = '+',
    RL_TileDoorOpen = '='
} RL_Tile;
typedef struct RL_Map {
    unsigned int width;
    unsigned int height;
    RL_Byte *tiles; /* a sequential array of RL_Tiles, stride for each row equals the map width. */
} RL_Map;

/* BSP tree */
typedef struct RL_BSP {
    unsigned int width;
    unsigned int height;
    unsigned int x;
    unsigned int y;
    struct RL_BSP *parent;
    struct RL_BSP *left;  /* left child */
    struct RL_BSP *right; /* right child */
} RL_BSP;

/* BSP split direction */
typedef enum {
    RL_SplitHorizontally, /* split the BSP node on the x axis (splits width) */
    RL_SplitVertically   /* split the BSP node on the y axis (splits height) */
} RL_SplitDirection;

/**
 * Random map generation
 */

/* Creates an empty map. Make sure to call rl_map_destroy to clear memory. */
RL_Map *rl_map_create(unsigned int width, unsigned int height);

/* Frees the map & internal memory. */
void rl_map_destroy(RL_Map *map);

/* Enum representing the type of corridor connection algorithm. RL_ConnectRandomly is the default and results in the
 * most interesting & aesthetic maps. */
typedef enum {
    RL_ConnectNone = 0,       /* don't connect corridors */
    RL_ConnectRandomly,       /* connect corridors to random leaf nodes (requires RL_ENABLE_PATHFINDING, by default this is on) */
    RL_ConnectBSP,            /* connect corridors by traversing the BSP graph (faster than above but less circular/interesting maps, requires RL_ENABLE_PATHFINDING) */
    RL_ConnectSimple          /* connect corridors by traversing the BSP graph without Dijkstra pathfinding (fastest) */
} RL_MapgenCorridorConnection;

/* The config for BSP map generation - note that the dimensions *include* the walls on both sides, so the min room width
 * & height the library accepts is 3. */
typedef struct {
    unsigned int room_min_width;
    unsigned int room_max_width;
    unsigned int room_min_height;
    unsigned int room_max_height;
    unsigned int room_padding;
    RL_MapgenCorridorConnection draw_corridors; /* type of corridor connection algorithm to use */
    bool draw_doors; /* whether to draw doors while connecting corridors */
    int max_splits; /* max times to split BSP - set lower for less rooms */
} RL_MapgenConfigBSP;

/* Provide some defaults for mapgen. */
#define RL_MAPGEN_BSP_DEFAULTS RL_CLITERAL(RL_MapgenConfigBSP) { \
    /*.room_min_width =*/      4, \
    /*.room_max_width =*/      6, \
    /*.room_min_height =*/     4, \
    /*.room_max_height =*/     6, \
    /*.room_padding =*/        1, \
    /*.draw_corridors =*/      RL_ConnectRandomly, \
    /*.draw_doors =*/          true, \
    /*.max_splits =*/          100 \
}

typedef enum {
    RL_OK = 0,
    RL_ErrorMemory,
    RL_ErrorNullParameter,
    RL_ErrorMapgenInvalidConfig
} RL_Status;

/* Generate map with recursive BSP split algorithm. This fills the map tiles with RL_TileRock before generation. */
RL_Status rl_mapgen_bsp(RL_Map *map, RL_MapgenConfigBSP config);

/* Generates map with recursive BSP split algorithm. This splits the BSP pointer passed, and uses the BSP to constrain
 * the dimensions of the map generation.
 *
 * This allocates memory for the BSP children - make sure to use rl_bsp_destroy or free them yourself. Note that this
 * does not set the tiles to RL_TileRock before generation. This way you can have separate regions of the map with
 * different mapgen algorithms. */
RL_Status rl_mapgen_bsp_ex(RL_Map *map, RL_BSP *bsp, const RL_MapgenConfigBSP *config);

/* The config for BSP map generation - note that the dimensions *include* the walls on both sides, so the min room width
 * & height the library accepts is 3. */
typedef struct {
    unsigned int chance_cell_initialized; /* chance (from 1-100) a cell is initialized with rock */
    unsigned int birth_threshold;         /* threshold of neighbors for a cell to be born */
    unsigned int survival_threshold;      /* threshold of neighbors for a cell to die from overpopulation */
    unsigned int max_iterations;          /* recursion limit */
    bool draw_corridors;                  /* after generation, whether to randomly draw corridors to unconnected space
                                           * note - you still need cull_unconnected if you want a fully connected map
                                           *
                                           * requires RL_ENABLE_PATHFINDING */
    bool cull_unconnected;                /* after generation, whether to remove unconnected space from the larger map - requires RL_ENABLE_PATHFINDING */
    bool fill_border;                     /* after generation, whether to fill the border with rock to ensure enclosed map*/
} RL_MapgenConfigAutomata;

/* Provide some defaults for automata mapgen. */
#define RL_MAPGEN_AUTOMATA_DEFAULTS RL_CLITERAL(RL_MapgenConfigAutomata) { \
    /*.chance_cell_initialized =*/  45, \
    /*.birth_threshold =*/          5, \
    /*.survival_threshold =*/       4, \
    /*.max_iterations =*/           3, \
    /*.draw_corridors = */          true, \
    /*.cull_unconnected =*/         true, \
    /*.fill_border =*/              true \
}

/* Generate map with cellular automata. This clears out the previous tiles before generation. */
RL_Status rl_mapgen_automata(RL_Map *map, RL_MapgenConfigAutomata config);

/* Same as above function, but constrains generation according to passed dimensions. */
RL_Status rl_mapgen_automata_ex(RL_Map *map, unsigned int x, unsigned int y, unsigned int width, unsigned int height,  const RL_MapgenConfigAutomata *config);

/* Generate map with a random maze (via simplistic BFS). Tiles are carved with RL_TileCorridor. Fully connected. */
RL_Status rl_mapgen_maze(RL_Map *map);

/* Generate map with a random maze (via simplistic BFS). Tiles are carved with RL_TileCorridor. Fully connected. */
RL_Status rl_mapgen_maze_ex(RL_Map *map, unsigned int x, unsigned int y, unsigned int width, unsigned int height);

/* Connect map via corridors using the supplied BSP graph. */
RL_Status rl_mapgen_connect_corridors(RL_Map *map, RL_BSP *root, bool draw_doors, RL_MapgenCorridorConnection connection_algorithm);

/**
 * Generic map helper functions.
 */

/* Verifies a coordinates is within bounds of map. */
bool rl_map_in_bounds(const RL_Map *map, unsigned int x, unsigned int y);

/* Checks if a tile is passable. */
bool rl_map_is_passable(const RL_Map *map, unsigned int x, unsigned int y);

/* Checks if a tile is opaque (for FOV calculations). */
bool rl_map_is_opaque(const RL_Map *map, unsigned int x, unsigned int y);

/* Get tile at point */
RL_Byte *rl_map_tile(const RL_Map *map, unsigned int x, unsigned int y);

/* Returns 1 if tile at point matches given parameter. */
bool rl_map_tile_is(const RL_Map *map, unsigned int x, unsigned int y, RL_Byte tile);

/* Type of wall on the map - idea is they can be bitmasked together (e.g. for corners). See rl_map_wall and other
 * related functions. */
typedef enum {
    RL_WallToWest  = 1,
    RL_WallToEast  = 1 << 1,
    RL_WallToNorth = 1 << 2,
    RL_WallToSouth = 1 << 3,
    RL_WallOther   = 1 << 7 /* e.g. a wall that has no connecting walls */
} RL_Wall;

/* A tile is considered a wall if it is touching a passable tile.
 *
 * Returns a bitmask of the RL_Wall enum. For example, a wall with a wall tile to the south, west, and east would have a
 * bitmask of 0b1011. */
RL_Byte rl_map_wall(const RL_Map *map, unsigned int x, unsigned int y);

/* Is the tile a wall tile? */
bool rl_map_is_wall(const RL_Map *map, unsigned int x, unsigned int y);

/* Is the wall a corner? */
bool rl_map_is_corner_wall(const RL_Map *map, unsigned int x, unsigned int y);

/* Is this a wall that is touching a room tile? */
bool rl_map_is_room_wall(const RL_Map *map, unsigned int x, unsigned int y);

/* A wall that is touching a room tile (e.g. to display it lit). */
RL_Byte rl_map_room_wall(const RL_Map *map, unsigned int x, unsigned int y);

/**
 * Simple priority queue implementation
 */

typedef struct {
    void **heap;
    int cap;
    int len;
    int (*comparison_f)(const void *heap_item_a, const void *heap_item_b);
} RL_Heap;

/* Allocates memory for the heap. Make sure to call rl_heap_destroy after you are done.
 *
 * capacity - initial capacity for the heap
 * comparison_f - A comparison function that returns 1 if heap_item_a should be
 *  popped from the queue before heap_item_b. If NULL the heap will still work
 *  but order will be undefined. */
RL_Heap *rl_heap_create(int capacity, int (*comparison_f)(const void *heap_item_a, const void *heap_item_b));

/* Frees the heap & internal memory. */
void rl_heap_destroy(RL_Heap *h);

/* Return the length of the heap items */
int rl_heap_length(const RL_Heap *h);

/* Insert item into the heap. This will resize the heap if necessary. */
bool rl_heap_insert(RL_Heap *h, void *item);

/* Returns & removes an item from the queue. */
void *rl_heap_pop(RL_Heap *h);

/* Peek at the first item in the queue. This does not remove the item from the queue. */
void *rl_heap_peek(RL_Heap *h);

/**
 * BSP Manipulation
 */

/* Params width & height must be positive. Make sure to free with rl_bsp_destroy. */
RL_BSP *rl_bsp_create(unsigned int width, unsigned int height);

/* Frees the BSP root & all children */
void rl_bsp_destroy(RL_BSP *root);

/* Split the BSP by direction - this creates the left & right leaf and */
/* populates them in the BSP node. Position must be positive and within */
/* the BSP root node. Also node->left & node->right must be NULL */
void rl_bsp_split(RL_BSP *node, unsigned int position, RL_SplitDirection direction);

/* Recursively split the BSP. Used for map generation. */
/* */
/* Returns true if the BSP was able to split at least once */
RL_Status rl_bsp_recursive_split(RL_BSP *root, unsigned int min_width, unsigned int min_height, unsigned int max_recursion);

/* Returns 1 if the node is a leaf node. */
bool rl_bsp_is_leaf(const RL_BSP *node);

/* Return sibling node. Returns NULL if there is no parent (i.e. for the root */
/* node). */
RL_BSP *rl_bsp_sibling(const RL_BSP *node);

/* Returns amount of leaves in tree. */
size_t rl_bsp_leaf_count(const RL_BSP *root);

/* Return the next leaf node to the right if it exists. */
RL_BSP *rl_bsp_next_leaf(const RL_BSP *node);

/* Returns a random leaf node beneath root */
RL_BSP* rl_bsp_random_leaf(const RL_BSP *root);

/**
 * Pathfinding - disable with #define RL_ENABLE_PATHFINDING 0
 */

/* A point on the map used for pathfinding. The points are a float type for flexibility since pathfinding works for maps */
/* of all data types. */
typedef struct RL_Point {
    float x, y;
} RL_Point;

/* Macro to easily create a RL_Point (compound literals only available in C99, which MSVC doesn't support). */
#define RL_XY(x, y) RL_CLITERAL(RL_Point) { (float)(x), (float)(y) }

/* Max neighbors for a pathfinding node. */
#ifndef RL_MAX_NEIGHBOR_COUNT
#define RL_MAX_NEIGHBOR_COUNT 8
#endif

/* Represents a graph of pathfinding nodes that has been scored for pathfinding (e.g. with the Dijkstra algorithm). */
/* TODO store weights on graph nodes ? */
typedef struct RL_GraphNode {
    float score; /* will be FLT_MAX for an unreachable/unscored node in the Dijkstra algorithm */
    RL_Point point;
    size_t neighbors_length;
    struct RL_GraphNode *neighbors[RL_MAX_NEIGHBOR_COUNT];
} RL_GraphNode;
typedef struct RL_Graph {
    size_t length; /* length of nodes */
    RL_GraphNode *nodes; /* array of nodes - length will be the size of the map.width * map.height */
} RL_Graph;

/* A path is a linked list of paths. You can "walk" a path using rl_path_walk which will simultaneously free the
 * previous path. */
typedef struct RL_Path {
    RL_Point point;
    struct RL_Path *next;
} RL_Path;

/* Useful distance functions for pathfinding. */
float rl_distance_manhattan(RL_Point node, RL_Point end);
float rl_distance_euclidian(RL_Point node, RL_Point end);
float rl_distance_chebyshev(RL_Point node, RL_Point end);

/* Custom distance function for pathfinding - calculates distance between map nodes */
typedef float (*RL_DistanceFun)(RL_Point from, RL_Point to);

/* Custom passable function for pathfinding. Return 0 to prevent neighbor from being included in graph. */
typedef bool (*RL_PassableFun)(void *context, unsigned int x, unsigned int y);

/* Custom score function for pathfinding - most users won't need this, but it gives flexibility in weighting the
 * Dijkstra graph. Note that Dijkstra expects you to add the current node's score to the newly calculated score. */
typedef float (*RL_ScoreFun)(const RL_GraphNode *current, const RL_GraphNode *neighbor, void *context);

/* Generates a line starting at from ending at to. Each path in the line will be incremented by step. */
RL_Path *rl_line_create(RL_Point from, RL_Point to, float step);

/* Find a path between start and end via Dijkstra algorithm. Make sure to call rl_path_destroy when done with path.
 * Pass NULL to distance_f to use rough approximation for euclidian. */
RL_Path *rl_path_create(const RL_Map *map, RL_Point start, RL_Point end, RL_DistanceFun distance_f);

/* Find a path between start and end via the scored Dijkstra graph. Make sure to call rl_path_destroy when done with path (or
 * use rl_path_walk). */
RL_Path *rl_path_create_from_graph(const RL_Graph *graph, RL_Point start);

/* Convenience function to "walk" the path. This will return the next path, freeing the current path. You do not need to
 * call rl_path_destroy if you walk the full path. */
RL_Path *rl_path_walk(RL_Path *path);

/* Frees the path & all linked nodes. */
void rl_path_destroy(RL_Path *path);

/* Dijkstra pathfinding algorithm. Pass NULL to distance_f to use rough approximation for euclidian.
 *
 * You can use Dijkstra maps for pathfinding, simple AI, and much more. For example, by setting the player point to
 * "start" then you can pick the highest scored tile in the map and set that as the new "start" point. As with all
 * Dijkstra maps, you just walk the map by picking the lowest scored neighbor. This is a simplistic AI resembling a
 * wounded NPC fleeing from the player.
 *
 * Make sure to destroy the resulting RL_Graph with rl_graph_destroy. */
RL_Graph *rl_dijkstra_create(const RL_Map *map,
                            RL_Point start,
                            RL_DistanceFun distance_f);

/* Dijkstra pathfinding algorithm. Uses RL_Graph so that your code doesn't need to rely on RL_Map. Each node's
 * distance should equal FLT_MAX in the resulting graph if it is impassable. */
void rl_dijkstra_score(RL_Graph *graph, RL_Point start, RL_DistanceFun distance_f);

/* Dijkstra pathfinding algorithm for advanced use cases such as weighting certain tiles higher than others. Uses
 * RL_Graph so that your code doesn't need to rely on RL_Map. Each node's distance should equal FLT_MAX in the resulting
 * graph if it is impassable. Most users should just use rl_dijkstra_score - only use this if you have a specific need. */
void rl_dijkstra_score_ex(RL_Graph *graph, RL_Point start, RL_ScoreFun score_f, void *score_context);

/* Returns a the largest connected area (of passable tiles) on the map. Make sure to destroy the graph with
 * rl_graph_destroy after you are done. */
RL_Graph *rl_graph_floodfill_largest_area(const RL_Map *map);

/* Create an unscored graph based on the 2d map. Make sure to call rl_graph_destroy when finished. */
RL_Graph *rl_graph_create(const RL_Map *map);

/* Create an unscored graph based on the 2d map. Make sure to call rl_graph_destroy when finished. */
RL_Graph *rl_graph_create_ex(const RL_Map *map, void *context, RL_PassableFun passable_f, bool allow_diagonal_neighbors);

/* Reset scores of Dijkstra map */
void rl_graph_reset(RL_Graph *graph);

/* Add two graphs together, adding their scores */
/* Note that this assumes the graph lengths are identical */
void rl_graph_add(RL_Graph *graph, const RL_Graph *graph_b);

/* Multiply the scores of a graph by a coefficient (e.g. to weight a graph) */
void rl_graph_weight(RL_Graph *graph, float coefficient);

/* Frees the graph & internal memory. */
void rl_graph_destroy(RL_Graph *graph);

/* Checks if coordinate is scored in graph (e.g. its score is less than FLT_MAX). */
bool rl_graph_is_scored(const RL_Graph *graph, RL_Point point);

/* Returns the node of a point within a graph if it exists. */
RL_GraphNode *rl_graph_node(const RL_Graph *graph, RL_Point point);

/* Returns the lowest scored neighbor within a graph if it exists - returns NULL if the lowest scored neighbor is scored
 * with FLT_MAX (meaning it is unscored). */
RL_GraphNode *rl_graph_node_lowest_neighbor(const RL_GraphNode *node);

/* Sort node neighbors based on score - lowest score will be at node->neighbors[0] */
void rl_graph_node_sort_neighbors(RL_GraphNode *node);

/**
 * FOV - disable with #define RL_ENABLE_FOV 0
 */

/* Structure containing information for the FOV algorithm, along with the associated visibility enum. */
typedef enum {
    RL_TileCannotSee = 0,
    RL_TileVisible,
    RL_TileSeen
} RL_TileVisibility;
typedef struct {
    unsigned int width;
    unsigned int height;
    RL_Byte *visibility; /* a sequential array of RL_Visibility, stride for each row = the map width */
} RL_FOV;

/* Creates empty FOV and fills it with opaque tiles. Make sure to call rl_fov_destroy to clear memory. */
RL_FOV *rl_fov_create(unsigned int width, unsigned int height);

/* Frees the FOV & internal memory. */
void rl_fov_destroy(RL_FOV *fov);

/* Function to determine if a tile is within the range of the FOV. Returns true if point is in range. */
typedef bool (*RL_IsInRangeFun)(unsigned int x, unsigned int y, void *context);
/* Function to determine if a tile is considered Opaque for FOV calculation. Make sure you do bounds checking that the
 * point is within your map. Returns true if point is considered "opaque" (i.e. unable to see through). */
typedef bool (*RL_IsOpaqueFun)(unsigned int x, unsigned int y, void *context);
/* Function to mark a tile as visible within the FOV. Make sure you do bounds checking that the point is within your map. */
typedef void (*RL_MarkAsVisibleFun)(unsigned int x, unsigned int y, void *context);

/* Calculate FOV using simple shadowcasting algorithm. Set fov_radius to a negative value to have unlimited FOV (note
 * this is limited by RL_MAX_RECURSION).
 *
 * Note that this sets previously visible tiles to RL_TileSeen. */
void rl_fov_calculate(RL_FOV *fov, const RL_Map *map, unsigned int x, unsigned int y, int fov_radius);

/* Calculate FOV using simple shadowcasting algorithm. Set fov_radius to a negative value to have unlimited FOV (note
 * this is limited by RL_MAX_RECURSION).
 *
 * Generic version of above function. */
void rl_fov_calculate_ex(void *context, unsigned int x, unsigned int y, RL_IsInRangeFun in_range_f, RL_IsOpaqueFun opaque_f, RL_MarkAsVisibleFun mark_visible_f);

/* Checks if a point is visible within FOV. Make sure to call rl_fov_calculate first. */
bool rl_fov_is_visible(const RL_FOV *map, unsigned int x, unsigned int y);

/* Checks if a point has been seen within FOV. Make sure to call rl_fov_calculate first. */
bool rl_fov_is_seen(const RL_FOV *map, unsigned int x, unsigned int y);

/**
 * Random number generation
 */

/* Default implementation of RNG using standard library. */
unsigned int rl_rng_generate(unsigned int min, unsigned int max);

/**
 * Saving & Loading helper functions - to use these make sure to open the file beforehand in binary mode.
 *
 * The file is a FILE pointer (void* so we don't have to depend on stdio).
 */

bool rl_file_save_map(const RL_Map *data, void *file);
bool rl_file_load_map(RL_Map **data, void *file);
bool rl_file_save_fov(const RL_FOV *data, void *file);
bool rl_file_load_fov(RL_FOV **data, void *file);
#endif /* RL_ROGUELIKE_H */

#ifdef RL_IMPLEMENTATION

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>

#ifndef RL_FOV_SYMMETRIC
#define RL_FOV_SYMMETRIC 1
#endif

#ifndef RL_MAX_RECURSION
#define RL_MAX_RECURSION 100
#endif

/* define this to 0 to put the rooms in the middle of the BSP leaf during dungeon generation */
#ifndef RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC
#define RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC 1
#endif

/* define to 0 to disable pathfinding */
#ifndef RL_ENABLE_PATHFINDING
#define RL_ENABLE_PATHFINDING 1
#endif

/* define to 0 to disable FOV */
#ifndef RL_ENABLE_FOV
#define RL_ENABLE_FOV 1
#endif

/* define to 0 to disable save & load */
#ifndef RL_ENABLE_FILE
#define RL_ENABLE_FILE 1
#endif

/* convenience macro for custom passable tile logic (for mapgen & pathfinding) */
#ifndef RL_IS_PASSABLE
#define RL_IS_PASSABLE(t, x, y) (t == RL_TileRoom || t == RL_TileCorridor || t == RL_TileDoor || t == RL_TileDoorOpen)
#endif
/* convenience macro for custom opaque tile logic (for FOV) */
#ifndef RL_IS_OPAQUE
#define RL_IS_OPAQUE(t, x, y) (t == RL_TileDoor || !RL_IS_PASSABLE(t, x, y))
#endif
/* convenience macro for custom wall tile logic (for connections) */
#ifndef RL_IS_WALL_TILE
#define RL_IS_WALL_TILE(t, x, y) (!RL_IS_PASSABLE(t,x,y) || t == RL_TileDoor || t == RL_TileDoorOpen)
#endif

#ifndef RL_PASSABLE_F
#define RL_PASSABLE_F rl_map_is_passable
#endif
#ifndef RL_OPAQUE_F
#define RL_OPAQUE_F rl_map_is_opaque
#endif
#ifndef RL_WALL_F
#define RL_WALL_F rl_map_is_wall
#endif
#ifndef RL_FOV_DISTANCE_F
#define RL_FOV_DISTANCE_F rl_distance_euclidian
#endif
#ifndef RL_RNG_F
#define RL_RNG_F rl_rng_generate
#endif
#ifndef RL_ASSERT
#include <assert.h>
#define RL_ASSERT(expr)		(assert(expr));
#endif
#ifndef RL_MALLOC
#define RL_MALLOC malloc
#endif
#ifndef RL_CALLOC
#define RL_CALLOC calloc
#endif
#ifndef RL_REALLOC
#define RL_REALLOC realloc
#endif
#ifndef RL_FREE
#define RL_FREE free
#endif

#define RL_UNUSED(x) (void)x

#if RL_ENABLE_PATHFINDING
#include <float.h>
#include <math.h>
#endif

RL_Map *rl_map_create(unsigned int width, unsigned int height)
{
    RL_Map *map;
    unsigned char *memory;
    RL_ASSERT(width*height < UINT_MAX);
    RL_ASSERT(width > 0 && height > 0);
    map = NULL;
    /* allocate all the memory we need at once */
    memory = (unsigned char*) RL_MALLOC(sizeof(*map) + sizeof(*map->tiles)*width*height);
    RL_ASSERT(memory);
    if (memory == NULL) return NULL;
    map = (RL_Map*) memory;
    RL_ASSERT(map);
    map->width = width;
    map->height = height;
    map->tiles = (RL_Byte*) (memory + sizeof(*map));
    RL_ASSERT(map->tiles);
    memset(map->tiles, RL_TileRock, sizeof(*map->tiles)*map->width*map->height);

    return map;
}

void rl_map_destroy(RL_Map *map)
{
    if (map) {
        RL_FREE(map);
    }
}

bool rl_map_in_bounds(const RL_Map *map, unsigned int x, unsigned int y)
{
    return x < map->width && y < map->height;
}

bool rl_map_is_passable(const RL_Map *map, unsigned int x, unsigned int y)
{
    if (rl_map_in_bounds(map, x, y)) {
        return RL_IS_PASSABLE(map->tiles[y * map->width + x], x, y);
    }

    return 0;
}

bool rl_map_is_opaque(const RL_Map *map, unsigned int x, unsigned int y)
{
    if (!rl_map_in_bounds(map, x, y)) {
        return true;
    }

    return RL_IS_OPAQUE(map->tiles[y * map->width + x], x, y);
}

RL_Byte *rl_map_tile(const RL_Map *map, unsigned int x, unsigned int y)
{
    if (rl_map_in_bounds(map, x, y)) {
        return &map->tiles[x + y*map->width];
    }

    return NULL;
}

bool rl_map_is_wall(const RL_Map *map, unsigned int x, unsigned int y)
{
    if (!rl_map_in_bounds(map, x, y))
        return 0;
    if (RL_IS_WALL_TILE(map->tiles[x + y*map->width], x, y)) {
        return RL_PASSABLE_F(map, x, y + 1) ||
               RL_PASSABLE_F(map, x, y - 1) ||
               RL_PASSABLE_F(map, x + 1, y) ||
               RL_PASSABLE_F(map, x - 1, y) ||
               RL_PASSABLE_F(map, x + 1, y - 1) ||
               RL_PASSABLE_F(map, x - 1, y - 1) ||
               RL_PASSABLE_F(map, x + 1, y + 1) ||
               RL_PASSABLE_F(map, x - 1, y + 1);
    }

    return 0;
}

static bool rl_map_wall_connects_ew(const RL_Map *map, unsigned int x, unsigned int y)
{
    return (rl_map_in_bounds(map, x, y - 1) && !RL_IS_WALL_TILE(map->tiles[x+(y-1)*map->width], x, y - 1)) ||
           (rl_map_in_bounds(map, x, y + 1) && !RL_IS_WALL_TILE(map->tiles[x+(y+1)*map->width], x, y + 1));
}
static bool rl_map_wall_connects_ns(const RL_Map *map, unsigned int x, unsigned int y)
{
    return (rl_map_in_bounds(map, x - 1, y) && !RL_IS_WALL_TILE(map->tiles[(x-1)+y*map->width], x - 1, y)) ||
           (rl_map_in_bounds(map, x + 1, y) && !RL_IS_WALL_TILE(map->tiles[(x+1)+y*map->width], x + 1, y));
}

/* checks if target tile is connecting from source (e.g. they can reach it) */
RL_Byte rl_map_wall(const RL_Map *map, unsigned int x, unsigned int y)
{
    RL_Byte mask = 0;
    if (!RL_WALL_F(map, x, y))
        return mask;
    if ((rl_map_in_bounds(map, x + 1, y    ) && RL_IS_WALL_TILE(map->tiles[(x+1)+y*map->width], x + 1, y    )) && (rl_map_wall_connects_ew(map, x, y    ) || rl_map_wall_connects_ew(map, x + 1, y)))
        mask |= RL_WallToEast;
    if ((rl_map_in_bounds(map, x - 1, y    ) && RL_IS_WALL_TILE(map->tiles[(x-1)+y*map->width], x - 1, y    )) && (rl_map_wall_connects_ew(map, x, y    ) || rl_map_wall_connects_ew(map, x - 1, y)))
        mask |= RL_WallToWest;
    if ((rl_map_in_bounds(map, x    , y - 1) && RL_IS_WALL_TILE(map->tiles[x+(y-1)*map->width], x    , y - 1)) && (rl_map_wall_connects_ns(map, x, y    ) || rl_map_wall_connects_ns(map, x    , y - 1)))
        mask |= RL_WallToNorth;
    if ((rl_map_in_bounds(map, x    , y + 1) && RL_IS_WALL_TILE(map->tiles[x+(y+1)*map->width], x    , y + 1)) && (rl_map_wall_connects_ns(map, x, y    ) || rl_map_wall_connects_ns(map, x    , y + 1)))
        mask |= RL_WallToSouth;
    return mask ? mask : RL_WallOther;
}

bool rl_map_is_corner_wall(const RL_Map *map, unsigned int x, unsigned int y)
{
    int wall = rl_map_wall(map, x, y);
    if (!wall) return 0;
    return (wall & RL_WallToWest && wall & RL_WallToNorth) ||
           (wall & RL_WallToWest && wall & RL_WallToSouth) ||
           (wall & RL_WallToEast && wall & RL_WallToNorth) ||
           (wall & RL_WallToEast && wall & RL_WallToSouth);
}

bool rl_map_tile_is(const RL_Map *map, unsigned int x, unsigned int y, RL_Byte tile)
{
    if (!rl_map_in_bounds(map, x, y)) return 0;
    return map->tiles[x + y*map->width] == tile;
}

bool rl_map_is_room_wall(const RL_Map *map, unsigned int x, unsigned int y)
{
    if (!RL_WALL_F(map, x, y))
        return 0;

    return rl_map_tile_is(map, x, y + 1,     RL_TileRoom) ||
           rl_map_tile_is(map, x, y - 1,     RL_TileRoom) ||
           rl_map_tile_is(map, x + 1, y,     RL_TileRoom) ||
           rl_map_tile_is(map, x - 1, y,     RL_TileRoom) ||
           rl_map_tile_is(map, x + 1, y - 1, RL_TileRoom) ||
           rl_map_tile_is(map, x - 1, y - 1, RL_TileRoom) ||
           rl_map_tile_is(map, x + 1, y + 1, RL_TileRoom) ||
           rl_map_tile_is(map, x - 1, y + 1, RL_TileRoom);
}

RL_Byte rl_map_room_wall(const RL_Map *map, unsigned int x, unsigned int y)
{
    RL_Byte mask = 0;
    if (!rl_map_is_room_wall(map, x,     y))
        return mask;
    if (rl_map_is_room_wall(map,  x + 1, y))
        mask |= RL_WallToEast;
    if (rl_map_is_room_wall(map,  x - 1, y))
        mask |= RL_WallToWest;
    if (rl_map_is_room_wall(map,  x,     y - 1))
        mask |= RL_WallToNorth;
    if (rl_map_is_room_wall(map,  x,     y + 1))
        mask |= RL_WallToSouth;
    return mask ? mask : RL_WallOther;
}

unsigned int rl_rng_generate(unsigned int min, unsigned int max)
{
    int rnd;

    RL_ASSERT(max >= min);
    RL_ASSERT(max < RAND_MAX);
    RL_ASSERT(max < UINT_MAX);

    if (max < min || max >= RAND_MAX || max >= UINT_MAX)
        return min;
    if (min == max)
        return min;

    rnd = rand();
    if (rnd < 0) rnd *= -1; /* fixes issue on LLVM MOS */

    /* produces more uniformity than using mod */
    return min + rnd / (RAND_MAX / (max - min + 1) + 1);
}

RL_BSP *rl_bsp_create(unsigned int width, unsigned int height)
{
    RL_BSP *bsp;

    RL_ASSERT(width > 0 && height > 0);
    bsp = (RL_BSP*) RL_CALLOC(1, sizeof(*bsp));
    if (bsp == NULL) return NULL;
    bsp->width = width;
    bsp->height = height;

    return bsp;
}
void rl_bsp_destroy(RL_BSP* root)
{
    if (root) {
        if (root->left) {
            rl_bsp_destroy(root->left);
            root->left = NULL;
        }
        if (root->right) {
            rl_bsp_destroy(root->right);
            root->right = NULL;
        }
        RL_FREE(root);
    }
}

void rl_bsp_split(RL_BSP *node, unsigned int position, RL_SplitDirection direction)
{
    RL_BSP *left, *right;

    /* can't split something already split */
    RL_ASSERT(node->left == NULL && node->right == NULL);

    if (node->left || node->right)
        return;

    if (direction == RL_SplitVertically && position >= node->height)
        return;
    if (direction == RL_SplitHorizontally && position >= node->width)
        return;

    left = (RL_BSP*) RL_CALLOC(1, sizeof(RL_BSP));
    if (left == NULL)
        return;
    right = (RL_BSP*) RL_CALLOC(1, sizeof(RL_BSP));
    if (right == NULL) {
        RL_FREE(left);
        return;
    }

    if (direction == RL_SplitVertically) {
        left->width = node->width;
        left->height = position;
        left->x = node->x;
        left->y = node->y;
        right->width = node->width;
        right->height = node->height - position;
        right->x = node->x;
        right->y = node->y + position;
    } else {
        left->width = position;
        left->height = node->height;
        left->x = node->x;
        left->y = node->y;
        right->width = node->width - position;
        right->height = node->height;
        right->x = node->x + position;
        right->y = node->y;
    }

    left->parent = right->parent = node;
    node->left = left;
    node->right = right;
}

RL_Status rl_bsp_recursive_split(RL_BSP *root, unsigned int min_width, unsigned int min_height, unsigned int max_recursion)
{
    unsigned int width, height, split_position;
    RL_SplitDirection dir;
    RL_BSP *left, *right;
    RL_Status ret;

    RL_ASSERT(root);
    RL_ASSERT(min_width > 0 && min_height > 0 && root != NULL);
    RL_ASSERT(min_width <= root->width && min_height <= root->height);

    if (root == NULL)
        return RL_ErrorNullParameter;
    if (max_recursion <= 0)
        return RL_OK;

    width = root->width;
    height = root->height;

    /* determine split dir & split */
    if (RL_RNG_F(0, 1)) {
        if (width < min_width*2)
            dir = RL_SplitVertically;
        else
            dir = RL_SplitHorizontally;
    } else {
        if (height < min_height*2)
            dir = RL_SplitHorizontally;
        else
            dir = RL_SplitVertically;
    }

    if (dir == RL_SplitHorizontally) {
        /* cannot split if current node size is too small - end splitting */
        if (width < min_width*2)
            return RL_OK;
        split_position = width / 2;
    } else {
        /* cannot split if current node size is too small - end splitting */
        if (height < min_height*2)
            return RL_OK;
        split_position = height / 2;
    }

    rl_bsp_split(root, split_position, dir);

    /* continue recursion */
    left = root->left;
    right = root->right;

    if (left == NULL || right == NULL)
        return RL_ErrorMemory;

    ret = rl_bsp_recursive_split(left, min_width, min_height, max_recursion - 1);
    if (ret != RL_OK) {
        RL_FREE(left);
        RL_FREE(right);
        root->left = root->right = NULL;
        return ret;
    }

    ret = rl_bsp_recursive_split(right, min_width, min_height, max_recursion - 1);
    if (ret != RL_OK) {
        RL_FREE(left);
        RL_FREE(right);
        root->left = root->right = NULL;
        return ret;
    }

    return RL_OK;
}

bool rl_bsp_is_leaf(const RL_BSP *node)
{
    if (node == NULL) return 0;
    return (node->left == NULL && node->right == NULL);
}

RL_BSP *rl_bsp_sibling(const RL_BSP *node)
{
    if (node && node->parent) {
        if (node->parent->left == node)
            return node->parent->right;
        if (node->parent->right == node)
            return node->parent->left;

        RL_ASSERT("BSP structure is invalid" && 0); /* BSP structure is invalid */
    }

    return NULL;
}

RL_BSP *rl_bsp_next_node_recursive_down(RL_BSP *node, int depth)
{
    if (node == NULL)
        return NULL;
    if (depth == 0) /* found the node */
        return node;
    if (node->left == NULL)
        return NULL;
    return rl_bsp_next_node_recursive_down(node->left, depth + 1);
}
RL_BSP *rl_bsp_next_node_recursive(RL_BSP *node, int depth)
{
    if (node == NULL || node->parent == NULL)
        return NULL;
    if (node->parent->left == node) /* traverse back down */
        return rl_bsp_next_node_recursive_down(node->parent->right, depth);
    return rl_bsp_next_node_recursive(node->parent, depth - 1);
}
RL_BSP *rl_bsp_next_node(RL_BSP *node)
{
    if (node == NULL || node->parent == NULL)
        return NULL;

    /* LOOP up until we are on the left, then go back down */
    return rl_bsp_next_node_recursive(node, 0);
}

RL_BSP *rl_bsp_next_leaf_recursive_down(RL_BSP *node)
{
    if (node == NULL)
        return NULL;
    if (rl_bsp_is_leaf(node)) /* found the node */
        return node;
    if (node->left == NULL)
        return NULL;
    return rl_bsp_next_leaf_recursive_down(node->left);
}
RL_BSP *rl_bsp_next_leaf_recursive(const RL_BSP *node)
{
    if (node == NULL || node->parent == NULL)
        return NULL;
    if (node->parent->left == node) /* traverse back down */
        return rl_bsp_next_leaf_recursive_down(node->parent->right);
    return rl_bsp_next_leaf_recursive(node->parent);
}
RL_BSP *rl_bsp_next_leaf(const RL_BSP *node)
{
    if (node == NULL || node->parent == NULL)
        return NULL;
    RL_ASSERT(rl_bsp_is_leaf(node));

    /* LOOP up until we are on the left, then go back down */
    return rl_bsp_next_leaf_recursive(node);
}
RL_BSP* rl_bsp_random_leaf(const RL_BSP *root)
{
    const RL_BSP *node;

    if (root == NULL)
        return NULL;

    node = root;
    while (!rl_bsp_is_leaf(node)) {
        if (RL_RNG_F(0, 1)) {
            node = node->left;
        } else {
            node = node->right;
        }
    }

    return (RL_BSP*) node;
}

size_t rl_bsp_leaf_count(const RL_BSP *root)
{
    int count;
    const RL_BSP *node;
    if (root == NULL) return 0;
    RL_ASSERT(root->parent == NULL);
    /* find first leaf */
    node = root;
    while (node->left != NULL) {
        node = node->left;
    }
    /* count leaves */
    count = 1;
    while ((node = rl_bsp_next_leaf(node)) != NULL) {
        count++;
    }
    return count;
}

static void rl_map_bsp_generate_room(RL_Map *map, unsigned int room_width, unsigned int room_height, unsigned int room_x, unsigned int room_y)
{
    unsigned int x, y;
    RL_ASSERT(map && room_width + room_x <= map->width);
    RL_ASSERT(map && room_height + room_y <= map->height);
    if (map == NULL) return;
    for (x = room_x; x < room_x + room_width; ++x) {
        for (y = room_y; y < room_y + room_height; ++y) {
            if (x == room_x || x == room_x + room_width - 1 ||
                    y == room_y || y == room_y + room_height - 1
               ) {
                /* set sides of room to walls */
                map->tiles[y*map->width + x] = RL_TileRock;
            } else {
                map->tiles[y*map->width + x] = RL_TileRoom;
            }
        }
    }
}
static void rl_map_bsp_generate_rooms(RL_BSP *node, RL_Map *map, unsigned int room_min_width, unsigned int room_max_width, unsigned int room_min_height, unsigned int room_max_height, unsigned int room_padding)
{
    RL_ASSERT(map);
    RL_ASSERT(room_min_width < room_max_width);
    RL_ASSERT(room_min_height < room_max_height);
    RL_ASSERT(room_max_width + room_padding*2 < UINT_MAX);
    RL_ASSERT(room_max_height + room_padding*2 < UINT_MAX);
    RL_ASSERT(room_min_width > 2 && room_min_height > 2); /* width of 2 can end up having rooms made of nothing but walls */
    RL_ASSERT(node && room_min_width < node->width);
    RL_ASSERT(node && room_min_height < node->height);
    RL_ASSERT(node && room_max_width <= node->width);
    RL_ASSERT(node && room_max_height <= node->height);
    if (map == NULL) return;
    if (node && node->left) {
        if (rl_bsp_is_leaf(node->left)) {
            unsigned int room_width, room_height, room_x, room_y;
            RL_BSP *leaf = node->left;
            room_width = RL_RNG_F(room_min_width, room_max_width);
            if (room_width + room_padding*2 > leaf->width)
                room_width = leaf->width - room_padding*2;
            room_height = RL_RNG_F(room_min_height, room_max_height);
            if (room_height + room_padding*2 > leaf->height)
                room_height = leaf->height - room_padding*2;
#if(RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC)
            room_x = RL_RNG_F(leaf->x + room_padding, leaf->x + leaf->width - room_width - room_padding);
            room_y = RL_RNG_F(leaf->y + room_padding, leaf->y + leaf->height - room_height - room_padding);
#else
            room_x = leaf->x + leaf->width/2 - room_width/2 - room_padding/2;
            room_y = leaf->y + leaf->height/2 - room_height/2 - room_padding/2;
#endif

            rl_map_bsp_generate_room(map, room_width, room_height, room_x, room_y);
        } else {
            rl_map_bsp_generate_rooms(node->left, map, room_min_width, room_max_width, room_min_height, room_max_height, room_padding);
        }
    }
    if (node && node->right) {
        if (rl_bsp_is_leaf(node->left)) {
            unsigned int room_width, room_height, room_x, room_y;
            RL_BSP *leaf = node->right;
            room_width = RL_RNG_F(room_min_width, room_max_width);
            if (room_width + room_padding*2 > leaf->width)
                room_width = leaf->width - room_padding*2;
            room_height = RL_RNG_F(room_min_height, room_max_height);
            if (room_height + room_padding*2 > leaf->height)
                room_height = leaf->height - room_padding*2;
#if(RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC)
            room_x = RL_RNG_F(leaf->x + room_padding, leaf->x + leaf->width - room_width - room_padding);
            room_y = RL_RNG_F(leaf->y + room_padding, leaf->y + leaf->height - room_height - room_padding);
#else
            room_x = leaf->x + leaf->width/2 - room_width/2 - room_padding/2;
            room_y = leaf->y + leaf->height/2 - room_height/2 - room_padding/2;
#endif

            rl_map_bsp_generate_room(map, room_width, room_height, room_x, room_y);
        } else {
            rl_map_bsp_generate_rooms(node->right, map, room_min_width, room_max_width, room_min_height, room_max_height, room_padding);
        }
    }
}

RL_Status rl_mapgen_bsp(RL_Map *map, RL_MapgenConfigBSP config)
{
    RL_Status ret;
    RL_BSP *bsp;
    RL_ASSERT(map);
    if (map == NULL) return RL_ErrorMemory;
    bsp = rl_bsp_create(map->width, map->height);
    RL_ASSERT(bsp);
    if (bsp == NULL) return RL_ErrorMemory;
    memset(map->tiles, RL_TileRock, sizeof(*map->tiles)*map->width*map->height);
    ret = rl_mapgen_bsp_ex(map, bsp, &config);
    rl_bsp_destroy(bsp);

    return ret;
}

RL_Status rl_mapgen_bsp_ex(RL_Map *map, RL_BSP *root, const RL_MapgenConfigBSP *config)
{
    RL_Status ret;

    RL_ASSERT(map);
    RL_ASSERT(root);
    RL_ASSERT(root->width > 0 && root->height > 0);
    RL_ASSERT(root->x < root->width && root->y < root->height);
    RL_ASSERT(config);
    RL_ASSERT(config->room_min_width > 0 && config->room_max_width >= config->room_min_width && config->room_min_height > 0 && config->room_max_height >= config->room_min_height);
    RL_ASSERT(config->room_max_width <= map->width && config->room_max_height <= map->height);
    RL_ASSERT(config->max_splits > 0);

    if (map == NULL || root == NULL || config == NULL) {
        return RL_ErrorNullParameter;
    }

    ret = rl_bsp_recursive_split(root, config->room_max_width + config->room_padding, config->room_max_height + config->room_padding, config->max_splits);
    if (ret != RL_OK) return ret;
    rl_map_bsp_generate_rooms(root, map, config->room_min_width, config->room_max_width, config->room_min_height, config->room_max_height, config->room_padding);
    ret = rl_mapgen_connect_corridors(map, root, config->draw_doors, config->draw_corridors);
    if (ret != RL_OK) return ret;

    /* if (config->use_secret_passages) { */
        /* TODO connect secret passages */
    /* } */

    return RL_OK;
}

/* find the room tile within BSP */
void rl_bsp_find_room(RL_Map *map, RL_BSP *leaf, unsigned int *dx, unsigned int *dy)
{
    unsigned int x, y;
    unsigned int start_x, start_y, end_x, end_y;
    bool found_start = false;
    RL_ASSERT(dx && dy);
    RL_ASSERT(map);
    RL_ASSERT(leaf);
    for (x = leaf->x; x < leaf->width + leaf->x; ++x) {
        for (y = leaf->y; y < leaf->height + leaf->y; ++y) {
            if (!found_start) {
                if (rl_map_tile_is(map, x, y, RL_TileRoom)) {
                    start_x = x;
                    start_y = y;
                    end_x = x;
                    end_y = y;
                    found_start = true;
                }
            } else {
                if (rl_map_tile_is(map, x, y, RL_TileRoom)) {
                    end_x = x;
                    end_y = y;
                } else {
                    /* found end - return middle of room */
                    int diff_x = end_x - start_x;
                    int diff_y = end_y - start_y;
                    RL_ASSERT(diff_x >= 0 && diff_y >= 0);
                    *dx = start_x + diff_x/2;
                    *dy = start_y + diff_y/2;
                }
            }
        }
    }
}

bool rl_mapgen_automata_is_alive(const RL_Map *map, int x, int y)
{
    if (!rl_map_in_bounds(map, x, y)) return true;
    return rl_map_tile_is(map, x, y, RL_TileRock);
}
unsigned int rl_mapgen_automata_alive_neighbors(const RL_Map *map, int x, int y)
{
    return rl_mapgen_automata_is_alive(map, x + 1, y) +
           rl_mapgen_automata_is_alive(map, x - 1, y) +
           rl_mapgen_automata_is_alive(map, x,     y + 1) +
           rl_mapgen_automata_is_alive(map, x,     y - 1) +
           rl_mapgen_automata_is_alive(map, x + 1, y + 1) +
           rl_mapgen_automata_is_alive(map, x - 1, y + 1) +
           rl_mapgen_automata_is_alive(map, x + 1, y - 1) +
           rl_mapgen_automata_is_alive(map, x - 1, y - 1);
}

RL_Status rl_mapgen_automata(RL_Map *map, RL_MapgenConfigAutomata config)
{
    return rl_mapgen_automata_ex(map, 0, 0, map->width, map->height, &config);
}

#if RL_ENABLE_PATHFINDING
static inline float rl_mapgen_corridor_scorer(const RL_GraphNode *current, const RL_GraphNode *neighbor, void *context);
#endif
RL_Status rl_mapgen_automata_ex(RL_Map *map, unsigned int offset_x, unsigned int offset_y, unsigned int width, unsigned int height,  const RL_MapgenConfigAutomata *config)
{
    unsigned int i, x, y;

    RL_ASSERT(map && config);
    RL_ASSERT(width > 0 && height > 0);
    RL_ASSERT(offset_x < width && offset_y < height);
    RL_ASSERT(offset_x < map->width && offset_y < map->height);
    RL_ASSERT(offset_x + width <= map->width && offset_y + height <= map->height);
    RL_ASSERT(config->chance_cell_initialized > 0 && config->chance_cell_initialized <= 100);

    if (map == NULL || config == NULL) {
        return RL_ErrorNullParameter;
    }

    /* initialize map */
    for (x=offset_x; x<offset_x + width; ++x) {
        for (y=offset_y; y<offset_y + height; ++y) {
            unsigned int r = RL_RNG_F(1, 100);
            if (r <= config->chance_cell_initialized) {
                map->tiles[x + y*map->width] = RL_TileRock;
            } else {
                map->tiles[x + y*map->width] = RL_TileRoom;
            }
        }
    }

    /* cellular automata algorithm */
    for (i=config->max_iterations; i>0; i--) {
        for (x=offset_x; x<offset_x + width; ++x) {
            for (y=offset_y; y<offset_y + height; ++y) {
                unsigned int alive_neighbors = rl_mapgen_automata_alive_neighbors(map, x, y);
                if (!rl_mapgen_automata_is_alive(map, x, y) && alive_neighbors >= config->birth_threshold) {
                    /* cell isn't alive but has enough alive neighbors to be born */
                    map->tiles[x + y*map->width] = RL_TileRock;
                } else if (rl_mapgen_automata_is_alive(map, x, y) && alive_neighbors >= config->survival_threshold) {
                    /* cell is alive and has enough alive neighbors to survive */
                } else {
                    /* cell dies */
                    map->tiles[x + y*map->width] = RL_TileRoom;
                }
            }
        }
    }

    if (config->draw_corridors) {
#if RL_ENABLE_PATHFINDING
        /* A very crude algorithm for connecting corridors within the cellular automata. This creates a heap of Dijkstra
         * graphs, containing each floodfilled region of the map. Then, it goes through each of these regions and
         * connects it to another random region. This is pretty slow and can be optimized in the future, but works for
         * now. */

        RL_Heap *heap = rl_heap_create(1, NULL);
        /* fill floodfills array with a floodfill of each connected space */
        for (x=offset_x; x<offset_x + width; ++x) {
            for (y=offset_y; y<offset_y + height; ++y) {
                int i;
                bool is_scored = false;
                if (RL_PASSABLE_F(map, x, y)) {
                    for (i=0; i<heap->len; ++i) {
                        RL_Graph *floodfill = (RL_Graph*) heap->heap[i];
                        if (rl_graph_is_scored(floodfill, RL_XY(x, y))) {
                            is_scored = true;
                            break;
                        }
                    }
                    if (!is_scored) {
                        RL_Graph *floodfill = rl_dijkstra_create(map, RL_XY(x, y), NULL);
                        rl_heap_insert(heap, floodfill);
                    }
                }
            }
        }
        /* connect each floodfill with another random one */
        if (heap->len > 1) {
            int i;
            RL_Graph *graph = rl_graph_create_ex(map, map, NULL, 0);
            RL_ASSERT(graph);
            for (i=0; i<heap->len; ++i) {
                RL_Graph *floodfill_target;
                RL_Graph *floodfill;
                RL_Point dig_start, dig_end;
                size_t node_idx;
                int j = i;
                floodfill = (RL_Graph*) heap->heap[i];
                /* find a random target node to connect to */
                while (j == i) {
                    j = RL_RNG_F(0, heap->len - 1);
                }
                floodfill_target = (RL_Graph*) heap->heap[j];
                RL_ASSERT(floodfill && floodfill_target);
                /* find start & end point for corridor pathfinding */
                for (node_idx=0; node_idx<floodfill->length; ++node_idx) {
                    RL_GraphNode *n = &floodfill->nodes[node_idx];
                    RL_ASSERT(n);
                    if (n->score < FLT_MAX && RL_PASSABLE_F(map, n->point.x, n->point.y)) {
                        dig_start = n->point;
                        break;
                    }
                }
                RL_ASSERT(RL_PASSABLE_F(map, dig_start.x, dig_start.y));
                for (node_idx=0; node_idx<floodfill_target->length; ++node_idx) {
                    RL_GraphNode *n = &floodfill_target->nodes[node_idx];
                    RL_ASSERT(n);
                    if (n->score < FLT_MAX && RL_PASSABLE_F(map, n->point.x, n->point.y)) {
                        dig_end = n->point;
                        break;
                    }
                }
                RL_ASSERT(RL_PASSABLE_F(map, dig_end.x, dig_end.y));
                RL_ASSERT(!(dig_start.x == dig_end.x && dig_start.y == dig_end.y));
                /* carve out corridors */
                rl_dijkstra_score_ex(graph, dig_end, rl_mapgen_corridor_scorer, map);
                RL_Path *path = rl_path_create_from_graph(graph, dig_start);
                RL_ASSERT(path);
                while ((path = rl_path_walk(path))) {
                    if (rl_map_tile_is(map, path->point.x, path->point.y, RL_TileRock)) {
                        map->tiles[(size_t)floor(path->point.x) + (size_t)floor(path->point.y) * map->width] = RL_TileCorridor;
                    }
                }
            }
            rl_graph_destroy(graph);
        }
        /* cleanup */
        RL_Graph *floodfill = NULL;
        while ((floodfill = (RL_Graph*) rl_heap_pop(heap))) {
            rl_graph_destroy(floodfill);
        }
        rl_heap_destroy(heap);
#else
        return RL_ErrorMapgenInvalidConfig;
#endif
    }
    if (config->fill_border) {
        x = 0;
        for (y=offset_y; y<height; ++y) map->tiles[x + y*map->width] = RL_TileRock;
        x = width - 1;
        for (y=offset_y; y<height; ++y) map->tiles[x + y*map->width] = RL_TileRock;
        y = 0;
        for (x=offset_x; x<width; ++x) map->tiles[x + y*map->width] = RL_TileRock;
        y = height - 1;
        for (x=offset_x; x<width; ++x) map->tiles[x + y*map->width] = RL_TileRock;
    }
    if (config->cull_unconnected) {
#if RL_ENABLE_PATHFINDING
        RL_Graph *floodfill = rl_graph_floodfill_largest_area(map);
        if (floodfill) {
            for (x=offset_x; x<offset_x + width; ++x) {
                for (y=offset_y; y<offset_y + height; ++y) {
                    if (!rl_graph_is_scored(floodfill, RL_XY(x, y))) {
                        map->tiles[x + y*map->width] = RL_TileRock;
                    }
                }
            }
            rl_graph_destroy(floodfill);
        }
#else
        return RL_ErrorMapgenInvalidConfig;
#endif
    }

    return RL_OK;
}

typedef struct {
    int x, y;
} RL_MapPoint;

int rl_mapgen_maze_unvisited_neighbors(RL_MapPoint ps[4], const RL_Map *map, int x, int y, int sx, int mx, int sy, int my)
{
    RL_MapPoint neighbors[4];
    int i, count = 0;
    neighbors[0].x = x - 2;
    neighbors[0].y = y;
    neighbors[1].x = x + 2;
    neighbors[1].y = y;
    neighbors[2].x = x;
    neighbors[2].y = y - 2;
    neighbors[3].x = x;
    neighbors[3].y = y + 2;
    for (i = 0; i<4; ++i) {
        int x = neighbors[i].x;
        int y = neighbors[i].y;
        if (x < sx || x >= mx || y < sy || y >= my) continue;
        if (map->tiles[x + y*map->width] == RL_TileRock) {
            /* matching neighbor */
            ps[count].x = x;
            ps[count].y = y;
            count ++;
        }
    }

    return count;
}

RL_Status rl_mapgen_maze(RL_Map *map)
{
    RL_ASSERT(map);
    if (map == NULL) return RL_ErrorNullParameter;
    RL_ASSERT(map->width > 2 && map->height > 2);
    memset(map->tiles, RL_TileRock, sizeof(*map->tiles) * map->width * map->height);
    return rl_mapgen_maze_ex(map, 1, 1, map->width - 2, map->height - 2);
}

RL_Status rl_mapgen_maze_ex(RL_Map *map, unsigned int offset_x, unsigned int offset_y, unsigned int width, unsigned int height)
{
    int x, y;
    RL_MapPoint *ps;
    RL_MapPoint *p;
    RL_Heap *heap;

    RL_ASSERT(map);
    RL_ASSERT(width > 0 && height > 0);
    RL_ASSERT(offset_x < width && offset_y < height);
    RL_ASSERT(offset_x < map->width && offset_y < map->height);
    RL_ASSERT(offset_x + width <= map->width && offset_y + height <= map->height);
    RL_ASSERT(offset_x + width < INT_MAX);
    RL_ASSERT(offset_y + height < INT_MAX);

    if (map == NULL) {
        return RL_ErrorNullParameter;
    }

    /* reset all tiles within range to rock */
    for (x = (int)offset_x; x < (int)offset_x + (int)width; ++x) {
        for (y = (int)offset_y; y < (int)offset_y + (int)height; ++y) {
            map->tiles[x + y*map->width] = RL_TileRock;
        }
    }

    /* allocate memory for BFS */
    heap = rl_heap_create(width * height, NULL);
    ps = (RL_MapPoint*) RL_MALLOC(sizeof(*ps) * map->width * map->height);

    RL_ASSERT(ps && heap);
    if (ps == NULL || heap == NULL) {
        return RL_ErrorMemory;
    }

    /* choose random starting tile */
    x = RL_RNG_F(offset_x, offset_x + width - 1);
    y = RL_RNG_F(offset_y, offset_y + height - 1);
    map->tiles[x + y*map->width] = RL_TileCorridor;
    p = &ps[x + y*map->width];
    p->x = x;
    p->y = y;
    rl_heap_insert(heap, p);
    while ((p = (RL_MapPoint*) rl_heap_pop(heap)) != NULL) {
        /* check unvisited neighbors (+2 so we have enough space for walls) */
        RL_MapPoint neighbors[4];
        RL_MapPoint *p2;
        int wall_x, wall_y, i;
        int neighbors_count = rl_mapgen_maze_unvisited_neighbors(neighbors, map, p->x, p->y, offset_x, offset_x + width, offset_y, offset_y + height);
        if (neighbors_count == 0) continue;
        /* choose one unvisitied neighbor */
        i = RL_RNG_F(0, neighbors_count - 1);
        x = neighbors[i].x;
        y = neighbors[i].y;
        RL_ASSERT(rl_map_in_bounds(map, x, y));
        RL_ASSERT(map->tiles[x + y*map->width] == RL_TileRock);
        /* unvisited neighbor - remove wall and push to heap */
        wall_x = x;
        wall_y = y;
        if (x < p->x) wall_x = x + 1;
        if (x > p->x) wall_x = x - 1;
        if (y < p->y) wall_y = y + 1;
        if (y > p->y) wall_y = y - 1;
        map->tiles[wall_x + wall_y*map->width] = RL_TileCorridor;
        map->tiles[x + y*map->width] = RL_TileCorridor;
        p2 = &ps[x + y*map->width];
        p2->x = x;
        p2->y = y;
        rl_heap_insert(heap, p);
        rl_heap_insert(heap, p2);
    }

    /* free memory for BFS */
    rl_heap_destroy(heap);
    RL_FREE(ps);

    return RL_OK;
}

/* custom corridor connection to most efficiently connect leaves of the BSP tree */
void rl_mapgen_connect_corridors_simple(RL_Map *map, RL_BSP *root, bool draw_doors)
{
    /* unsigned int dig_start_x, dig_start_y, dig_end_x, dig_end_y, cur_x, cur_y; */
    unsigned int dig_start_x, dig_start_y, dig_end_x, dig_end_y, cur_x, cur_y;
    int direction, diff_y, diff_x;
    RL_BSP *node, *sibling, *left, *right;

    RL_ASSERT(map && root);
    if (!map || !root) return;

    /* connect siblings */
    node = root->left;
    sibling = root->right;
    if (node == NULL || sibling == NULL) return;

    /* find rooms in BSP */
    left = rl_bsp_random_leaf(node);
    right = rl_bsp_random_leaf(sibling);
#if RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC
    rl_bsp_find_room(map, left, &dig_start_x, &dig_start_y);
#else
    dig_start_x = left->x + left->width / 2;
    dig_start_y = left->y + left->height / 2;
#endif
#if RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC
    rl_bsp_find_room(map, right, &dig_end_x, &dig_end_y);
#else
    dig_end_x = right->x + right->width / 2;
    dig_end_y = right->y + right->height / 2;
#endif
    RL_ASSERT(RL_PASSABLE_F(map, dig_start_x, dig_start_y));
    RL_ASSERT(RL_PASSABLE_F(map, dig_end_x, dig_end_y));
    RL_ASSERT(!(dig_start_x == dig_end_x && dig_start_y == dig_end_y));

    /* carve out corridors */
    cur_x = dig_start_x;
    cur_y = dig_start_y;
    direction = 0;
    diff_y = cur_y - dig_end_y;
    if (diff_y < 0) diff_y *= -1;
    diff_x = cur_x - dig_end_x;
    if (diff_x < 0) diff_x *= -1;
    if (diff_y > diff_x) {
        direction = 1;
    }
    while (cur_x != dig_end_x || cur_y != dig_end_y) {
        /* prevent digging float wide corridors */
        unsigned int next_x, next_y; next_x = cur_x;
        next_y = cur_y;
        if (direction == 0) { /* digging left<->right */
            if (cur_x == dig_end_x) {
                direction = !direction;
            } else {
                next_x += dig_end_x < cur_x ? -1 : 1;
            }
        }
        if (direction == 1) { /* digging up<->down */
            if (cur_y == dig_end_y) {
                direction = !direction;
            } else {
                next_y += dig_end_y < cur_y ? -1 : 1;
            }
        }
        /* dig */
        if (map->tiles[cur_x + cur_y*map->width] == RL_TileRock) {
            if (draw_doors && rl_map_is_room_wall(map, cur_x, cur_y))
                map->tiles[cur_x + cur_y*map->width] = RL_TileDoor;
            else
                map->tiles[cur_x + cur_y*map->width] = RL_TileCorridor;
        }
        cur_x = next_x;
        cur_y = next_y;
    }

    /* connect siblings' children */
    rl_mapgen_connect_corridors_simple(map, node, draw_doors);
    rl_mapgen_connect_corridors_simple(map, sibling, draw_doors);
}


void rl_mapgen_connect_corridors_bsp(RL_Map *map, RL_BSP *root, bool draw_doors);
void rl_mapgen_connect_corridors_randomly(RL_Map *map, RL_BSP *root, bool draw_doors);
RL_Status rl_mapgen_connect_corridors(RL_Map *map, RL_BSP *root, bool draw_doors, RL_MapgenCorridorConnection connection_algorithm)
{
    switch (connection_algorithm) {
        case RL_ConnectNone:
            RL_UNUSED(map);
            RL_UNUSED(root);
            RL_UNUSED(draw_doors);
            break;
        case RL_ConnectSimple:
            rl_mapgen_connect_corridors_simple(map, root, draw_doors);
            break;
        case RL_ConnectRandomly:
#if RL_ENABLE_PATHFINDING
            rl_mapgen_connect_corridors_randomly(map, root, draw_doors);
            {
                /* cull non-connected tiles */
                RL_Graph *floodfill = rl_graph_floodfill_largest_area(map);
                RL_ASSERT(floodfill);
                if (floodfill) {
                    for (size_t x=0; x < map->width; ++x) {
                        for (size_t y=0; y < map->height; ++y) {
                            if (floodfill->nodes[x + y*map->width].score == FLT_MAX) {
                                /* set unreachable tiles to rock */
                                map->tiles[x + y*map->width] = RL_TileRock;
                            }
                        }
                    }
                    rl_graph_destroy(floodfill);
                }
            }
            break;
#endif
        case RL_ConnectBSP:
#if RL_ENABLE_PATHFINDING
            rl_mapgen_connect_corridors_bsp(map, root, draw_doors);
            break;
#endif
        default:
            return RL_ErrorMapgenInvalidConfig;
    }

    return RL_OK;
}

/**
 * Heap functions for pathfinding
 *
 * Ref: https://gist.github.com/skeeto/f012a207aff1753662b679917f706de6
 */

static int rl_heap_noop_comparison_f(const void *_a, const void *_b)
{
    RL_UNUSED(_a);
    RL_UNUSED(_b);
    return 1;
}

RL_Heap *rl_heap_create(int capacity, int (*comparison_f)(const void *heap_item_a, const void *heap_item_b))
{
    RL_Heap *heap;
    heap = (RL_Heap*) RL_MALLOC(sizeof(*heap));
    RL_ASSERT(heap);
    RL_ASSERT(capacity > 0);
    if (heap == NULL) {
        return NULL;
    }
    heap->heap = (void**) RL_MALLOC(sizeof(*heap->heap) * capacity);
    RL_ASSERT(heap->heap);
    if (heap->heap == NULL) {
        RL_FREE(heap);
        return NULL;
    }

    if (comparison_f == NULL) {
        comparison_f = rl_heap_noop_comparison_f;
    }

    heap->cap = capacity;
    heap->comparison_f = comparison_f;
    heap->len = 0;

    return heap;
}

void rl_heap_destroy(RL_Heap *h)
{
    if (h) {
        if (h->heap) {
            RL_FREE(h->heap);
        }
        RL_FREE(h);
    }
}

int rl_heap_length(const RL_Heap *h)
{
    if (h == NULL) return 0;
    return h->len;
}

bool rl_heap_insert(RL_Heap *h, void *item)
{
    int i;
    RL_ASSERT(h != NULL);
    if (h == NULL) return false;

    if (h->len == h->cap) {
        /* resize the heap */
        void **heap_items = (void**) RL_REALLOC(h->heap, sizeof(void*) * h->cap * 2);
        RL_ASSERT(heap_items);
        if (heap_items == NULL) {
            rl_heap_destroy(h);
            return false;
        }
        h->heap = heap_items;
        h->cap *= 2;
    }

    h->heap[h->len] = item;
    for (i = h->len++; i;) {
        void *tmp;
        int p = (i - 1) / 2;
        if (h->comparison_f(h->heap[p], h->heap[i])) {
            break;
        }
        tmp = h->heap[p];
        h->heap[p] = h->heap[i];
        h->heap[i] = tmp;
        i = p;
    }
    return true;
}

static void rl_heap_remove(RL_Heap *h, int index)
{
    int i;
    RL_ASSERT(h);
    if (h == NULL) {
        return;
    }

    h->heap[index] = h->heap[--h->len];
    for (i = index;;) {
        int a = 2*i + 1;
        int b = 2*i + 2;
        int j = i;
        void *tmp;
        if (a < h->len && h->comparison_f(h->heap[a], h->heap[j])) j = a;
        if (b < h->len && h->comparison_f(h->heap[b], h->heap[j])) j = b;
        if (i == j) break;
        tmp = h->heap[j];
        h->heap[j] = h->heap[i];
        h->heap[i] = tmp;
        i = j;
    }
}

void *rl_heap_pop(RL_Heap *h)
{
    void *r;
    if (h == NULL) {
        return NULL;
    }

    r = NULL;
    if (h->len) {
        RL_ASSERT(h->heap);
        r = h->heap[0];
        rl_heap_remove(h, 0);
    }
    return r;
}

void *rl_heap_peek(RL_Heap *h)
{
    if (h == NULL) {
        return NULL;
    }

    RL_ASSERT(h->heap);
    if (h->len) {
        return h->heap[0];
    } else {
        return NULL;
    }
}

#if RL_ENABLE_PATHFINDING
/* simplified distance for side by side nodes */
static float rl_distance_simple(RL_Point node, RL_Point end)
{
    if (node.x == end.x && node.y == end.y) return 0;
    if (node.x == end.x || node.y == end.y) return 1;
    return 1.4;
}

static int rl_scored_graph_heap_comparison(const void *heap_item_a, const void *heap_item_b)
{
    RL_GraphNode *node_a = (RL_GraphNode*) heap_item_a;
    RL_GraphNode *node_b = (RL_GraphNode*) heap_item_b;

    return node_a->score < node_b->score;
}

RL_Path *rl_path(RL_Point p)
{
    RL_Path *path = (RL_Path*) RL_MALLOC(sizeof(*path));
    RL_ASSERT(path);
    if (path == NULL) return NULL;
    path->next = NULL;
    path->point = p;

    return path;
}

float rl_distance_manhattan(RL_Point node, RL_Point end)
{
    return fabs(node.x - end.x) + fabs(node.y - end.y);
}

float rl_distance_euclidian(RL_Point node, RL_Point end)
{
    float distance_x = node.x - end.x;
    float distance_y = node.y - end.y;

    return sqrt(distance_x * distance_x + distance_y * distance_y);
}

float rl_distance_chebyshev(RL_Point node, RL_Point end)
{
    float distance_x = fabs(node.x - end.x);
    float distance_y = fabs(node.y - end.y);

    return distance_x > distance_y ? distance_x : distance_y;
}

/* custom Dijkstra scorer function to prevent carving double wide doors when carving corridors */
static inline float rl_mapgen_corridor_scorer(const RL_GraphNode *current, const RL_GraphNode *neighbor, void *context)
{
    RL_Map *map = (RL_Map*) context;
    RL_Point start = current->point;
    RL_Point end = neighbor->point;
    float r = current->score + rl_distance_manhattan(start, end);

    if (rl_map_tile_is(map, end.x, end.y, RL_TileDoor)) {
        return r; /* doors are passable but count as "walls" - encourage passing through them */
    }
    if (rl_map_is_corner_wall(map, end.x, end.y)) {
        return r + 99; /* discourage double wide corridors & double carving into walls */
    }
    if (RL_WALL_F(map, end.x, end.y)) {
        return r + 9; /* discourage double wide corridors & double carving into walls */
    }

    return r;
}

void rl_mapgen_connect_corridors_bsp_recursive(RL_Map *map, RL_BSP *root, bool draw_doors, RL_Graph *graph)
{
    RL_ASSERT(map && root && graph);
    if (map == NULL || root == NULL || graph == NULL) return;

    /* connect siblings */
    RL_BSP *node = root->left;
    RL_BSP *sibling = root->right;
    if (node == NULL || sibling == NULL) return;

    /* find rooms in BSP */
    unsigned int x, y;
    RL_BSP *leaf = rl_bsp_random_leaf(node);
    rl_bsp_find_room(map, leaf, &x, &y);
    RL_Point dig_start = {x, y};
    RL_ASSERT(RL_PASSABLE_F(map, dig_start.x, dig_start.y));
    leaf = rl_bsp_random_leaf(sibling);
    rl_bsp_find_room(map, leaf, &x, &y);
    RL_Point dig_end = {x, y};
    RL_ASSERT(RL_PASSABLE_F(map, dig_end.x, dig_end.y));
    RL_ASSERT(!(dig_start.x == dig_end.x && dig_start.y == dig_end.y));

    /* carve out corridors */
    rl_dijkstra_score_ex(graph, dig_end, rl_mapgen_corridor_scorer, map);
    RL_Path *path = rl_path_create_from_graph(graph, dig_start);
    RL_ASSERT(path);
    while ((path = rl_path_walk(path))) {
        if (rl_map_tile_is(map, path->point.x, path->point.y, RL_TileRock)) {
            if (rl_map_is_room_wall(map, path->point.x, path->point.y) && draw_doors) {
                map->tiles[(size_t)floor(path->point.x) + (size_t)floor(path->point.y) * map->width] = RL_TileDoor;
            } else {
                map->tiles[(size_t)floor(path->point.x) + (size_t)floor(path->point.y) * map->width] = RL_TileCorridor;
            }
        }
    }

    /* connect siblings' children */
    rl_mapgen_connect_corridors_bsp_recursive(map, node, draw_doors, graph);
    rl_mapgen_connect_corridors_bsp_recursive(map, sibling, draw_doors, graph);
}
void rl_mapgen_connect_corridors_bsp(RL_Map *map, RL_BSP *root, bool draw_doors)
{
    RL_Graph *graph = rl_graph_create_ex(map, map, NULL, 0);
    RL_ASSERT(graph);
    if (graph) {
        rl_mapgen_connect_corridors_bsp_recursive(map, root, draw_doors, graph);
        rl_graph_destroy(graph);
    }
}

void rl_mapgen_connect_corridors_randomly(RL_Map *map, RL_BSP *root, bool draw_doors)
{
    RL_ASSERT(map && root);
    if (!map || !root) return;

    /* find deepest left-most node */
    RL_BSP *leftmost_node = root;
    while (leftmost_node->left != NULL) {
        leftmost_node = leftmost_node->left;
    }
    RL_ASSERT(leftmost_node && rl_bsp_is_leaf(leftmost_node));
    RL_BSP *node = leftmost_node;
    RL_Graph *graph = rl_graph_create_ex(map, map, NULL, 0);
    RL_ASSERT(graph);
    if (graph == NULL) return;
    while (node) {
        RL_BSP *sibling;

        /* find random sibling */
        while ((sibling = rl_bsp_random_leaf(root)) == node) {}
        RL_ASSERT(sibling);

        /* TODO need to change this to find the *actual* room (e.g. what if the user provides a map filled with "."?) */
        unsigned int x, y;
        rl_bsp_find_room(map, node, &x, &y);
        RL_ASSERT(RL_PASSABLE_F(map, x, y));
        RL_Point dig_start = {x, y};
        RL_ASSERT(RL_PASSABLE_F(map, dig_start.x, dig_start.y));
        rl_bsp_find_room(map, sibling, &x, &y);
        RL_ASSERT(RL_PASSABLE_F(map, x, y));
        RL_Point dig_end = {x, y};
        RL_ASSERT(RL_PASSABLE_F(map, dig_end.x, dig_end.y));
        RL_ASSERT(!(dig_start.x == dig_end.x && dig_start.y == dig_end.y));

        /* carve out corridors */
        rl_dijkstra_score_ex(graph, dig_end, rl_mapgen_corridor_scorer, map);
        RL_Path *path = rl_path_create_from_graph(graph, dig_start);
        RL_ASSERT(path);
        while ((path = rl_path_walk(path))) {
            if (rl_map_tile_is(map, path->point.x, path->point.y, RL_TileRock)) {
                if (rl_map_is_room_wall(map, path->point.x, path->point.y) && draw_doors) {
                    map->tiles[(size_t)floor(path->point.x) + (size_t)floor(path->point.y) * map->width] = RL_TileDoor;
                } else {
                    map->tiles[(size_t)floor(path->point.x) + (size_t)floor(path->point.y) * map->width] = RL_TileCorridor;
                }
            }
        }

        /* find start node for next loop iteration */
        node = rl_bsp_next_leaf(node);
    }

    rl_graph_destroy(graph);
}


RL_Graph *rl_graph_floodfill_largest_area(const RL_Map *map)
{
    RL_ASSERT(map);
    if (map == NULL) return NULL;
    int *visited = (int*) RL_CALLOC(map->width * map->height, sizeof(*visited));
    RL_ASSERT(visited);
    if (visited == NULL) return NULL;
    RL_Graph *floodfill = NULL; /* largest floodfill */
    int floodfill_scored = 0;
    for (unsigned int x = 0; x < map->width; ++x) {
        for (unsigned int y = 0; y < map->height; ++y) {
            if (RL_PASSABLE_F(map, x, y) && !visited[x + y*map->width]) {
                RL_Graph *test = rl_dijkstra_create(map, RL_XY(x, y), NULL);
                RL_ASSERT(test);
                if (test == NULL) {
                    RL_FREE(visited);
                    if (floodfill) {
                        rl_graph_destroy(floodfill);
                    }
                    return NULL;
                }
                int test_scored = 0;
                for (size_t i = 0; i < test->length; i++) {
                    if (test->nodes[i].score != FLT_MAX) {
                        visited[i] = 1;
                        test_scored ++;
                    }
                }
                if (test_scored > floodfill_scored) {
                    floodfill_scored = test_scored;
                    if (floodfill) {
                        rl_graph_destroy(floodfill);
                    }
                    floodfill = test;
                } else {
                    rl_graph_destroy(test);
                }
            }
        }
    }

    RL_FREE(visited);

    return floodfill;
}


RL_Path *rl_line_create(RL_Point a, RL_Point b, float step)
{
    float delta_x = fabs(a.x - b.x);
    float x_increment = b.x > a.x ? step : -step;
    float delta_y = fabs(a.y - b.y);
    float y_increment = b.y > a.y ? step : -step;
    float error = 0.0;
    float slope = delta_x ? delta_y / delta_x : 0.0;

    RL_Path *head = rl_path(a);
    if (head == NULL) return NULL;
    RL_Path *path = head;
    while (path->point.x != b.x || path->point.y != b.y) {
        RL_Point point = path->point;

        if (delta_x > delta_y) {
            error += slope;
            if (error > 0.5 && point.y != b.y) {
                error -= 1.0;
                point.y += y_increment;
            }

            point.x += x_increment;
        } else {
            error += 1/slope;
            if (error > 0.5 && point.x != b.x) {
                error -= 1.0;
                point.x += x_increment;
            }

            point.y += y_increment;
        }

        /* add new member to linked list & advance */
        path->next = rl_path(point);
        path = path->next;
    }

    return head;
}

RL_Path *rl_path_create(const RL_Map *map, RL_Point start, RL_Point end, RL_DistanceFun distance_f)
{
    RL_Graph *graph = rl_dijkstra_create(map, end, distance_f);
    RL_ASSERT(graph);
    if (graph == NULL) return NULL;
    RL_Path *path = rl_path_create_from_graph(graph, start);
    RL_ASSERT(path);
    rl_graph_destroy(graph);

    return path;
}

RL_Path *rl_path_create_from_graph(const RL_Graph *graph, RL_Point start)
{
    RL_Path *path = rl_path(start);
    RL_Path *path_start = path;
    RL_GraphNode *node = NULL;
    RL_ASSERT(path);
    RL_ASSERT(graph && graph->nodes);
    if (path == NULL || graph == NULL || graph->nodes == NULL) return NULL;
    for (size_t i=0; i<graph->length; i++) {
        if (graph->nodes[i].point.x == start.x && graph->nodes[i].point.y == start.y) {
            node = &graph->nodes[i];
        }
    }
    if (node == NULL) {
        return path;
    }
    while (node != NULL && node->score > 0) {
        node = rl_graph_node_lowest_neighbor(node);
        if (node == NULL) break;
        path->next = rl_path(node->point);
        RL_ASSERT(path->next);
        if (path->next == NULL) {
            rl_path_destroy(path);
            return NULL;
        }
        path = path->next;
    }

    return path_start;
}

RL_Path *rl_path_walk(RL_Path *path)
{
    if (!path) return NULL;
    RL_Path *next = path->next;
    path->next = NULL;
    RL_FREE(path);

    return next;
}

void rl_path_destroy(RL_Path *path)
{
    if (path) {
        while ((path = rl_path_walk(path))) {}
    }
}

bool rl_graph_default_passable_fun(void *context, unsigned int x, unsigned int y)
{
    RL_Map *map = (RL_Map*) context;
    return RL_PASSABLE_F(map, x, y);
}

RL_Graph *rl_graph_create(const RL_Map *map)
{
    return rl_graph_create_ex(map, (void*) map, rl_graph_default_passable_fun, true);
}

RL_Graph *rl_graph_create_ex(const RL_Map *map, void *context, RL_PassableFun passable_f, bool allow_diagonal_neighbors)
{
    RL_Graph *graph = (RL_Graph*) RL_MALLOC(sizeof(*graph));
    RL_ASSERT(graph);
    if (graph == NULL) return NULL;
    size_t length = map->width * map->height;
    RL_GraphNode *nodes = (RL_GraphNode*) RL_CALLOC(length, sizeof(*nodes));
    RL_ASSERT(nodes != NULL);
    if (nodes == NULL) {
        RL_FREE(graph);
        return NULL;
    }
    for (unsigned int x=0; x<map->width; x++) {
        for (unsigned int y=0; y<map->height; y++) {
            size_t idx = x + y*map->width;
            RL_GraphNode *node = &nodes[idx];
            node->point.x = (float) x;
            node->point.y = (float) y;
            node->neighbors_length = 0;
            node->score = FLT_MAX;
            /* calculate neighbors */
            RL_Point neighbor_coords[8];
            neighbor_coords[0].x = (int)x + 1;
            neighbor_coords[0].y = (int)y;
            neighbor_coords[1].x = (int)x - 1;
            neighbor_coords[1].y = (int)y;
            neighbor_coords[2].x = (int)x;
            neighbor_coords[2].y = (int)y + 1;
            neighbor_coords[3].x = (int)x;
            neighbor_coords[3].y = (int)y - 1;
            neighbor_coords[4].x = (int)x + 1;
            neighbor_coords[4].y = (int)y + 1;
            neighbor_coords[5].x = (int)x + 1;
            neighbor_coords[5].y = (int)y - 1;
            neighbor_coords[6].x = (int)x - 1;
            neighbor_coords[6].y = (int)y + 1;
            neighbor_coords[7].x = (int)x - 1;
            neighbor_coords[7].y = (int)y - 1;
            for (int i=0; i<8; i++) {
                if (passable_f && !passable_f(context, neighbor_coords[i].x, neighbor_coords[i].y))
                    continue;
                if (!rl_map_in_bounds(map, neighbor_coords[i].x, neighbor_coords[i].y))
                    continue;
                if (!allow_diagonal_neighbors && i >= 4)
                    continue;

                size_t idx = neighbor_coords[i].x + neighbor_coords[i].y*map->width;
                node->neighbors[node->neighbors_length] = &nodes[idx];
                node->neighbors_length++;
            }
        }
    }

    graph->length = length;
    graph->nodes = nodes;

    return graph;
}

void rl_graph_add(RL_Graph *graph, const RL_Graph *graph_b)
{
    RL_ASSERT(graph != NULL);
    RL_ASSERT(graph_b != NULL);
    RL_ASSERT(graph->length == graph_b->length);
    for (size_t i=0; i < graph->length; i++) {
        RL_GraphNode *node = &graph->nodes[i];
        if (node->score <= FLT_MAX - graph_b->nodes[i].score) {
            node->score += graph_b->nodes[i].score;
        } else {
            node->score = FLT_MAX;
        }
    }
}

void rl_graph_weight(RL_Graph *graph, float coefficient)
{
    RL_ASSERT(graph != NULL);
    RL_ASSERT(coefficient <= 1 && coefficient >= 0);
    for (size_t i=0; i < graph->length; i++) {
        RL_GraphNode *node = &graph->nodes[i];
        node->score *= coefficient;
    }
}

RL_Graph *rl_dijkstra_create(const RL_Map *map,
                            RL_Point start,
                            RL_DistanceFun distance_f)
{
    RL_Graph *graph = rl_graph_create(map);
    rl_dijkstra_score(graph, start, distance_f);

    return graph;
}

/* default scorer function for Dijkstra - this simply accepts a RL_DistanceFun as context and adds the current nodes */
/* score to the result of the distance function */
struct rl_score_context { RL_DistanceFun fun; };
float rl_dijkstra_default_score_f(const RL_GraphNode *current, const RL_GraphNode *neighbor, void *context)
{
    struct rl_score_context *distance_f = (struct rl_score_context*) context;

    return current->score + distance_f->fun(current->point, neighbor->point);
}

void rl_dijkstra_score(RL_Graph *graph, RL_Point start, RL_DistanceFun distance_f)
{
    struct { RL_DistanceFun fun; } scorer_context;
    scorer_context.fun = distance_f ? distance_f : rl_distance_simple; /* default to rl_distance_simple */
    rl_dijkstra_score_ex(graph, start, rl_dijkstra_default_score_f, &scorer_context);
}

void rl_graph_reset(RL_Graph *graph)
{
    RL_ASSERT(graph != NULL);
    if (graph == NULL) return;
    /* reset scores of dijkstra map */
    for (size_t i=0; i < graph->length; i++) {
        RL_GraphNode *node = &graph->nodes[i];
        node->score = FLT_MAX;
    }
}

void rl_dijkstra_score_ex(RL_Graph *graph, RL_Point start, RL_ScoreFun score_f, void *score_context)
{
    RL_ASSERT(graph);
    RL_ASSERT(score_f);
    if (graph == NULL) return;

    RL_GraphNode *current;
    RL_Heap *heap = rl_heap_create(graph->length, &rl_scored_graph_heap_comparison);

    /* reset scores of dijkstra map, setting the start point to 0 */
    for (size_t i=0; i < graph->length; i++) {
        RL_GraphNode *node = &graph->nodes[i];
        if (node->point.x == start.x && node->point.y == start.y) {
            node->score = 0;
            current = node;
        } else {
            node->score = FLT_MAX;
        }
    }

    rl_heap_insert(heap, (void*) current);
    current = (RL_GraphNode*) rl_heap_pop(heap);
    while (current) {
        for (size_t i=0; i<current->neighbors_length; i++) {
            RL_GraphNode *neighbor = current->neighbors[i];
            float distance = score_f(current, neighbor, score_context);
            if (distance < neighbor->score) {
                if (neighbor->score == FLT_MAX) {
                    rl_heap_insert(heap, neighbor);
                }
                neighbor->score = distance;
            }
        }

        current = (RL_GraphNode *) rl_heap_pop(heap);
    }

    rl_heap_destroy(heap);
}

void rl_graph_destroy(RL_Graph *graph)
{
    if (graph) {
        if (graph->nodes) {
            RL_FREE(graph->nodes);
        }
        RL_FREE(graph);
    }
}

bool rl_graph_is_scored(const RL_Graph *graph, RL_Point point)
{
    RL_GraphNode *n = rl_graph_node(graph, point);
    if (n) {
        return n->score < FLT_MAX;
    } else {
        return false;
    }
}

RL_GraphNode *rl_graph_node(const RL_Graph *graph, RL_Point point)
{
    RL_ASSERT(graph);
    if (graph == NULL) return NULL;
    for (unsigned int i=0; i<graph->length; ++i) {
        RL_GraphNode *n = &graph->nodes[i];
        RL_ASSERT(n);
        if (n && n->point.x == point.x && n->point.y == point.y) {
            return n;
        }
    }
    return NULL;
}

RL_GraphNode *rl_graph_node_lowest_neighbor(const RL_GraphNode *node)
{
    RL_ASSERT(node);
    if (node == NULL) return NULL;
    RL_GraphNode *lowest_neighbor = NULL;
    for (size_t i=0; i<node->neighbors_length; i++) {
        RL_GraphNode *neighbor = node->neighbors[i];
        if (!lowest_neighbor || neighbor->score < lowest_neighbor->score) {
            lowest_neighbor = neighbor;
        }
    }
    if (lowest_neighbor->score == FLT_MAX) return NULL;
    return lowest_neighbor;
}

int rl_graph_compare_neighbors(const void *a, const void *b)
{
    RL_GraphNode **node_a = (RL_GraphNode**) a;
    RL_GraphNode **node_b = (RL_GraphNode**) b;

    if ((*node_a)->score < (*node_b)->score) return -1;
    if ((*node_a)->score > (*node_b)->score) return 1;
    return 0;
}

void rl_graph_node_sort_neighbors(RL_GraphNode *node)
{
    qsort(node->neighbors, node->neighbors_length, sizeof(*node->neighbors), rl_graph_compare_neighbors);
}
#endif /* RL_ENABLE_PATHFINDING */

#if RL_ENABLE_FOV
RL_FOV *rl_fov_create(unsigned int width, unsigned int height)
{
    RL_FOV *fov;
    unsigned char *memory;
    RL_ASSERT(width > 0 && height > 0);
    RL_ASSERT(width != UINT_MAX && !(width > UINT_MAX / height)); /* check for overflow */
    fov = NULL;
    /* allocate all the memory we need at once */
    memory = (unsigned char*) RL_CALLOC(sizeof(*fov) + sizeof(*fov->visibility)*width*height, 1);
    RL_ASSERT(memory);
    if (memory == NULL) return NULL;
    fov = (RL_FOV*) memory;
    fov->width = width;
    fov->height = height;
    fov->visibility = (RL_Byte*) (memory + sizeof(*fov));
    RL_ASSERT(fov);
    RL_ASSERT(fov->visibility);

    return fov;
}

void rl_fov_destroy(RL_FOV *fov)
{
    if (fov) {
        RL_FREE(fov);
    }
}

typedef struct {
    int Y;
    int X;
} RL_Slope;

/* adapted from: https://www.adammil.net/blog/v125_Roguelike_Vision_Algorithms.html#shadowcode (public domain) */
/* also see: https://www.roguebasin.com/index.php/FOV_using_recursive_shadowcasting */
void rl_fov_calculate_recursive(void *map, unsigned int origin_x, unsigned int origin_y, RL_IsInRangeFun in_range_f, RL_IsOpaqueFun opaque_f, RL_MarkAsVisibleFun mark_visible_f, unsigned int octant, float original_x, RL_Slope top, RL_Slope bottom)
{
    int x;
    RL_ASSERT(in_range_f);
    RL_ASSERT(opaque_f);
    RL_ASSERT(mark_visible_f);
    for(x = original_x; x < RL_MAX_RECURSION; x++)
    {
        /* compute the Y coordinates where the top vector leaves the column (on the right) and where the bottom vector */
        /* enters the column (on the left). this equals (x+0.5)*top+0.5 and (x-0.5)*bottom+0.5 respectively, which can */
        /* be computed like (x+0.5)*top+0.5 = (2(x+0.5)*top+1)/2 = ((2x+1)*top+1)/2 to avoid floating point math */
        /* the rounding is a bit tricky, though */
        int topY = top.X == 1 ? x : ((x*2+1) * top.Y + top.X - 1) / (top.X*2); /* the rounding is a bit tricky, though */
        int bottomY = bottom.Y == 0 ? 0 : ((x*2-1) * bottom.Y + bottom.X) / (bottom.X*2);
        int wasOpaque = -1; /* 0:false, 1:true, -1:not applicable */
        int y;
        for(y=topY; y >= bottomY; y--)
        {
            float tx = origin_x, ty = origin_y;
            bool inRange, isOpaque;
            switch(octant) /* translate local coordinates to map coordinates */
            {
                case 0: tx += x; ty -= y; break;
                case 1: tx += y; ty -= x; break;
                case 2: tx -= y; ty -= x; break;
                case 3: tx -= x; ty -= y; break;
                case 4: tx -= x; ty += y; break;
                case 5: tx -= y; ty += x; break;
                case 6: tx += y; ty += x; break;
                case 7: tx += x; ty += y; break;
            }

            inRange = in_range_f(tx, ty, map);
            if(inRange) {
                if (RL_FOV_SYMMETRIC && (y != topY || top.Y*(int)x >= top.X*y) && (y != bottomY || bottom.Y*(int)x <= bottom.X*y)) {
                    mark_visible_f(tx, ty, map);
                } else if (!RL_FOV_SYMMETRIC) {
                    mark_visible_f(tx, ty, map);
                }
            }

            if (x == original_x && !inRange) {
                return;
            }

            isOpaque = !inRange || opaque_f(tx, ty, map);
            if(isOpaque)
            {
                if(wasOpaque == 0) /* if we found a transition from clear to opaque, this sector is done in this column, so */
                {                  /* adjust the bottom vector upwards and continue processing it in the next column. */
                    RL_Slope newBottom;
                    newBottom.Y = y*2 + 1; /* (x*2-1, y*2+1) is a vector to the top-left of the opaque tile */
                    newBottom.X = x*2 - 1;
                    if(!inRange || y == bottomY) { bottom = newBottom; break; } /* don't recurse unless we have to */
                    else if (inRange) rl_fov_calculate_recursive(map, origin_x, origin_y, in_range_f, opaque_f, mark_visible_f, octant, x+1, top, newBottom);
                }
                wasOpaque = 1;
            }
            else /* adjust top vector downwards and continue if we found a transition from opaque to clear */
            {    /* (x*2+1, y*2+1) is the top-right corner of the clear tile (i.e. the bottom-right of the opaque tile) */
                if(wasOpaque > 0) {
                    top.Y = y*2 + 1;
                    top.X = x*2 + 1;
                }
                wasOpaque = 0;
            }
        }

        if(wasOpaque != 0) break; /* if the column ended in a clear tile, continue processing the current sector */
    }
}

struct RL_FOVMap {
    RL_FOV *fov;
    const RL_Map *map;
    unsigned int origin_x;
    unsigned int origin_y;
    int fov_radius;
};

void rl_fovmap_mark_visible_f(unsigned int x, unsigned int y, void *context)
{
    struct RL_FOVMap *map = (struct RL_FOVMap*) context;
    if (rl_map_in_bounds(map->map, x, y)) {
        map->fov->visibility[x + y*map->map->width] = RL_TileVisible;
    }
}

bool rl_fovmap_opaque_f(unsigned int x, unsigned int y, void *context)
{
    struct RL_FOVMap *map = (struct RL_FOVMap*) context;
    return RL_OPAQUE_F(map->map, x, y);
}

bool rl_fovmap_in_range_f(unsigned int x, unsigned int y, void *context)
{
    struct RL_FOVMap *map = (struct RL_FOVMap*) context;
#if RL_ENABLE_PATHFINDING
    RL_Point p1, p2;
    p1.x = map->origin_x;
    p1.y = map->origin_y;
    p2.x = x;
    p2.y = y;
    return map->fov_radius < 0 || RL_FOV_DISTANCE_F(p1, p2) <= (float)map->fov_radius;
#else
    /* simplistic manhattan distance distance */
    int diff_x = (int)map->origin_x - (int)x;
    int diff_y = (int)map->origin_y - (int)y;
    if (diff_x < 0) diff_x *= -1;
    if (diff_y < 0) diff_y *= -1;
    return map->fov_radius < 0 || diff_x + diff_y < map->fov_radius;
#endif
}

void rl_fov_calculate(RL_FOV *fov, const RL_Map *map, unsigned int x, unsigned int y, int fov_radius)
{
    struct RL_FOVMap fovmap;
    unsigned int cur_x, cur_y;
    if (!rl_map_in_bounds(map, x, y)) {
        return;
    }
    /* set previously visible tiles to seen */
    for (cur_x=0; cur_x<map->width; ++cur_x) {
        for (cur_y=0; cur_y<map->height; ++cur_y) {
            if (fov->visibility[cur_x + cur_y*map->width] == RL_TileVisible) {
                fov->visibility[cur_x + cur_y*map->width] = RL_TileSeen;
            }
        }
    }
    fovmap.map = map;
    fovmap.fov = fov;
    fovmap.origin_x = x;
    fovmap.origin_y = y;
    fovmap.fov_radius = fov_radius;
    rl_fov_calculate_ex(&fovmap, x, y, rl_fovmap_in_range_f, rl_fovmap_opaque_f, rl_fovmap_mark_visible_f);
}

void rl_fov_calculate_ex(void *context, unsigned int x, unsigned int y, RL_IsInRangeFun in_range_f, RL_IsOpaqueFun opaque_f, RL_MarkAsVisibleFun mark_visible_f)
{
    int octant;
    RL_Slope from = { 1, 1 };
    RL_Slope to = { 0, 1 };
    mark_visible_f(x, y, context);
    for (octant=0; octant<8; ++octant) {
        rl_fov_calculate_recursive(context, x, y, in_range_f, opaque_f, mark_visible_f, octant, 1, from, to);
    }
}

bool rl_fov_is_visible(const RL_FOV *map, unsigned int x, unsigned int y)
{
    if (map == NULL) return false;
    if (!rl_map_in_bounds((const RL_Map*) map, x, y)) {
        return false;
    }
    return map->visibility[x + y*map->width] == RL_TileVisible;
}

bool rl_fov_is_seen(const RL_FOV *map, unsigned int x, unsigned int y)
{
    if (map == NULL) return false;
    if (!rl_map_in_bounds((const RL_Map*) map, x, y)) {
        return false;
    }
    return map->visibility[x + y*map->width] == RL_TileSeen;
}
#endif /* if RL_ENABLE_FOV */

#if RL_ENABLE_FILE
#include <stdio.h>

bool rl_file_save_map(const RL_Map *data, void *file)
{
    int version = 0;

    RL_ASSERT(data != NULL && file != NULL);
    if (fwrite(&version, sizeof(version), 1, (FILE*) file) < 1) {
        return false;
    }
    if (fwrite(data, sizeof(*data), 1, (FILE*)file) < 1) {
        return false;
    }
    if (fwrite(data->tiles, sizeof(*data->tiles), data->width * data->height, (FILE*) file) < data->width * data->height) {
        return false;
    }
    return true;
}

bool rl_file_load_map(RL_Map **data, void *file)
{
    int version;
    RL_Map dest;

    RL_ASSERT(data != NULL && file != NULL);
    if (fread(&version, sizeof(version), 1, (FILE*) file) < 1) {
        return false;
    }
    if (version != 0) {
        return false;
    }

    if (fread(&dest, sizeof(dest), 1, (FILE*) file) < 1) {
        return false;
    }
    RL_ASSERT(dest.width > 0 && dest.height > 0);
    dest.tiles = (RL_Byte*) malloc(sizeof(*dest.tiles) * dest.width * dest.height);
    RL_ASSERT(dest.tiles != NULL);
    if (fread(dest.tiles, sizeof(*dest.tiles), dest.width * dest.height, (FILE*) file) < dest.width * dest.height) {
        return false;
    }

    *data = (RL_Map*) malloc(sizeof(dest));
    RL_ASSERT(*data != NULL);
    *data = (RL_Map*) memcpy(*data, &dest, sizeof(dest));
    if (data == NULL) {
        return false;
    }

    return true;
}

bool rl_file_save_fov(const RL_FOV *data, void *file)
{
    int version = 0;

    RL_ASSERT(data != NULL && file != NULL);
    if (fwrite(&version, sizeof(version), 1, (FILE*) file) < 1) {
        return false;
    }
    if (fwrite(data, sizeof(*data), 1, (FILE*) file) < 1) {
        return false;
    }
    if (fwrite(data->visibility, sizeof(*data->visibility), data->width * data->height, (FILE*) file) < data->width * data->height) {
        return false;
    }
    return true;
}

bool rl_file_load_fov(RL_FOV **data, void *file)
{
    int version;
    RL_FOV dest;

    RL_ASSERT(data != NULL && file != NULL);
    if (fread(&version, sizeof(version), 1, (FILE*) file) < 1) {
        return false;
    }
    if (version != 0) {
        return false;
    }

    if (fread(&dest, sizeof(dest), 1, (FILE*) file) < 1) {
        return false;
    }
    RL_ASSERT(dest.width > 0 && dest.height > 0);
    dest.visibility = (RL_Byte*) malloc(sizeof(*dest.visibility) * dest.width * dest.height);
    RL_ASSERT(dest.visibility != NULL);
    if (fread(dest.visibility, sizeof(*dest.visibility), dest.width * dest.height, (FILE*)file) < dest.width * dest.height) {
        return false;
    }

    *data = (RL_FOV*) malloc(sizeof(dest));
    RL_ASSERT(*data != NULL);
    *data = (RL_FOV*) memcpy(*data, &dest, sizeof(dest));
    if (data == NULL) {
        return false;
    }

    return true;
}
#endif /* if RL_ENABLE_FILE */

#endif /* RL_IMPLEMENTATION */

#ifdef __cplusplus
}
#endif
