#include "catch.hpp"

#include "test_tile_data.hpp"

#include <osmium/geom/tile.hpp>

#include <cmath>
#include <sstream>

TEST_CASE("Helper functions") {
    REQUIRE(osmium::geom::num_tiles_in_zoom(0) == 1);
    REQUIRE(osmium::geom::num_tiles_in_zoom(1) == 2);
    REQUIRE(osmium::geom::num_tiles_in_zoom(12) == 4096);

    REQUIRE(osmium::geom::tile_extent_in_zoom(1) == osmium::geom::detail::max_coordinate_epsg3857);
}

TEST_CASE("Tile from x0.0 y0.0 at zoom 0") {
    const osmium::Location l{0.0, 0.0};

    const osmium::geom::Tile t{0, l};

    REQUIRE(t.x == 0);
    REQUIRE(t.y == 0);
    REQUIRE(t.z == 0);
    REQUIRE(t.valid());
}

TEST_CASE("Tile from x180.0 y90.0 at zoom 0") {
    const osmium::Location l{180.0, 90.0};

    const osmium::geom::Tile t{0, l};

    REQUIRE(t.x == 0);
    REQUIRE(t.y == 0);
    REQUIRE(t.z == 0);
    REQUIRE(t.valid());
}

TEST_CASE("Tile from x180.0 y90.0 at zoom 4") {
    const osmium::Location l{180.0, 90.0};

    const osmium::geom::Tile t{4, l};

    REQUIRE(t.x == (1U << 4U) - 1);
    REQUIRE(t.y == 0);
    REQUIRE(t.z == 4);
    REQUIRE(t.valid());
}

TEST_CASE("Tile from x0.0 y0.0 at zoom 4") {
    const osmium::Location l{0.0, 0.0};

    const osmium::geom::Tile t{4, l};

    const auto n = 1U << (4U - 1U);
    REQUIRE(t.x == n);
    REQUIRE(t.y == n);
    REQUIRE(t.z == 4);
    REQUIRE(t.valid());
}

TEST_CASE("Tile from max values at zoom 4") {
    const osmium::geom::Tile t{4U, 15U, 15U};
    REQUIRE(t.valid());
}

TEST_CASE("Tile from max values at zoom 30") {
    const osmium::geom::Tile t{30U, (1U << 30U) - 1, (1U << 30U) - 1};
    REQUIRE(t.valid());
}

TEST_CASE("Tile from coordinates") {
    const osmium::geom::Coordinates c{9.99312, 53.55078};
    const osmium::geom::Tile t{12, osmium::geom::lonlat_to_mercator(c)};
    REQUIRE(t.valid());
    REQUIRE(t.x == 2161);
    REQUIRE(t.y == 1323);
}

TEST_CASE("Tile equality") {
    const osmium::geom::Tile a{4, 3, 4};
    const osmium::geom::Tile b{4, 3, 4};
    const osmium::geom::Tile c{4, 4, 3};
    REQUIRE(a == b);
    REQUIRE(a != c);
    REQUIRE(b != c);
}

TEST_CASE("Tile order") {
    const osmium::geom::Tile a{4, 3, 4};
    const osmium::geom::Tile b{6, 3, 4};
    const osmium::geom::Tile c{6, 4, 3};
    const osmium::geom::Tile d{6, 4, 2};
    REQUIRE(a < b);
    REQUIRE(a < c);
    REQUIRE(b < c);
    REQUIRE(d < c);
}

TEST_CASE("Check a random list of tiles") {
    std::istringstream input_data{s};

    int n = 0;
    while (input_data) {
        double lon = NAN;
        double lat = NAN;
        uint32_t x = 0;
        uint32_t y = 0;
        uint32_t zoom = 0;

        input_data >> lon;
        input_data >> lat;
        input_data >> x;
        input_data >> y;
        input_data >> zoom;

        if (std::isnan(lon)) {
            break;
        }

        const osmium::Location l{lon, lat};
        const osmium::geom::Tile t{zoom, l};
        REQUIRE(t.x == x);
        REQUIRE(t.y == y);

        ++n;
    }

    REQUIRE(n == 472);
}

TEST_CASE("Invalid tiles") {
    osmium::geom::Tile tile{0, 0, 0};

    REQUIRE(tile.valid());

    SECTION("Zoom level out of bounds") {
        tile.z = 100;
    }
    SECTION("x out of bounds") {
        tile.x = 1;
    }
    SECTION("y out of bounds") {
        tile.y = 1;
    }
    SECTION("x out of bounds") {
        tile.z = 4;
        tile.x = 100;
    }
    SECTION("y out of bounds") {
        tile.z = 4;
        tile.y = 100;
    }

    REQUIRE_FALSE(tile.valid());
}

