#include "FloatingPanelPlacement.h"

#include <algorithm>

namespace
{

// The part every placement shares, whatever put the panel where it wants to be: it is
// pushed inside band rather than hanging off it, shortened to what its own column has
// free, and lifted by however much of it hangs below that. wantedLeftPx and wantedTopPx
// are where the panel would stand if nothing were in the way.
QRect fitInBand(const QRect& band, int wantedLeftPx, int wantedTopPx, QSize wantedSize,
                const std::vector<QRect>& occupiedRects, int marginPx)
{
    const int widthPx = std::min(wantedSize.width(), band.width());

    int leftPx = std::min(wantedLeftPx, band.right() - widthPx + 1);
    leftPx     = std::max(leftPx, band.left());

    // Only the widgets its own column meets can shorten it.
    const int bottomPx = getAvailableBottomPx(band, occupiedRects, leftPx,
                                              leftPx + widthPx - 1, marginPx);
    const int heightPx =
        std::min(wantedSize.height(), std::max(0, bottomPx - band.top() + 1));

    // Never above the band: a panel taller than the space left is capped instead, and
    // scrolls.
    int topPx = std::max(wantedTopPx, band.top());
    topPx     = std::min(topPx, bottomPx - heightPx + 1);
    topPx     = std::max(topPx, band.top());

    return QRect(leftPx, topPx, widthPx, heightPx);
}

}  // namespace


int getAvailableBottomPx(const QRect& band, const std::vector<QRect>& occupiedRects,
                         int leftPx, int rightPx, int marginPx)
{
    int bottomPx = band.bottom();
    for (const QRect& occupied : occupiedRects)
    {
        if (occupied.isEmpty())
        {
            continue;
        }
        // Only what is actually in the way counts: a widget entirely to one side of this
        // span is not below it, however tall it is.
        if (occupied.right() < leftPx || occupied.left() > rightPx)
        {
            continue;
        }
        bottomPx = std::min(bottomPx, occupied.top() - marginPx - 1);
    }
    return bottomPx;
}

PanelSide chooseSide(const QRect& band, const QRect& anchorRect, int widthPx,
                     int selectionGapPx)
{
    // What each side offers: the room between the anchor and that edge of the band, less
    // the gap the panel keeps from the anchor. The band's own inset from the view has
    // already taken the edge margin off.
    const int roomRightPx = band.right() - anchorRect.right() - selectionGapPx;
    const int roomLeftPx  = anchorRect.left() - band.left() - selectionGapPx;

    if (roomRightPx >= widthPx)
    {
        return PanelSide::Right;
    }
    if (roomLeftPx >= widthPx)
    {
        return PanelSide::Left;
    }
    // Neither side can hold it without covering the selection, so it goes where it
    // covers the least of it.
    return (roomRightPx >= roomLeftPx) ? PanelSide::Right : PanelSide::Left;
}

QRect placeBesideAnchor(const QRect& band, const QRect& anchorRect, PanelSide side,
                        QSize wantedSize, const std::vector<QRect>& occupiedRects,
                        int selectionGapPx, int marginPx)
{
    const int widthPx = std::min(wantedSize.width(), band.width());

    // A gap from the anchor on the chosen side, growing away from it: the edge facing the
    // selection is the one that stays put as the panel's content resizes. A panel that
    // does not fit there is pushed back inside the view rather than hanging off it, which
    // is what puts it over the selection when neither side had room.
    const int wantedLeftPx = (side == PanelSide::Right)
                                 ? anchorRect.right() + selectionGapPx + 1
                                 : anchorRect.left() - selectionGapPx - widthPx;

    // Top-aligned with the anchor -- the gap separates the two horizontally and plays no
    // part here -- then lifted by however much of the panel hangs below what is free.
    return fitInBand(band, wantedLeftPx, anchorRect.top(), wantedSize, occupiedRects,
                     marginPx);
}

QRect placeAtDesiredTopLeft(const QRect& band, const QPoint& desiredTopLeftPx,
                            QSize wantedSize, const std::vector<QRect>& occupiedRects,
                            int marginPx)
{
    // Where the player dropped it, resolved by the same rules as any other placement
    // (REQ-UI-SELECTION-PANEL-DRAG). Nothing here is written back to the desired point:
    // a panel lifted above the build button bar returns to where it was dropped as soon
    // as the bar stops meeting its column.
    return fitInBand(band, desiredTopLeftPx.x(), desiredTopLeftPx.y(), wantedSize,
                     occupiedRects, marginPx);
}
