#include <client_api.h>
#include "grid.h"
#include "ent.h"
#include "parse.h"
#include "hashmap.h"

#include <ALLOC_IMPORTS.h>
#include "level_imports.h"

#include "constants.h"


typedef enum
{
    BG_SOLID = 1,
    BG_TOP = 2,
} bg_flags;

typedef struct
{
    char* tile;
    pbm_t* pbm;
    uint8_t flags;
} bg_t;

bg_t bg[100];
int num_bg = 0;


grid_t grid[COLS][ROWS];

__EXPORT__ void grid_pos(grid_t* g, int* gx, int* gy)
{
    int index = (int)(g - &grid[0][0]);
    *gx = index / ROWS;
    *gy = index - (*gx * ROWS);
}

struct hashmap_s tile_tags;


ent_t player;

#define OUTLINE 2

bool load_level(char* fname);
void unload_level();

void set_outline(ent_t* ent, int pixels, bool expand, bool dither)
{
    ent->outline = d_expand_bitmap(ent->graphic, pixels, expand, dither);
    ent->outline_pixels = expand ? pixels : 0;
}
void set_standard_sprite_off(ent_t* ent)
{
    ent->off_x = ent->off_y = SPRITEOFF;
}
void reset_pos(ent_t* ent, int x, int y)
{
    ent->x = ent->ox = x;
    ent->y = ent->oy = y;
}
__EXPORT__ int setup(int a)
{
    hashmap_create(32, &tile_tags);

    player.graphic = f_bitmap("/player.pbm");
    set_outline(&player, OUTLINE, true, false);
    set_standard_sprite_off(&player);
    reset_pos(&player, 0, 0);

    if (!load_level("level14"))
    {
        return 1;
    }

    return 0;
}

int remove_all_hashmap(void* const ctx, struct hashmap_element_s* const value) { return -1; }

char* last_level = NULL;
void* level_code = NULL;
void unload_level()
{
    level_update = NULL;
    level_entered_tile = NULL;
    level_exited_tile = NULL;

    if (level_code) unload_code(level_code);

    for (int i = 0; i < num_bg; i++)
    {
        free(bg[i].pbm);
    }
    num_bg = 0;

    if (last_level)
    {
        free(last_level);
        last_level = NULL;
    }

    hashmap_iterate_pairs(&tile_tags, remove_all_hashmap, NULL);
}

#define SOLID 1
#define TOP 2


bool is_space(char c) { return isspace(c); }
bool is_comma_or_space(char c) { return c == ',' || is_space(c); }

bool load_level(char* level_name)
{
    unload_level();
    memset(grid, 0, sizeof(grid));

    char filename[100];
    sprintf(filename, "/%s.txt", level_name);

    size_t len;
    last_level = (char*)f_contents(filename, &len);
    if (!last_level)
    {
        printf("Couldn't find level: %s\n", filename);
        return false;
    }

    reset_parse = true;

    // name, file, [flags]
    char* tile_def[] = { NULL, NULL, NULL };

    while (true)
    {
        int res = parse(last_level, len, is_space, false);
        //printf("Parse res: %d\n", res);

        if (res == DONE)
        {
            printf("No tile grid given in level %s\n", filename);
            return false;
        }

        int not_null = 0;
        for (; not_null < 3; not_null++)
        {
            if (!tile_def[not_null]) break;
        }

        if (res == NL)
        {
            if (not_null < 2)
            {
                printf("Invalid tile def on line %d\n", parse_line - 1);
                return false;
            }

            if (!tile_def[2]) tile_def[2] = "NULL";

            printf("Parsed tile %d %s %s %s\n", not_null, tile_def[0], tile_def[1], tile_def[2]);

            bg_flags flags = 0;
            if (not_null >= 3)
            {
                size_t flag_len = strlen(tile_def[2]);
                for (int i = 0; i < flag_len; i++)
                {
                    if (tile_def[2][i] == 's') flags |= BG_SOLID;
                    if (tile_def[2][i] == 't') flags |= BG_TOP;
                }
            }


            bool found = false;
            char f[100];
            sprintf(f, "/%s.pbm", tile_def[1]);
            pbm_t* n = f_bitmap(f);
            if (n)
            {
                found = true;

                bg_t b = { tile_def[0], n, flags };
                bg[num_bg++] = b;

                printf("Tile %s registered as word %s\n", f, tile_def[0]);
            }
            else
            {
                int i = 1;
                sprintf(f, "/%s%d.pbm", tile_def[1], i);
                while (n = f_bitmap(f))
                {
                    found = true;

                    char name[10];
                    sprintf(name, "%s%d", tile_def[0], i);


                    bg_t b = { malloc(strlen(name) + 1), n, flags }; 
                    strcpy(b.tile, name);
                    bg[num_bg++] = b;

                    printf("Tile %s registered as word %s\n", f, name);

                    i++;
                    sprintf(f, "/%s%d.pbm", tile_def[1], i);
                }
            }

            if (!found)
            {
                printf("Not found: graphic with name %s\n", tile_def[1]);
            }

            memset(tile_def, 0, sizeof(tile_def));
            continue;
        }

        if (res == BREAK) break;

        if (not_null < 3)
        {
            tile_def[not_null++] = parse_word;
        }
        else
        {
            printf("Too many words on line %d\n", parse_line);
        }
    }

    int gx = 0;
    int gy = 0;
    while (true)
    {
        int res = parse(last_level, len, is_comma_or_space, false);
        //printf("Parse res: %d\n", res);
        if (res == DONE || res == BREAK)
        {
            if (gy < ROWS)
            {
                printf("Didn't read full grid, only %d,%d\n", gx, gy);
                return false;
            }
            break;
        }
        if (res == NL)
        {
            if (gx < COLS)
            {
                printf("Couldn't read full row, only to index %d on line %d\n", gx - 1, parse_line);
                return false;
            }
            gx = 0;
            gy++;
        }
        if (res == WORD)
        {
            if (gx >= COLS || gy >= ROWS)
            {
                printf("Grid is too big, reached %d,%d on line %d\n", gx, gy, parse_line);
                return false;
            }
            char* prob = NULL;
            char* tag = NULL;
            grid_flags flags = 0;
            for (int i = 0; i < 2; i++)
            {
                if (parse_word[0] == '-')
                {
                    flags |= GRID_FLIPX;
                    parse_word++;
                }
                else if (parse_word[0] == '!')
                {
                    flags |= GRID_FLIPY;
                    parse_word++;
                }
            }

            char* last_word = parse_word;

            size_t l = strlen(last_word);
            for (int i = 0; i < l; i++)
            {
                if (last_word[i] == '.')
                {
                    last_word[i] = 0;
                    prob = last_word + (i + 1);
                    last_word = prob;
                    l = strlen(prob);
                    break;
                }
            }

            for (int i = 0; i < l; i++)
            {
                if (last_word[i] == ':')
                {
                    last_word[i] = 0;
                    tag = last_word + (i + 1);
                    last_word = tag;
                    l = strlen(tag);
                    break;
                }
            }

            //printf("Searching %s\n", parse_word);
            for (int i = 0; i < num_bg; i++)
            {
                //printf("  Check %s\n", bg[i].tile);
                if (strcmp(bg[i].tile, parse_word) == 0)
                {
                    if (prob)
                    {
                        int p = max(0, min(9, atoi(prob)));
                        if (random(1, 10) > p) continue;
                    }
                    //printf("Tile %d on %d,%d\n", i, gx, gy);
                    grid_t g = { i + 1, flags, tag };
                    if (tag) hashmap_put(&tile_tags, tag, strlen(tag), &grid[gx][gy]);
                    grid[gx][gy] = g;
                }
            }

            gx++;

        }
    }

    sprintf(filename, "/%s.bin", level_name);
    level_code = f_code(filename);

    if (level_code)
    {
        LOAD_DYN(level_entered_tile)
        LOAD_DYN(level_exited_tile)
        LOAD_DYN(level_update)
    }

    return 0;
}


float speed = 80;

bool move_debug = false;

bool r_intersect(int x1, int y1, int w1, int h1,
                 int x2, int y2, int w2, int h2)
{
    if (x1 >= x2 + w2 ||
        x1 + w2 <= x2 ||
        y1 >= y2 + h2 ||
        y1 + h2 <= y2) return false;
    if (move_debug) printf("INTERSECT [%d,%d + %d,%d]   [%d,%d + %d,%d]\n", x1, y1, w1, h1, x2, y2, w2, h2);
    return true;
}

bool r_free(int px, int py, int w, int h)
{
    int minX = px / GRID;
    int maxX = (px + w - 1) / GRID;
    int minY = py / GRID;
    int maxY = (py + h - 1) / GRID;

    for (int i = max(0, minX); i <= min(COLS - 1, maxX); i++)
    {
        for (int j = max(0, minY); j <= min(ROWS - 1, maxY); j++)
        {
            if (grid[i][j].tile != 0 && (bg[grid[i][j].tile - 1].flags & SOLID))
            {
                if (r_intersect(px, py, w, h,
                                i * GRID + 1,
                                j * GRID + 1,
                                GRID - 2,
                                GRID - 2)) return false;
            }
        }
    }
    return true;
}

__EXPORT__ bool is_player(ent_t* ent) { return ent == &player; }
__EXPORT__ grid_t* get_tile_by_tag(char* tag) { return (grid_t*)hashmap_get(&tile_tags, tag, strlen(tag)); }



void entered_tile(ent_t* ent, int gx, int gy)
{
    grid_t g = grid[gx][gy];
    if (level_entered_tile) level_entered_tile(ent, gx, gy, g);
}
void exited_tile(ent_t* ent, int gx, int gy)
{
    grid_t g = grid[gx][gy];
    if (level_exited_tile) level_exited_tile(ent, gx, gy, g);
}

void render_ent(ent_t* ent)
{

    if (ent->outline)
    {
        d_pbm(
            (uint16_t)roundf(ent->x) - ent->outline_pixels + PADX + ent->off_x,
            (uint16_t)roundf(ent->y) - ent->outline_pixels + PADY + ent->off_y,
                ent->outline, 0, 0, 0, 0, WHITE, TRANSPARENT, R_NONE, false, false);
    }
    d_pbm(
        (uint16_t)roundf(ent->x) + PADX + ent->off_x,
        (uint16_t)roundf(ent->y) + PADY + ent->off_y,
            ent->graphic, 0, 0, 0, 0, BLACK, TRANSPARENT, R_NONE, false, false);
}

void update_ent_movement(ent_t* ent, bool debug);

__EXPORT__ int loop(int ms)
{
    float t = ms / 1000.0f;

    if (button_down(DPAD_RIGHT)) player.x += t * speed;
    if (button_down(DPAD_LEFT)) player.x -= t * speed;
    if (button_down(DPAD_DOWN)) player.y += t * speed;
    if (button_down(DPAD_UP)) player.y -= t * speed;
    if (button_pressed(BUTTON_A)) move_debug = !move_debug;

    update_ent_movement(&player, move_debug);

    if (level_update) level_update(t);

    d_clear();

    for (int i = 0; i < COLS; i++)
    {
        for (int j = 0; j < ROWS; j++)
        {
            if (grid[i][j].tile != 0 && !(bg[grid[i][j].tile - 1].flags & TOP))
            {
                d_pbm(
                        PADX + i * GRID + SPRITEOFF,
                        PADY + j * GRID + SPRITEOFF,
                        bg[grid[i][j].tile - 1].pbm,
                        0,0,0,0, BLACK, TRANSPARENT, R_NONE, grid[i][j].flags & GRID_FLIPX, grid[i][j].flags & GRID_FLIPY);
            }
        }
    }

    render_ent(&player);


    for (int i = 0; i < COLS; i++)
    {
        for (int j = 0; j < ROWS; j++)
        {
            if (grid[i][j].tile != 0 && (bg[grid[i][j].tile - 1].flags & TOP))
            {
                d_fillRect(
                        PADX + i * GRID,
                        PADY + j * GRID,
                        GRID, GRID,
                        WHITE
                        );
            }
        }
    }

    for (int i = 0; i < COLS; i++)
    {
        for (int j = 0; j < ROWS; j++)
        {
            if (grid[i][j].tile != 0 && (bg[grid[i][j].tile - 1].flags & TOP))
            {
                d_pbm(
                        PADX + i * GRID + SPRITEOFF,
                        PADY + j * GRID + SPRITEOFF,
                        bg[grid[i][j].tile - 1].pbm,
                        0,0,0,0, BLACK, TRANSPARENT, R_NONE, grid[i][j].flags & GRID_FLIPX, grid[i][j].flags & GRID_FLIPY);
            }
        }
    }

    return 0;
}

#define ENTER_GRID_PCT 0.2f
void get_close_grid(float x, float y, int* gx, int* gy, float* pct_x, float* pct_y)
{
    float grid_x, grid_y;
    *pct_x = modff((x + GRID * 0.5f) / GRID, &grid_x) - 0.5f;
    *pct_y = modff((y + GRID * 0.5f) / GRID, &grid_y) - 0.5f;
    if (fabsf(*pct_x) > ENTER_GRID_PCT || fabsf(*pct_y) > ENTER_GRID_PCT)
    {
        grid_x = -1;
        grid_y = -1;
    }
    *gx = (int)grid_x;
    *gy = (int)grid_y;
}

void update_ent_movement(ent_t* ent, bool debug)
{
    int ox_i = (int)roundf(ent->ox);
    int oy_i = (int)roundf(ent->oy);

    if (ent->x != ent->ox || ent->y != ent->oy)
    {
        int x_i = (int)roundf(ent->x);
        int y_i = (int)roundf(ent->y);

        if (ent->x > ent->ox) x_i = (int)roundf(ent->x + 0.5f);
        if (ent->x < ent->ox) x_i = (int)roundf(ent->x - 0.5f);

        if (ent->y > ent->oy) y_i = (int)roundf(ent->y + 0.5f);
        if (ent->y < ent->oy) y_i = (int)roundf(ent->y - 0.5f);

        bool goX = ox_i != x_i;
        bool goY = oy_i != y_i;

        if (debug) printf("(%f,%f)->(%f,%f) I: (%d,%d)->(%d,%d) [%d, %d]\n", ent->ox, ent->oy, ent->x, ent->y, ox_i, oy_i, x_i, y_i, goX, goY);

        while (goX || goY)
        {
            if (goX)
            {
                int testX = ox_i < x_i ? ox_i + 1 : ox_i - 1;
                bool test = r_free(testX + 1, oy_i + 1, GRID - 2, GRID - 2);
                if (move_debug) printf("testX: %d => %d\n", testX, test);
                if (test)
                {
                    ox_i = testX;
                    if (ox_i == x_i) goX = false;
                    if (move_debug) printf("ox_i = %d, goX = %d\n", ox_i, goX);
                }
                else
                {
                    goX = false;
                    if (move_debug) printf("goX = false\n");
                }
            }
            if (goY)
            {
                int testY = oy_i < y_i ? oy_i + 1 : oy_i - 1;
                bool test = r_free(ox_i + 1, testY + 1, GRID - 2, GRID - 2);
                if (move_debug) printf("testY: %d => %d\n", testY, test);
                if (test)
                {
                    oy_i = testY;
                    if (oy_i == y_i) goY = false;
                    if (move_debug) printf("oy_i = %d, goY = %d\n", oy_i, goY);
                }
                else
                {
                    goY = false;
                    if (move_debug) printf("goY = false\n");
                }
            }
        }

        if (ox_i != x_i) ent->x = ox_i;
        if (oy_i != y_i) ent->y = oy_i;
        if (debug) printf("reached (%d,%d) on target of (%d,%d), x,y=(%f,%f)\n", ox_i, oy_i, x_i, y_i, ent->x, ent->y);

        if (debug) printf("\n====================\n\n");

        int o_grid_x, o_grid_y;
        float o_ingrid_x, o_ingrid_y;

        int grid_x, grid_y;
        float ingrid_x, ingrid_y;

        get_close_grid(ent->ox, ent->oy, &o_grid_x, &o_grid_y, &o_ingrid_x, &o_ingrid_y);

        int tries = 10;
        for (; tries > 0; tries--)
        {
            get_close_grid(ent->x, ent->y, &grid_x, &grid_y, &ingrid_x, &ingrid_y);

            if (grid_x == o_grid_x && grid_y == o_grid_y)
            {
                break;
            }
            else
            {
                if (o_grid_x != -1)
                {
                    exited_tile(ent, o_grid_x, o_grid_y);
                    //printf("Exited: %f %f\n", o_grid_x, o_grid_y);
                }
                if (grid_x != -1)
                {
                    entered_tile(ent, grid_x, grid_y);
                    //printf("Entered: %f %f\n", grid_x, grid_y);
                }

                o_grid_x = grid_x;
                o_grid_y = grid_y;
            }
        }
        if (tries == 0) printf("RAN OUT OF TILE CHECK TRIES\n");
    }

    ent->ox = ent->x;
    ent->oy = ent->y;

}
