<commit_before>#include "map/framework.hpp"
#include "map/ge0_parser.hpp"
#include "map/geourl_process.hpp"
#include "map/gps_tracker.hpp"

#include "defines.hpp"

#include "routing/online_absent_fetcher.hpp"
#include "routing/osrm_router.hpp"
#include "routing/road_graph_router.hpp"
#include "routing/route.hpp"
#include "routing/routing_algorithm.hpp"

#include "search/geometry_utils.hpp"
#include "search/intermediate_result.hpp"
#include "search/result.hpp"
#include "search/reverse_geocoder.hpp"
#include "search/search_engine.hpp"
#include "search/search_query_factory.hpp"

#include "storage/storage_helpers.hpp"

#include "drape_frontend/color_constants.hpp"
#include "drape_frontend/gps_track_point.hpp"
#include "drape_frontend/visual_params.hpp"
#include "drape_frontend/watch/cpu_drawer.hpp"
#include "drape_frontend/watch/feature_processor.hpp"

#include "indexer/categories_holder.hpp"
#include "indexer/classificator_loader.hpp"
#include "indexer/drawing_rules.hpp"
#include "indexer/editable_map_object.hpp"
#include "indexer/feature.hpp"
#include "indexer/map_style_reader.hpp"
#include "indexer/osm_editor.hpp"
#include "indexer/scales.hpp"

/// @todo Probably it's better to join this functionality.
//@{
#include "indexer/feature_algo.hpp"
#include "indexer/feature_utils.hpp"
//@}

#include "storage/country_info_getter.hpp"

#include "platform/local_country_file_utils.hpp"
#include "platform/measurement_utils.hpp"
#include "platform/mwm_version.hpp"
#include "platform/platform.hpp"
#include "platform/preferred_languages.hpp"
#include "platform/settings.hpp"

#include "coding/internal/file_data.hpp"
#include "coding/zip_reader.hpp"
#include "coding/url_encode.hpp"
#include "coding/file_name_utils.hpp"
#include "coding/png_memory_encoder.hpp"

#include "geometry/angles.hpp"
#include "geometry/distance_on_sphere.hpp"

#include "base/math.hpp"
#include "base/timer.hpp"
#include "base/scope_guard.hpp"

#include "std/algorithm.hpp"
#include "std/bind.hpp"
#include "std/target_os.hpp"
#include "std/vector.hpp"

#include "api/internal/c/api-client-internals.h"
#include "api/src/c/api-client.h"

#include "3party/Alohalytics/src/alohalytics.h"

#define KMZ_EXTENSION ".kmz"

#define DEFAULT_BOOKMARK_TYPE "placemark-red"

using namespace storage;
using namespace routing;
using namespace location;

using platform::CountryFile;
using platform::LocalCountryFile;

#ifdef FIXED_LOCATION
Framework::FixedPosition::FixedPosition()
{
  m_fixedLatLon = Settings::Get("FixPosition", m_latlon);
  m_fixedDir = Settings::Get("FixDirection", m_dirFromNorth);
}
#endif

namespace
{
static const int BM_TOUCH_PIXEL_INCREASE = 20;
static const int kKeepPedestrianDistanceMeters = 10000;
char const kRouterTypeKey[] = "router";
char const kMapStyleKey[] = "MapStyleKeyV1";
char const kAllow3dKey[] = "Allow3d";
char const kAllow3dBuildingsKey[] = "Buildings3d";

double const kDistEqualQuery = 100.0;

double const kRotationAngle = math::pi4;
double const kAngleFOV = math::pi / 3.0;

// TODO!
// To adjust GpsTrackFilter was added secret command "?gpstrackaccuracy:xxx;"
// where xxx is a new value for horizontal accuracy.
// This is temporary solution while we don't have a good filter.
void ParseSetGpsTrackMinAccuracyCommand(string const & query)
{
  const char kGpsAccuracy[] = "?gpstrackaccuracy:";
  if (strings::StartsWith(query, kGpsAccuracy))
  {
    size_t const end = query.find(';', sizeof(kGpsAccuracy) - 1);
    if (end != string::npos)
    {
      string s(query.begin() + sizeof(kGpsAccuracy) - 1, query.begin() + end);
      double value;
      if (strings::to_double(s, value))
      {
        GpsTrackFilter::StoreMinHorizontalAccuracy(value);
      }
    }
  }
}

// Cancels search query by |handle|.
void CancelQuery(weak_ptr<search::QueryHandle> & handle)
{
  auto queryHandle = handle.lock();
  if (queryHandle)
    queryHandle->Cancel();
  handle.reset();
}
}  // namespace

pair<MwmSet::MwmId, MwmSet::RegResult> Framework::RegisterMap(
    LocalCountryFile const & localFile)
{
  LOG(LINFO, ("Loading map:", localFile.GetCountryName()));
  return m_model.RegisterMap(localFile);
}

void Framework::OnLocationError(TLocationError /*error*/)
{
  CallDrapeFunction(bind(&df::DrapeEngine::CancelMyPosition, _1));
}

void Framework::OnLocationUpdate(GpsInfo const & info)
{
#ifdef FIXED_LOCATION
  GpsInfo rInfo(info);

  // get fixed coordinates
  m_fixedPos.GetLon(rInfo.m_longitude);
  m_fixedPos.GetLat(rInfo.m_latitude);

  // pretend like GPS position
  rInfo.m_horizontalAccuracy = 5.0;

  if (m_fixedPos.HasNorth())
  {
    // pass compass value (for devices without compass)
    CompassInfo compass;
    m_fixedPos.GetNorth(compass.m_bearing);
    OnCompassUpdate(compass);
  }

#else
  GpsInfo rInfo(info);
#endif

#ifdef OMIM_OS_ANDROID
  m_lastGPSInfo.reset(new GpsInfo(rInfo));
#endif
  location::RouteMatchingInfo routeMatchingInfo;
  CheckLocationForRouting(rInfo);

  MatchLocationToRoute(rInfo, routeMatchingInfo);

  CallDrapeFunction(bind(&df::DrapeEngine::SetGpsInfo, _1, rInfo,
                         m_routingSession.IsNavigable(), routeMatchingInfo));
}

void Framework::OnCompassUpdate(CompassInfo const & info)
{
#ifdef FIXED_LOCATION
  CompassInfo rInfo(info);
  m_fixedPos.GetNorth(rInfo.m_bearing);
#else
  CompassInfo const & rInfo = info;
#endif

#ifdef OMIM_OS_ANDROID
  m_lastCompassInfo.reset(new CompassInfo(rInfo));
#endif
  CallDrapeFunction(bind(&df::DrapeEngine::SetCompassInfo, _1, rInfo));
}

void Framework::SwitchMyPositionNextMode()
{
  CallDrapeFunction(bind(&df::DrapeEngine::MyPositionNextMode, _1));
}

void Framework::InvalidateMyPosition()
{
  CallDrapeFunction(bind(&df::DrapeEngine::InvalidateMyPosition, _1));
}

void Framework::SetMyPositionModeListener(TMyPositionModeChanged && fn)
{
  m_myPositionListener = move(fn);
}

void Framework::OnUserPositionChanged(m2::PointD const & position)
{
  MyPositionMarkPoint * myPosition = UserMarkContainer::UserMarkForMyPostion();
  myPosition->SetUserPosition(position);

  if (IsRoutingActive())
    m_routingSession.SetUserCurrentPosition(position);
}

void Framework::CallDrapeFunction(TDrapeFunction const & fn) const
{
  if (m_drapeEngine)
    fn(m_drapeEngine.get());
}

void Framework::StopLocationFollow()
{
  CallDrapeFunction(bind(&df::DrapeEngine::StopLocationFollow, _1));
}

bool Framework::IsEnoughSpaceForMigrate() const
{
  uint64_t const kSpaceSize = 100 /*Mb*/ * 1024 * 1024;
  return GetPlatform().GetWritableStorageStatus(kSpaceSize) == Platform::TStorageStatus::STORAGE_OK;
}

TCountryId Framework::PreMigrate(ms::LatLon const & position,
                           Storage::TChangeCountryFunction const & change,
                           Storage::TProgressFunction const & progress)
{
  Storage().PrefetchMigrateData();

  auto const infoGetter =
      CountryInfoReader::CreateCountryInfoReaderOneComponentMwms(GetPlatform());

  TCountryId currentCountryId =
      infoGetter->GetRegionCountryId(MercatorBounds::FromLatLon(position));

  if (currentCountryId == kInvalidCountryId)
    return kInvalidCountryId;

  Storage().GetPrefetchStorage()->Subscribe(change, progress);
  Storage().GetPrefetchStorage()->DownloadNode(currentCountryId);
  return currentCountryId;
}

void Framework::Migrate(bool keepDownloaded)
{
  // Drape must be suspended while migration is performed since it access different parts of
  // framework (i.e. m_infoGetter) which are reinitialized during migration process.
  // If we do not suspend drape, it tries to access framework fields (i.e. m_infoGetter) which are null
  // while migration is performed.
  SetRenderingEnabled(false);
  m_searchEngine.reset();
  m_infoGetter.reset();
  TCountriesVec existedCountries;
  Storage().DeleteAllLocalMaps(&existedCountries);
  DeregisterAllMaps();
  m_model.Clear();
  Storage().Migrate(keepDownloaded ? existedCountries : TCountriesVec());
  InitCountryInfoGetter();
  InitSearchEngine();
  RegisterAllMaps();
  SetRenderingEnabled(true);
  InvalidateRect(MercatorBounds::FullRect());
}

Framework::Framework()
  : m_storage(platform::migrate::NeedMigrate() ? COUNTRIES_OBSOLETE_FILE : COUNTRIES_FILE)
  , m_bmManager(*this)
  , m_fixedSearchResults(0)
  , m_lastReportedCountry(kInvalidCountryId)
  , m_watchdogCountryUpdate(seconds(2))
{
  // Restore map style before classificator loading
  int mapStyle = MapStyleLight;
  if (!Settings::Get(kMapStyleKey, mapStyle))
    mapStyle = MapStyleClear;
  GetStyleReader().SetCurrentStyle(static_cast<MapStyle>(mapStyle));

  m_connectToGpsTrack = GpsTracker::Instance().IsEnabled();

  m_ParsedMapApi.SetBookmarkManager(&m_bmManager);

  // Init strings bundle.
  // @TODO. There are hardcoded strings below which are defined in strings.txt as well.
  // It's better to use strings form strings.txt intead of hardcoding them here.
  m_stringsBundle.SetDefaultString("dropped_pin", "Dropped Pin");
  m_stringsBundle.SetDefaultString("my_places", "My Places");
  m_stringsBundle.SetDefaultString("routes", "Routes");
  m_stringsBundle.SetDefaultString("wifi", "WiFi");

  m_stringsBundle.SetDefaultString("routing_failed_unknown_my_position", "Current location is undefined. Please specify location to create route.");
  m_stringsBundle.SetDefaultString("routing_failed_has_no_routing_file", "Additional data is required to create the route. Download data now?");
  m_stringsBundle.SetDefaultString("routing_failed_start_point_not_found", "Cannot calculate the route. No roads near your starting point.");
  m_stringsBundle.SetDefaultString("routing_failed_dst_point_not_found", "Cannot calculate the route. No roads near your destination.");
  m_stringsBundle.SetDefaultString("routing_failed_cross_mwm_building", "Routes can only be created that are fully contained within a single map.");
  m_stringsBundle.SetDefaultString("routing_failed_route_not_found", "There is no route found between the selected origin and destination.Please select a different start or end point.");
  m_stringsBundle.SetDefaultString("routing_failed_internal_error", "Internal error occurred. Please try to delete and download the map again. If problem persist please contact us at <EMAIL>.");

  m_model.InitClassificator();
  m_model.SetOnMapDeregisteredCallback(bind(&Framework::OnMapDeregistered, this, _1));
  LOG(LDEBUG, ("Classificator initialized"));


  // To avoid possible races - init country info getter once in constructor.
  InitCountryInfoGetter();
  LOG(LDEBUG, ("Country info getter initialized"));

  // To avoid possible races - init search engine once in constructor.
  InitSearchEngine();
  LOG(LDEBUG, ("Search engine initialized"));

  RegisterAllMaps();
  LOG(LDEBUG, ("Maps initialized"));

  // Init storage with needed callback.
  m_storage.Init(bind(&Framework::UpdateLatestCountryFile, this, _1));
  LOG(LDEBUG, ("Storage initialized"));

  auto const routingStatisticsFn = [](map<string, string> const & statistics)
  {
    alohalytics::LogEvent("Routing_CalculatingRoute", statistics);
  };
#ifdef DEBUG
  auto const routingVisualizerFn = [this](m2::PointD const & pt)
  {
    UserMarkControllerGuard guard(m_bmManager, UserMarkType::DEBUG_MARK);
    guard.m_controller.SetIsVisible(true);
    guard.m_controller.SetIsDrawable(true);

    guard.m_controller.CreateUserMark(pt);
  };
#else
  routing::RouterDelegate::TPointCheckCallback const routingVisualizerFn = nullptr;
#endif
  m_routingSession.Init(routingStatisticsFn, routingVisualizerFn);

  SetRouterImpl(RouterType::Vehicle);

  LOG(LDEBUG, ("Routing engine initialized"));

  LOG(LINFO, ("System languages:", languages::GetPreferred()));

  osm::Editor & editor = osm::Editor::Instance();
  editor.SetMwmIdByNameAndVersionFn([this](string const & name) -> MwmSet::MwmId
  {
    return m_model.GetIndex().GetMwmIdByCountryFile(platform::CountryFile(name));
  });
  editor.SetInvalidateFn([this](){ InvalidateRect(GetCurrentViewport()); });
  editor.SetFeatureLoaderFn([this](FeatureID const & fid) -> unique_ptr<FeatureType>
  {
    unique_ptr<FeatureType> feature(new FeatureType());
    Index::FeaturesLoaderGuard const guard(m_model.GetIndex(), fid.m_mwmId);
    guard.GetOriginalFeatureByIndex(fid.m_index, *feature);
    feature->ParseEverything();
    return feature;
  });
  editor.SetFeatureOriginalStreetFn([this](FeatureType const & ft) -> string
  {
    search::ReverseGeocoder const coder(m_model.GetIndex());
    auto const streets = coder.GetNearbyFeatureStreets(ft);
    if (streets.second < streets.first.size())
      return streets.first[streets.second].m_name;
    return {};
  });
  editor.SetForEachFeatureAtPointFn(bind(&Framework::ForEachFeatureAtPoint, this, _1, _2));
  editor.LoadMapEdits();
}

Framework::~Framework()
{
  m_drapeEngine.reset();

  m_model.SetOnMapDeregisteredCallback(nullptr);
}

void Framework::DrawWatchFrame(m2::PointD const & center, int zoomModifier,
                               uint32_t pxWidth, uint32_t pxHeight,
                               df::watch::FrameSymbols const & symbols,
                               df::watch::FrameImage & image)
{
  ASSERT(IsWatchFrameRendererInited(), ());

  int resultZoom = -1;
  ScreenBase screen = m_cpuDrawer->CalculateScreen(center, zoomModifier, pxWidth, pxHeight, symbols, resultZoom);
  ASSERT_GREATER(resultZoom, 0, ());

  uint32_t const bgColor = drule::rules().GetBgColor(resultZoom);
  m_cpuDrawer->BeginFrame(pxWidth, pxHeight, dp::Extract(bgColor, 255 - (bgColor >> 24)));

  m2::RectD renderRect = m2::RectD(0, 0, pxWidth, pxHeight);
  m2::RectD selectRect;
  m2::RectD clipRect;
  double const inflationSize = 24 * m_cpuDrawer->GetVisualScale();
  screen.PtoG(m2::Inflate(renderRect, inflationSize, inflationSize), clipRect);
  screen.PtoG(renderRect, selectRect);

  uint32_t const tileSize = static_cast<uint32_t>(df::CalculateTileSize(pxWidth, pxHeight));
  int const drawScale = df::GetDrawTileScale(screen, tileSize, m_cpuDrawer->GetVisualScale());
  df::watch::FeatureProcessor doDraw(make_ref(m_cpuDrawer), clipRect, screen, drawScale);

  int const upperScale = scales::GetUpperScale();
  m_model.ForEachFeature(selectRect, doDraw, min(upperScale, drawScale));

  m_cpuDrawer->Flush();
  m_cpuDrawer->DrawMyPosition(screen.GtoP(center));

  if (symbols.m_showSearchResult)
  {
    if (!screen.PixelRect().IsPointInside(screen.GtoP(symbols.m_searchResult)))
      m_cpuDrawer->DrawSearchArrow(ang::AngleTo(center, symbols.m_searchResult));
    else
      m_cpuDrawer->DrawSearchResult(screen.GtoP(symbols.m_searchResult));
  }

  m_cpuDrawer->EndFrame(image);
}

void Framework::InitWatchFrameRenderer(float visualScale)
{
  using namespace df::watch;

  ASSERT(!IsWatchFrameRendererInited(), ());
  if (m_cpuDrawer == nullptr)
  {
    string resPostfix = df::VisualParams::GetResourcePostfix(visualScale);
    m_cpuDrawer = make_unique_dp<CPUDrawer>(CPUDrawer::Params(resPostfix, visualScale));
  }
}

void Framework::ReleaseWatchFrameRenderer()
{
  if (IsWatchFrameRendererInited())
    m_cpuDrawer.reset();
}

bool Framework::IsWatchFrameRendererInited() const
{
  return m_cpuDrawer != nullptr;
}

void Framework::ShowNode(storage::TCountryId const & countryId)
{
  StopLocationFollow();

  ShowRect(CalcLimitRect(countryId, Storage(), CountryInfoGetter()));
}

void Framework::UpdateLatestCountryFile(LocalCountryFile const & localFile)
{
  // Soft reset to signal that mwm file may be out of date in routing caches.
  m_routingSession.Reset();

  if (!HasOptions(localFile.GetFiles(), MapOptions::Map))
    return;

  // Add downloaded map.
  auto p = m_model.RegisterMap(localFile);
  MwmSet::MwmId const & id = p.first;
  if (id.IsAlive())
    InvalidateRect(id.GetInfo()->m_limitRect);

  m_searchEngine->ClearCaches();
}

void Framework::OnMapDeregistered(platform::LocalCountryFile const & localFile)
{
  m_storage.DeleteCustomCountryVersion(localFile);
}

bool Framework::HasUnsavedEdits(storage::TCountryId const & countryId)
{
  bool hasUnsavedChanges = false;
  auto const forEachInSubtree = [&hasUnsavedChanges](storage::TCountryId const & /* descendantId */,
      bool groupNode)
  {
    if (!groupNode)
    {
      /// @TODO(AlexZ) |countryId| could be a group or leaf node in the country tree graph.
      /// This string is called for each leaf node of the subtree with the root at |countryId|
      /// including the root. |descendantId| is an id of leaf node. It's the same with
      /// an mwm file name (for leaf nodes). It's necessary to check if there're some changes
      /// in mwm with |descendantId| and if so to set hasUnsavedChanges to true.
    }
  };
  Storage().ForEachInSubtree(countryId, forEachInSubtree);
  return hasUnsavedChanges;
}

void Framework::RegisterAllMaps()
{
  ASSERT(!m_storage.IsDownloadInProgress(),
         ("Registering maps while map downloading leads to removing downloading maps from "
          "ActiveMapsListener::m_items."));

  m_storage.RegisterAllLocalMaps();

  // Fast migrate in case there are no downloaded MWM.
  if (platform::migrate::NeedMigrate())
  {
    bool disableFastMigrate = false;
    Settings::Get("DisableFastMigrate", disableFastMigrate);
    if (!disableFastMigrate && !m_storage.HaveDownloadedCountries())
    {
      Storage().PrefetchMigrateData();
      Migrate();
      return;
    }
  }

  int minFormat = numeric_limits<int>::max();

  vector<shared_ptr<LocalCountryFile>> maps;
  m_storage.GetLocalMaps(maps);
  for (auto const & localFile : maps)
  {
    auto p = RegisterMap(*localFile);
    if (p.second != MwmSet::RegResult::Success)
      continue;

    MwmSet::MwmId const & id = p.first;
    ASSERT(id.IsAlive(), ());
    minFormat = min(minFormat, static_cast<int>(id.GetInfo()->m_version.GetFormat()));
  }

  m_searchEngine->SetSupportOldFormat(minFormat < static_cast<int>(version::Format::v3));
}

void Framework::DeregisterAllMaps()
{
  m_model.Clear();
  m_storage.Clear();
}

void Framework::LoadBookmarks()
{
  m_bmManager.LoadBookmarks();
}

size_t Framework::AddBookmark(size_t categoryIndex, const m2::PointD & ptOrg, BookmarkData & bm)
{
  return m_bmManager.AddBookmark(categoryIndex, ptOrg, bm);
}

size_t Framework::MoveBookmark(size_t bmIndex, size_t curCatIndex, size_t newCatIndex)
{
  return m_bmManager.MoveBookmark(bmIndex, curCatIndex, newCatIndex);
}

void Framework::ReplaceBookmark(size_t catIndex, size_t bmIndex, BookmarkData const & bm)
{
  m_bmManager.ReplaceBookmark(catIndex, bmIndex, bm);
}

size_t Framework::AddCategory(string const & categoryName)
{
  return m_bmManager.CreateBmCategory(categoryName);
}

namespace
{
  class EqualCategoryName
  {
    string const & m_name;
  public:
    EqualCategoryName(string const & name) : m_name(name) {}
    bool operator() (BookmarkCategory const * cat) const
    {
      return (cat->GetName() == m_name);
    }
  };
}

BookmarkCategory * Framework::GetBmCategory(size_t index) const
{
  return m_bmManager.GetBmCategory(index);
}

bool Framework::DeleteBmCategory(size_t index)
{
  return m_bmManager.DeleteBmCategory(index);
}

void Framework::FillBookmarkInfo(Bookmark const & bmk, BookmarkAndCategory const & bac, place_page::Info & info) const
{
  FillPointInfo(bmk.GetPivot(), string(), info);
  info.m_bac = bac;
}

void Framework::FillFeatureInfo(FeatureID const & fid, place_page::Info & info) const
{
  if (!fid.IsValid())
  {
    LOG(LERROR, ("FeatureID is invalid:", fid));
    return;
  }

  Index::FeaturesLoaderGuard const guard(m_model.GetIndex(), fid.m_mwmId);
  FeatureType ft;
  guard.GetFeatureByIndex(fid.m_index, ft);
  info.SetFromFeatureType(ft);
  info.m_isEditable = osm::Editor::Instance().GetEditableProperties(ft).IsEditable();
  info.m_localizedWifiString = m_stringsBundle.GetString("wifi");
}

void Framework::FillPointInfo(m2::PointD const & mercator, string const & customTitle, place_page::Info & info) const
{
  auto feature = GetFeatureAtPoint(mercator);
  if (feature)
  {
    info.SetFromFeatureType(*feature);
    info.m_isEditable = osm::Editor::Instance().GetEditableProperties(*feature).IsEditable();
    info.m_localizedWifiString = m_stringsBundle.GetString("WiFi");
  }
  else
  {
    info.m_customName = customTitle.empty() ? m_stringsBundle.GetString("dropped_pin") : customTitle;
  }
  // This line overwrites mercator center from area feature which can be far away.
  info.SetMercator(mercator);
}

void Framework::FillApiMarkInfo(ApiMarkPoint const & api, place_page::Info & info) const
{
  FillPointInfo(api.GetPivot(), api.GetName(), info);
  info.m_apiUrl = GenerateApiBackUrl(api);
}

void Framework::FillMyPositionInfo(place_page::Info & info) const
{
  double lat, lon;
  VERIFY(GetCurrentPosition(lat, lon), ());
  info.SetMercator(MercatorBounds::FromLatLon(lat, lon));
  info.m_isMyPosition = true;
  info.m_customName = m_stringsBundle.GetString("my_position");
}

void Framework::ShowBookmark(BookmarkAndCategory const & bnc)
{
  StopLocationFollow();

  Bookmark const * mark = static_cast<Bookmark const *>(GetBmCategory(bnc.first)->GetUserMark(bnc.second));

  double scale = mark->GetScale();
  if (scale == -1.0)
    scale = scales::GetUpperComfortScale();

  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewCenter, _1, mark->GetPivot(), scale, true));

  place_page::Info info;
  FillBookmarkInfo(*mark, bnc, info);
  ActivateMapSelection(true, df::SelectionShape::OBJECT_USER_MARK, info);
}

void Framework::ShowTrack(Track const & track)
{
  StopLocationFollow();
  ShowRect(track.GetLimitRect());
}

void Framework::ClearBookmarks()
{
  m_bmManager.ClearItems();
}

namespace
{

/// @return extension with a dot in lower case
string const GetFileExt(string const & filePath)
{
  string ext = my::GetFileExtension(filePath);
  transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
  return ext;
}

string const GetFileName(string const & filePath)
{
  string ret = filePath;
  my::GetNameFromFullPath(ret);
  return ret;
}

string const GenerateValidAndUniqueFilePathForKML(string const & fileName)
{
  string filePath = BookmarkCategory::RemoveInvalidSymbols(fileName);
  filePath = BookmarkCategory::GenerateUniqueFileName(GetPlatform().SettingsDir(), filePath);
  return filePath;
}

} // namespace

bool Framework::AddBookmarksFile(string const & filePath)
{
  string const fileExt = GetFileExt(filePath);
  string fileSavePath;
  if (fileExt == BOOKMARKS_FILE_EXTENSION)
  {
    fileSavePath = GenerateValidAndUniqueFilePathForKML(GetFileName(filePath));
    if (!my::CopyFileX(filePath, fileSavePath))
      return false;
  }
  else if (fileExt == KMZ_EXTENSION)
  {
    try
    {
      ZipFileReader::FileListT files;
      ZipFileReader::FilesList(filePath, files);
      string kmlFileName;
      for (size_t i = 0; i < files.size(); ++i)
      {
        if (GetFileExt(files[i].first) == BOOKMARKS_FILE_EXTENSION)
        {
          kmlFileName = files[i].first;
          break;
        }
      }
      if (kmlFileName.empty())
        return false;

      fileSavePath = GenerateValidAndUniqueFilePathForKML(kmlFileName);
      ZipFileReader::UnzipFile(filePath, kmlFileName, fileSavePath);
    }
    catch (RootException const & e)
    {
      LOG(LWARNING, ("Error unzipping file", filePath, e.Msg()));
      return false;
    }
  }
  else
  {
    LOG(LWARNING, ("Unknown file type", filePath));
    return false;
  }

  // Update freshly added bookmarks
  m_bmManager.LoadBookmark(fileSavePath);

  return true;
}

void Framework::PrepareToShutdown()
{
  DestroyDrapeEngine();
}

void Framework::SaveViewport()
{
  m2::AnyRectD rect;
  if (m_currentModelView.isPerspective())
  {
    ScreenBase modelView = m_currentModelView;
    modelView.ResetPerspective();
    rect = modelView.GlobalRect();
  }
  else
  {
    rect = m_currentModelView.GlobalRect();
  }
  Settings::Set("ScreenClipRect", rect);
}

void Framework::LoadViewport()
{
  m2::AnyRectD rect;
  if (Settings::Get("ScreenClipRect", rect) && df::GetWorldRect().IsRectInside(rect.GetGlobalRect()))
    CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewAnyRect, _1, rect, false));
  else
    ShowAll();
}

void Framework::ShowAll()
{
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewAnyRect, _1, m2::AnyRectD(m_model.GetWorldRect()), false));
}

m2::PointD Framework::GetPixelCenter() const
{
  return m_currentModelView.isPerspective() ? m_currentModelView.PixelRectIn3d().Center()
                                            : m_currentModelView.PixelRect().Center();
}

m2::PointD const & Framework::GetViewportCenter() const
{
  return m_currentModelView.GetOrg();
}

void Framework::SetViewportCenter(m2::PointD const & pt)
{
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewCenter, _1, pt, -1, true));
}

m2::RectD Framework::GetCurrentViewport() const
{
  return m_currentModelView.ClipRect();
}

void Framework::ShowRect(double lat, double lon, double zoom)
{
  m2::PointD center(MercatorBounds::FromLatLon(lat, lon));
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewCenter, _1, center, zoom, true));
}

void Framework::ShowRect(m2::RectD const & rect, int maxScale)
{
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewRect, _1, rect, true, maxScale, true));
}

void Framework::ShowRect(m2::AnyRectD const & rect)
{
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewAnyRect, _1, rect, true));
}

void Framework::GetTouchRect(m2::PointD const & center, uint32_t pxRadius, m2::AnyRectD & rect)
{
  m_currentModelView.GetTouchRect(center, static_cast<double>(pxRadius), rect);
}

int Framework::AddViewportListener(TViewportChanged const & fn)
{
  ASSERT(m_drapeEngine, ());
  return m_drapeEngine->AddModelViewListener(fn);
}

void Framework::RemoveViewportListener(int slotID)
{
  ASSERT(m_drapeEngine, ());
  m_drapeEngine->RemoveModeViewListener(slotID);
}

void Framework::OnSize(int w, int h)
{
  CallDrapeFunction(bind(&df::DrapeEngine::Resize, _1, max(w, 2), max(h, 2)));
}

namespace
{

double ScaleModeToFactor(Framework::EScaleMode mode)
{
  double factors[] = { 2.0, 1.5, 0.5, 0.67 };
  return factors[mode];
}

} // namespace

void Framework::Scale(EScaleMode mode, bool isAnim)
{
  Scale(ScaleModeToFactor(mode), isAnim);
}

void Framework::Scale(Framework::EScaleMode mode, m2::PointD const & pxPoint, bool isAnim)
{
  Scale(ScaleModeToFactor(mode), pxPoint, isAnim);
}

void Framework::Scale(double factor, bool isAnim)
{
  Scale(factor, GetPixelCenter(), isAnim);
}

void Framework::Scale(double factor, m2::PointD const & pxPoint, bool isAnim)
{
  CallDrapeFunction(bind(&df::DrapeEngine::Scale, _1, factor, pxPoint, isAnim));
}

void Framework::TouchEvent(df::TouchEvent const & touch)
{
  CallDrapeFunction(bind(&df::DrapeEngine::AddTouchEvent, _1, touch));
}

int Framework::GetDrawScale() const
{
  return df::GetDrawTileScale(m_currentModelView);
}

bool Framework::IsCountryLoaded(m2::PointD const & pt) const
{
  // TODO (@gorshenin, @govako): the method's name is quite
  // obfuscating and should be fixed.

  // Correct, but slow version (check country polygon).
  string const fName = m_infoGetter->GetRegionCountryId(pt);
  if (fName.empty())
    return true;

  return m_model.IsLoaded(fName);
}

bool Framework::IsCountryLoadedByName(string const & name) const
{
  return m_model.IsLoaded(name);
}

void Framework::InvalidateRect(m2::RectD const & rect)
{
  CallDrapeFunction(bind(&df::DrapeEngine::InvalidateRect, _1, rect));
}

void Framework::StartInteractiveSearch(search::SearchParams const & params)
{
  using namespace search;

  m_lastInteractiveSearchParams = params;
  m_lastInteractiveSearchParams.SetForceSearch(false);
  m_lastInteractiveSearchParams.SetMode(Mode::Viewport);
  m_lastInteractiveSearchParams.m_onResults = [this](Results const & results)
  {
    if (!results.IsEndMarker())
    {
      GetPlatform().RunOnGuiThread([this, results]()
      {
        if (IsInteractiveSearchActive())
          FillSearchResultsMarks(results);
      });
    }
  };
}

void Framework::ClearAllCaches()
{
  m_model.ClearCaches();
  m_infoGetter->ClearCaches();
  m_searchEngine->ClearCaches();
}

void Framework::OnCheckUpdateCurrentCountry(m2::PointF const & pt, int zoomLevel)
{
  if (zoomLevel <= scales::GetUpperWorldScale())
    m_watchdogCountryUpdate.Drop();
  else
    m_watchdogCountryUpdate.RestartWith([this, pt, zoomLevel]{
      OnUpdateCurrentCountry(pt, zoomLevel);
    });
}

void Framework::OnUpdateCurrentCountry(m2::PointF const & pt, int zoomLevel)
{
  storage::TCountryId newCountryId = m_infoGetter->GetRegionCountryId(m2::PointD(pt));

  if (newCountryId == m_lastReportedCountry)
    return;

  m_lastReportedCountry = newCountryId;

  GetPlatform().RunOnGuiThread([this, newCountryId]()
  {
    if (m_currentCountryChanged != nullptr)
      m_currentCountryChanged(newCountryId);
  });
}

void Framework::SetCurrentCountryChangedListener(TCurrentCountryChanged const & listener)
{
  m_currentCountryChanged = listener;
  m_lastReportedCountry = kInvalidCountryId;
}

void Framework::UpdateUserViewportChanged()
{
  if (IsInteractiveSearchActive())
  {
    (void)GetCurrentPosition(m_lastInteractiveSearchParams.m_lat,
                             m_lastInteractiveSearchParams.m_lon);
    Search(m_lastInteractiveSearchParams);
  }
}

bool Framework::Search(search::SearchParams const & params)
{
#ifdef FIXED_LOCATION
  search::SearchParams rParams(params);
  if (params.IsValidPosition())
  {
    m_fixedPos.GetLat(rParams.m_lat);
    m_fixedPos.GetLon(rParams.m_lon);
  }
#else
  search::SearchParams const & rParams = params;
#endif

  ParseSetGpsTrackMinAccuracyCommand(params.m_query);
  if (ParseEditorDebugCommand(params))
    return true;

  m2::RectD const viewport = GetCurrentViewport();

  if (QueryMayBeSkipped(rParams, viewport))
    return false;

  m_lastQueryParams = rParams;
  m_lastQueryViewport = viewport;

  // Cancels previous search request (if any) and initiates new search request.
  CancelQuery(m_lastQueryHandle);
  m_lastQueryHandle = m_searchEngine->Search(m_lastQueryParams, m_lastQueryViewport);
  return true;
}

void Framework::MemoryWarning()
{
  LOG(LINFO, ("MemoryWarning"));
  ClearAllCaches();
  SharedBufferManager::instance().clearReserved();
}

void Framework::EnterBackground()
{
  SetRenderingEnabled(false);
  SaveViewport();

  ms::LatLon const ll = MercatorBounds::ToLatLon(GetViewportCenter());
  alohalytics::Stats::Instance().LogEvent("Framework::EnterBackground", {{"zoom", strings::to_string(GetDrawScale())},
                                          {"foregroundSeconds", strings::to_string(
                                           static_cast<int>(my::Timer::LocalTime() - m_startForegroundTime))}},
                                          alohalytics::Location::FromLatLon(ll.lat, ll.lon));
  // Do not clear caches for Android. This function is called when main activity is paused,
  // but at the same time search activity (for example) is enabled.
  // TODO(AlexZ): Use onStart/onStop on Android to correctly detect app background and remove #ifndef.
#ifndef OMIM_OS_ANDROID
  ClearAllCaches();
#endif
}

void Framework::EnterForeground()
{
  m_startForegroundTime = my::Timer::LocalTime();

  // Drape can be not initialized here in case of the first launch
  // TODO(AlexZ): Why it can't be initialized here? Is it because we call EnterForeground in Android for every
  // time when activity is created? If yes, then this code should be refactored:
  // EnterForeground and EnterBackground should be called only when user opens the app and
  // when user completely leaves the app (and is not just switching between app's activities).
  SetRenderingEnabled(true);
}

bool Framework::GetCurrentPosition(double & lat, double & lon) const
{
  m2::PointD pos;
  MyPositionMarkPoint * myPosMark = UserMarkContainer::UserMarkForMyPostion();
  if (!myPosMark->HasPosition())
    return false;

  pos = myPosMark->GetPivot();

  lat = MercatorBounds::YToLat(pos.y);
  lon = MercatorBounds::XToLon(pos.x);
  return true;
}

void Framework::InitCountryInfoGetter()
{
  ASSERT(!m_infoGetter.get(), ("InitCountryInfoGetter() must be called only once."));
  Platform const & platform = GetPlatform();
  m_infoGetter = CountryInfoReader::CreateCountryInfoReader(platform);
}

void Framework::InitSearchEngine()
{
  ASSERT(!m_searchEngine.get(), ("InitSearchEngine() must be called only once."));
  ASSERT(m_infoGetter.get(), ());
  try
  {
    search::Engine::Params params;
    params.m_locale = languages::GetCurrentOrig();
    params.m_numThreads = 1;
    m_searchEngine.reset(new search::Engine(const_cast<Index &>(m_model.GetIndex()),
                                            GetDefaultCategories(), *m_infoGetter,
                                            make_unique<search::SearchQueryFactory>(), params));
  }
  catch (RootException const & e)
  {
    LOG(LCRITICAL, ("Can't load needed resources for search::Engine:", e.Msg()));
  }
}

storage::TCountryId Framework::GetCountryIndex(m2::PointD const & pt) const
{
  return m_infoGetter->GetRegionCountryId(pt);
}

string Framework::GetCountryName(m2::PointD const & pt) const
{
  storage::CountryInfo info;
  m_infoGetter->GetRegionInfo(pt, info);
  return info.m_name;
}

bool Framework::QueryMayBeSkipped(search::SearchParams const & params,
                                  m2::RectD const & viewport) const
{
  if (params.IsForceSearch())
    return false;
  if (!m_lastQueryParams.IsEqualCommon(params))
    return false;
  if (!m_lastQueryViewport.IsValid() ||
      !search::IsEqualMercator(m_lastQueryViewport, viewport, kDistEqualQuery))
  {
    return false;
  }
  if (!m_lastQueryParams.IsSearchAroundPosition() ||
      ms::DistanceOnEarth(m_lastQueryParams.m_lat, m_lastQueryParams.m_lon, params.m_lat,
                          params.m_lon) <= kDistEqualQuery)
  {
    return false;
  }
  return true;
}

void Framework::LoadSearchResultMetadata(search::Result & res) const
{
  if (res.m_metadata.m_isInitialized)
    return;

  FeatureID const & id = res.GetFeatureID();
  if (id.IsValid())
    search::ProcessMetadata(*GetFeatureByID(id), res.m_metadata);
  // res.m_metadata.m_isInitialized is set to true in ProcessMetadata.
}

void Framework::ShowSearchResult(search::Result const & res)
{
  CancelInteractiveSearch();
  StopLocationFollow();

  alohalytics::LogEvent("searchShowResult", {{"pos", strings::to_string(res.GetPositionInResults())},
                                             {"result", res.ToStringForStats()}});
  place_page::Info info;
  using namespace search;
  int scale;
  switch (res.GetResultType())
  {
    case Result::RESULT_FEATURE:
      FillFeatureInfo(res.GetFeatureID(), info);
      scale = GetFeatureViewportScale(info.GetTypes());
      break;

    case Result::RESULT_LATLON:
    case Result::RESULT_ADDRESS:
      FillPointInfo(res.GetFeatureCenter(), res.GetString(), info);
      scale = scales::GetUpperComfortScale();
      break;

    case Result::RESULT_SUGGEST_PURE:
    case Result::RESULT_SUGGEST_FROM_FEATURE:
      ASSERT(false, ("Suggests should not be here."));
      return;
  }

  m2::PointD const center = info.GetMercator();
  if (m_currentModelView.isPerspective())
    CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewCenter, _1, center, scale, true));
  else
    ShowRect(df::GetRectForDrawScale(scale, center));

  {
    UserMarkControllerGuard guard(m_bmManager, UserMarkType::SEARCH_MARK);
    guard.m_controller.SetIsDrawable(false);
    guard.m_controller.Clear();
    guard.m_controller.SetIsVisible(true);
    UNUSED_VALUE(guard.m_controller.CreateUserMark(center));
  }
  ActivateMapSelection(false, df::SelectionShape::OBJECT_USER_MARK, info);
}

size_t Framework::ShowSearchResults(search::Results const & results)
{
  using namespace search;

  size_t count = results.GetCount();
  switch (count)
  {
  case 1:
    {
      Result const & r = results.GetResult(0);
      if (!r.IsSuggest())
        ShowSearchResult(r);
      else
        count = 0;
      // do not put break here
    }
  case 0:
    return count;
  }

  m_fixedSearchResults = 0;
  FillSearchResultsMarks(results);
  m_fixedSearchResults = count;

  // Setup viewport according to results.
  m2::AnyRectD viewport = m_currentModelView.GlobalRect();
  m2::PointD const center = viewport.Center();

  double minDistance = numeric_limits<double>::max();<commit_msg>Use POI selection type when clicking on a single search result.<commit_after>#include "map/framework.hpp"
#include "map/ge0_parser.hpp"
#include "map/geourl_process.hpp"
#include "map/gps_tracker.hpp"

#include "defines.hpp"

#include "routing/online_absent_fetcher.hpp"
#include "routing/osrm_router.hpp"
#include "routing/road_graph_router.hpp"
#include "routing/route.hpp"
#include "routing/routing_algorithm.hpp"

#include "search/geometry_utils.hpp"
#include "search/intermediate_result.hpp"
#include "search/result.hpp"
#include "search/reverse_geocoder.hpp"
#include "search/search_engine.hpp"
#include "search/search_query_factory.hpp"

#include "storage/storage_helpers.hpp"

#include "drape_frontend/color_constants.hpp"
#include "drape_frontend/gps_track_point.hpp"
#include "drape_frontend/visual_params.hpp"
#include "drape_frontend/watch/cpu_drawer.hpp"
#include "drape_frontend/watch/feature_processor.hpp"

#include "indexer/categories_holder.hpp"
#include "indexer/classificator_loader.hpp"
#include "indexer/drawing_rules.hpp"
#include "indexer/editable_map_object.hpp"
#include "indexer/feature.hpp"
#include "indexer/map_style_reader.hpp"
#include "indexer/osm_editor.hpp"
#include "indexer/scales.hpp"

/// @todo Probably it's better to join this functionality.
//@{
#include "indexer/feature_algo.hpp"
#include "indexer/feature_utils.hpp"
//@}

#include "storage/country_info_getter.hpp"

#include "platform/local_country_file_utils.hpp"
#include "platform/measurement_utils.hpp"
#include "platform/mwm_version.hpp"
#include "platform/platform.hpp"
#include "platform/preferred_languages.hpp"
#include "platform/settings.hpp"

#include "coding/internal/file_data.hpp"
#include "coding/zip_reader.hpp"
#include "coding/url_encode.hpp"
#include "coding/file_name_utils.hpp"
#include "coding/png_memory_encoder.hpp"

#include "geometry/angles.hpp"
#include "geometry/distance_on_sphere.hpp"

#include "base/math.hpp"
#include "base/timer.hpp"
#include "base/scope_guard.hpp"

#include "std/algorithm.hpp"
#include "std/bind.hpp"
#include "std/target_os.hpp"
#include "std/vector.hpp"

#include "api/internal/c/api-client-internals.h"
#include "api/src/c/api-client.h"

#include "3party/Alohalytics/src/alohalytics.h"

#define KMZ_EXTENSION ".kmz"

#define DEFAULT_BOOKMARK_TYPE "placemark-red"

using namespace storage;
using namespace routing;
using namespace location;

using platform::CountryFile;
using platform::LocalCountryFile;

#ifdef FIXED_LOCATION
Framework::FixedPosition::FixedPosition()
{
  m_fixedLatLon = Settings::Get("FixPosition", m_latlon);
  m_fixedDir = Settings::Get("FixDirection", m_dirFromNorth);
}
#endif

namespace
{
static const int BM_TOUCH_PIXEL_INCREASE = 20;
static const int kKeepPedestrianDistanceMeters = 10000;
char const kRouterTypeKey[] = "router";
char const kMapStyleKey[] = "MapStyleKeyV1";
char const kAllow3dKey[] = "Allow3d";
char const kAllow3dBuildingsKey[] = "Buildings3d";

double const kDistEqualQuery = 100.0;

double const kRotationAngle = math::pi4;
double const kAngleFOV = math::pi / 3.0;

// TODO!
// To adjust GpsTrackFilter was added secret command "?gpstrackaccuracy:xxx;"
// where xxx is a new value for horizontal accuracy.
// This is temporary solution while we don't have a good filter.
void ParseSetGpsTrackMinAccuracyCommand(string const & query)
{
  const char kGpsAccuracy[] = "?gpstrackaccuracy:";
  if (strings::StartsWith(query, kGpsAccuracy))
  {
    size_t const end = query.find(';', sizeof(kGpsAccuracy) - 1);
    if (end != string::npos)
    {
      string s(query.begin() + sizeof(kGpsAccuracy) - 1, query.begin() + end);
      double value;
      if (strings::to_double(s, value))
      {
        GpsTrackFilter::StoreMinHorizontalAccuracy(value);
      }
    }
  }
}

// Cancels search query by |handle|.
void CancelQuery(weak_ptr<search::QueryHandle> & handle)
{
  auto queryHandle = handle.lock();
  if (queryHandle)
    queryHandle->Cancel();
  handle.reset();
}
}  // namespace

pair<MwmSet::MwmId, MwmSet::RegResult> Framework::RegisterMap(
    LocalCountryFile const & localFile)
{
  LOG(LINFO, ("Loading map:", localFile.GetCountryName()));
  return m_model.RegisterMap(localFile);
}

void Framework::OnLocationError(TLocationError /*error*/)
{
  CallDrapeFunction(bind(&df::DrapeEngine::CancelMyPosition, _1));
}

void Framework::OnLocationUpdate(GpsInfo const & info)
{
#ifdef FIXED_LOCATION
  GpsInfo rInfo(info);

  // get fixed coordinates
  m_fixedPos.GetLon(rInfo.m_longitude);
  m_fixedPos.GetLat(rInfo.m_latitude);

  // pretend like GPS position
  rInfo.m_horizontalAccuracy = 5.0;

  if (m_fixedPos.HasNorth())
  {
    // pass compass value (for devices without compass)
    CompassInfo compass;
    m_fixedPos.GetNorth(compass.m_bearing);
    OnCompassUpdate(compass);
  }

#else
  GpsInfo rInfo(info);
#endif

#ifdef OMIM_OS_ANDROID
  m_lastGPSInfo.reset(new GpsInfo(rInfo));
#endif
  location::RouteMatchingInfo routeMatchingInfo;
  CheckLocationForRouting(rInfo);

  MatchLocationToRoute(rInfo, routeMatchingInfo);

  CallDrapeFunction(bind(&df::DrapeEngine::SetGpsInfo, _1, rInfo,
                         m_routingSession.IsNavigable(), routeMatchingInfo));
}

void Framework::OnCompassUpdate(CompassInfo const & info)
{
#ifdef FIXED_LOCATION
  CompassInfo rInfo(info);
  m_fixedPos.GetNorth(rInfo.m_bearing);
#else
  CompassInfo const & rInfo = info;
#endif

#ifdef OMIM_OS_ANDROID
  m_lastCompassInfo.reset(new CompassInfo(rInfo));
#endif
  CallDrapeFunction(bind(&df::DrapeEngine::SetCompassInfo, _1, rInfo));
}

void Framework::SwitchMyPositionNextMode()
{
  CallDrapeFunction(bind(&df::DrapeEngine::MyPositionNextMode, _1));
}

void Framework::InvalidateMyPosition()
{
  CallDrapeFunction(bind(&df::DrapeEngine::InvalidateMyPosition, _1));
}

void Framework::SetMyPositionModeListener(TMyPositionModeChanged && fn)
{
  m_myPositionListener = move(fn);
}

void Framework::OnUserPositionChanged(m2::PointD const & position)
{
  MyPositionMarkPoint * myPosition = UserMarkContainer::UserMarkForMyPostion();
  myPosition->SetUserPosition(position);

  if (IsRoutingActive())
    m_routingSession.SetUserCurrentPosition(position);
}

void Framework::CallDrapeFunction(TDrapeFunction const & fn) const
{
  if (m_drapeEngine)
    fn(m_drapeEngine.get());
}

void Framework::StopLocationFollow()
{
  CallDrapeFunction(bind(&df::DrapeEngine::StopLocationFollow, _1));
}

bool Framework::IsEnoughSpaceForMigrate() const
{
  uint64_t const kSpaceSize = 100 /*Mb*/ * 1024 * 1024;
  return GetPlatform().GetWritableStorageStatus(kSpaceSize) == Platform::TStorageStatus::STORAGE_OK;
}

TCountryId Framework::PreMigrate(ms::LatLon const & position,
                           Storage::TChangeCountryFunction const & change,
                           Storage::TProgressFunction const & progress)
{
  Storage().PrefetchMigrateData();

  auto const infoGetter =
      CountryInfoReader::CreateCountryInfoReaderOneComponentMwms(GetPlatform());

  TCountryId currentCountryId =
      infoGetter->GetRegionCountryId(MercatorBounds::FromLatLon(position));

  if (currentCountryId == kInvalidCountryId)
    return kInvalidCountryId;

  Storage().GetPrefetchStorage()->Subscribe(change, progress);
  Storage().GetPrefetchStorage()->DownloadNode(currentCountryId);
  return currentCountryId;
}

void Framework::Migrate(bool keepDownloaded)
{
  // Drape must be suspended while migration is performed since it access different parts of
  // framework (i.e. m_infoGetter) which are reinitialized during migration process.
  // If we do not suspend drape, it tries to access framework fields (i.e. m_infoGetter) which are null
  // while migration is performed.
  SetRenderingEnabled(false);
  m_searchEngine.reset();
  m_infoGetter.reset();
  TCountriesVec existedCountries;
  Storage().DeleteAllLocalMaps(&existedCountries);
  DeregisterAllMaps();
  m_model.Clear();
  Storage().Migrate(keepDownloaded ? existedCountries : TCountriesVec());
  InitCountryInfoGetter();
  InitSearchEngine();
  RegisterAllMaps();
  SetRenderingEnabled(true);
  InvalidateRect(MercatorBounds::FullRect());
}

Framework::Framework()
  : m_storage(platform::migrate::NeedMigrate() ? COUNTRIES_OBSOLETE_FILE : COUNTRIES_FILE)
  , m_bmManager(*this)
  , m_fixedSearchResults(0)
  , m_lastReportedCountry(kInvalidCountryId)
  , m_watchdogCountryUpdate(seconds(2))
{
  // Restore map style before classificator loading
  int mapStyle = MapStyleLight;
  if (!Settings::Get(kMapStyleKey, mapStyle))
    mapStyle = MapStyleClear;
  GetStyleReader().SetCurrentStyle(static_cast<MapStyle>(mapStyle));

  m_connectToGpsTrack = GpsTracker::Instance().IsEnabled();

  m_ParsedMapApi.SetBookmarkManager(&m_bmManager);

  // Init strings bundle.
  // @TODO. There are hardcoded strings below which are defined in strings.txt as well.
  // It's better to use strings form strings.txt intead of hardcoding them here.
  m_stringsBundle.SetDefaultString("dropped_pin", "Dropped Pin");
  m_stringsBundle.SetDefaultString("my_places", "My Places");
  m_stringsBundle.SetDefaultString("routes", "Routes");
  m_stringsBundle.SetDefaultString("wifi", "WiFi");

  m_stringsBundle.SetDefaultString("routing_failed_unknown_my_position", "Current location is undefined. Please specify location to create route.");
  m_stringsBundle.SetDefaultString("routing_failed_has_no_routing_file", "Additional data is required to create the route. Download data now?");
  m_stringsBundle.SetDefaultString("routing_failed_start_point_not_found", "Cannot calculate the route. No roads near your starting point.");
  m_stringsBundle.SetDefaultString("routing_failed_dst_point_not_found", "Cannot calculate the route. No roads near your destination.");
  m_stringsBundle.SetDefaultString("routing_failed_cross_mwm_building", "Routes can only be created that are fully contained within a single map.");
  m_stringsBundle.SetDefaultString("routing_failed_route_not_found", "There is no route found between the selected origin and destination.Please select a different start or end point.");
  m_stringsBundle.SetDefaultString("routing_failed_internal_error", "Internal error occurred. Please try to delete and download the map again. If problem persist please contact us at <EMAIL>.");

  m_model.InitClassificator();
  m_model.SetOnMapDeregisteredCallback(bind(&Framework::OnMapDeregistered, this, _1));
  LOG(LDEBUG, ("Classificator initialized"));


  // To avoid possible races - init country info getter once in constructor.
  InitCountryInfoGetter();
  LOG(LDEBUG, ("Country info getter initialized"));

  // To avoid possible races - init search engine once in constructor.
  InitSearchEngine();
  LOG(LDEBUG, ("Search engine initialized"));

  RegisterAllMaps();
  LOG(LDEBUG, ("Maps initialized"));

  // Init storage with needed callback.
  m_storage.Init(bind(&Framework::UpdateLatestCountryFile, this, _1));
  LOG(LDEBUG, ("Storage initialized"));

  auto const routingStatisticsFn = [](map<string, string> const & statistics)
  {
    alohalytics::LogEvent("Routing_CalculatingRoute", statistics);
  };
#ifdef DEBUG
  auto const routingVisualizerFn = [this](m2::PointD const & pt)
  {
    UserMarkControllerGuard guard(m_bmManager, UserMarkType::DEBUG_MARK);
    guard.m_controller.SetIsVisible(true);
    guard.m_controller.SetIsDrawable(true);

    guard.m_controller.CreateUserMark(pt);
  };
#else
  routing::RouterDelegate::TPointCheckCallback const routingVisualizerFn = nullptr;
#endif
  m_routingSession.Init(routingStatisticsFn, routingVisualizerFn);

  SetRouterImpl(RouterType::Vehicle);

  LOG(LDEBUG, ("Routing engine initialized"));

  LOG(LINFO, ("System languages:", languages::GetPreferred()));

  osm::Editor & editor = osm::Editor::Instance();
  editor.SetMwmIdByNameAndVersionFn([this](string const & name) -> MwmSet::MwmId
  {
    return m_model.GetIndex().GetMwmIdByCountryFile(platform::CountryFile(name));
  });
  editor.SetInvalidateFn([this](){ InvalidateRect(GetCurrentViewport()); });
  editor.SetFeatureLoaderFn([this](FeatureID const & fid) -> unique_ptr<FeatureType>
  {
    unique_ptr<FeatureType> feature(new FeatureType());
    Index::FeaturesLoaderGuard const guard(m_model.GetIndex(), fid.m_mwmId);
    guard.GetOriginalFeatureByIndex(fid.m_index, *feature);
    feature->ParseEverything();
    return feature;
  });
  editor.SetFeatureOriginalStreetFn([this](FeatureType const & ft) -> string
  {
    search::ReverseGeocoder const coder(m_model.GetIndex());
    auto const streets = coder.GetNearbyFeatureStreets(ft);
    if (streets.second < streets.first.size())
      return streets.first[streets.second].m_name;
    return {};
  });
  editor.SetForEachFeatureAtPointFn(bind(&Framework::ForEachFeatureAtPoint, this, _1, _2));
  editor.LoadMapEdits();
}

Framework::~Framework()
{
  m_drapeEngine.reset();

  m_model.SetOnMapDeregisteredCallback(nullptr);
}

void Framework::DrawWatchFrame(m2::PointD const & center, int zoomModifier,
                               uint32_t pxWidth, uint32_t pxHeight,
                               df::watch::FrameSymbols const & symbols,
                               df::watch::FrameImage & image)
{
  ASSERT(IsWatchFrameRendererInited(), ());

  int resultZoom = -1;
  ScreenBase screen = m_cpuDrawer->CalculateScreen(center, zoomModifier, pxWidth, pxHeight, symbols, resultZoom);
  ASSERT_GREATER(resultZoom, 0, ());

  uint32_t const bgColor = drule::rules().GetBgColor(resultZoom);
  m_cpuDrawer->BeginFrame(pxWidth, pxHeight, dp::Extract(bgColor, 255 - (bgColor >> 24)));

  m2::RectD renderRect = m2::RectD(0, 0, pxWidth, pxHeight);
  m2::RectD selectRect;
  m2::RectD clipRect;
  double const inflationSize = 24 * m_cpuDrawer->GetVisualScale();
  screen.PtoG(m2::Inflate(renderRect, inflationSize, inflationSize), clipRect);
  screen.PtoG(renderRect, selectRect);

  uint32_t const tileSize = static_cast<uint32_t>(df::CalculateTileSize(pxWidth, pxHeight));
  int const drawScale = df::GetDrawTileScale(screen, tileSize, m_cpuDrawer->GetVisualScale());
  df::watch::FeatureProcessor doDraw(make_ref(m_cpuDrawer), clipRect, screen, drawScale);

  int const upperScale = scales::GetUpperScale();
  m_model.ForEachFeature(selectRect, doDraw, min(upperScale, drawScale));

  m_cpuDrawer->Flush();
  m_cpuDrawer->DrawMyPosition(screen.GtoP(center));

  if (symbols.m_showSearchResult)
  {
    if (!screen.PixelRect().IsPointInside(screen.GtoP(symbols.m_searchResult)))
      m_cpuDrawer->DrawSearchArrow(ang::AngleTo(center, symbols.m_searchResult));
    else
      m_cpuDrawer->DrawSearchResult(screen.GtoP(symbols.m_searchResult));
  }

  m_cpuDrawer->EndFrame(image);
}

void Framework::InitWatchFrameRenderer(float visualScale)
{
  using namespace df::watch;

  ASSERT(!IsWatchFrameRendererInited(), ());
  if (m_cpuDrawer == nullptr)
  {
    string resPostfix = df::VisualParams::GetResourcePostfix(visualScale);
    m_cpuDrawer = make_unique_dp<CPUDrawer>(CPUDrawer::Params(resPostfix, visualScale));
  }
}

void Framework::ReleaseWatchFrameRenderer()
{
  if (IsWatchFrameRendererInited())
    m_cpuDrawer.reset();
}

bool Framework::IsWatchFrameRendererInited() const
{
  return m_cpuDrawer != nullptr;
}

void Framework::ShowNode(storage::TCountryId const & countryId)
{
  StopLocationFollow();

  ShowRect(CalcLimitRect(countryId, Storage(), CountryInfoGetter()));
}

void Framework::UpdateLatestCountryFile(LocalCountryFile const & localFile)
{
  // Soft reset to signal that mwm file may be out of date in routing caches.
  m_routingSession.Reset();

  if (!HasOptions(localFile.GetFiles(), MapOptions::Map))
    return;

  // Add downloaded map.
  auto p = m_model.RegisterMap(localFile);
  MwmSet::MwmId const & id = p.first;
  if (id.IsAlive())
    InvalidateRect(id.GetInfo()->m_limitRect);

  m_searchEngine->ClearCaches();
}

void Framework::OnMapDeregistered(platform::LocalCountryFile const & localFile)
{
  m_storage.DeleteCustomCountryVersion(localFile);
}

bool Framework::HasUnsavedEdits(storage::TCountryId const & countryId)
{
  bool hasUnsavedChanges = false;
  auto const forEachInSubtree = [&hasUnsavedChanges](storage::TCountryId const & /* descendantId */,
      bool groupNode)
  {
    if (!groupNode)
    {
      /// @TODO(AlexZ) |countryId| could be a group or leaf node in the country tree graph.
      /// This string is called for each leaf node of the subtree with the root at |countryId|
      /// including the root. |descendantId| is an id of leaf node. It's the same with
      /// an mwm file name (for leaf nodes). It's necessary to check if there're some changes
      /// in mwm with |descendantId| and if so to set hasUnsavedChanges to true.
    }
  };
  Storage().ForEachInSubtree(countryId, forEachInSubtree);
  return hasUnsavedChanges;
}

void Framework::RegisterAllMaps()
{
  ASSERT(!m_storage.IsDownloadInProgress(),
         ("Registering maps while map downloading leads to removing downloading maps from "
          "ActiveMapsListener::m_items."));

  m_storage.RegisterAllLocalMaps();

  // Fast migrate in case there are no downloaded MWM.
  if (platform::migrate::NeedMigrate())
  {
    bool disableFastMigrate = false;
    Settings::Get("DisableFastMigrate", disableFastMigrate);
    if (!disableFastMigrate && !m_storage.HaveDownloadedCountries())
    {
      Storage().PrefetchMigrateData();
      Migrate();
      return;
    }
  }

  int minFormat = numeric_limits<int>::max();

  vector<shared_ptr<LocalCountryFile>> maps;
  m_storage.GetLocalMaps(maps);
  for (auto const & localFile : maps)
  {
    auto p = RegisterMap(*localFile);
    if (p.second != MwmSet::RegResult::Success)
      continue;

    MwmSet::MwmId const & id = p.first;
    ASSERT(id.IsAlive(), ());
    minFormat = min(minFormat, static_cast<int>(id.GetInfo()->m_version.GetFormat()));
  }

  m_searchEngine->SetSupportOldFormat(minFormat < static_cast<int>(version::Format::v3));
}

void Framework::DeregisterAllMaps()
{
  m_model.Clear();
  m_storage.Clear();
}

void Framework::LoadBookmarks()
{
  m_bmManager.LoadBookmarks();
}

size_t Framework::AddBookmark(size_t categoryIndex, const m2::PointD & ptOrg, BookmarkData & bm)
{
  return m_bmManager.AddBookmark(categoryIndex, ptOrg, bm);
}

size_t Framework::MoveBookmark(size_t bmIndex, size_t curCatIndex, size_t newCatIndex)
{
  return m_bmManager.MoveBookmark(bmIndex, curCatIndex, newCatIndex);
}

void Framework::ReplaceBookmark(size_t catIndex, size_t bmIndex, BookmarkData const & bm)
{
  m_bmManager.ReplaceBookmark(catIndex, bmIndex, bm);
}

size_t Framework::AddCategory(string const & categoryName)
{
  return m_bmManager.CreateBmCategory(categoryName);
}

namespace
{
  class EqualCategoryName
  {
    string const & m_name;
  public:
    EqualCategoryName(string const & name) : m_name(name) {}
    bool operator() (BookmarkCategory const * cat) const
    {
      return (cat->GetName() == m_name);
    }
  };
}

BookmarkCategory * Framework::GetBmCategory(size_t index) const
{
  return m_bmManager.GetBmCategory(index);
}

bool Framework::DeleteBmCategory(size_t index)
{
  return m_bmManager.DeleteBmCategory(index);
}

void Framework::FillBookmarkInfo(Bookmark const & bmk, BookmarkAndCategory const & bac, place_page::Info & info) const
{
  FillPointInfo(bmk.GetPivot(), string(), info);
  info.m_bac = bac;
}

void Framework::FillFeatureInfo(FeatureID const & fid, place_page::Info & info) const
{
  if (!fid.IsValid())
  {
    LOG(LERROR, ("FeatureID is invalid:", fid));
    return;
  }

  Index::FeaturesLoaderGuard const guard(m_model.GetIndex(), fid.m_mwmId);
  FeatureType ft;
  guard.GetFeatureByIndex(fid.m_index, ft);
  info.SetFromFeatureType(ft);
  info.m_isEditable = osm::Editor::Instance().GetEditableProperties(ft).IsEditable();
  info.m_localizedWifiString = m_stringsBundle.GetString("wifi");
}

void Framework::FillPointInfo(m2::PointD const & mercator, string const & customTitle, place_page::Info & info) const
{
  auto feature = GetFeatureAtPoint(mercator);
  if (feature)
  {
    info.SetFromFeatureType(*feature);
    info.m_isEditable = osm::Editor::Instance().GetEditableProperties(*feature).IsEditable();
    info.m_localizedWifiString = m_stringsBundle.GetString("WiFi");
  }
  else
  {
    info.m_customName = customTitle.empty() ? m_stringsBundle.GetString("dropped_pin") : customTitle;
  }
  // This line overwrites mercator center from area feature which can be far away.
  info.SetMercator(mercator);
}

void Framework::FillApiMarkInfo(ApiMarkPoint const & api, place_page::Info & info) const
{
  FillPointInfo(api.GetPivot(), api.GetName(), info);
  info.m_apiUrl = GenerateApiBackUrl(api);
}

void Framework::FillMyPositionInfo(place_page::Info & info) const
{
  double lat, lon;
  VERIFY(GetCurrentPosition(lat, lon), ());
  info.SetMercator(MercatorBounds::FromLatLon(lat, lon));
  info.m_isMyPosition = true;
  info.m_customName = m_stringsBundle.GetString("my_position");
}

void Framework::ShowBookmark(BookmarkAndCategory const & bnc)
{
  StopLocationFollow();

  Bookmark const * mark = static_cast<Bookmark const *>(GetBmCategory(bnc.first)->GetUserMark(bnc.second));

  double scale = mark->GetScale();
  if (scale == -1.0)
    scale = scales::GetUpperComfortScale();

  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewCenter, _1, mark->GetPivot(), scale, true));

  place_page::Info info;
  FillBookmarkInfo(*mark, bnc, info);
  ActivateMapSelection(true, df::SelectionShape::OBJECT_USER_MARK, info);
}

void Framework::ShowTrack(Track const & track)
{
  StopLocationFollow();
  ShowRect(track.GetLimitRect());
}

void Framework::ClearBookmarks()
{
  m_bmManager.ClearItems();
}

namespace
{

/// @return extension with a dot in lower case
string const GetFileExt(string const & filePath)
{
  string ext = my::GetFileExtension(filePath);
  transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
  return ext;
}

string const GetFileName(string const & filePath)
{
  string ret = filePath;
  my::GetNameFromFullPath(ret);
  return ret;
}

string const GenerateValidAndUniqueFilePathForKML(string const & fileName)
{
  string filePath = BookmarkCategory::RemoveInvalidSymbols(fileName);
  filePath = BookmarkCategory::GenerateUniqueFileName(GetPlatform().SettingsDir(), filePath);
  return filePath;
}

} // namespace

bool Framework::AddBookmarksFile(string const & filePath)
{
  string const fileExt = GetFileExt(filePath);
  string fileSavePath;
  if (fileExt == BOOKMARKS_FILE_EXTENSION)
  {
    fileSavePath = GenerateValidAndUniqueFilePathForKML(GetFileName(filePath));
    if (!my::CopyFileX(filePath, fileSavePath))
      return false;
  }
  else if (fileExt == KMZ_EXTENSION)
  {
    try
    {
      ZipFileReader::FileListT files;
      ZipFileReader::FilesList(filePath, files);
      string kmlFileName;
      for (size_t i = 0; i < files.size(); ++i)
      {
        if (GetFileExt(files[i].first) == BOOKMARKS_FILE_EXTENSION)
        {
          kmlFileName = files[i].first;
          break;
        }
      }
      if (kmlFileName.empty())
        return false;

      fileSavePath = GenerateValidAndUniqueFilePathForKML(kmlFileName);
      ZipFileReader::UnzipFile(filePath, kmlFileName, fileSavePath);
    }
    catch (RootException const & e)
    {
      LOG(LWARNING, ("Error unzipping file", filePath, e.Msg()));
      return false;
    }
  }
  else
  {
    LOG(LWARNING, ("Unknown file type", filePath));
    return false;
  }

  // Update freshly added bookmarks
  m_bmManager.LoadBookmark(fileSavePath);

  return true;
}

void Framework::PrepareToShutdown()
{
  DestroyDrapeEngine();
}

void Framework::SaveViewport()
{
  m2::AnyRectD rect;
  if (m_currentModelView.isPerspective())
  {
    ScreenBase modelView = m_currentModelView;
    modelView.ResetPerspective();
    rect = modelView.GlobalRect();
  }
  else
  {
    rect = m_currentModelView.GlobalRect();
  }
  Settings::Set("ScreenClipRect", rect);
}

void Framework::LoadViewport()
{
  m2::AnyRectD rect;
  if (Settings::Get("ScreenClipRect", rect) && df::GetWorldRect().IsRectInside(rect.GetGlobalRect()))
    CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewAnyRect, _1, rect, false));
  else
    ShowAll();
}

void Framework::ShowAll()
{
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewAnyRect, _1, m2::AnyRectD(m_model.GetWorldRect()), false));
}

m2::PointD Framework::GetPixelCenter() const
{
  return m_currentModelView.isPerspective() ? m_currentModelView.PixelRectIn3d().Center()
                                            : m_currentModelView.PixelRect().Center();
}

m2::PointD const & Framework::GetViewportCenter() const
{
  return m_currentModelView.GetOrg();
}

void Framework::SetViewportCenter(m2::PointD const & pt)
{
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewCenter, _1, pt, -1, true));
}

m2::RectD Framework::GetCurrentViewport() const
{
  return m_currentModelView.ClipRect();
}

void Framework::ShowRect(double lat, double lon, double zoom)
{
  m2::PointD center(MercatorBounds::FromLatLon(lat, lon));
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewCenter, _1, center, zoom, true));
}

void Framework::ShowRect(m2::RectD const & rect, int maxScale)
{
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewRect, _1, rect, true, maxScale, true));
}

void Framework::ShowRect(m2::AnyRectD const & rect)
{
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewAnyRect, _1, rect, true));
}

void Framework::GetTouchRect(m2::PointD const & center, uint32_t pxRadius, m2::AnyRectD & rect)
{
  m_currentModelView.GetTouchRect(center, static_cast<double>(pxRadius), rect);
}

int Framework::AddViewportListener(TViewportChanged const & fn)
{
  ASSERT(m_drapeEngine, ());
  return m_drapeEngine->AddModelViewListener(fn);
}

void Framework::RemoveViewportListener(int slotID)
{
  ASSERT(m_drapeEngine, ());
  m_drapeEngine->RemoveModeViewListener(slotID);
}

void Framework::OnSize(int w, int h)
{
  CallDrapeFunction(bind(&df::DrapeEngine::Resize, _1, max(w, 2), max(h, 2)));
}

namespace
{

double ScaleModeToFactor(Framework::EScaleMode mode)
{
  double factors[] = { 2.0, 1.5, 0.5, 0.67 };
  return factors[mode];
}

} // namespace

void Framework::Scale(EScaleMode mode, bool isAnim)
{
  Scale(ScaleModeToFactor(mode), isAnim);
}

void Framework::Scale(Framework::EScaleMode mode, m2::PointD const & pxPoint, bool isAnim)
{
  Scale(ScaleModeToFactor(mode), pxPoint, isAnim);
}

void Framework::Scale(double factor, bool isAnim)
{
  Scale(factor, GetPixelCenter(), isAnim);
}

void Framework::Scale(double factor, m2::PointD const & pxPoint, bool isAnim)
{
  CallDrapeFunction(bind(&df::DrapeEngine::Scale, _1, factor, pxPoint, isAnim));
}

void Framework::TouchEvent(df::TouchEvent const & touch)
{
  CallDrapeFunction(bind(&df::DrapeEngine::AddTouchEvent, _1, touch));
}

int Framework::GetDrawScale() const
{
  return df::GetDrawTileScale(m_currentModelView);
}

bool Framework::IsCountryLoaded(m2::PointD const & pt) const
{
  // TODO (@gorshenin, @govako): the method's name is quite
  // obfuscating and should be fixed.

  // Correct, but slow version (check country polygon).
  string const fName = m_infoGetter->GetRegionCountryId(pt);
  if (fName.empty())
    return true;

  return m_model.IsLoaded(fName);
}

bool Framework::IsCountryLoadedByName(string const & name) const
{
  return m_model.IsLoaded(name);
}

void Framework::InvalidateRect(m2::RectD const & rect)
{
  CallDrapeFunction(bind(&df::DrapeEngine::InvalidateRect, _1, rect));
}

void Framework::StartInteractiveSearch(search::SearchParams const & params)
{
  using namespace search;

  m_lastInteractiveSearchParams = params;
  m_lastInteractiveSearchParams.SetForceSearch(false);
  m_lastInteractiveSearchParams.SetMode(Mode::Viewport);
  m_lastInteractiveSearchParams.m_onResults = [this](Results const & results)
  {
    if (!results.IsEndMarker())
    {
      GetPlatform().RunOnGuiThread([this, results]()
      {
        if (IsInteractiveSearchActive())
          FillSearchResultsMarks(results);
      });
    }
  };
}

void Framework::ClearAllCaches()
{
  m_model.ClearCaches();
  m_infoGetter->ClearCaches();
  m_searchEngine->ClearCaches();
}

void Framework::OnCheckUpdateCurrentCountry(m2::PointF const & pt, int zoomLevel)
{
  if (zoomLevel <= scales::GetUpperWorldScale())
    m_watchdogCountryUpdate.Drop();
  else
    m_watchdogCountryUpdate.RestartWith([this, pt, zoomLevel]{
      OnUpdateCurrentCountry(pt, zoomLevel);
    });
}

void Framework::OnUpdateCurrentCountry(m2::PointF const & pt, int zoomLevel)
{
  storage::TCountryId newCountryId = m_infoGetter->GetRegionCountryId(m2::PointD(pt));

  if (newCountryId == m_lastReportedCountry)
    return;

  m_lastReportedCountry = newCountryId;

  GetPlatform().RunOnGuiThread([this, newCountryId]()
  {
    if (m_currentCountryChanged != nullptr)
      m_currentCountryChanged(newCountryId);
  });
}

void Framework::SetCurrentCountryChangedListener(TCurrentCountryChanged const & listener)
{
  m_currentCountryChanged = listener;
  m_lastReportedCountry = kInvalidCountryId;
}

void Framework::UpdateUserViewportChanged()
{
  if (IsInteractiveSearchActive())
  {
    (void)GetCurrentPosition(m_lastInteractiveSearchParams.m_lat,
                             m_lastInteractiveSearchParams.m_lon);
    Search(m_lastInteractiveSearchParams);
  }
}

bool Framework::Search(search::SearchParams const & params)
{
#ifdef FIXED_LOCATION
  search::SearchParams rParams(params);
  if (params.IsValidPosition())
  {
    m_fixedPos.GetLat(rParams.m_lat);
    m_fixedPos.GetLon(rParams.m_lon);
  }
#else
  search::SearchParams const & rParams = params;
#endif

  ParseSetGpsTrackMinAccuracyCommand(params.m_query);
  if (ParseEditorDebugCommand(params))
    return true;

  m2::RectD const viewport = GetCurrentViewport();

  if (QueryMayBeSkipped(rParams, viewport))
    return false;

  m_lastQueryParams = rParams;
  m_lastQueryViewport = viewport;

  // Cancels previous search request (if any) and initiates new search request.
  CancelQuery(m_lastQueryHandle);
  m_lastQueryHandle = m_searchEngine->Search(m_lastQueryParams, m_lastQueryViewport);
  return true;
}

void Framework::MemoryWarning()
{
  LOG(LINFO, ("MemoryWarning"));
  ClearAllCaches();
  SharedBufferManager::instance().clearReserved();
}

void Framework::EnterBackground()
{
  SetRenderingEnabled(false);
  SaveViewport();

  ms::LatLon const ll = MercatorBounds::ToLatLon(GetViewportCenter());
  alohalytics::Stats::Instance().LogEvent("Framework::EnterBackground", {{"zoom", strings::to_string(GetDrawScale())},
                                          {"foregroundSeconds", strings::to_string(
                                           static_cast<int>(my::Timer::LocalTime() - m_startForegroundTime))}},
                                          alohalytics::Location::FromLatLon(ll.lat, ll.lon));
  // Do not clear caches for Android. This function is called when main activity is paused,
  // but at the same time search activity (for example) is enabled.
  // TODO(AlexZ): Use onStart/onStop on Android to correctly detect app background and remove #ifndef.
#ifndef OMIM_OS_ANDROID
  ClearAllCaches();
#endif
}

void Framework::EnterForeground()
{
  m_startForegroundTime = my::Timer::LocalTime();

  // Drape can be not initialized here in case of the first launch
  // TODO(AlexZ): Why it can't be initialized here? Is it because we call EnterForeground in Android for every
  // time when activity is created? If yes, then this code should be refactored:
  // EnterForeground and EnterBackground should be called only when user opens the app and
  // when user completely leaves the app (and is not just switching between app's activities).
  SetRenderingEnabled(true);
}

bool Framework::GetCurrentPosition(double & lat, double & lon) const
{
  m2::PointD pos;
  MyPositionMarkPoint * myPosMark = UserMarkContainer::UserMarkForMyPostion();
  if (!myPosMark->HasPosition())
    return false;

  pos = myPosMark->GetPivot();

  lat = MercatorBounds::YToLat(pos.y);
  lon = MercatorBounds::XToLon(pos.x);
  return true;
}

void Framework::InitCountryInfoGetter()
{
  ASSERT(!m_infoGetter.get(), ("InitCountryInfoGetter() must be called only once."));
  Platform const & platform = GetPlatform();
  m_infoGetter = CountryInfoReader::CreateCountryInfoReader(platform);
}

void Framework::InitSearchEngine()
{
  ASSERT(!m_searchEngine.get(), ("InitSearchEngine() must be called only once."));
  ASSERT(m_infoGetter.get(), ());
  try
  {
    search::Engine::Params params;
    params.m_locale = languages::GetCurrentOrig();
    params.m_numThreads = 1;
    m_searchEngine.reset(new search::Engine(const_cast<Index &>(m_model.GetIndex()),
                                            GetDefaultCategories(), *m_infoGetter,
                                            make_unique<search::SearchQueryFactory>(), params));
  }
  catch (RootException const & e)
  {
    LOG(LCRITICAL, ("Can't load needed resources for search::Engine:", e.Msg()));
  }
}

storage::TCountryId Framework::GetCountryIndex(m2::PointD const & pt) const
{
  return m_infoGetter->GetRegionCountryId(pt);
}

string Framework::GetCountryName(m2::PointD const & pt) const
{
  storage::CountryInfo info;
  m_infoGetter->GetRegionInfo(pt, info);
  return info.m_name;
}

bool Framework::QueryMayBeSkipped(search::SearchParams const & params,
                                  m2::RectD const & viewport) const
{
  if (params.IsForceSearch())
    return false;
  if (!m_lastQueryParams.IsEqualCommon(params))
    return false;
  if (!m_lastQueryViewport.IsValid() ||
      !search::IsEqualMercator(m_lastQueryViewport, viewport, kDistEqualQuery))
  {
    return false;
  }
  if (!m_lastQueryParams.IsSearchAroundPosition() ||
      ms::DistanceOnEarth(m_lastQueryParams.m_lat, m_lastQueryParams.m_lon, params.m_lat,
                          params.m_lon) <= kDistEqualQuery)
  {
    return false;
  }
  return true;
}

void Framework::LoadSearchResultMetadata(search::Result & res) const
{
  if (res.m_metadata.m_isInitialized)
    return;

  FeatureID const & id = res.GetFeatureID();
  if (id.IsValid())
    search::ProcessMetadata(*GetFeatureByID(id), res.m_metadata);
  // res.m_metadata.m_isInitialized is set to true in ProcessMetadata.
}

void Framework::ShowSearchResult(search::Result const & res)
{
  CancelInteractiveSearch();
  StopLocationFollow();

  alohalytics::LogEvent("searchShowResult", {{"pos", strings::to_string(res.GetPositionInResults())},
                                             {"result", res.ToStringForStats()}});
  place_page::Info info;
  using namespace search;
  int scale;
  switch (res.GetResultType())
  {
    case Result::RESULT_FEATURE:
      FillFeatureInfo(res.GetFeatureID(), info);
      scale = GetFeatureViewportScale(info.GetTypes());
      break;

    case Result::RESULT_LATLON:
    case Result::RESULT_ADDRESS:
      FillPointInfo(res.GetFeatureCenter(), res.GetString(), info);
      scale = scales::GetUpperComfortScale();
      break;

    case Result::RESULT_SUGGEST_PURE:
    case Result::RESULT_SUGGEST_FROM_FEATURE:
      ASSERT(false, ("Suggests should not be here."));
      return;
  }

  m2::PointD const center = info.GetMercator();
  if (m_currentModelView.isPerspective())
    CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewCenter, _1, center, scale, true));
  else
    ShowRect(df::GetRectForDrawScale(scale, center));

  UserMarkContainer::UserMarkForPoi()->SetPtOrg(center);
  ActivateMapSelection(false, df::SelectionShape::OBJECT_POI, info);
}

size_t Framework::ShowSearchResults(search::Results const & results)
{
  using namespace search;

  size_t count = results.GetCount();
  switch (count)
  {
  case 1:
    {
      Result const & r = results.GetResult(0);
      if (!r.IsSuggest())
        ShowSearchResult(r);
      else
        count = 0;
      // do not put break here
    }
  case 0:
    return count;
  }

  m_fixedSearchResults = 0;
  FillSearchResultsMarks(results);
  m_fixedSearchResults = count;

  // Setup viewport according to results.
  m2::AnyRectD viewport = m_currentModelView.GlobalRect();
  m2::PointD const center = viewport.Center();

  double minDistance = numeric_limits<double>::max();
{'fraction_non_alphanumeric': 0.12234123419252298, 'fraction_numerical': 0.004008016032064128, 'mean_word_length': 5.038220812818159, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 122, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 54, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2834245', 'n_tokens_mistral': 23564, 'n_tokens_neox': 22996, 'n_words': 4579}