#include "generator/generator_tests_support/test_feature.hpp"

#include "generator/feature_builder.hpp"

#include "editor/osm_editor.hpp"

#include "indexer/classificator.hpp"
#include "indexer/editable_map_object.hpp"
#include "indexer/feature.hpp"
#include "indexer/feature_decl.hpp"
#include "indexer/feature_meta.hpp"
#include "indexer/mwm_set.hpp"

#include "coding/string_utf8_multilang.hpp"

#include "base/assert.hpp"
#include "base/stl_helpers.hpp"
#include "base/string_utils.hpp"

#include <atomic>
#include <sstream>

namespace generator
{
namespace tests_support
{
using namespace feature;
using std::ostringstream, std::string, std::string_view, std::vector;

namespace
{
uint64_t GenUniqueId()
{
  static std::atomic<uint64_t> id;
  return id.fetch_add(1);
}

vector<m2::PointD> MakePoly(m2::RectD const & rect)
{
  return {rect.LeftBottom(), rect.RightBottom(), rect.RightTop(), rect.LeftTop(), rect.LeftBottom()};
}

StringUtf8Multilang MakeName(string const & name, string const & lang)
{
  StringUtf8Multilang res;
  if (!name.empty())
    res.AddString(lang, name);

  // Names used for search depend on locale. Fill default name because we need to run tests with
  // different locales. If you do not need default name to be filled use
  // TestFeature::TestFeature(StringUtf8Multilang const & name).
  res.AddString("default", name);
  return res;
}
}  // namespace

// TestFeature -------------------------------------------------------------------------------------
TestFeature::TestFeature() : m_id(GenUniqueId()), m_center(0, 0), m_type(Type::Unknown)
{
  Init();
}

TestFeature::TestFeature(StringUtf8Multilang name)
  : m_id(GenUniqueId())
  , m_center(0, 0)
  , m_type(Type::Unknown)
  , m_names(std::move(name))
{
  Init();
}

TestFeature::TestFeature(m2::PointD const & center, StringUtf8Multilang name)
  : m_id(GenUniqueId())
  , m_center(center)
  , m_type(Type::Point)
  , m_names(std::move(name))
{
  Init();
}

TestFeature::TestFeature(m2::RectD const & boundary, StringUtf8Multilang name)
  : TestFeature(MakePoly(boundary), std::move(name), Type::Area)
{
  Init();
}

TestFeature::TestFeature(vector<m2::PointD> geometry, StringUtf8Multilang name, Type type)
  : m_id(GenUniqueId())
  , m_center(0, 0)
  , m_geometry(std::move(geometry))
  , m_type(type)
  , m_names(std::move(name))
{
  ASSERT(!m_geometry.empty(), ());
  Init();
}

void TestFeature::Init()
{
  m_metadata.Set(Metadata::FMD_TEST_ID, strings::to_string(m_id));
}

bool TestFeature::Matches(FeatureType & feature) const
{
  auto const sv = feature.GetMetadata(feature::Metadata::FMD_TEST_ID);
  if (sv.empty())
    return false;

  uint64_t id;
  CHECK(strings::to_uint(sv, id), (sv));
  return id == m_id;
}

void TestFeature::Serialize(FeatureBuilder & fb) const
{
  using feature::Metadata;

  // Iterate [1, FMD_COUNT). Absent types don't matter here.
  size_t i = 1;
  auto constexpr count = static_cast<size_t>(Metadata::EType::FMD_COUNT);
  for (; i < count; ++i)
  {
    auto const type = static_cast<Metadata::EType>(i);
    if (m_metadata.Has(type))
      fb.GetMetadata().Set(type, string(m_metadata.Get(type)));
  }

  if (!m_geometry.empty())
    fb.AssignPoints(m_geometry);

  switch (m_type)
  {
  case Type::Point:
  {
    fb.SetCenter(m_center);
    break;
  }
  case Type::Line:
  {
    fb.SetLinear();
    break;
  }
  case Type::Area:
  {
    fb.SetArea();
    break;
  }
  case Type::Unknown: break;
  }

  m_names.ForEach([&](int8_t langCode, string_view name)
  {
    if (!name.empty())
      fb.SetName(langCode, name);
  });

  if (!m_postcode.empty())
    fb.GetParams().SetPostcode(m_postcode);
}

// TestPlace -------------------------------------------------------------------------------------
TestPlace::TestPlace(m2::PointD const & center, string const & name, string const & lang, uint32_t type,
                     uint8_t rank /* = 0 */)
  : TestFeature(center, MakeName(name, lang))
  , m_type(type)
  , m_rank(rank)
{}

TestPlace::TestPlace(m2::PointD const & center, StringUtf8Multilang const & name, uint32_t type, uint8_t rank)
  : TestFeature(center, name)
  , m_type(type)
  , m_rank(rank)
{}

TestPlace::TestPlace(std::vector<m2::PointD> const & boundary, std::string const & name, std::string const & lang,
                     uint32_t type, uint8_t rank)
  : TestFeature(boundary, MakeName(name, lang), Type::Area)
  , m_type(type)
  , m_rank(rank)
{}

void TestPlace::Serialize(FeatureBuilder & fb) const
{
  TestFeature::Serialize(fb);
  fb.AddType(m_type);
  fb.GetParams().rank = m_rank;
}

string TestPlace::ToDebugString() const
{
  ostringstream os;
  os << "TestPlace { " << DebugPrint(m_names) << ", " << DebugPrint(m_center) << ", "
     << classif().GetReadableObjectName(m_type) << ", " << int(m_rank) << " }";
  return os.str();
}

void TestPlace::SetType(base::StringIL const & e)
{
  m_type = classif().GetTypeByPath(e);
}

TestCountry::TestCountry(m2::PointD const & center, std::string const & name, std::string const & lang)
  : TestPlace(center, name, lang, classif().GetTypeByPath({"place", "country"}), 170 /* rank */)
{
  // Default rank for Country with population ~ 1.0E7
}

TestState::TestState(m2::PointD const & center, string const & name, string const & lang)
  : TestPlace(center, name, lang, classif().GetTypeByPath({"place", "state"}))
{}

TestSea::TestSea(m2::PointD const & center, std::string const & name, std::string const & lang)
  : TestPlace(center, name, lang, classif().GetTypeByPath({"place", "sea"}))
{}

uint32_t TestCity::GetCityType()
{
  return classif().GetTypeByPath({"place", "city"});
}

TestCity::TestCity(m2::PointD const & center, string const & name, string const & lang, uint8_t rank)
  : TestPlace(center, name, lang, GetCityType(), rank)
{}

TestCity::TestCity(m2::PointD const & center, StringUtf8Multilang const & name, uint8_t rank)
  : TestPlace(center, name, GetCityType(), rank)
{}

TestCity::TestCity(vector<m2::PointD> const & boundary, string const & name, string const & lang, uint8_t rank)
  : TestPlace(boundary, name, lang, GetCityType(), rank)
{}

TestVillage::TestVillage(m2::PointD const & center, string const & name, string const & lang, uint8_t rank)
  : TestPlace(center, name, lang, classif().GetTypeByPath({"place", "village"}), rank)
{}

TestSuburb::TestSuburb(m2::PointD const & center, string const & name, string const & lang)
  : TestPlace(center, name, lang, classif().GetTypeByPath({"place", "suburb"}))
{}

// TestStreet --------------------------------------------------------------------------------------
TestStreet::TestStreet(vector<m2::PointD> const & points, string const & name, string const & lang)
  : TestFeature(points, MakeName(name, lang), Type::Line)
{
  SetType({"highway", "living_street"});
}

TestStreet::TestStreet(vector<m2::PointD> const & points, StringUtf8Multilang const & name)
  : TestFeature(points, name, Type::Line)
{
  SetType({"highway", "living_street"});
}

void TestStreet::SetType(base::StringIL const & e)
{
  m_highwayType = classif().GetTypeByPath(e);
}

void TestStreet::Serialize(FeatureBuilder & fb) const
{
  TestFeature::Serialize(fb);

  fb.SetType(m_highwayType);

  fb.GetParams().ref = m_roadNumber;
}

string TestStreet::ToDebugString() const
{
  ostringstream os;
  os << "TestStreet [" << DebugPrint(m_names) << ", " << ::DebugPrint(m_geometry) << "]";
  return os.str();
}

// TestSquare --------------------------------------------------------------------------------------
TestSquare::TestSquare(m2::RectD const & rect, string const & name, string const & lang)
  : TestFeature(rect, MakeName(name, lang))
{}

void TestSquare::Serialize(FeatureBuilder & fb) const
{
  TestFeature::Serialize(fb);

  auto const & classificator = classif();
  fb.SetType(classificator.GetTypeByPath({"place", "square"}));
}

string TestSquare::ToDebugString() const
{
  ostringstream os;
  os << "TestSquare [" << DebugPrint(m_names) << "]";
  return os.str();
}

// TestPOI -----------------------------------------------------------------------------------------
TestPOI::TestPOI(m2::PointD const & center, string const & name, string const & lang)
  : TestFeature(center, MakeName(name, lang))
{
  SetTypes({{"railway", "station"}});
}

// static
std::pair<TestPOI, FeatureID> TestPOI::AddWithEditor(osm::Editor & editor, MwmSet::MwmId const & mwmId,
                                                     string const & enName, m2::PointD const & pt)
{
  TestPOI poi(pt, enName, "en");

  osm::EditableMapObject emo;
  editor.CreatePoint(classif().GetTypeByPath({"shop", "bakery"}), pt, mwmId, emo);

  emo.SetName(enName, StringUtf8Multilang::GetLangIndex("en"));
  emo.SetTestId(poi.GetId());

  editor.SaveEditedFeature(emo);
  return {poi, emo.GetID()};
}

void TestPOI::Serialize(FeatureBuilder & fb) const
{
  TestFeature::Serialize(fb);

  for (uint32_t t : m_types)
    fb.AddType(t);

  auto & params = fb.GetParams();
  if (!m_houseNumber.empty())
    params.AddHouseNumber(m_houseNumber);

  if (!m_streetName.empty())
    params.SetStreet(m_streetName);
}

string TestPOI::ToDebugString() const
{
  ostringstream os;
  os << "TestPOI [" << DebugPrint(m_names) << ", " << DebugPrint(m_center);
  if (!m_houseNumber.empty())
    os << ", " << m_houseNumber;
  if (!m_streetName.empty())
    os << ", " << m_streetName;
  os << "]";
  return os.str();
}

TypesHolder TestPOI::GetTypes() const
{
  TypesHolder types;
  types.Assign(m_types.begin(), m_types.end());
  return types;
}

void TestPOI::SetTypes(std::initializer_list<base::StringIL> const & types)
{
  m_types.clear();
  auto const & c = classif();
  for (auto const & e : types)
    m_types.push_back(c.GetTypeByPath(e));
}

// TestMultilingualPOI -----------------------------------------------------------------------------
TestMultilingualPOI::TestMultilingualPOI(m2::PointD const & center, string const & defaultName,
                                         std::map<string, string> const & multilingualNames)
  : TestPOI(center, defaultName, "default")
  , m_multilingualNames(multilingualNames)
{}

void TestMultilingualPOI::Serialize(FeatureBuilder & fb) const
{
  TestPOI::Serialize(fb);

  for (auto const & kv : m_multilingualNames)
    CHECK(fb.AddName(kv.first, kv.second), ("Can't set feature name:", kv.second, "(", kv.first, ")"));
}

string TestMultilingualPOI::ToDebugString() const
{
  ostringstream os;
  os << "TestPOI [" << DebugPrint(m_names) << ", ";
  for (auto const & kv : m_multilingualNames)
    os << "( " << kv.second << ", " << kv.first << "), ";
  os << DebugPrint(m_center);
  if (!m_houseNumber.empty())
    os << ", " << m_houseNumber;
  if (!m_streetName.empty())
    os << ", " << m_streetName;
  os << "]";
  return os.str();
}

// TestBuilding ------------------------------------------------------------------------------------
TestBuilding::TestBuilding(m2::PointD const & center, string const & name, string const & houseNumber,
                           string const & lang)
  : TestFeature(center, MakeName(name, lang))
  , m_houseNumber(houseNumber)
{}

TestBuilding::TestBuilding(m2::PointD const & center, string const & name, string const & houseNumber,
                           string_view street, string const & lang)
  : TestFeature(center, MakeName(name, lang))
  , m_houseNumber(houseNumber)
{
  m_addr.Set(AddressData::Type::Street, street);
}

TestBuilding::TestBuilding(m2::RectD const & boundary, string const & name, string const & houseNumber,
                           string_view street, string const & lang)
  : TestFeature(boundary, MakeName(name, lang))
  , m_houseNumber(houseNumber)
{
  m_addr.Set(AddressData::Type::Street, street);
}

void TestBuilding::Serialize(FeatureBuilder & fb) const
{
  TestFeature::Serialize(fb);

  auto & params = fb.GetParams();
  if (!m_houseNumber.empty())
    params.AddHouseNumber(m_houseNumber);

  params.SetAddress(AddressData(m_addr));

  if (m_type == 0)
    fb.AddType(classif().GetTypeByPath({"building"}));
  else
    fb.AddType(m_type);
}

string TestBuilding::ToDebugString() const
{
  ostringstream os;
  os << "TestBuilding [" << DebugPrint(m_names) << ", " << m_houseNumber << ", " << DebugPrint(m_center) << "]";
  return os.str();
}

// TestPark ----------------------------------------------------------------------------------------
TestPark::TestPark(vector<m2::PointD> const & boundary, string const & name, string const & lang)
  : TestFeature(boundary, MakeName(name, lang), Type::Area)
{}

void TestPark::Serialize(FeatureBuilder & fb) const
{
  TestFeature::Serialize(fb);

  auto const & classificator = classif();
  fb.AddType(classificator.GetTypeByPath({"leisure", "park"}));
}

string TestPark::ToDebugString() const
{
  ostringstream os;
  os << "TestPark [" << DebugPrint(m_names) << ", "
     << "]";
  return os.str();
}

// TestRoad ----------------------------------------------------------------------------------------
TestRoad::TestRoad(vector<m2::PointD> const & points, string const & name, string const & lang)
  : TestFeature(points, MakeName(name, lang), Type::Line)
{}

void TestRoad::Serialize(FeatureBuilder & fb) const
{
  TestFeature::Serialize(fb);

  auto const & classificator = classif();
  fb.AddType(classificator.GetTypeByPath({"highway", "road"}));
}

string TestRoad::ToDebugString() const
{
  ostringstream os;
  os << "TestRoad [" << DebugPrint(m_names) << "]";
  return os.str();
}

// Functions ---------------------------------------------------------------------------------------
string DebugPrint(TestFeature const & feature)
{
  return feature.ToDebugString();
}
}  // namespace tests_support
}  // namespace generator
