//    Copyright 2025 Bill Rossi
//    
//    This file is part of Starship Futuretime
//
//    Starship Futuretime is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
//
//    Starship Futuretime is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License along with Starship Futuretime. If not, see <https://www.gnu.org/licenses/>. 
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <raylib.h>

#include "game.h"
#include "enemy.h"
#include "bullet.h"

#define FOREACH_ENEMY for (size_t i = 0; i < g->enemies->count; i++)
#define INIT_ENEMY Entity *e = g->enemies->entities[i]
#define FOREACH_BULLET for (size_t i = 0; i < g->bullets->count; i++)
#define INIT_BULLET Entity *e = g->bullets->entities[i]

void initialize_game(Game *g) {
  g->should_close = false;
  g->player = malloc(sizeof(Player));
  initialize_player(g, g->player);
  g->enemies = malloc(sizeof(Entities));
  g->enemies->entities = malloc(10 * sizeof(Entity*));
  g->enemies->count = 0;
  g->enemies->capacity = 10;
  g->bullets = malloc(sizeof(Entities));
  g->bullets->entities = malloc(10 * sizeof(Entity*));
  g->bullets->count = 0;
  g->bullets->capacity = 10;

  g->camera = malloc(sizeof(Camera));
  g->camera->zoom = 1.;
  g->camera->rotation = 0.;

  g->level = malloc(sizeof(Level));
  init_level(g, g->level, "levels/test_level.csv");

  g->player->position.y = g->level->length * 32;
}

void add_entity(Entities *entities, Entity *e) {
  if (entities->count >= entities->capacity) {
    entities->capacity *= 2;
    entities->entities = realloc(entities->entities, entities->capacity * sizeof(Entity*));
  }
  entities->entities[entities->count++] = e;
}

void remove_entity(Entities *entities, Entity *e) {
  size_t i;
  for (i = 0; entities->entities[i] != e; i++) {
    if (i > entities->count) {
      printf("Couldn't find entity %s\n", e->name);
      return;
    }
  }
  for (; i < entities->count - 1; i++) {
    entities->entities[i] = entities->entities[i + 1];
  }
  entities->count--;
}

void handle_input(Game *g) {
  handle_player_input(g->player);
  if (IsKeyPressed(KEY_S)) {
    add_entity(g->enemies, spawn_enemy());
  }
  if (IsKeyPressed(KEY_SPACE)) {
    add_entity(g->bullets, spawn_bullet(g->player->position, g->player->velocity, false));
  }
}

void process_bullet_collisions(Game *g, Entity *bullet) {
  BulletProperties *bullet_props = bullet->properties;
  Rectangle bullet_rec = bullet_props->position;
  if (bullet_props->from_enemy) {
    if (CheckCollisionPointRec(g->player->position, bullet_rec)) {
      remove_entity(g->bullets, bullet);
    }
  } else {
    FOREACH_ENEMY {
      INIT_ENEMY;
      Rectangle enemy_rec = ((EnemyProperties*)e->properties)->position;
      if (CheckCollisionRecs(bullet_rec, enemy_rec)) {
	remove_entity(g->bullets, bullet);
	remove_entity(g->enemies, e);
      }
    }
  }
}

void run_frame(Game *g) {
  float dt = GetFrameTime();
  handle_input(g);
  tick_player(g->player, dt);
  FOREACH_ENEMY {
    INIT_ENEMY;
    if (entity_expired(e)) {
      remove_entity(g->enemies, e);
      continue;
    }
    e->tick(g, e, GetFrameTime());
  }

  FOREACH_BULLET {
    INIT_BULLET;

    if (entity_expired(e)) {
      remove_entity(g->bullets, e);
      continue;
    }
    e->tick(g, e, GetFrameTime());
    process_bullet_collisions(g, e);
  }
}

void draw_frame(Game *g) {
  BeginDrawing();
  ClearBackground(RAYWHITE);
  BeginMode2D(*g->camera);
  draw_level(g->level);
  draw_player(g->player);
  FOREACH_ENEMY {
    INIT_ENEMY;
    e->draw(e);
  }
  FOREACH_BULLET {
    INIT_BULLET;
    e->draw(e);
  }
  EndMode2D();
  EndDrawing();
}

void run_until_closing(Game *g) {
  while (!WindowShouldClose() && !g->should_close) {
    run_frame(g);
    draw_frame(g);
  }
}