#include "search/query_params.hpp"

#include "search/ranking_utils.hpp"
#include "search/token_range.hpp"

#include <map>
#include <sstream>

namespace search
{
using namespace std;

namespace
{
// All synonyms should be lowercase.

/// @todo These should check the map language and use only the corresponding translation.
map<string, vector<string>> const kSynonyms = {
    {"n", {"north"}},
    {"w", {"west"}},
    {"s", {"south"}},
    {"e", {"east"}},
    {"nw", {"northwest"}},
    {"ne", {"northeast"}},
    {"sw", {"southwest"}},
    {"se", {"southeast"}},

    /// @todo Should not duplicate Street synonyms defined in StreetsSynonymsHolder (avoid useless double queries).
    /// Remove "street" and "avenue" here, but should update GetNameScore.
    {"st", {"saint", "street"}},
    {"dr", {"doctor"}},

    // widely used in LATAM, like "Ntra Sra Asuncion Zelaya"
    {"ntra", {"nuestra"}},
    {"sra", {"senora"}},
    {"sta", {"santa"}},

    {"al", {"allee", "alle"}},
    {"ave", {"avenue"}},
    /// @todo Should process synonyms with errors like "blvrd" -> "blvd".
    /// @see HouseOnStreetSynonymsWithMisprints test.
    {"blvd", {"boulevard"}},
    {"blvrd", {"boulevard"}},
    {"cir", {"circle"}},
    {"ct", {"court"}},
    {"hwy", {"highway"}},
    {"pl", {"place", "platz"}},
    {"rt", {"route"}},
    {"sq", {"square"}},

    {"ал", {"аллея", "алея"}},
    {"бул", {"бульвар"}},
    {"зав", {"завулак"}},
    {"кв", {"квартал"}},
    {"наб", {"набережная", "набярэжная", "набережна"}},
    {"пер", {"переулок"}},
    {"пл", {"площадь", "площа"}},
    {"пр", {"проспект", "праспект", "провулок", "проезд", "праезд", "проїзд"}},
    {"туп", {"тупик", "тупік"}},
    {"ш", {"шоссе", "шаша", "шосе"}},

    {"cd", {"caddesi"}},

    {"св", {"святой", "святого", "святая", "святые", "святых", "свято"}},
    {"б", {"большая", "большой"}},
    {"бол", {"большая", "большой"}},
    {"м", {"малая", "малый"}},
    {"мал", {"малая", "малый"}},
    {"нов", {"новая", "новый"}},
    {"стар", {"старая", "старый"}},
};
}  // namespace

// QueryParams::Token ------------------------------------------------------------------------------
void QueryParams::Token::AddSynonym(string const & s)
{
  AddSynonym(strings::MakeUniString(s));
}

void QueryParams::Token::AddSynonym(String const & s)
{
  if (!IsStopWord(s))
    m_synonyms.push_back(s);
}

string DebugPrint(QueryParams::Token const & token)
{
  ostringstream os;
  os << "Token [ m_original=" << DebugPrint(token.GetOriginal()) << ", m_synonyms=" << DebugPrint(token.m_synonyms)
     << " ]";
  return os.str();
}

// QueryParams -------------------------------------------------------------------------------------
void QueryParams::ClearStreetIndices()
{
  class AdditionalCommonTokens
  {
    set<String> m_strings;

  public:
    AdditionalCommonTokens()
    {
      char const * arr[] = {
          "the",                       // English
          "der", "zum", "und", "auf",  // German
          "del", "les",                // Spanish
          "в",   "на"                  // Cyrillic
      };
      for (char const * s : arr)
        m_strings.insert(NormalizeAndSimplifyString(s));
    }
    bool Has(String const & s) const { return m_strings.count(s) > 0; }
  };
  static AdditionalCommonTokens const s_addCommonTokens;

  size_t const count = GetNumTokens();
  m_isCommonToken.resize(count, false);

  for (size_t i = 0; i < count; ++i)
  {
    auto const & token = GetToken(i).GetOriginal();
    if (IsStreetSynonym(token))
    {
      m_typeIndices[i].clear();
      m_isCommonToken[i] = true;
    }
    else if (s_addCommonTokens.Has(token))
      m_isCommonToken[i] = true;
  }
}

void QueryParams::Clear()
{
  m_tokens.clear();
  m_prefixToken.Clear();
  m_hasPrefix = false;
  m_isCommonToken.clear();
  m_typeIndices.clear();
  m_langs.Clear();
}

bool QueryParams::IsCategorySynonym(size_t i) const
{
  return !GetTypeIndices(i).empty();
}

QueryParams::TypeIndices & QueryParams::GetTypeIndices(size_t i)
{
  ASSERT_LESS(i, GetNumTokens(), ());
  return m_typeIndices[i];
}

QueryParams::TypeIndices const & QueryParams::GetTypeIndices(size_t i) const
{
  ASSERT_LESS(i, GetNumTokens(), ());
  return m_typeIndices[i];
}

bool QueryParams::IsPrefixToken(size_t i) const
{
  ASSERT_LESS(i, GetNumTokens(), ());
  return i == m_tokens.size();
}

QueryParams::Token const & QueryParams::GetToken(size_t i) const
{
  ASSERT_LESS(i, GetNumTokens(), ());
  return i < m_tokens.size() ? m_tokens[i] : m_prefixToken;
}

QueryParams::Token & QueryParams::GetToken(size_t i)
{
  ASSERT_LESS(i, GetNumTokens(), ());
  return i < m_tokens.size() ? m_tokens[i] : m_prefixToken;
}

bool QueryParams::IsCommonToken(size_t i) const
{
  return i < m_isCommonToken.size() && m_isCommonToken[i];
}

bool QueryParams::IsNumberTokens(TokenRange const & range) const
{
  ASSERT(range.IsValid(), (range));
  ASSERT_LESS_OR_EQUAL(range.End(), GetNumTokens(), ());

  for (size_t i : range)
    if (!GetToken(i).AnyOfOriginalOrSynonyms([](String const & s) { return strings::IsASCIINumeric(s); }))
      return false;

  return true;
}

bool QueryParams::IsRangeWithPrefix(TokenRange const & range) const
{
  return m_hasPrefix && range.End() > m_tokens.size();
}

void QueryParams::RemoveToken(size_t i)
{
  ASSERT_LESS(i, GetNumTokens(), ());
  if (i == m_tokens.size())
  {
    m_prefixToken.Clear();
    m_hasPrefix = false;
  }
  else
  {
    m_tokens.erase(m_tokens.begin() + i);
  }
  m_typeIndices.erase(m_typeIndices.begin() + i);
}

void QueryParams::AddSynonyms()
{
  for (auto & token : m_tokens)
  {
    string const ss = ToUtf8(MakeLowerCase(token.GetOriginal()));
    auto const it = kSynonyms.find(ss);
    if (it != kSynonyms.end())
      for (auto const & synonym : it->second)
        token.AddSynonym(synonym);
  }
  if (m_hasPrefix)
  {
    string const ss = ToUtf8(MakeLowerCase(m_prefixToken.GetOriginal()));
    auto const it = kSynonyms.find(ss);
    if (it != kSynonyms.end())
      for (auto const & synonym : it->second)
        m_prefixToken.AddSynonym(synonym);
  }
}

string DebugPrint(QueryParams const & params)
{
  ostringstream os;
  os << boolalpha << "QueryParams "
     << "{ m_tokens: " << ::DebugPrint(params.m_tokens) << ", m_prefixToken: " << DebugPrint(params.m_prefixToken)
     << ", m_typeIndices: " << ::DebugPrint(params.m_typeIndices) << ", m_langs: " << DebugPrint(params.m_langs)
     << ", m_isCommonToken: " << ::DebugPrint(params.m_isCommonToken) << " }";
  return os.str();
}
}  // namespace search
