#include "catch.hpp"

#include <vector>

#include <QRect>

#include "FloatingPanelPlacement.h"

// The band every case below places into: 1000x600, so a bottom edge of 599.
static QRect makeBand()
{
    return QRect(0, 0, 1000, 600);
}

// The two distances the rules run on, deliberately different (REQ-UI-SELECTION-PANEL):
// the edge margin the panel keeps from the view and from the widgets it steps around, and
// the wider gap it keeps from the selection -- half a tile, so 20 px at a 40 px tile.
static const int kMarginPx       = 8;
static const int kSelectionGapPx = 20;

TEST_CASE("With nothing in the way a widget may use the whole band", "[layout]")
{
    // REQ-UI-SELECTION-PANEL: the band's own bottom is the limit when no other floating
    // widget has been placed yet.
    REQUIRE(getAvailableBottomPx(makeBand(), {}, 0, 999, kMarginPx) == 599);
}

TEST_CASE("A widget in the same column pushes the bottom above it", "[layout]")
{
    // The build button bar sitting at the bottom center leaves the space above it, less
    // the margin kept between the two (REQ-UI-BUILD-BAR).
    const std::vector<QRect> occupied = { QRect(400, 520, 200, 72) };
    REQUIRE(getAvailableBottomPx(makeBand(), occupied, 350, 650, kMarginPx) == 511);
}

TEST_CASE("A widget beside the column does not shorten it", "[layout]")
{
    // The controls panel in the bottom-left corner is not in the way of a panel standing
    // at the right edge, however tall it is (REQ-UI-CONTROLS-PANEL).
    const std::vector<QRect> occupied = { QRect(0, 100, 200, 499) };
    REQUIRE(getAvailableBottomPx(makeBand(), occupied, 700, 999, kMarginPx) == 599);

    // Touching columns do count as meeting: the panel starts exactly where the widget
    // ends, which is an overlap of one pixel.
    REQUIRE(getAvailableBottomPx(makeBand(), occupied, 199, 999, kMarginPx) == 91);
}

TEST_CASE("The topmost widget in the column decides", "[layout]")
{
    // Several widgets meet the column: the one that reaches highest is the binding one,
    // whatever order they are given in.
    const std::vector<QRect> occupied = { QRect(400, 520, 200, 72),
                                          QRect(0, 300, 500, 299),
                                          QRect(900, 560, 100, 40) };
    REQUIRE(getAvailableBottomPx(makeBand(), occupied, 450, 550, kMarginPx) == 291);
}

TEST_CASE("An empty rectangle occupies nothing", "[layout]")
{
    // A floating widget that is hidden contributes a null rect rather than being left
    // out of the pass.
    const std::vector<QRect> occupied = { QRect(), QRect(400, 520, 200, 0) };
    REQUIRE(getAvailableBottomPx(makeBand(), occupied, 0, 999, kMarginPx) == 599);
}

TEST_CASE("A widget filling the column leaves nothing", "[layout]")
{
    // The caller is expected to notice that the space left is not positive rather than
    // being handed a floor of its own.
    const std::vector<QRect> occupied = { QRect(0, 0, 1000, 600) };
    REQUIRE(getAvailableBottomPx(makeBand(), occupied, 0, 999, kMarginPx) == -9);
}

// ---------------------------------------------------------------------------
// Which side of the selection the panel takes
// ---------------------------------------------------------------------------

TEST_CASE("The panel stands to the right of the selection where it fits", "[layout]")
{
    // REQ-UI-SELECTION-PANEL: right of the anchor is the first choice.
    REQUIRE(chooseSide(makeBand(), QRect(100, 100, 60, 60), 300, kSelectionGapPx)
            == PanelSide::Right);
}

TEST_CASE("The panel goes left when the right cannot hold it", "[layout]")
{
    // A selection near the right edge leaves 80 px there once the gap it keeps from the
    // selection is taken off, not enough for a 300 px panel, and the left is wide open.
    REQUIRE(chooseSide(makeBand(), QRect(880, 100, 20, 60), 300, kSelectionGapPx)
            == PanelSide::Left);
}

TEST_CASE("The room a side offers is measured less the gap", "[layout]")
{
    // A 200 px panel beside a selection whose right edge leaves 214 px to the band's: it
    // fits there on the margin alone, but not once the wider gap is taken off.
    const QRect anchorRect(766, 100, 20, 60);
    REQUIRE(chooseSide(makeBand(), anchorRect, 200, kMarginPx) == PanelSide::Right);
    REQUIRE(chooseSide(makeBand(), anchorRect, 200, kSelectionGapPx) == PanelSide::Left);
}

TEST_CASE("Fitting on neither side, the panel takes the roomier one", "[layout]")
{
    // A bounding box spanning most of the view: 180 px free on the left, 80 on the
    // right, and a 300 px panel fits in neither. It covers as little as it can.
    REQUIRE(chooseSide(makeBand(), QRect(200, 100, 700, 200), 300, kSelectionGapPx)
            == PanelSide::Left);
    REQUIRE(chooseSide(makeBand(), QRect(100, 100, 700, 200), 300, kSelectionGapPx)
            == PanelSide::Right);
}

// ---------------------------------------------------------------------------
// Where it then stands
// ---------------------------------------------------------------------------

TEST_CASE("The panel sits beside the anchor with its top edges aligned", "[layout]")
{
    // REQ-UI-SELECTION-PANEL: separated by the gap it keeps from the selection, growing
    // away from it, top edge on the anchor's top edge.
    const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 120, 60, 60),
                                           PanelSide::Right, QSize(300, 200), {},
                                           kSelectionGapPx, kMarginPx);
    REQUIRE(placed == QRect(180, 120, 300, 200));

    const QRect placedLeft = placeBesideAnchor(makeBand(), QRect(520, 120, 60, 60),
                                               PanelSide::Left, QSize(300, 200), {},
                                               kSelectionGapPx, kMarginPx);
    REQUIRE(placedLeft == QRect(200, 120, 300, 200));
}

TEST_CASE("The gap separates the panel horizontally only", "[layout]")
{
    // REQ-UI-SELECTION-PANEL: widening the gap moves the panel further from the selection
    // sideways and nowhere else -- its top stays level with the top of what it describes.
    const QRect anchorRect(100, 120, 60, 60);
    REQUIRE(placeBesideAnchor(makeBand(), anchorRect, PanelSide::Right, QSize(300, 200),
                              {}, 0, kMarginPx)
            == QRect(160, 120, 300, 200));
    REQUIRE(placeBesideAnchor(makeBand(), anchorRect, PanelSide::Right, QSize(300, 200),
                              {}, kSelectionGapPx, kMarginPx)
            == QRect(180, 120, 300, 200));
}

TEST_CASE("A panel that would hang below the view is lifted", "[layout]")
{
    // Top-aligning with a selection low in the view would put the panel's bottom past
    // the band, so it rises until it fits rather than overrunning it.
    const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 500, 60, 60),
                                           PanelSide::Right, QSize(300, 200), {},
                                           kSelectionGapPx, kMarginPx);
    REQUIRE(placed == QRect(180, 400, 300, 200));
}

TEST_CASE("A panel standing over another widget rises above it", "[layout]")
{
    // The controls panel in the bottom-left is in the way of a panel placed to the left
    // of a selection: it clears the top of it by the edge margin, the gap from the
    // selection having settled its left edge (REQ-UI-CONTROLS-PANEL).
    const std::vector<QRect> occupied = { QRect(0, 300, 260, 300) };
    const QRect placed = placeBesideAnchor(makeBand(), QRect(500, 250, 60, 60),
                                           PanelSide::Left, QSize(300, 200), occupied,
                                           kSelectionGapPx, kMarginPx);
    REQUIRE(placed == QRect(180, 92, 300, 200));
}

TEST_CASE("A panel taller than the space left is capped", "[layout]")
{
    // Capping is the caller's cue to scroll: it asked for 700 and got what there was.
    const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 100, 60, 60),
                                           PanelSide::Right, QSize(300, 700), {},
                                           kSelectionGapPx, kMarginPx);
    REQUIRE(placed == QRect(180, 0, 300, 600));
}

TEST_CASE("A panel that fits on neither side is pushed inside the view", "[layout]")
{
    // The one case where it covers part of the selection (REQ-UI-SELECTION-PANEL): it
    // stands as far from the anchor as the band allows, not off the edge of it.
    const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 100, 700, 200),
                                           PanelSide::Right, QSize(300, 200), {},
                                           kSelectionGapPx, kMarginPx);
    REQUIRE(placed == QRect(700, 100, 300, 200));
}

// ---------------------------------------------------------------------------
// Where it stands once the player has dragged it (REQ-UI-SELECTION-PANEL-DRAG)
// ---------------------------------------------------------------------------

TEST_CASE("A dragged panel stands where it was dropped", "[layout]")
{
    // With room for it there, the desired top-left corner is the answer: nothing about
    // the selection it describes is consulted any more.
    const QRect placed = placeAtDesiredTopLeft(makeBand(), QPoint(420, 180),
                                               QSize(300, 200), {}, kMarginPx);
    REQUIRE(placed == QRect(420, 180, 300, 200));
}

TEST_CASE("A panel dragged past the view's edges is pushed back inside", "[layout]")
{
    // Dropped off the right edge and below the bottom, it is brought back within the
    // band, as an anchored panel is.
    REQUIRE(placeAtDesiredTopLeft(makeBand(), QPoint(900, 500), QSize(300, 200), {},
                                  kMarginPx)
            == QRect(700, 400, 300, 200));

    // And off the left edge and above the top, the other way.
    REQUIRE(placeAtDesiredTopLeft(makeBand(), QPoint(-120, -60), QSize(300, 200), {},
                                  kMarginPx)
            == QRect(0, 0, 300, 200));
}

TEST_CASE("A dragged panel rises above the widget it was dropped over", "[layout]")
{
    // The player drops it over the build button bar; it lands above the bar's top by the
    // margin instead, the bar staying where it is (REQ-UI-BUILD-BAR).
    const std::vector<QRect> occupied = { QRect(400, 520, 200, 72) };
    const QRect placed = placeAtDesiredTopLeft(makeBand(), QPoint(450, 480),
                                               QSize(300, 200), occupied, kMarginPx);
    REQUIRE(placed == QRect(450, 312, 300, 200));
}

TEST_CASE("A widget beside a dragged panel's column does not move it", "[layout]")
{
    // Only what the panel's own column meets is in its way: the controls panel in the
    // bottom-left corner leaves a panel dropped at the right edge alone
    // (REQ-UI-CONTROLS-PANEL).
    const std::vector<QRect> occupied = { QRect(0, 300, 260, 300) };
    const QRect placed = placeAtDesiredTopLeft(makeBand(), QPoint(600, 380),
                                               QSize(300, 200), occupied, kMarginPx);
    REQUIRE(placed == QRect(600, 380, 300, 200));
}

TEST_CASE("A dragged panel too tall for the room left is capped", "[layout]")
{
    // Capping is the caller's cue to scroll here too: a card asking for 700 px over a bar
    // that leaves 512 px of band gets what there is, from the top of the band.
    const std::vector<QRect> occupied = { QRect(400, 520, 200, 72) };
    const QRect placed = placeAtDesiredTopLeft(makeBand(), QPoint(450, 100),
                                               QSize(300, 700), occupied, kMarginPx);
    REQUIRE(placed == QRect(450, 0, 300, 512));
}

TEST_CASE("A dragged panel returns to where it was dropped", "[layout]")
{
    // The resolution never writes back to the desired position: a panel lifted above the
    // build button bar goes back to the point the player dropped it at as soon as the bar
    // stops meeting its column -- here because the bar's button set shrank.
    const QPoint desired(450, 380);
    const std::vector<QRect> wideBar = { QRect(400, 520, 200, 72) };
    REQUIRE(placeAtDesiredTopLeft(makeBand(), desired, QSize(300, 200), wideBar,
                                  kMarginPx)
            == QRect(450, 312, 300, 200));

    const std::vector<QRect> narrowBar = { QRect(400, 520, 40, 72) };
    REQUIRE(placeAtDesiredTopLeft(makeBand(), desired, QSize(300, 200), narrowBar,
                                  kMarginPx)
            == QRect(450, 380, 300, 200));
}
