#pragma once

#include "ros_bridge/include/messages/geographic_msgs/geopoint.h"
#include "ros_bridge/rapidjson/include/rapidjson/document.h"

#include <vector>

namespace ros_bridge {
//! @brief Namespace containing classes and methodes ros message generation.
namespace messages {
//! @brief Namespace containing classes and methodes for nemo_msgs
//! generation.
namespace nemo_msgs {
//! @brief Namespace containing methodes for nemo_msgs/tile message
//! generation.
namespace tile {

std::string messageType();

namespace {
const char *progressKey = "progress";
const char *idKey = "id";
const char *tileKey = "tile";
} // namespace

//! @brief C++ representation of nemo_msgs/tile message
template <class GeoPoint = geographic_msgs::geo_point::GeoPoint,
          template <class, class...> class Container = std::vector>
class GenericTile {
public:
  typedef Container<GeoPoint> GeoContainer;
  GenericTile() {}
  GenericTile(const GeoContainer &tile, double progress, long id)
      : _tile(tile), _id(id), _progress(progress) {}

  long id() const { return _id; }
  void setId(long id) { _id = id; }

  double progress() const { return _progress; }
  void setProgress(double progress) { _progress = progress; }

  const GeoContainer &tile() const { return _tile; }
  GeoContainer &tile() { return _tile; }
  void setTile(const GeoContainer &tile) { _tile = tile; }

protected:
  GeoContainer _tile;
  long _id;
  double _progress;
};
typedef GenericTile<> Tile;

template <class TileType>
bool toJson(const TileType &tile, rapidjson::Value &value,
            rapidjson::Document::AllocatorType &allocator) {

  using namespace rapidjson;
  value.AddMember(Value(idKey, allocator), Value(tile.id()), allocator);
  value.AddMember(Value(progressKey, allocator), Value(tile.progress()),
                  allocator);

  using namespace messages::geographic_msgs;
  rapidjson::Value geoPoints(rapidjson::kArrayType);
  for (unsigned long i = 0; i < std::uint64_t(tile.tile().size()); ++i) {
    rapidjson::Value geoPoint(rapidjson::kObjectType);
    if (!geo_point::toJson(tile.tile()[i], geoPoint, allocator)) {
      return false;
    }
    geoPoints.PushBack(geoPoint, allocator);
  }

  rapidjson::Value key(tileKey, allocator);
  value.AddMember(key, geoPoints, allocator);

  return true;
}

template <class TileType>
bool fromJson(const rapidjson::Value &value, TileType &p) {
  if (!value.HasMember(progressKey) || !value[progressKey].IsDouble()) {
    return false;
  }

  if (!value.HasMember(idKey) || !value[idKey].IsInt()) {
    return false;
  }

  if (!value.HasMember(tileKey) || !value[tileKey].IsArray()) {
    return false;
  }

  p.setId(value[idKey].GetInt());
  p.setProgress(value[progressKey].GetDouble());

  using namespace geographic_msgs;

  const auto &jsonArray = value[tileKey].GetArray();
  p.tile().clear();
  p.tile().reserve(jsonArray.Size());
  typedef decltype(p.tile()[0]) PointTypeCVR;
  typedef
      typename std::remove_cv_t<typename std::remove_reference_t<PointTypeCVR>>
          PointType;
  for (long i = 0; i < jsonArray.Size(); ++i) {
    PointType pt;
    if (!geo_point::fromJson(jsonArray[i], pt)) {
      return false;
    }
    p.tile().push_back(std::move(pt));
  }

  return true;
}

} // namespace tile
} // namespace nemo_msgs
} // namespace messages
} // namespace ros_bridge
