#include "twn_game_api.h"

#include "world.h"

#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include <tgmath.h>


static void update_tiles(struct World *world) {
    for (size_t row = 0; row < world->tilemap_height; ++row) {
        for (size_t col = 0; col < world->tilemap_width; ++col) {
            world->tiles[(row * world->tilemap_width) + col] = (struct Tile) {
                .rect = (Recti) {
                    .x = (int)col * world->tile_size,
                    .y = (int)row * world->tile_size,
                    .w = world->tile_size,
                    .h = world->tile_size,
                },
                .type = world->tilemap[row][col],
            };
        }
    }
}


static Vec2i to_grid_location(struct World *world, float x, float y) {
    return (Vec2i) {
        .x = (int)floor(x / (float)world->tile_size),
        .y = (int)floor(y / (float)world->tile_size),
    };
}


static void drawdef_debug(struct World *world) {
    if (!ctx.debug) return;

    for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
        if (world->tiles[i].type == TILE_TYPE_VOID) continue;

        draw_rectangle(to_frect(world->tiles[i].rect),
                        (Color) { 255, 0, 255, 128 });
    }
}


struct World *world_create(void) {
    struct World *world = cmalloc(sizeof *world);

    *world = (struct World) {
        .tiles = NULL,
        .tile_size = 42,
        .tilemap_width = 20,
        .tilemap_height = 12,
        .gravity = 1,
    };

    /* create the tilemap */
    /* it simply stores what's in each tile as a 2d array */
    /* on its own, it's entirely unrelated to drawing or logic */
    world->tilemap = cmalloc(sizeof *world->tilemap * world->tilemap_height);
    for (size_t i = 0; i < world->tilemap_height; ++i) {
        world->tilemap[i] = cmalloc(sizeof **world->tilemap * world->tilemap_width);

        for (size_t j = 0; j < world->tilemap_width; ++j) {
            world->tilemap[i][j] = TILE_TYPE_VOID;
        }
    }

    for (size_t i = 0; i < 12; ++i) {
        world->tilemap[world->tilemap_height-2][2+i] = TILE_TYPE_SOLID;
    }
    world->tilemap[world->tilemap_height-3][8] = TILE_TYPE_SOLID;
    world->tilemap[world->tilemap_height-4][8] = TILE_TYPE_SOLID;
    world->tilemap[world->tilemap_height-5][10] = TILE_TYPE_SOLID;
    world->tilemap[world->tilemap_height-6][10] = TILE_TYPE_SOLID;
    for (size_t i = 0; i < 7; ++i) {
        world->tilemap[world->tilemap_height-6][12+i] = TILE_TYPE_SOLID;
    }

    /* the tiles array contains data meant to be used by other logic */
    /* most importantly, it is used to draw the tiles */
    const size_t tile_count = world->tilemap_height * world->tilemap_width;
    world->tiles = cmalloc(sizeof *world->tiles * tile_count);
    update_tiles(world);

    return world;
}


void world_destroy(struct World *world) {
    free(world->tiles);

    for (size_t i = 0; i < world->tilemap_height; ++i) {
        free(world->tilemap[i]);
    }
    free(world->tilemap);

    free(world);
}


void world_drawdef(struct World *world) {
    for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
        if (world->tiles[i].type == TILE_TYPE_VOID)
            continue;

        m_sprite("/assets/white.png", to_frect(world->tiles[i].rect));
    }

    drawdef_debug(world);
}


bool world_find_intersect_frect(struct World *world, Rect rect, Rect *intersection) {
    bool is_intersecting = false;

    const size_t tile_count = world->tilemap_height * world->tilemap_width;
    for (size_t i = 0; i < tile_count; ++i) {
        if (world->tiles[i].type == TILE_TYPE_VOID)
            continue;

        Rect tile_frect = {
            .x = (float)(world->tiles[i].rect.x),
            .y = (float)(world->tiles[i].rect.y),
            .w = (float)(world->tiles[i].rect.w),
            .h = (float)(world->tiles[i].rect.h),
        };

        if (intersection == NULL) {
            Rect temp;
            is_intersecting = overlap_frect(&rect, &tile_frect, &temp);
        } else {
            is_intersecting = overlap_frect(&rect, &tile_frect, intersection);
        }

        if (is_intersecting)
            break; 
    }

    return is_intersecting;
}


bool world_find_intersect_rect(struct World *world, Recti rect, Recti *intersection) {
    bool is_intersecting = false;

    const size_t tile_count = world->tilemap_height * world->tilemap_width;
    for (size_t i = 0; i < tile_count; ++i) {
        if (world->tiles[i].type == TILE_TYPE_VOID)
            continue;

        Recti *tile_rect = &world->tiles[i].rect;

        if (intersection == NULL) {
            Recti temp;
            is_intersecting = overlap_rect(&rect, tile_rect, &temp);
        } else {
            is_intersecting = overlap_rect(&rect, tile_rect, intersection);
        }

        if (is_intersecting)
            break;
    }

    return is_intersecting;
}


bool world_is_tile_at(struct World *world, float x, float y) {
    Vec2i position_in_grid = to_grid_location(world, x, y);
    return world->tilemap[position_in_grid.y][position_in_grid.x] != TILE_TYPE_VOID;
}


void world_place_tile(struct World *world, float x, float y) {
    Vec2i position_in_grid = to_grid_location(world, x, y);
    world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_SOLID;
    update_tiles(world);
}


void world_remove_tile(struct World *world, float x, float y) {
    Vec2i position_in_grid = to_grid_location(world, x, y);
    world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_VOID;
    update_tiles(world);
}