#include "../DataStructures/OriginalEdgeData.h"
#include "../DataStructures/QueryNode.h"
#include "../DataStructures/SharedMemoryVectorWrapper.h"
#include "../DataStructures/StaticRTree.h"
#include "../Util/BoostFileSystemFix.h"
#include "../DataStructures/EdgeBasedNode.h"

#include <osrm/Coordinate.h>

#include <random>

// Choosen by a fair W20 dice roll (this value is completely arbitrary)
constexpr unsigned RANDOM_SEED = 13;
constexpr int32_t WORLD_MIN_LAT = -90*COORDINATE_PRECISION;
constexpr int32_t WORLD_MAX_LAT = 90*COORDINATE_PRECISION;
constexpr int32_t WORLD_MIN_LON = -180*COORDINATE_PRECISION;
constexpr int32_t WORLD_MAX_LON = 180*COORDINATE_PRECISION;

typedef EdgeBasedNode RTreeLeaf;
typedef std::shared_ptr<std::vector<FixedPointCoordinate>> FixedPointCoordinateListPtr;
typedef StaticRTree<RTreeLeaf, ShM<FixedPointCoordinate, false>::vector, false> BenchStaticRTree;

FixedPointCoordinateListPtr LoadCoordinates(const boost::filesystem::path& nodes_file)
{
    boost::filesystem::ifstream nodes_input_stream(nodes_file, std::ios::binary);

    NodeInfo current_node;
    unsigned number_of_coordinates = 0;
    nodes_input_stream.read((char *)&number_of_coordinates, sizeof(unsigned));
    auto coords = std::make_shared<std::vector<FixedPointCoordinate>>(number_of_coordinates);
    for (unsigned i = 0; i < number_of_coordinates; ++i)
    {
        nodes_input_stream.read((char *)&current_node, sizeof(NodeInfo));
        coords->at(i) = FixedPointCoordinate(current_node.lat, current_node.lon);
        BOOST_ASSERT((std::abs(coords->at(i).lat) >> 30) == 0);
        BOOST_ASSERT((std::abs(coords->at(i).lon) >> 30) == 0);
    }
    nodes_input_stream.close();
    return coords;
}

void Benchmark(BenchStaticRTree& rtree, unsigned num_queries)
{
    std::mt19937 g(RANDOM_SEED);
    std::uniform_int_distribution<> lat_udist(WORLD_MIN_LAT, WORLD_MAX_LAT);
    std::uniform_int_distribution<> lon_udist(WORLD_MIN_LON, WORLD_MAX_LON);
    std::vector<FixedPointCoordinate> queries;
    for (unsigned i = 0; i < num_queries; i++)
    {
        queries.emplace_back(
            FixedPointCoordinate(lat_udist(g), lon_udist(g))
        );
    }

    const unsigned num_results = 5;
    std::cout << "#### IncrementalFindPhantomNodeForCoordinate : " << num_results << " phantom nodes" << std::endl;

    TIMER_START(query_phantom);
    std::vector<PhantomNode> resulting_phantom_node_vector;
    for (const auto& q : queries)
    {
        resulting_phantom_node_vector.clear();
        rtree.IncrementalFindPhantomNodeForCoordinate(q, resulting_phantom_node_vector, 3, num_results);
        resulting_phantom_node_vector.clear();
        rtree.IncrementalFindPhantomNodeForCoordinate(q, resulting_phantom_node_vector, 17, num_results);
    }
    TIMER_STOP(query_phantom);

    std::cout << "Took " << TIMER_MSEC(query_phantom) << " msec for " << num_queries << " queries." << std::endl;
    std::cout << TIMER_MSEC(query_phantom)/((double) num_queries) << " msec/query." << std::endl;

    std::cout << "#### LocateClosestEndPointForCoordinate" << std::endl;

    TIMER_START(query_endpoint);
    FixedPointCoordinate result;
    for (const auto& q : queries)
    {
        rtree.LocateClosestEndPointForCoordinate(q, result, 3);
    }
    TIMER_STOP(query_endpoint);

    std::cout << "Took " << TIMER_MSEC(query_endpoint) << " msec for " << num_queries << " queries." << std::endl;
    std::cout << TIMER_MSEC(query_endpoint)/((double) num_queries) << " msec/query." << std::endl;

    std::cout << "#### FindPhantomNodeForCoordinate" << std::endl;

    TIMER_START(query_phantomnode);
    for (const auto& q : queries)
    {
        PhantomNode phantom;
        rtree.FindPhantomNodeForCoordinate(q, phantom, 3);
    }
    TIMER_STOP(query_phantomnode);

    std::cout << "Took " << TIMER_MSEC(query_phantomnode) << " msec for " << num_queries << " queries." << std::endl;
    std::cout << TIMER_MSEC(query_phantomnode)/((double) num_queries) << " msec/query." << std::endl;
}

int main(int argc, char** argv)
{
    if (argc < 4)
    {
        std::cout << "./rtree-bench file.ramIndex file.fileIndx file.nodes" << std::endl;
        return 1;
    }

    const char* ramPath  = argv[1];
    const char* filePath = argv[2];
    const char* nodesPath = argv[3];

    auto coords = LoadCoordinates(nodesPath);

    BenchStaticRTree rtree(ramPath, filePath, coords);

    Benchmark(rtree, 10000);

    return 0;
}