#include <catch2/catch_test_macros.hpp>
#include <fmt/core.h>
#include <string>
#include "collider.hpp"
#include "dinkyecs.hpp"

using DinkyECS::Entity;
using namespace fmt;

EntityList require_found(const spatial_map& collider, Point at, bool diag, size_t expect_size) {
  println("TEST require_found at={},{}", at.x, at.y);
  auto [found, nearby] = collider.neighbors(at, diag);
  REQUIRE(found == true);
  REQUIRE(nearby.size() == expect_size);
  return nearby;
}


TEST_CASE("confirm basic collision operations", "[collision]") {
  DinkyECS::World world;
  Entity player = world.entity();
  Entity enemy = world.entity();

  spatial_map collider;
  collider.insert({11,11}, player);
  collider.insert({21,21}, enemy);

  { // not found
    auto [found, nearby] = collider.neighbors({1,1});
    REQUIRE(!found);
    REQUIRE(nearby.empty());
  }

  // found
  EntityList nearby = require_found(collider, {10,10}, true, 1);
  REQUIRE(nearby[0] == player);

  { // removed
    collider.remove({11,11});
    auto [found, nearby] = collider.neighbors({10,10}, true);
    REQUIRE(!found);
    REQUIRE(nearby.empty());
  }

  collider.insert({11,11}, player); // setup for the move test
  { // moving, not found
    collider.move({11,11}, {12, 12}, player);
    auto [found, nearby] = collider.neighbors({10,10}, true);
    REQUIRE(!found);
    REQUIRE(nearby.empty());
  }

  nearby = require_found(collider, {11,11}, true, 1);
  REQUIRE(nearby[0] == player);


  // confirm occupied works
  REQUIRE(collider.occupied({12,12}));
  REQUIRE(collider.occupied({21,21}));
  REQUIRE(!collider.occupied({1,10}));
}


TEST_CASE("confirm multiple entities moving", "[collision]") {
  DinkyECS::World world;
  Entity player = world.entity();
  Entity e1 = world.entity();
  Entity e2 = world.entity();
  Entity e3 = world.entity();

  spatial_map collider;
  collider.insert({11,11}, player);
  collider.insert({10,10}, e2);
  collider.insert({11,10}, e3);
  collider.insert({21,21}, e1);

  EntityList nearby = require_found(collider, {11,11}, false, 1);
  REQUIRE(nearby[0] == e3);

  nearby = require_found(collider, {11,11}, true, 2);
  REQUIRE(nearby[0] == e3);
  REQUIRE(nearby[1] == e2);

  collider.move({11,11}, {20,20}, player);
  nearby = require_found(collider, {20,20}, true, 1);
  REQUIRE(nearby[0] == e1);
}

TEST_CASE("test edge cases that might crash", "[collision]") {
  DinkyECS::World world;
  Entity player = world.entity();
  Entity enemy = world.entity();

  spatial_map collider;
  collider.insert({0,0}, player);

  Point enemy_at = {1, 0};
  collider.insert(enemy_at, enemy);

  EntityList nearby = require_found(collider, {0,0}, true, 1);

  collider.move({1,0}, {1,1}, enemy);
  nearby = require_found(collider, {0,0}, true, 1);
  REQUIRE(nearby[0] == enemy);

  collider.move({1,1}, {0,1}, enemy);
  nearby = require_found(collider, {0,0}, true, 1);
  REQUIRE(nearby[0] == enemy);
}

TEST_CASE("check all diagonal works", "[collision]") {
  DinkyECS::World world;
  Entity player = world.entity();
  Entity enemy = world.entity();

  spatial_map collider;
  Point player_at = {1,1};
  collider.insert(player_at, player);

  Point enemy_at = {1, 0};
  collider.insert(enemy_at, enemy);

  for(size_t x = 0; x <= 2; x++) {
    for(size_t y = 0; y <= 2; y++) {
      if(enemy_at.x == player_at.x && enemy_at.y == player_at.y) continue; // skip player spot
      EntityList nearby = require_found(collider, player_at, true, 1);
      REQUIRE(nearby[0] == enemy);

      // move the enemy to a new spot around the player
      Point move_to = {enemy_at.x + x, enemy_at.y + y};
      collider.move(enemy_at, move_to, enemy);
      enemy_at = move_to;
    }
  }
}
