int main(int argc, char* argv[])
{
  osmscout::CmdLineParser     argParser("PerformanceTest",
                                        argc,argv);
  Arguments                   args;
  osmscout::DatabaseParameter databaseParameter;

  argParser.AddOption(osmscout::CmdLineFlag([&args](const bool& value) {
                        args.help=value;
                      }),
                      std::vector<std::string>{"h","help"},
                      "Display help",
                      true);
  argParser.AddOption(osmscout::CmdLineFlag([&args](const bool& value) {
                        args.debug=value;
                      }),
                      "debug",
                      "Enable debug output",
                      false);
  argParser.AddOption(osmscout::CmdLineUIntOption([&args](const unsigned int& value) {
                        args.startZoom=osmscout::MagnificationLevel(value);
                      }),
                      "start-zoom",
                      "Start zoom, default: " + std::to_string(args.startZoom.Get()),
                      false);
  argParser.AddOption(osmscout::CmdLineUIntOption([&args](const unsigned int& value) {
                        args.endZoom=osmscout::MagnificationLevel(value);
                      }),
                      "end-zoom",
                      "End zoom, default: " + std::to_string(args.endZoom.Get()),
                      false);
  argParser.AddOption(osmscout::CmdLineUIntOption([&args](const unsigned int& value) {
                        args.tileDimension=std::make_tuple(value, std::get<1>(args.tileDimension));
                      }),
                      "tile-width",
                      "Tile width, default: " + std::to_string(std::get<0>(args.tileDimension)),
                      false);
  argParser.AddOption(osmscout::CmdLineUIntOption([&args](const unsigned int& value) {
                        args.tileDimension=std::make_tuple(std::get<0>(args.tileDimension), value);
                      }),
                      "tile-height",
                      "Tile height, default: " + std::to_string(std::get<1>(args.tileDimension)),
                      false);
  argParser.AddOption(osmscout::CmdLineStringOption([&args](const std::string& value) {
                        args.driver = value;
                      }),
                      "driver",
                      "Rendering driver (cairo|Qt|ag|opengl|noop|none), default: " + args.driver,
                      false);
  argParser.AddOption(osmscout::CmdLineDoubleOption([&args](const double& value) {
                        if (value > 0) {
                          args.dpi = value;
                        } else {
                          std::cerr << "DPI can't be negative or zero" << std::endl;
                        }
                      }),
                      "dpi",
                      "Rendering DPI, default: " + std::to_string(args.dpi),
                      false);
  argParser.AddOption(osmscout::CmdLineUIntOption([&args](const unsigned int& value) {
                        args.drawRepeat = value;
                      }),
                      "draw-repeat",
                      "Repeat every draw call, default: " + std::to_string(args.drawRepeat),
                      false);
  argParser.AddOption(osmscout::CmdLineUIntOption([&args](const unsigned int& value) {
                        args.loadRepeat = value;
                      }),
                      "load-repeat",
                      "Repeat every load call, default: " + std::to_string(args.loadRepeat),
                      false);
  argParser.AddOption(osmscout::CmdLineFlag([&args](const bool& value) {
                        args.flushCache=value;
                      }),
                      "flush-cache",
                      "Flush data caches after each data load, default: " + std::to_string(args.flushCache),
                      false);
  argParser.AddOption(osmscout::CmdLineFlag([&args](const bool& value) {
                        args.flushDiskCache=value;
                      }),
                      "flush-disk",
                      "Flush system disk caches after each data load, default: " + std::to_string(args.flushDiskCache) +
                      " (It work just on Linux with admin rights.)",
                      false);

  argParser.AddOption(osmscout::CmdLineUIntOption([&databaseParameter](const unsigned int& value) {
                        databaseParameter.SetNodeDataCacheSize(value);
                      }),
                      "cache-nodes",
                      "Cache size for nodes, default: " + std::to_string(databaseParameter.GetNodeDataCacheSize()),
                      false);
  argParser.AddOption(osmscout::CmdLineUIntOption([&databaseParameter](const unsigned int& value) {
                        databaseParameter.SetWayDataCacheSize(value);
                      }),
                      "cache-ways",
                      "Cache size for ways, default: " + std::to_string(databaseParameter.GetWayDataCacheSize()),
                      false);
  argParser.AddOption(osmscout::CmdLineUIntOption([&databaseParameter](const unsigned int& value) {
                        databaseParameter.SetAreaDataCacheSize(value);
                      }),
                      "cache-areas",
                      "Cache size for areas, default: " + std::to_string(databaseParameter.GetAreaDataCacheSize()),
                      false);

#if defined(HAVE_LIB_GPERFTOOLS)
  argParser.AddOption(osmscout::CmdLineStringOption([&args](const std::string& value) {
                        args.heapProfilePrefix = value;
                        args.heapProfile = !args.heapProfilePrefix.empty();
                      }),
                      "heap-profile",
                      "GPerf heap profile prefix, profiler is disabled by default",
                      false);
#endif

  argParser.AddPositional(osmscout::CmdLineStringOption([&args](const std::string& value) {
                            args.databaseDirectory=value;
                          }),
                          "databaseDir",
                          "Database directory");
  argParser.AddPositional(osmscout::CmdLineStringOption([&args](const std::string& value) {
                            args.style=value;
                          }),
                          "stylesheet",
                          "Map stylesheet");
  argParser.AddPositional(osmscout::CmdLineGeoCoordOption([&args](const osmscout::GeoCoord& coord) {
                            args.coordTopLeft = coord;
                          }),
                          "lat_top lon_left",
                          "Bounding box top-left coordinate");
  argParser.AddPositional(osmscout::CmdLineGeoCoordOption([&args](const osmscout::GeoCoord& coord) {
                            args.coordBottomRight = coord;
                          }),
                          "lat_bottom lon_right",
                          "Bounding box bottom-right coordinate");

  osmscout::CmdLineParseResult argResult=argParser.Parse();
  if (argResult.HasError()) {
    std::cerr << "ERROR: " << argResult.GetErrorDescription() << std::endl;
    std::cout << argParser.GetHelp() << std::endl;
    return 1;
  }
  else if (args.help) {
    std::cout << argParser.GetHelp() << std::endl;
    return 0;
  }

  osmscout::log.Debug(args.debug);
  //databaseParameter.SetDebugPerformance(true);

  osmscout::DatabaseRef       database=std::make_shared<osmscout::Database>(databaseParameter);
  osmscout::MapServiceRef     mapService=std::make_shared<osmscout::MapService>(database);

  if (!database->Open(args.databaseDirectory)) {
    std::cerr << "Cannot open database" << std::endl;
    return 1;
  }

  osmscout::StyleConfigRef styleConfig=std::make_shared<osmscout::StyleConfig>(database->GetTypeConfig());

  if (!styleConfig->Load(args.style)) {
    std::cerr << "Cannot open style" << std::endl;
    return 1;
  }

  PerformanceTestBackendPtr backendPtr = PrepareBackend(argc, argv, args, styleConfig);
  if (!backendPtr){
    return 1;
  }

#if defined(HAVE_LIB_GPERFTOOLS)
  if (args.heapProfile){
    HeapProfilerStart(args.heapProfilePrefix.c_str());
  }
#endif

  osmscout::TileProjection      projection;
  osmscout::MapParameter        drawParameter;
  osmscout::AreaSearchParameter searchParameter;
  std::list<LevelStats>         statistics;

  // TODO: Use some way to find a valid font on the system (Agg display a ton of messages otherwise)
  drawParameter.SetFontName("/usr/share/fonts/TTF/DejaVuSans.ttf");
  searchParameter.SetUseMultithreading(true);

  for (osmscout::MagnificationLevel level=osmscout::MagnificationLevel(std::min(args.startZoom,args.endZoom));
       level<=osmscout::MagnificationLevel(std::max(args.startZoom,args.endZoom));
       level++) {
    LevelStats              stats(level.Get());
    osmscout::Magnification magnification(level);

    osmscout::OSMTileId     tileA(osmscout::OSMTileId::GetOSMTile(magnification,
                                                                  osmscout::GeoCoord(args.LatBottom(),args.LonLeft())));
    osmscout::OSMTileId     tileB(osmscout::OSMTileId::GetOSMTile(magnification,
                                                                  osmscout::GeoCoord(args.LatTop(),args.LonRight())));
    osmscout::OSMTileIdBox  tileArea(tileA,tileB);

    std::cout << "----------" << std::endl;
    std::cout << "Drawing level " << level << ", " << tileArea.GetCount() << " tiles " << tileArea.GetDisplayText() << std::endl;

    size_t current=1;
    size_t tileCount=tileArea.GetCount();
    size_t delta=tileCount/20;

    if (delta==0) {
      delta=1;
    }

    for (const auto& tile : tileArea) {
      osmscout::MapData       data;
      osmscout::OSMTileIdBox  tileBox(osmscout::OSMTileId(tile.GetX()-1,tile.GetY()-1),
                                      osmscout::OSMTileId(tile.GetX()+1,tile.GetY()+1));
      osmscout::GeoBox        boundingBox;

      if ((current % delta)==0) {
        std::cout << current*100/tileCount << "% " << current;

        if (stats.tileCount>0) {
          std::cout << " " << stats.dbTotalTime/stats.tileCount;
          std::cout << " " << stats.drawTotalTime/stats.tileCount;
        }

        std::cout << std::endl;
      }

      projection.Set(tile,
                     magnification,
                     args.dpi,
                     args.TileWidth(),
                     args.TileHeight());

      projection.GetDimensions(boundingBox);
      projection.SetLinearInterpolationUsage(level.Get() >= 10);

      for (size_t i=0; i<args.loadRepeat; i++) {
        data.nodes.clear();
        data.ways.clear();
        data.areas.clear();

        osmscout::StopClock dbTimer;

        osmscout::GeoBox dataBoundingBox(tileBox.GetBoundingBox(magnification));

        std::list<osmscout::TileRef> tiles;

        // set cache size almost unlimited,
        // for better estimate of peak memory usage by tile loading
        mapService->SetCacheSize(10000000);

        mapService->LookupTiles(magnification, dataBoundingBox, tiles);
        mapService->LoadMissingTileData(searchParameter, *styleConfig, tiles);
        mapService->AddTileDataToMapData(tiles, data);

#if defined(HAVE_LIB_GPERFTOOLS)
        if (args.heapProfile) {
          std::ostringstream buff;
          buff << "load-" << level << "-" << tile.GetX() << "-" << tile.GetY();
          HeapProfilerDump(buff.str().c_str());
        }
        struct mallinfo alloc_info = tc_mallinfo();
#else
#if defined(HAVE_MALLINFO)
        struct mallinfo alloc_info = mallinfo();
#endif
#endif
#if defined(HAVE_MALLINFO) || defined(HAVE_LIB_GPERFTOOLS)
        std::cout << "memory usage: " << formatAlloc(alloc_info.uordblks) << std::endl;
        stats.allocMax = std::max(stats.allocMax, (double) alloc_info.uordblks);
        stats.allocSum = stats.allocSum + (double) alloc_info.uordblks;
#endif

        // set cache size back to default
        mapService->SetCacheSize(25);
        dbTimer.Stop();

        double dbTime = dbTimer.GetMilliseconds();

        stats.dbMinTime = std::min(stats.dbMinTime, dbTime);
        stats.dbMaxTime = std::max(stats.dbMaxTime, dbTime);
        stats.dbTotalTime += dbTime;

        if (args.flushCache) {
          tiles.clear(); // following flush method removes only tiles with use_count() == 1
          mapService->FlushTileCache();

          // simplest way howto flush database caches is close it and open again
          database->Close();
          if (!database->Open(args.databaseDirectory)) {
            std::cerr << "Cannot open database" << std::endl;
            return 1;
          }
        }
        if (args.flushDiskCache) {
          // Linux specific
          if (osmscout::ExistsInFilesystem("/proc/sys/vm/drop_caches")){
            osmscout::FileWriter f;
            try {
              f.Open("/proc/sys/vm/drop_caches");
              f.Write(std::string("3"));
              f.Close();
            }catch(const osmscout::IOException &e){
              std::cerr << "Can't flush disk cache: " << e.what() << std::endl;
            }
          }else{
            std::cerr << "Can't flush disk cache, \"/proc/sys/vm/drop_caches\" file don't exists" << std::endl;
          }
        }
      }

      stats.nodeCount+=data.nodes.size();
      stats.wayCount+=data.ways.size();
      stats.areaCount+=data.areas.size();

      stats.tileCount++;
      for (size_t i=0; i<args.drawRepeat; i++) {
        osmscout::StopClock drawTimer;
        backendPtr->DrawMap(projection, drawParameter, data);
        drawTimer.Stop();

        double drawTime = drawTimer.GetMilliseconds();

        stats.drawMinTime = std::min(stats.drawMinTime, drawTime);
        stats.drawMaxTime = std::max(stats.drawMaxTime, drawTime);
        stats.drawTotalTime += drawTime;
      }

      current++;
    }

    statistics.push_back(stats);
  }

#if defined(HAVE_LIB_GPERFTOOLS)
  if (args.heapProfile){
    HeapProfilerStop();
  }
#endif

  std::cout << "==========" << std::endl;

  for (const auto& stats : statistics) {
    std::cout << "Level: " << stats.level << std::endl;
    std::cout << "Tiles: " << stats.tileCount << " (load " << args.loadRepeat << "x, drawn " << args.drawRepeat << "x)" << std::endl;

#if defined(HAVE_MALLINFO) || defined(HAVE_LIB_GPERFTOOLS)
    std::cout << " Used memory: ";
    std::cout << "max: " << formatAlloc(stats.allocMax) << " ";
    std::cout << "avg: " << formatAlloc(stats.allocSum / (stats.tileCount * args.loadRepeat)) << std::endl;
#endif

    std::cout << " Tot. data  : ";
    std::cout << "nodes: " << stats.nodeCount << " ";
    std::cout << "way: " << stats.wayCount << " ";
    std::cout << "areas: " << stats.areaCount << std::endl;

    if (stats.tileCount>0) {
      std::cout << " Avg. data  : ";
      std::cout << "nodes: " << stats.nodeCount/stats.tileCount << " ";
      std::cout << "way: " << stats.wayCount/stats.tileCount << " ";
      std::cout << "areas: " << stats.areaCount/stats.tileCount << std::endl;
    }

    std::cout << " DB         : ";
    std::cout << "total: " << stats.dbTotalTime << " ";
    std::cout << "min: " << stats.dbMinTime << " ";
    if (stats.tileCount>0) {
      std::cout << "avg: " << stats.dbTotalTime/(stats.tileCount * args.loadRepeat) << " ";
    }
    std::cout << "max: " << stats.dbMaxTime << " " << std::endl;

    std::cout << " Map        : ";
    std::cout << "total: " << stats.drawTotalTime << " ";
    std::cout << "min: " << stats.drawMinTime << " ";
    if (stats.tileCount>0) {
      std::cout << "avg: " << stats.drawTotalTime/(stats.tileCount * args.drawRepeat) << " ";
    }
    std::cout << "max: " << stats.drawMaxTime << std::endl;
  }

  database->Close();

  return 0;
}
PerformanceTestBackendPtr PrepareBackend(int argc, char* argv[], const Arguments &args, osmscout::StyleConfigRef styleConfig)
{
  if (args.driver=="cairo") {
    std::cout << "Using driver 'cairo'..." << std::endl;
#if defined(HAVE_LIB_OSMSCOUTMAPCAIRO)
    try{
      return std::make_shared<PerformanceTestBackendCairo>(args.TileWidth(),args.TileHeight(),styleConfig);
    } catch (std::runtime_error &e){
      std::cerr << e.what() << std::endl;
      return nullptr;
    }
#else
    std::cerr << "Driver 'cairo' is not enabled" << std::endl;
    return nullptr;
#endif
  }
  else if (args.driver=="Qt") {
    std::cout << "Using driver 'Qt'..." << std::endl;
#if defined(HAVE_LIB_OSMSCOUTMAPQT)
    SailfishApp::application(argc, argv);
    std::cout << "QGuiApplication created..." << std::endl;
    return std::make_shared<PerformanceTestBackendQt>(argc, argv, args.TileWidth(), args.TileHeight(), styleConfig);
#else
    std::cerr << "Driver 'Qt' is not enabled" << std::endl;
    return nullptr;
#endif
  } else if (args.driver == "agg") {
    std::cout << "Using driver 'Agg'..." << std::endl;
#if defined(HAVE_LIB_OSMSCOUTMAPAGG)
    return std::make_shared<PerformanceTestBackendAGG>(args.TileWidth(), args.TileHeight(), styleConfig);
#else
    std::cerr << "Driver 'Agg' is not enabled" << std::endl;
    return nullptr;
#endif
  } else if (args.driver == "opengl") {
    std::cout << "Using driver 'OpenGL'..." << std::endl;
#if defined(HAVE_LIB_OSMSCOUTMAPOPENGL)
    try{
      return std::make_shared<PerformanceTestBackendOGL>(args.TileWidth(), args.TileHeight(), args.dpi, styleConfig);
    } catch (std::runtime_error &e){
      std::cerr << e.what() << std::endl;
      return nullptr;
    }
#else
    std::cerr << "Driver 'OpenGL' is not enabled" << std::endl;
    return nullptr;
#endif
  }
  else if (args.driver=="noop") {
    std::cout << "Using driver 'noop'..." << std::endl;
    return std::make_shared<PerformanceTestBackendNoOp>(styleConfig);
  }
  else if (args.driver=="none") {
    std::cout << "Using driver 'none'..." << std::endl;
    return std::make_shared<PerformanceTestBackend>();
  }
  else {
    std::cerr << "Unsupported driver '" << args.driver << "'" << std::endl;
    return nullptr;
  }
}