#include <stdlib.h>
#include <math.h>

#include "../include/enemy.h"
#include "../include/const.h"
#include "../include/entity.h"
#include "../include/effect.h"

enemy_t *enemy_create(pos_t pos, enemy_e type) {
  enemy_t *enemy = malloc(sizeof(enemy_t));
  *enemy = (enemy_t) {
    .pos = pos,
    .type = type,
    .on_ground = false,
    .velocity = 0.0,
    .gravity = 0.0,
    .dir = 1,
    .stunned = false,
    .frozen = false,
    .frozen_timer = 0,
    .fall_height = 0.0,
    .timer_walking = fz_timer_create(9, 8),
    .timer_sneaking = fz_timer_create(9, 16),
  };
  return enemy;
}

void enemy_update(enemy_t *enemy, game_t *game) {
  enemy->stunned = false;
  bool on_ground = false;
  // not beeing held
  if (game->player->held_enemy != enemy) {
    // detect on ground
    if (!(enemy->velocity > 0.0)) {
      for (int i = 0; i < game->tiles_len; i++) {
        tile_t *tile = game->tiles[i];
        if (tile_ground(tile)) {
          if (
            enemy->pos.x + PLAYER_WIDTH > tile->pos.x &&
            enemy->pos.x < tile->pos.x + TILE_WIDTH
          ) {
            float tolerance = 4.0;
            if (enemy->gravity > tolerance) {
              tolerance = enemy->gravity;
            }
            if (fabs(enemy->pos.y + PLAYER_HEIGHT - tile->pos.y) < tolerance) {
              enemy->pos.y = tile->pos.y - PLAYER_HEIGHT;
              on_ground = true;
              if (enemy->fall_height >= 2 * TILE_HEIGHT) {
                effect_play(enemy->pos, EFFECT_BREAK, game);
                effect_play(enemy->pos, EFFECT_PARTICLE, game);
                enemy_kill(enemy, game);
              }
            }
          }
        }
      }
    }
    enemy->on_ground = on_ground;
    if (enemy->on_ground) {
      enemy->gravity = 0.0;
      enemy->fall_height = 0.0;
    }
    else {
      enemy->pos.y += enemy->gravity;
      enemy->fall_height += enemy->gravity;
      if (enemy->gravity < PLAYER_GRAVITY) {
        enemy->gravity += 0.2;
      }
      else {
        enemy->gravity = PLAYER_GRAVITY;
      }
    }
    // handle shooting
    if (
      game->player->shooting &&
      game->player->target_entity != NULL &&
      game->player->target_entity->type == ENTITY_ENEMY &&
      game->player->target_entity->enemy == enemy
    ) {
      if (enemy->frozen) {
        bool is_colliding = false;
        rect_t enemy_rect = { enemy->pos.x, enemy->pos.y, ENEMY_WIDTH, ENEMY_HEIGHT };
        for (int i = 0; i < game->tiles_len; i++) {
          tile_t *tile = game->tiles[i];
          if (tile_wall(tile)) {
            rect_t tile_rect = { tile->pos.x, tile->pos.y, TILE_WIDTH, TILE_HEIGHT };
            if (rect_collide(enemy_rect, tile_rect)) {
              is_colliding = true;
              break;
            }
          }
        }
        if (!is_colliding) {
          if (game->player->dir > 0) {
            enemy->pos.x += 2.0;
          }
          else {
            enemy->pos.x -= 2.0;
          }
        }
      }
      else {
        enemy->stunned = true;
      }
    }
  }
  // handle freezing
  if (!enemy->frozen) {
    if (enemy->stunned) {
      if (enemy->frozen_timer >= 100) {
        enemy->frozen = true;
        enemy->frozen_timer = 0;
      }
      enemy->frozen_timer++;
    }
    else if (enemy->frozen_timer > 0) {
      enemy->frozen_timer--;
    }
  }
  else {
    if (enemy->frozen_timer >= 1500) {
      enemy->frozen = false;
      enemy->frozen_timer = 0;
      if (game->player->held_enemy == enemy) {
        game->player->held_enemy = NULL;
      }
    }
    enemy->frozen_timer++;
  }
  // handle movement
  tile_t *next_ground_tile = tile_get(game, enemy->pos.x + ENEMY_WIDTH / 2.0 + enemy->dir * ENEMY_WIDTH / 6.0, enemy->pos.y + ENEMY_HEIGHT + 1);
  if (!enemy->frozen) {
    if (enemy->on_ground && (next_ground_tile == NULL || next_ground_tile->type == TILE_AIR)) {
      enemy->dir = -enemy->dir;
    }
    if (enemy->stunned) {
      enemy->pos.x += enemy->dir * 0.1;
    }
    else {
      enemy->pos.x += enemy->dir * 0.5;
    }
  }
  // detect wall and direction change
  if (!enemy->frozen) {
    for (int i = 0; i < game->tiles_len; i++) {
      tile_t *tile = game->tiles[i];
      if (tile_wall(tile)) {
        if (
          enemy->pos.y + ENEMY_HEIGHT > tile->pos.y &&
          enemy->pos.y < tile->pos.y + TILE_HEIGHT
        ) {
          float tolerance = 4.0;
          if (enemy->dir > 0) {
            if (fabs((enemy->pos.x + ENEMY_WIDTH - 0.4 * TILE_WIDTH) - tile->pos.x) < tolerance) {
              enemy->dir = -enemy->dir;
            }
          }
          else {
            if (fabs((enemy->pos.x + 0.4 * TILE_WIDTH) - (tile->pos.x + TILE_WIDTH)) < tolerance) {
              enemy->dir = -enemy->dir;
            }
          }
        }
      }
    }
  }
  // fall out of map
  if (enemy->pos.y > TILE_HEIGHT * game->level->height) {
    pos_t pos = { enemy->pos.x, enemy->pos.y - ENEMY_HEIGHT + ENEMY_HEIGHT / 3.0 };
    effect_play(pos, EFFECT_PARTICLE, game);
    enemy_kill(enemy, game);
  }
  // update timer
  if (enemy->on_ground && !enemy->frozen) {
    fz_timer_update(enemy->timer_walking);
    fz_timer_update(enemy->timer_sneaking);
  }
  else {
    fz_timer_reset(enemy->timer_walking);
    fz_timer_reset(enemy->timer_sneaking);
  }
}

void enemy_draw(enemy_t *enemy, game_t *game) {
  pos_t pos = pos_snap(enemy->pos);
  int frame_walking = fz_timer_get(enemy->timer_walking);
  int frame_sneaking = fz_timer_get(enemy->timer_sneaking);
  if (enemy->dir > 0) {
    DrawTextureRec(game->assets.entities, texture_rect(enemy->stunned ? frame_sneaking : frame_walking, 10, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE);
  }
  else {
    DrawTextureRec(game->assets.entities, texture_rect(enemy->stunned ? frame_sneaking : frame_walking, 11, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE);
  }
  if (enemy->frozen) {
    if (enemy->frozen_timer <= 1200) {
      DrawTextureRec(game->assets.entities, texture_rect(3, 2, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE);
    }
    else if (enemy->frozen_timer <= 1350) {
      DrawTextureRec(game->assets.entities, texture_rect(4, 2, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE);
    }
    else if (enemy->frozen_timer <= 1450) {
      DrawTextureRec(game->assets.entities, texture_rect(5, 2, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE);
    }
    else {
      DrawTextureRec(game->assets.entities, texture_rect(6, 2, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE);
    }
  }
  else if (enemy->stunned) {
    if (enemy->frozen_timer <= 33) {
      DrawTextureRec(game->assets.entities, texture_rect(0, 2, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE);
    }
    else if (enemy->frozen_timer <= 66) {
      DrawTextureRec(game->assets.entities, texture_rect(1, 2, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE);
    }
    else {
      DrawTextureRec(game->assets.entities, texture_rect(2, 2, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE);
    }
  }
}

void enemy_kill(enemy_t *enemy, game_t *game) {
  for (int i = 0; i < game->entities_len; i++) {
    if (game->entities[i].type == ENTITY_ENEMY && game->entities[i].enemy == enemy) {
      if (i + 1 < game->entities_len) {
        for (int j = i; j < game->entities_len - 1; j++) {
          game->entities[j] = game->entities[j + 1];
        }
      }
      game->entities_len--;
      game->entities = realloc(game->entities, sizeof(entity_t) * game->entities_len);
      game->xp += 10;
      break;
    }
  }
}

void enemy_free(enemy_t *enemy) {
  fz_timer_free(enemy->timer_walking);
  fz_timer_free(enemy->timer_sneaking);
  free(enemy);
}
