#include <stdio.h>
#include <string.h>

#include "raylib.h"

typedef struct Vec2i {
    int x, y;
} Vec2i;

int vec2i_equals(Vec2i v1, Vec2i v2)
{
    return v1.x == v2.x && v1.y == v2.y;
}

#include "maps.h"
#include "winfun.c"

typedef struct Shot {
    int up, down, left, right;
} Shot;

#ifdef RELEASE
Vector2 window = {1920, 1080};
#else
Vector2 window = {600, 600};
#endif
#ifdef RELEASE
int in_menu = 1;
#else
int in_menu = 0;
#endif
int door_open = 0;
float level_finished = 0;
float alarme_sonne = 0;
float player_dead = 0;
float shoot_cooldown = 0;
float end_time_game = 0;
float begin_play_time = 0;

Texture tex_fond;
Texture tex_tuto_key;
Texture tex_tuto_space;

void draw_menu(void)
{
    BeginDrawing();
    ClearBackground(BLUE);

    DrawTexturePro(tex_fond, (Rectangle){0,0,tex_fond.width,tex_fond.height}, (Rectangle){0,0,window.x,window.y}, (Vector2){0,0}, 0, WHITE);

    const char *title = "DORIANOCACASORUS";
    int size = 50;
    int width = MeasureText(title, size);
    DrawText(title, window.x/2-width/2, window.y/2-size/2, size, GOLD);

    Vector2 mouse = GetMousePosition();
    Rectangle button = {
        .x = window.x/2-250,
        .y = window.y*0.6,
        .width = 500,
        .height = 100,
    };
    Color button_color = GRAY;
    if (CheckCollisionPointRec(mouse, button)) {
        button_color = LIGHTGRAY;
        if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
            in_menu = 0;
            begin_play_time = GetTime();
#ifdef RELEASE
            HideCursor();
            DisableCursor();
#endif
        }
    }

    DrawRectangleRec(button, button_color);
    DrawText("PLAY", window.x/2-MeasureText("PLAY", size)/2, window.y*0.6+size/2, size, WHITE);
    EndDrawing();
}

void draw_win(void)
{
    BeginDrawing();
    ClearBackground(DARKBLUE);

    const char *title = "Étage réussie";
    int size = 75;
    int width = MeasureText(title, size);
    DrawText(title, window.x/2-width/2, window.y/2-size/2, size, GOLD);

    EndDrawing();
}

void draw_alarme_lose(void)
{
    BeginDrawing();
    ClearBackground(DARKBLUE);

    const char *title = "L'alarme sonne";
    int size = 75;
    int width = MeasureText(title, size);
    DrawText(title, window.x/2-width/2, window.y/2-size/2, size, VIOLET);

    EndDrawing();
}

void draw_lose(void)
{
    BeginDrawing();
    ClearBackground(DARKBLUE);

    const char *title = "Un guarde ta attraper";
    int size = 75;
    int width = MeasureText(title, size);
    DrawText(title, window.x/2-width/2, window.y/2-size/2, size, RED);

    EndDrawing();
}

void draw_end(void)
{
    BeginDrawing();
    ClearBackground(DARKBLUE);

    const char *title = "GG ta finit le ju";
    int size = 75;
    int width = MeasureText(title, size);
    DrawText(title, window.x/2-width/2, window.y/2-size/2, size, GOLD);

    if (end_time_game < 0.1) end_time_game = GetTime()-begin_play_time;

    title = TextFormat("temps: %d", (int)(end_time_game));
    size = 50;
    width = MeasureText(title, size);
    DrawText(title, window.x/2-width/2, window.y/2+size, size, GOLD);

    EndDrawing();
}

void draw_tuto(void)
{
    int scale = 7;
    int y_offset = 32;

    int key_frame = 4;
    Vec2i key_size = {32, 20};
    int key_offset = ((int)(GetTime()*5)%key_frame)*key_size.x;

    int space_frame = 2;
    Vec2i space_size = {28, 13};
    int space_offset = ((int)(GetTime()*5)%space_frame)*space_size.x;

    DrawTexturePro(
        tex_tuto_key,
        (Rectangle) {key_offset, 0, key_size.x, key_size.y},
        (Rectangle) {
            window.x/2 - space_size.x*scale/2 - key_size.x*(scale+1)/2,
            y_offset,
            key_size.x*scale,
            key_size.y*scale
        },
        (Vector2)   {0, 0},
        0, WHITE
    );

    DrawTexturePro(
        tex_tuto_space,
        (Rectangle) {space_offset, 0, space_size.x, space_size.y},
        (Rectangle) {
            window.x/2 - space_size.x*scale/2 + key_size.x*(scale+1)/2,
            y_offset + key_size.y*scale - space_size.y*scale,
            space_size.x*scale,
            space_size.y*scale
        },
        (Vector2)   {0, 0},
        0, WHITE
    );
}

Vec2i find_player(Map map, Vec2i map_size)
{
    for (int x=0; x<map_size.x; x++) {
        for (int y=0; y<map_size.y; y++) {
            if (map[y][x] == 'S') {
                return (Vec2i){x, y};
            }
        }
    }
    return (Vec2i){0, 0};
}

void load_map(Map *map, Vec2i *map_size, int etage)
{
    door_open = 0;
    *map_size = map_sizes[etage];
    memcpy(map, maps[etage], (map_size->x)*(map_size->y)*sizeof(char)*2 /*jsp pk il faut 2*/);

    for (int g=0; g<MAX_GUARD_ETAGE; g++) {
        Guard guard = guards[etage][g];

        if (guard.start.x == guard.end.x && guard.start.y == guard.end.y) {
            guards[etage][g].dead = 1;
            continue;
        }

        if (guard.start.x == guard.end.x && guard.start.y < guard.end .y) guards[etage][g].dir = BAS;
        if (guard.start.x == guard.end.x && guard.start.y > guard.end .y) guards[etage][g].dir = HAUT;
        if (guard.start.x < guard.end.x && guard.start.y == guard.end .y) guards[etage][g].dir = DROITE;
        if (guard.start.x > guard.end.x && guard.start.y == guard.end .y) guards[etage][g].dir = GAUCHE;

        guards[etage][g].pos = guard.start;
        guards[etage][g].dead = 0;
    }
}

int update_player_move(Map map, Vec2i map_size)
{
    Vec2i old_pos = find_player(map, map_size);
    Vec2i new_pos = old_pos;

    if (IsKeyPressed(KEY_LEFT))  new_pos.x--;
    if (IsKeyPressed(KEY_RIGHT)) new_pos.x++;
    if (IsKeyPressed(KEY_DOWN))  new_pos.y++;
    if (IsKeyPressed(KEY_UP))    new_pos.y--;

    if (
        new_pos.x < 0 ||
        new_pos.y < 0 ||
        new_pos.x >= map_size.x ||
        new_pos.y >= map_size.y
    ) return 0;

    if (door_open && map[new_pos.y][new_pos.x] == 'P') {
        level_finished = 1.2;
        etage++;
    }

    for (int g=0; g<MAX_GUARD_ETAGE; g++) {
        Guard guard = guards[etage][g];
        if (!guard.dead && vec2i_equals(guard.pos, new_pos)) return 0;
    }

    if (map[new_pos.y][new_pos.x] == ' ') {
        map[new_pos.y][new_pos.x] = 'S';
        map[old_pos.y][old_pos.x] = ' ';

        return 1;
    }

    return 0;
}

void update_guard_pos(void)
{
    for (int g=0; g<MAX_GUARD_ETAGE; g++) {
        Guard *guard = &guards[etage][g];
        if (guard->dead) continue;

        switch (guard->dir) {
            case HAUT:   guard->pos.y--; break;
            case BAS:    guard->pos.y++; break;
            case GAUCHE: guard->pos.x--; break;
            case DROITE: guard->pos.x++; break;
        }

        if (vec2i_equals(guard->pos, guard->start) || vec2i_equals(guard->pos, guard->end)) {
            switch (guard->dir) {
                case HAUT:   guard->dir = BAS; break;
                case BAS:    guard->dir = HAUT; break;
                case GAUCHE: guard->dir = DROITE; break;
                case DROITE: guard->dir = GAUCHE; break;
            }
        }
    }
}

void check_player_dead(Map map, Vec2i map_size)
{
    Vec2i player = find_player(map, map_size);
    for (int g=0; g<MAX_GUARD_ETAGE; g++) {
        Guard guard = guards[etage][g];
        if (!guard.dead && vec2i_equals(player, guard.pos)) {
            player_dead = 1.2;
            return;
        }
    }

    for (int x=player.x; x<map_size.x; x++) {
        if (map[player.y][x] != ' ' && map[player.y][x] != 'S') break;
        for (int g=0; g<MAX_GUARD_ETAGE; g++) {
            Guard guard = guards[etage][g];
            if (!guard.dead && guard.dir != DROITE && vec2i_equals((Vec2i){x, player.y}, guard.pos)) {
                player_dead = 1.2;
                return;
            }
        }
    }
    for (int x=player.x; x>=0; x--) {
        if (map[player.y][x] != ' ' && map[player.y][x] != 'S') break;
        for (int g=0; g<MAX_GUARD_ETAGE; g++) {
            Guard guard = guards[etage][g];
            if (!guard.dead && guard.dir != GAUCHE && vec2i_equals((Vec2i){x, player.y}, guard.pos)) {
                player_dead = 1.2;
                return;
            }
        }
    }

    for (int y=player.y; y<map_size.y; y++) {
        if (map[y][player.x] != ' ' && map[y][player.x] != 'S') break;
        for (int g=0; g<MAX_GUARD_ETAGE; g++) {
            Guard guard = guards[etage][g];
            if (!guard.dead && guard.dir != BAS && vec2i_equals((Vec2i){player.x, y}, guard.pos)) {
                player_dead = 1.2;
                return;
            }
        }
    }
    for (int y=player.y; y>=0; y--) {
        if (map[y][player.x] != ' ' && map[y][player.x] != 'S') break;
        for (int g=0; g<MAX_GUARD_ETAGE; g++) {
            Guard guard = guards[etage][g];
            if (!guard.dead && guard.dir != HAUT && vec2i_equals((Vec2i){player.x, y}, guard.pos)) {
                player_dead = 1.2;
                return;
            }
        }
    }
}

int kill(Map map, Vec2i pos)
{
    int killed = 1;
    switch (map[pos.y][pos.x]) {
    case 'G':
        map[pos.y][pos.x] = ' ';
        break;
    case 'O':
        map[pos.y][pos.x] = ' ';
        break;
    case 'B':
        map[pos.y][pos.x] = ' ';
        door_open = 1;
        break;
    case 'A':
        map[pos.y][pos.x] = ' ';
        alarme_sonne = 1.2;
        shoot_cooldown = 0;
        break;
    default:
        killed = 0;
        break;
    }

    for (int g=0; g<MAX_GUARD_ETAGE; g++) {
        Guard guard = guards[etage][g];
        if (!guard.dead && vec2i_equals(pos, guard.pos)) {
            guards[etage][g].dead = 1;
            return 1;
        }
    }

    return killed;
}

Shot shoot(Map map, Vec2i map_size, Vec2i from)
{
    Shot shot;

    int up = from.y;
    for (; up>0 && map[up-1][from.x] != 'M'; up--)
        if (kill(map, (Vec2i){from.x, up-1})) break;
    shot.up = up;

    int down = from.y;
    for (; down<map_size.y-1 && map[down+1][from.x] != 'M'; down++)
        if (kill(map, (Vec2i){from.x, down+1})) break;
    shot.down = down;

    int left = from.x;
    for (; left>0 && map[from.y][left-1] != 'M'; left--)
        if (kill(map, (Vec2i){left-1, from.y})) break;
    shot.left = left;

    int right = from.x;
    for (; right<map_size.x-1 && map[from.y][right+1] != 'M'; right++)
        if (kill(map, (Vec2i){right+1, from.y})) break;
    shot.right = right;

    return shot;
}

int main(void)
{
#ifdef RELEASE
    SetTraceLogLevel(LOG_FATAL);
#endif
    SetRandomSeed(69);
    InitWindow(window.x, window.y, "DIRANOCACASORUS");

#ifdef RELEASE
    SetWindowState(FLAG_FULLSCREEN_MODE);
#else
    SetWindowState(FLAG_WINDOW_RESIZABLE);
#endif
    SetTargetFPS(60);

#ifdef RELEASE
#include "assets/fond.h"
    Image img_fond = LoadImageFromMemory(".png", menufond_data, menufond_size);
    tex_fond = LoadTextureFromImage(img_fond);
#else
    tex_fond = LoadTexture("assets/fond.png");
#endif

    InitAudioDevice();

#ifdef RELEASE
#include "assets/bittersweet.h"
    Wave wave_musique = LoadWaveFromMemory(".mp3", musique_data, musique_size);
    Sound musique = LoadSoundFromWave(wave_musique);
#else
    Sound musique = LoadSound("assets/bittersweet.mp3");
#endif
    PlaySound(musique);

    Vec2i map_size;
    Map map;
    load_map(&map, &map_size, etage);

#ifdef RELEASE
#include "assets/Keyboard_Arrows.h"
    Image img_tuto_key = LoadImageFromMemory(".png", tuto_key_data, tuto_key_size);
    tex_tuto_key = LoadTextureFromImage(img_tuto_key);
#include "assets/Keyboard_Space.h"
    Image img_tuto_space = LoadImageFromMemory(".png", tuto_space_data, tuto_space_size);
    tex_tuto_space = LoadTextureFromImage(img_tuto_space);
#else
    tex_tuto_key = LoadTexture("assets/Keyboard_Arrows.png");
    tex_tuto_space = LoadTexture("assets/Keyboard_Space.png");
#endif

    while (!WindowShouldClose()) {
        if (IsWindowResized()) {
            window.x = GetScreenWidth();
            window.y = GetScreenHeight();
        }

        if (!IsSoundPlaying(musique)) PlaySound(musique);

        if (in_menu) {
            draw_menu();
            continue;
        }

        if (alarme_sonne > 0) {
            alarme_sonne -= GetFrameTime();
            load_map(&map, &map_size, etage); // NOTE: memcpy pendant 1.2s mais flemme
            draw_alarme_lose();
            continue;
        }

        if (player_dead > 0) {
            player_dead -= GetFrameTime();
            load_map(&map, &map_size, etage); // NOTE: memcpy pendant 1.2s mais flemme
            draw_lose();
            continue;
        }

        Vec2i player_pos = find_player(map, map_size);

        static Shot shot;
        if (IsKeyPressed(KEY_SPACE)) {
            shoot_cooldown = 0.12;
            shot = shoot(map, map_size, player_pos);
            update_guard_pos();
        }

        check_player_dead(map, map_size);

        if (shoot_cooldown > 0) {
            shoot_cooldown -= GetFrameTime();
        }
        if (shoot_cooldown <= 0) {
            int moved = update_player_move(map, map_size);
            if (moved) {
                update_guard_pos();
            }
        }

        if (level_finished > 0) {
            level_finished -= GetFrameTime();
            load_map(&map, &map_size, etage); // NOTE: memcpy pendant 1.2s mais flemme
            draw_win();
            continue;
        }

        if (level_finished > -2.7 && etage >= NB_ETAGE_TOTAL) {
            level_finished -= GetFrameTime();
            draw_end();
            continue;
        }

        if (IsKeyDown(KEY_LEFT_SHIFT) && IsKeyDown(KEY_LEFT_CONTROL) && IsKeyDown(KEY_E)) etage = NB_ETAGE_TOTAL + 69;

        if (etage >= NB_ETAGE_TOTAL) {
            win_fun();
            continue;
        }

        BeginDrawing();
        ClearBackground(DARKBLUE);

        if (etage == 0) {
            draw_tuto();
        }

        int mult = 50;
        int size = 50;
        Vec2i offset;
        offset.x = window.x/2 - map_size.x*size/2;
        offset.y = window.y/2 - map_size.y*size/2;

        for (int x=0; x<map_size.x; x++) {
            for (int y=0; y<map_size.y; y++) {
                int is_danger = 0;
                for (int g=0; g<MAX_GUARD_ETAGE; g++) {
                    Guard guard = guards[etage][g];
                    if (guard.dead) continue;
                    Vec2i pos = {x,y};
                    if (vec2i_equals(pos, guard.pos)) goto skip;
                    if (vec2i_equals(pos, guard.start) && !vec2i_equals(pos, guard.start)) goto skip;
                    if (vec2i_equals(pos, guard.end) && !vec2i_equals(pos, guard.end)) goto skip;

                    if (pos.x == guard.pos.x) {
                        if (pos.y > guard.pos.y && guard.dir == HAUT) continue;
                        if (pos.y < guard.pos.y && guard.dir == BAS) continue;
                        int min = pos.y < guard.pos.y ? pos.y : guard.pos.y;
                        int max = pos.y > guard.pos.y ? pos.y : guard.pos.y;
                        int walled = 0;
                        for (int i=min; i<max; i++) {
                            if (map[i][x] == 'M' || map[i][x] == 'O' || map[i][x] == 'A') {
                                walled = 1;
                                break;
                            }
                        }
                        if (!walled) is_danger = 1;
                    }
                    else if (pos.y == guard.pos.y) {
                        if (pos.x > guard.pos.x && guard.dir == GAUCHE) continue;
                        if (pos.x < guard.pos.x && guard.dir == DROITE) continue;
                        int min = pos.x < guard.pos.x ? pos.x : guard.pos.x;
                        int max = pos.x > guard.pos.x ? pos.x : guard.pos.x;
                        int walled = 0;
                        for (int i=min; i<max; i++) {
                            if (map[y][i] == 'M' || map[y][i] == 'O' || map[y][i] == 'A') {
                                walled = 1;
                                break;
                            }
                        }
                        if (!walled) is_danger = 1;
                    }
                }

                switch (map[y][x]) {
                case 'M':
                    DrawText("M", offset.x+x*mult, offset.y+y*mult, size, GOLD);
                    break;
                case 'O':
                    DrawText("O", offset.x+x*mult, offset.y+y*mult, size, YELLOW);
                    break;
                case 'P':
                    if (!door_open) DrawText("P", offset.x+x*mult, offset.y+y*mult, size, BLACK);
                    else            DrawText("E", offset.x+x*mult, offset.y+y*mult, size, LIME);
                    break;
                case 'B':
                    DrawText("B", offset.x+x*mult, offset.y+y*mult, size, WHITE);
                    break;
                case 'S':
                    DrawText("S", offset.x+x*mult, offset.y+y*mult, size, GREEN);
                    break;
                case 'G':
                    DrawText("G", offset.x+x*mult, offset.y+y*mult, size, RED);
                    break;
                case 'A':
                    DrawText("A", offset.x+x*mult, offset.y+y*mult, size, VIOLET);
                    break;
                default:
                    DrawText("-", offset.x+x*mult, offset.y+y*mult, size, is_danger?RED:WHITE);
                    break;
                }
skip:
            }
        }

        for (int i = 0; i < MAX_GUARD_ETAGE; i++) {
            Guard guard = guards[etage][i];

            if (guard.dead) continue;

            DrawText("G", offset.x+guard.pos.x*mult, offset.y+guard.pos.y*mult, size, RED);

            int to_end = 0;
            switch (guard.dir) {
                case HAUT:   to_end = guard.pos.y <= guard.end.y; break;
                case BAS:    to_end = guard.pos.y > guard.end.y; break;
                case GAUCHE: to_end = guard.pos.x <= guard.end.x; break;
                case DROITE: to_end = guard.pos.x > guard.end.x; break;
            }

            if (!vec2i_equals(guard.pos, guard.start) && !vec2i_equals(guard.start, player_pos))
                DrawText("+", offset.x+guard.start.x*mult, offset.y+guard.start.y*mult, size, !to_end?RED:ORANGE);
            if (!vec2i_equals(guard.pos, guard.end) && !vec2i_equals(guard.end, player_pos))
                DrawText("+", offset.x+guard.end.x*mult, offset.y+guard.end.y*mult, size, to_end?RED:ORANGE);
        }

        if (shoot_cooldown > 0) {
            int up    = shot.up;
            int down  = shot.down;
            int left  = shot.left;
            int rigth = shot.right;
            if (up    != player_pos.y) for (int i=player_pos.y-1; i>=up; i--)    DrawText("@", offset.x+player_pos.x*mult, offset.y+i*mult, size, ORANGE);
            if (down  != player_pos.y) for (int i=player_pos.y+1; i<=down; i++)  DrawText("@", offset.x+player_pos.x*mult, offset.y+i*mult, size, ORANGE);
            if (left  != player_pos.x) for (int i=player_pos.x-1; i>=left; i--)  DrawText("@", offset.x+i*mult, offset.y+player_pos.y*mult, size, ORANGE);
            if (rigth != player_pos.x) for (int i=player_pos.x+1; i<=rigth; i++) DrawText("@", offset.x+i*mult, offset.y+player_pos.y*mult, size, ORANGE);
        }

        DrawText(TextFormat("Étage %d/%d", etage+1, NB_ETAGE_TOTAL), 10, 10, size/2, RAYWHITE);

        DrawText(TextFormat("Timer: %d", (int)(GetTime()-begin_play_time)), 10, 20+size/2, size/2, RAYWHITE);

        EndDrawing();
    }
}
