#include <catch2/catch_test_macros.hpp>
#include <fmt/core.h>
#include <string>
#include "dinkyecs.hpp"
#include "components.hpp"
#include "save.hpp"
#include <optional>
#include <iostream>
#include "map.hpp"
#include "worldbuilder.hpp"
#include "tser.hpp"

using namespace fmt;
using std::string;
using namespace components;


enum class Item : char {
  RADAR = 'R',
  TRAP = 'T',
  ORE = 'O'
};

struct Pixel {
  int x = 0;
  int y = 0;

  DEFINE_SERIALIZABLE(Pixel, x, y);
};

struct Robot {
  Pixel point;
  std::wstring name;
  std::optional<Item> item;

  DEFINE_SERIALIZABLE(Robot, point, name, item);
};


TEST_CASE("test using tser for serialization", "[config]") {
  auto robot = Robot{ Pixel{3,4}, L"BIG NAME", Item::RADAR};

  tser::BinaryArchive archive;
  archive.save(robot);
  std::string_view archive_view = archive.get_buffer();

  tser::BinaryArchive archive2(0);
  archive2.initialize(archive_view);
  auto loadedRobot = archive2.load<Robot>();

  REQUIRE(loadedRobot.point.x == robot.point.x);
  REQUIRE(loadedRobot.point.y == robot.point.y);
  REQUIRE(loadedRobot.name == robot.name);
  REQUIRE(loadedRobot.item == robot.item);
}

TEST_CASE("basic save a world", "[save]") {
  DinkyECS::World world;
  Map map(20, 20);
  WorldBuilder builder(map);
  builder.generate();

  // configure a player as a fact of the world
  Player player{world.entity()};
  world.set_the<Player>(player);

  world.set<Position>(player.entity, {10,10});
  world.set<Motion>(player.entity, {0, 0});
  world.set<Combat>(player.entity, {100, 10});
  world.set<Tile>(player.entity, {"@"});
  world.set<Inventory>(player.entity, {102});

  save::to_file("./savetest.world", world, map);

  DinkyECS::World in_world;
  Map in_map(0, 0); // this will be changed on load
  save::from_file("./savetest.world", in_world, in_map);

  Position &position1 = world.get<Position>(player.entity);
  Position &position2 = in_world.get<Position>(player.entity);
  REQUIRE(position1.location.x == position2.location.x);
  REQUIRE(position1.location.y == position2.location.y);

  Combat &combat1 = world.get<Combat>(player.entity);
  Combat &combat2 = in_world.get<Combat>(player.entity);
  REQUIRE(combat1.hp == combat2.hp);

  Motion &motion1 = world.get<Motion>(player.entity);
  Motion &motion2 = in_world.get<Motion>(player.entity);
  REQUIRE(motion1.dx == motion2.dx);
  REQUIRE(motion1.dy == motion2.dy);

  Tile &tile1 = world.get<Tile>(player.entity);
  Tile &tile2 = in_world.get<Tile>(player.entity);
  REQUIRE(tile1.chr == tile2.chr);

  REQUIRE(map.width() == in_map.width());
  REQUIRE(map.height() == in_map.height());
  REQUIRE(map.$walls == in_map.$walls);

  Inventory &inv = world.get<Inventory>(player.entity);
  REQUIRE(inv.gold == 102);
}
