#include "catch.hpp"

#include <vector>

#include <QPoint>

#include "BeltDragPath.h"
#include "Rotation.h"

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

static std::vector<QPoint> tilesOf(const std::vector<BeltPathTile>& path)
{
    std::vector<QPoint> tiles;
    for (const BeltPathTile& entry : path) { tiles.push_back(entry.tile); }
    return tiles;
}

static std::vector<Rotation> rotationsOf(const std::vector<BeltPathTile>& path)
{
    std::vector<Rotation> rotations;
    for (const BeltPathTile& entry : path) { rotations.push_back(entry.rotation); }
    return rotations;
}

// ---------------------------------------------------------------------------
// Single tile
// ---------------------------------------------------------------------------

TEST_CASE("Single-tile path keeps the belt orientation")
{
    for (Rotation orientation : { Rotation::North, Rotation::East,
                                  Rotation::South, Rotation::West })
    {
        const std::vector<BeltPathTile> path =
            computeBeltDragPath(QPoint(3, 4), QPoint(3, 4), orientation);
        REQUIRE(path.size() == 1);
        REQUIRE(path[0].tile == QPoint(3, 4));
        REQUIRE(path[0].rotation == orientation);
    }
}

// ---------------------------------------------------------------------------
// Straight runs
// ---------------------------------------------------------------------------

TEST_CASE("Straight horizontal run faces along the row toward the cursor")
{
    const std::vector<BeltPathTile> path =
        computeBeltDragPath(QPoint(0, 0), QPoint(3, 0), Rotation::East);
    REQUIRE(tilesOf(path) == std::vector<QPoint>{
        QPoint(0, 0), QPoint(1, 0), QPoint(2, 0), QPoint(3, 0) });
    REQUIRE(rotationsOf(path) == std::vector<Rotation>{
        Rotation::East, Rotation::East, Rotation::East, Rotation::East });
}

TEST_CASE("Straight horizontal run toward the left faces West")
{
    const std::vector<BeltPathTile> path =
        computeBeltDragPath(QPoint(0, 0), QPoint(-2, 0), Rotation::East);
    REQUIRE(tilesOf(path) == std::vector<QPoint>{
        QPoint(0, 0), QPoint(-1, 0), QPoint(-2, 0) });
    REQUIRE(rotationsOf(path) == std::vector<Rotation>{
        Rotation::West, Rotation::West, Rotation::West });
}

TEST_CASE("Straight vertical run faces along the column toward the cursor")
{
    const std::vector<BeltPathTile> path =
        computeBeltDragPath(QPoint(0, 0), QPoint(0, 3), Rotation::South);
    REQUIRE(tilesOf(path) == std::vector<QPoint>{
        QPoint(0, 0), QPoint(0, 1), QPoint(0, 2), QPoint(0, 3) });
    REQUIRE(rotationsOf(path) == std::vector<Rotation>{
        Rotation::South, Rotation::South, Rotation::South, Rotation::South });
}

// A vertical target with a horizontal orientation still yields a straight vertical
// line (the parallel-axis leg is zero-length).
TEST_CASE("Vertical target with horizontal orientation is a straight vertical line")
{
    const std::vector<BeltPathTile> path =
        computeBeltDragPath(QPoint(2, 0), QPoint(2, 2), Rotation::East);
    REQUIRE(tilesOf(path) == std::vector<QPoint>{
        QPoint(2, 0), QPoint(2, 1), QPoint(2, 2) });
    REQUIRE(rotationsOf(path) == std::vector<Rotation>{
        Rotation::South, Rotation::South, Rotation::South });
}

// ---------------------------------------------------------------------------
// L-shaped paths — horizontal-first (East/West orientation)
// ---------------------------------------------------------------------------

TEST_CASE("East orientation goes horizontal then vertical (down-right)")
{
    const std::vector<BeltPathTile> path =
        computeBeltDragPath(QPoint(0, 0), QPoint(2, 2), Rotation::East);
    // Leg 1 East to the corner (2,0), then Leg 2 South to the cursor (2,2).
    REQUIRE(tilesOf(path) == std::vector<QPoint>{
        QPoint(0, 0), QPoint(1, 0), QPoint(2, 0), QPoint(2, 1), QPoint(2, 2) });
    REQUIRE(rotationsOf(path) == std::vector<Rotation>{
        Rotation::East, Rotation::East, Rotation::South, Rotation::South,
        Rotation::South });
}

TEST_CASE("East orientation with cursor up-left goes horizontal (West) then vertical (North)")
{
    const std::vector<BeltPathTile> path =
        computeBeltDragPath(QPoint(0, 0), QPoint(-2, -2), Rotation::East);
    REQUIRE(tilesOf(path) == std::vector<QPoint>{
        QPoint(0, 0), QPoint(-1, 0), QPoint(-2, 0), QPoint(-2, -1),
        QPoint(-2, -2) });
    REQUIRE(rotationsOf(path) == std::vector<Rotation>{
        Rotation::West, Rotation::West, Rotation::North, Rotation::North,
        Rotation::North });
}

TEST_CASE("West orientation is horizontal-first as well (up-right cursor)")
{
    const std::vector<BeltPathTile> path =
        computeBeltDragPath(QPoint(0, 0), QPoint(2, -2), Rotation::West);
    // Horizontal axis first: East toward the cursor to the corner (2,0), then North.
    REQUIRE(tilesOf(path) == std::vector<QPoint>{
        QPoint(0, 0), QPoint(1, 0), QPoint(2, 0), QPoint(2, -1), QPoint(2, -2) });
    REQUIRE(rotationsOf(path) == std::vector<Rotation>{
        Rotation::East, Rotation::East, Rotation::North, Rotation::North,
        Rotation::North });
}

// ---------------------------------------------------------------------------
// L-shaped paths — vertical-first (North/South orientation)
// ---------------------------------------------------------------------------

TEST_CASE("South orientation goes vertical then horizontal (down-right)")
{
    const std::vector<BeltPathTile> path =
        computeBeltDragPath(QPoint(0, 0), QPoint(2, 2), Rotation::South);
    // Leg 1 South to the corner (0,2), then Leg 2 East to the cursor (2,2).
    REQUIRE(tilesOf(path) == std::vector<QPoint>{
        QPoint(0, 0), QPoint(0, 1), QPoint(0, 2), QPoint(1, 2), QPoint(2, 2) });
    REQUIRE(rotationsOf(path) == std::vector<Rotation>{
        Rotation::South, Rotation::South, Rotation::East, Rotation::East,
        Rotation::East });
}

TEST_CASE("North orientation is vertical-first (down-left cursor)")
{
    const std::vector<BeltPathTile> path =
        computeBeltDragPath(QPoint(0, 0), QPoint(-2, 2), Rotation::North);
    // Vertical axis first: South toward the cursor to the corner (0,2), then West.
    REQUIRE(tilesOf(path) == std::vector<QPoint>{
        QPoint(0, 0), QPoint(0, 1), QPoint(0, 2), QPoint(-1, 2), QPoint(-2, 2) });
    REQUIRE(rotationsOf(path) == std::vector<Rotation>{
        Rotation::South, Rotation::South, Rotation::West, Rotation::West,
        Rotation::West });
}
