/*
 * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
 * All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.
 *
 * Author: Alec Thomas <alec@swapoff.org>
 */

#define CATCH_CONFIG_MAIN

#include <algorithm>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
#include <set>
#include <map>
#include "entityx/3rdparty/catch.hpp"
#include "entityx/entityx.h"

// using namespace std;
using namespace entityx;

using std::ostream;
using std::vector;
using std::set;
using std::map;
using std::pair;
using std::string;

template <typename T>
int size(const T &t) {
  int n = 0;
  for (auto i : t) {
    ++n;
    (void)i;  // Unused on purpose, suppress warning
  }
  return n;
}

struct Position {
  Position(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}

  bool operator==(const Position &other) const {
    return x == other.x && y == other.y;
  }

  float x, y;
};

ostream &operator<<(ostream &out, const Position &position) {
  out << "Position(" << position.x << ", " << position.y << ")";
  return out;
}

struct Direction {
  Direction(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}

  bool operator==(const Direction &other) const {
    return x == other.x && y == other.y;
  }

  float x, y;
};

ostream &operator<<(ostream &out, const Direction &direction) {
  out << "Direction(" << direction.x << ", " << direction.y << ")";
  return out;
}

struct Tag : Component<Tag> {
  explicit Tag(string tag) : tag(tag) {}

  bool operator==(const Tag &other) const { return tag == other.tag; }

  string tag;
};

ostream &operator<<(ostream &out, const Tag &tag) {
  out << "Tag(" << tag.tag << ")";
  return out;
}


struct EntityManagerFixture {
  EntityManagerFixture() : em(ev) {}

  EventManager ev;
  EntityManager em;
};


TEST_CASE_METHOD(EntityManagerFixture, "TestCreateEntity") {
  REQUIRE(em.size() ==  0UL);

  Entity e2;
  REQUIRE(!(e2.valid()));

  Entity e = em.create();
  REQUIRE(e.valid());
  REQUIRE(em.size() ==  1UL);

  e2 = e;
  REQUIRE(e2.valid());
}

TEST_CASE_METHOD(EntityManagerFixture, "TestEntityAsBoolean") {
  REQUIRE(em.size() ==  0UL);
  Entity e = em.create();
  REQUIRE(e.valid());
  REQUIRE(em.size() ==  1UL);
  REQUIRE(!(!e));

  e.destroy();

  REQUIRE(em.size() ==  0UL);

  REQUIRE(!e);

  Entity e2;  // Not initialized
  REQUIRE(!e2);
}

TEST_CASE_METHOD(EntityManagerFixture, "TestEntityReuse") {
  Entity e1 = em.create();
  Entity e2 = e1;
  auto id = e1.id();
  REQUIRE(e1.valid());
  REQUIRE(e2.valid());
  e1.destroy();
  REQUIRE(!e1.valid());
  REQUIRE(!e2.valid());
  Entity e3 = em.create();
  // It is assumed that the allocation will reuse the same entity id, though
  // the version will change.
  auto new_id = e3.id();
  REQUIRE(new_id !=  id);
  REQUIRE((new_id.id() & 0xffffffffUL) == (id.id() & 0xffffffffUL));
}

TEST_CASE_METHOD(EntityManagerFixture, "TestComponentConstruction") {
  auto e = em.create();
  auto p = e.assign<Position>(1, 2);
  auto cp = e.component<Position>();
  REQUIRE(p ==  cp);
  REQUIRE(1.0 == Approx(cp->x));
  REQUIRE(2.0 == Approx(cp->y));
}

TEST_CASE_METHOD(EntityManagerFixture, "TestDestroyEntity") {
  Entity e = em.create();
  Entity f = em.create();
  e.assign<Position>();
  f.assign<Position>();
  e.assign<Direction>();
  f.assign<Direction>();

  REQUIRE(e.valid());
  REQUIRE(f.valid());
  REQUIRE(static_cast<bool>(e.component<Position>()));
  REQUIRE(static_cast<bool>(e.component<Direction>()));
  REQUIRE(static_cast<bool>(f.component<Position>()));
  REQUIRE(static_cast<bool>(f.component<Direction>()));

  e.destroy();

  REQUIRE(!(e.valid()));
  REQUIRE(f.valid());
  REQUIRE(static_cast<bool>(f.component<Position>()));
  REQUIRE(static_cast<bool>(f.component<Direction>()));
}

TEST_CASE_METHOD(EntityManagerFixture, "TestGetEntitiesWithComponent") {
  Entity e = em.create();
  Entity f = em.create();
  Entity g = em.create();
  e.assign<Position>();
  e.assign<Direction>();
  f.assign<Position>();
  g.assign<Position>();
  REQUIRE(3 ==  size(em.entities_with_components<Position>()));
  REQUIRE(1 ==  size(em.entities_with_components<Direction>()));
}

TEST_CASE_METHOD(EntityManagerFixture, "TestGetEntitiesWithIntersectionOfComponents") {
  vector<Entity> entities;
  for (int i = 0; i < 150; ++i) {
    Entity e = em.create();
    entities.push_back(e);
    if (i % 2 == 0) e.assign<Position>();
    if (i % 3 == 0) e.assign<Direction>();
  }
  REQUIRE(50 ==  size(em.entities_with_components<Direction>()));
  REQUIRE(75 ==  size(em.entities_with_components<Position>()));
  REQUIRE(25 ==  size(em.entities_with_components<Direction, Position>()));
}

TEST_CASE_METHOD(EntityManagerFixture, "TestGetEntitiesWithComponentAndUnpacking") {
  vector<Entity::Id> entities;
  Entity e = em.create();
  Entity f = em.create();
  Entity g = em.create();
  std::vector<std::pair<ComponentHandle<Position>, ComponentHandle<Direction>>> position_directions;
  position_directions.push_back(std::make_pair(
      e.assign<Position>(1.0f, 2.0f), e.assign<Direction>(3.0f, 4.0f)));
  position_directions.push_back(std::make_pair(
      f.assign<Position>(7.0f, 8.0f), f.assign<Direction>(9.0f, 10.0f)));
  auto thetag = f.assign<Tag>("tag");
  g.assign<Position>(5.0f, 6.0f);
  int i = 0;

  ComponentHandle<Position> position;
  REQUIRE(3 ==  size(em.entities_with_components(position)));

  ComponentHandle<Direction> direction;
  for (auto unused_entity : em.entities_with_components(position, direction)) {
    (void)unused_entity;
    REQUIRE(position);
    REQUIRE(direction);
    auto pd = position_directions.at(i);
    REQUIRE(position ==  pd.first);
    REQUIRE(direction ==  pd.second);
    ++i;
  }
  REQUIRE(2 ==  i);
  ComponentHandle<Tag> tag;
  i = 0;
  for (auto unused_entity :
       em.entities_with_components(position, direction, tag)) {
    (void)unused_entity;
    REQUIRE(static_cast<bool>(position));
    REQUIRE(static_cast<bool>(direction));
    REQUIRE(static_cast<bool>(tag));
    auto pd = position_directions.at(1);
    REQUIRE(position ==  pd.first);
    REQUIRE(direction ==  pd.second);
    REQUIRE(tag ==  thetag);
    i++;
  }
  REQUIRE(1 ==  i);
}

TEST_CASE_METHOD(EntityManagerFixture, "TestIterateAllEntitiesSkipsDestroyed") {
  Entity a = em.create();
  Entity b = em.create();
  Entity c = em.create();

  b.destroy();

  auto it = em.entities_for_debugging().begin();
  REQUIRE(a.id() == (*it).id());
  ++it;
  REQUIRE(c.id() == (*it).id());
}

TEST_CASE_METHOD(EntityManagerFixture, "TestUnpack") {
  Entity e = em.create();
  auto p = e.assign<Position>(1.0, 2.0);
  auto d = e.assign<Direction>(3.0, 4.0);
  auto t = e.assign<Tag>("tag");

  ComponentHandle<Position> up;
  ComponentHandle<Direction> ud;
  ComponentHandle<Tag> ut;
  e.unpack(up);
  REQUIRE(p ==  up);
  e.unpack(up, ud);
  REQUIRE(p ==  up);
  REQUIRE(d ==  ud);
  e.unpack(up, ud, ut);
  REQUIRE(p ==  up);
  REQUIRE(d ==  ud);
  REQUIRE(t ==  ut);
}

// gcc 4.7.2 does not allow this struct to be declared locally inside the
// TEST_CASE_METHOD.EntityManagerFixture, " //" TEST_CASE_METHOD(EntityManagerFixture, "TestUnpackNullMissing") {
//   Entity e = em.create();
//   auto p = e.assign<Position>();

//   std::shared_ptr<Position> up(reinterpret_cast<Position*>(0Xdeadbeef),
// NullDeleter());
//   std::shared_ptr<Direction> ud(reinterpret_cast<Direction*>(0Xdeadbeef),
// NullDeleter());
//   e.unpack<Position, Direction>(up, ud);
//   REQUIRE(p ==  up);
//   REQUIRE(std::shared_ptr<Direction>() ==  ud);
// }

TEST_CASE_METHOD(EntityManagerFixture, "TestComponentIdsDiffer") {
  REQUIRE(EntityManager::component_family<Position>() !=  EntityManager::component_family<Direction>());
}

TEST_CASE_METHOD(EntityManagerFixture, "TestEntityCreatedEvent") {
  struct EntityCreatedEventReceiver
      : public Receiver<EntityCreatedEventReceiver> {
    void receive(const EntityCreatedEvent &event) {
      created.push_back(event.entity);
    }

    vector<Entity> created;
  };

  EntityCreatedEventReceiver receiver;
  ev.subscribe<EntityCreatedEvent>(receiver);

  REQUIRE(0UL ==  receiver.created.size());
  for (int i = 0; i < 10; ++i) {
    em.create();
  }
  REQUIRE(10UL ==  receiver.created.size());
}

TEST_CASE_METHOD(EntityManagerFixture, "TestEntityDestroyedEvent") {
  struct EntityDestroyedEventReceiver
      : public Receiver<EntityDestroyedEventReceiver> {
    void receive(const EntityDestroyedEvent &event) {
      destroyed.push_back(event.entity);
    }

    vector<Entity> destroyed;
  };

  EntityDestroyedEventReceiver receiver;
  ev.subscribe<EntityDestroyedEvent>(receiver);

  REQUIRE(0UL ==  receiver.destroyed.size());
  vector<Entity> entities;
  for (int i = 0; i < 10; ++i) {
    entities.push_back(em.create());
  }
  REQUIRE(0UL ==  receiver.destroyed.size());
  for (auto e : entities) {
    e.destroy();
  }
  REQUIRE(10UL == receiver.destroyed.size());
  REQUIRE(entities ==  receiver.destroyed);
}

TEST_CASE_METHOD(EntityManagerFixture, "TestComponentAddedEvent") {
  struct ComponentAddedEventReceiver
      : public Receiver<ComponentAddedEventReceiver> {

    ComponentAddedEventReceiver()
      : position_events(0), direction_events(0) {}

    void receive(const ComponentAddedEvent<Position> &event) {
      auto p = event.component;
      float n = static_cast<float>(position_events);
      REQUIRE(p->x ==  n);
      REQUIRE(p->y ==  n);
      position_events++;
    }

    void receive(const ComponentAddedEvent<Direction> &event) {
      auto p = event.component;
      float n = static_cast<float>(direction_events);
      REQUIRE(p->x ==  -n);
      REQUIRE(p->y ==  -n);
      direction_events++;
    }

    int position_events;
    int direction_events;
  };

  ComponentAddedEventReceiver receiver;
  ev.subscribe<ComponentAddedEvent<Position>>(receiver);
  ev.subscribe<ComponentAddedEvent<Direction>>(receiver);

  REQUIRE(ComponentAddedEvent<Position>::family() !=
            ComponentAddedEvent<Direction>::family());

  REQUIRE(0 ==  receiver.position_events);
  REQUIRE(0 ==  receiver.direction_events);
  for (int i = 0; i < 10; ++i) {
    Entity e = em.create();
    e.assign<Position>(static_cast<float>(i), static_cast<float>(i));
    e.assign<Direction>(static_cast<float>(-i), static_cast<float>(-i));
  }
  REQUIRE(10 ==  receiver.position_events);
  REQUIRE(10 ==  receiver.direction_events);
}


TEST_CASE_METHOD(EntityManagerFixture, "TestComponentRemovedEvent") {
  struct ComponentRemovedReceiver : public Receiver<ComponentRemovedReceiver> {
    void receive(const ComponentRemovedEvent<Direction> &event) {
      removed = event.component;
    }

    ComponentHandle<Direction> removed;
  };

  ComponentRemovedReceiver receiver;
  ev.subscribe<ComponentRemovedEvent<Direction>>(receiver);

  REQUIRE(!(receiver.removed));
  Entity e = em.create();
  ComponentHandle<Direction> p = e.assign<Direction>(1.0, 2.0);
  e.remove<Direction>();
  REQUIRE(receiver.removed ==  p);
  REQUIRE(!(e.component<Direction>()));
}

TEST_CASE_METHOD(EntityManagerFixture, "TestEntityAssignment") {
  Entity a, b;
  a = em.create();
  REQUIRE(a !=  b);
  b = a;
  REQUIRE(a ==  b);
  a.invalidate();
  REQUIRE(a !=  b);
}

TEST_CASE_METHOD(EntityManagerFixture, "TestEntityDestroyAll") {
  Entity a = em.create(), b = em.create();
  em.reset();
  REQUIRE(!(a.valid()));
  REQUIRE(!(b.valid()));
}

TEST_CASE_METHOD(EntityManagerFixture, "TestEntityDestroyHole") {
  std::vector<Entity> entities;

  auto count = [this]()->int {
    auto e = em.entities_with_components<Position>();
    return std::count_if(e.begin(), e.end(),
                         [](const Entity &) { return true; });
  };

  for (int i = 0; i < 5000; i++) {
    auto e = em.create();
    e.assign<Position>();
    entities.push_back(e);
  }

  REQUIRE(count() ==  5000);

  entities[2500].destroy();

  REQUIRE(count() ==  4999);
}

// TODO(alec): Disable this on OSX - it doesn't seem to be possible to catch it?!?
// TEST_CASE_METHOD(EntityManagerFixture, "DeleteComponentThrowsBadAlloc") {
//   Position *position = new Position();
//   REQUIRE_THROWS_AS(delete position, std::bad_alloc);
// }


TEST_CASE_METHOD(EntityManagerFixture, "TestComponentHandleInvalidatedWhenEntityDestroyed") {
  Entity a = em.create();
  ComponentHandle<Position> position = a.assign<Position>(1, 2);
  REQUIRE(position);
  REQUIRE(position->x == 1);
  REQUIRE(position->y == 2);
  a.destroy();
  REQUIRE(!position);
}

struct CopyVerifier : Component<CopyVerifier> {
  CopyVerifier() : copied(false) {}
  CopyVerifier(const CopyVerifier &other) {
    copied = other.copied + 1;
  }

  int copied;
};

TEST_CASE_METHOD(EntityManagerFixture, "TestComponentAssignmentFromCopy") {
  Entity a = em.create();
  CopyVerifier original;
  ComponentHandle<CopyVerifier> copy = a.assign_from_copy(original);
  REQUIRE(copy);
  REQUIRE(copy->copied == 1);
  a.destroy();
  REQUIRE(!copy);
}

TEST_CASE_METHOD(EntityManagerFixture, "TestComponentHandleInvalidatedWhenComponentDestroyed") {
  Entity a = em.create();
  ComponentHandle<Position> position = a.assign<Position>(1, 2);
  REQUIRE(position);
  REQUIRE(position->x == 1);
  REQUIRE(position->y == 2);
  a.remove<Position>();
  REQUIRE(!position);
}

TEST_CASE_METHOD(EntityManagerFixture, "TestDeleteEntityWithNoComponents") {
  Entity a = em.create();
  a.assign<Position>(1, 2);
  Entity b = em.create();
  b.destroy();
}

TEST_CASE_METHOD(EntityManagerFixture, "TestEntityInStdSet") {
  Entity a = em.create();
  Entity b = em.create();
  Entity c = em.create();
  set<Entity> entitySet;
  REQUIRE(entitySet.insert(a).second);
  REQUIRE(entitySet.insert(b).second);
  REQUIRE(entitySet.insert(c).second);
}

TEST_CASE_METHOD(EntityManagerFixture, "TestEntityInStdMap") {
  Entity a = em.create();
  Entity b = em.create();
  Entity c = em.create();
  map<Entity, int> entityMap;
  REQUIRE(entityMap.insert(pair<Entity, int>(a, 1)).second);
  REQUIRE(entityMap.insert(pair<Entity, int>(b, 2)).second);
  REQUIRE(entityMap.insert(pair<Entity, int>(c, 3)).second);
  REQUIRE(entityMap[a] == 1);
  REQUIRE(entityMap[b] == 2);
  REQUIRE(entityMap[c] == 3);
}

TEST_CASE_METHOD(EntityManagerFixture, "TestEntityComponentsFromTuple") {
  Entity e = em.create();
  e.assign<Position>(1, 2);
  e.assign<Direction>(3, 4);

  std::tuple<ComponentHandle<Position>, ComponentHandle<Direction>> components = e.components<Position, Direction>();

  REQUIRE(std::get<0>(components)->x == 1);
  REQUIRE(std::get<0>(components)->y == 2);
  REQUIRE(std::get<1>(components)->x == 3);
  REQUIRE(std::get<1>(components)->y == 4);
}

TEST_CASE("TestComponentDestructorCalledWhenManagerDestroyed") {
  struct Freed {
    explicit Freed(bool &yes) : yes(yes) {}
    ~Freed() { yes = true; }

    bool &yes;
  };

  struct Test : Component<Test> {
    explicit Test(bool &yes) : freed(yes) {}

    Freed freed;
  };

  bool freed = false;
  {
    EntityX e;
    auto test = e.entities.create();
    test.assign<Test>(freed);
  }
  REQUIRE(freed == true);
}

TEST_CASE("TestComponentDestructorCalledWhenEntityDestroyed") {
  struct Freed {
    explicit Freed(bool &yes) : yes(yes) {}
    ~Freed() { yes = true; }

    bool &yes;
  };

  struct Test : Component<Test> {
    explicit Test(bool &yes) : freed(yes) {}

    Freed freed;
  };

  bool freed = false;
  EntityX e;
  auto test = e.entities.create();
  test.assign<Test>(freed);
  REQUIRE(freed == false);
  test.destroy();
  REQUIRE(freed == true);
}

TEST_CASE_METHOD(EntityManagerFixture, "TestComponentsRemovedFromReusedEntities") {
  Entity a = em.create();
  Entity::Id aid = a.id();
  a.assign<Position>(1, 2);
  a.destroy();

  Entity b = em.create();
  Entity::Id bid = b.id();

  REQUIRE(aid.index() == bid.index());
  REQUIRE(!b.has_component<Position>());
  b.assign<Position>(3, 4);
}

TEST_CASE_METHOD(EntityManagerFixture, "TestConstComponentsNotInstantiatedTwice") {
  Entity a = em.create();
  a.assign<Position>(1, 2);

  const Entity b = a;

  REQUIRE(a.component<Position>().valid());
  REQUIRE(b.component<const Position>().valid());
  REQUIRE(b.component<const Position>()->x == 1);
  REQUIRE(b.component<const Position>()->y == 2);
}
