text: #include "search/result.hpp"
  
  #include "search/common.hpp"
  #include "search/geometry_utils.hpp"
  
  #include "indexer/classificator.hpp"
  
  #include "base/string_utils.hpp"
  
  #include <sstream>
  
  namespace search
  {
  using namespace std;
  
  // Result ------------------------------------------------------------------------------------------
  Result::Result(FeatureID const & id, m2::PointD const & pt, string const & str,
                 string const & address, uint32_t featureType, Details const & details)
    : m_resultType(Type::Feature)
    , m_id(id)
    , m_center(pt)
    , m_str(str)
    , m_address(address)
    , m_featureType(featureType)
    , m_details(details)
  {
  }
  
  Result::Result(m2::PointD const & pt, string const & latlon, string const & address)
    : m_resultType(Type::LatLon), m_center(pt), m_str(latlon), m_address(address)
  {
  }
  
  Result::Result(m2::PointD const & pt, string const & postcode)
    : m_resultType(Type::Postcode), m_center(pt), m_str(postcode)
  {
  }
  
  Result::Result(string const & str, string const & suggest)
    : m_resultType(Type::PureSuggest), m_str(str), m_suggestionStr(suggest)
  {
  }
  
  Result::Result(Result const & res, string const & suggest)
    : m_id(res.m_id)
    , m_center(res.m_center)
    , m_str(res.m_str)
    , m_address(res.m_address)
    , m_featureType(res.m_featureType)
    , m_suggestionStr(suggest)
    , m_hightlightRanges(res.m_hightlightRanges)
  {
    m_resultType = m_id.IsValid() ? Type::SuggestFromFeature : Type::PureSuggest;
  }
  
  bool Result::IsSuggest() const
  {
    return m_resultType == Type::SuggestFromFeature || m_resultType == Type::PureSuggest;
  }
  
  bool Result::HasPoint() const
  {
    return m_resultType != Type::PureSuggest;
  }
  
  FeatureID const & Result::GetFeatureID() const
  {
    ASSERT_EQUAL(m_resultType, Type::Feature, (m_resultType));
    return m_id;
  }
  
  uint32_t Result::GetFeatureType() const
  {
    ASSERT_EQUAL(m_resultType, Type::Feature, (m_resultType));
    return m_featureType;
  }
  
  m2::PointD Result::GetFeatureCenter() const
  {
    ASSERT(HasPoint(), ());
    return m_center;
  }
  
  string const & Result::GetSuggestionString() const
  {
    ASSERT(IsSuggest(), ());
    return m_suggestionStr;
  }
  
  bool Result::IsEqualSuggest(Result const & r) const
  {
    return m_suggestionStr == r.m_suggestionStr;
  }
  
  bool Result::IsEqualFeature(Result const & r) const
  {
    if (m_resultType != r.m_resultType)
      return false;
  
    ASSERT_EQUAL(m_resultType, Result::Type::Feature, ());
  
    ASSERT(m_id.IsValid() && r.m_id.IsValid(), ());
    if (m_id == r.m_id)
      return true;
  
    // This function is used to filter duplicate results in cases:
    // - emitted World.mwm and Country.mwm
    // - after additional search in all mwm
    // so it's suitable here to test for 500m
    return m_str == r.m_str && m_address == r.m_address && m_featureType == r.m_featureType &&
           PointDistance(m_center, r.m_center) < 500.0;
  }
  
  void Result::AddHighlightRange(pair<uint16_t, uint16_t> const & range)
  {
    m_hightlightRanges.push_back(range);
  }
  
  pair<uint16_t, uint16_t> const & Result::GetHighlightRange(size_t idx) const
  {
    ASSERT(idx < m_hightlightRanges.size(), ());
    return m_hightlightRanges[idx];
  }
  
  void Result::PrependCity(string const & city)
  {
    // It is expected that if |m_address| is not empty,
    // it starts with the region name. Avoid duplication
    // in the case where this region name coincides with
    // the city name and prepend otherwise.
    strings::SimpleTokenizer tok(m_address, ",");
    if (tok && *tok != city)
      m_address = city + ", " + m_address;
  }
  
  string Result::ToStringForStats() const
  {
    string readableType;
    if (GetResultType() == Type::Feature)
      readableType = classif().GetReadableObjectName(m_featureType);
  
    string s;
    s.append(GetString());
    s.append("|");
    s.append(readableType);
    s.append("|");
    s.append(IsSuggest() ? "1" : "0");
    s.append("|");
    s.append(to_string(mercator::YToLat(m_center.y)));
    s.append("|");
    s.append(to_string(mercator::XToLon(m_center.x)));
    return s;
  }
  
  string DebugPrint(Result::Type type)
  {
    switch (type)
    {
    case Result::Type::Feature: return "Feature";
    case Result::Type::LatLon: return "LatLon";
    case Result::Type::PureSuggest: return "PureSuggest";
    case Result::Type::SuggestFromFeature: return "SuggestFromFeature";
    case Result::Type::Postcode: return "Postcode";
    }
  
    return "Unknown";
  }
  
  string DebugPrint(Result const & result)
  {
    string readableType;
    if (result.GetResultType() == Result::Type::Feature)
      readableType = classif().GetReadableObjectName(result.GetFeatureType());
  
    ostringstream os;
    os << "Result [";
    os << "name: " << result.GetString();
    os << ", type: " << readableType;
    os << ", info: " << DebugPrint(result.GetRankingInfo());
    if (!result.GetProvenance().empty())
      os << ", provenance: " << ::DebugPrint(result.GetProvenance());
    os << "]";
    return os.str();
  }
  
  // Results -----------------------------------------------------------------------------------------
  Results::Results()
  {
    Clear();
  }
  
  bool Results::AddResult(Result && result)
  {
    // Find first feature result.
    auto it = find_if(m_results.begin(), m_results.end(),
                      [](Result const & r) { return r.GetResultType() == Result::Type::Feature; });
  
    if (result.IsSuggest())
    {
      if (static_cast<size_t>(std::distance(m_results.begin(), it)) >= kMaxNumSuggests)
        return false;
  
      for (auto i = m_results.begin(); i != it; ++i)
      {
        if (result.IsEqualSuggest(*i))
          return false;
      }
      InsertResult(it, move(result));
    }
    else
    {
      for (; it != m_results.end(); ++it)
      {
        if (result.IsEqualFeature(*it))
          return false;
      }
      InsertResult(m_results.end(), move(result));
    }
  
    return true;
  }
  
  void Results::AddResultNoChecks(Result && result)
  {
    InsertResult(m_results.end(), move(result));
  }
  
  void Results::AddResultsNoChecks(ConstIter first, ConstIter last)
  {
    while (first != last)
    {
      auto resultCopy = *first++;
      AddResultNoChecks(move(resultCopy));
    }
  }
  
  void Results::AddBookmarkResult(bookmarks::Result const & result)
  {
    m_bookmarksResults.push_back(result);
  }
  
  void Results::Clear()
  {
    m_results.clear();
    m_bookmarksResults.clear();
    m_status = Status::None;
  }
  
  size_t Results::GetSuggestsCount() const
  {
    size_t i = 0;
  
    // Suggests always go first, so we need to compute length of prefix
    // of suggests.
    while (i < m_results.size() && m_results[i].IsSuggest())
      ++i;
    return i;
  }
  
  bookmarks::Results const & Results::GetBookmarksResults() const
  {
    return m_bookmarksResults;
  }
  
  void Results::InsertResult(vector<Result>::iterator where, Result && result)
  {
    ASSERT_LESS(m_results.size(), numeric_limits<int32_t>::max(), ());
  
    for (auto it = where; it != m_results.end(); ++it)
    {
      auto & r = *it;
      auto const position = r.GetPositionInResults();
      r.SetPositionInResults(position + 1);
    }
  
    result.SetPositionInResults(static_cast<int32_t>(distance(m_results.begin(), where)));
    m_results.insert(where, move(result));
  }
  
  string DebugPrint(search::Results const & results)
  {
    return DebugPrintSequence(results.begin(), results.end());
  }
  }  // namespace search
  