/**
 * SPDX-FileCopyrightText: (C) 2007 Luca Gugelmann <lucag@student.ethz.ch>
 *
 * SPDX-License-Identifier: LGPL-2.0-only
 */

#include "regiongrabber.h"

#include <QApplication>
#include <QLocale>
#include <QMouseEvent>
#include <QPainter>
#include <QScreen>
#include <QToolTip>

#include <KLocalizedString>
#include <KWindowSystem>

using namespace std::chrono_literals;

RegionGrabber::RegionGrabber()
    : QWidget(nullptr)
    , selection()
    , mouseDown(false)
    , newSelection(false)
    , handleSize(10)
    , mouseOverHandle(nullptr)
    , idleTimer()
    , showHelp(true)
    , grabbing(false)
    , TLHandle(0, 0, handleSize, handleSize)
    , TRHandle(0, 0, handleSize, handleSize)
    , BLHandle(0, 0, handleSize, handleSize)
    , BRHandle(0, 0, handleSize, handleSize)
    , LHandle(0, 0, handleSize, handleSize)
    , THandle(0, 0, handleSize, handleSize)
    , RHandle(0, 0, handleSize, handleSize)
    , BHandle(0, 0, handleSize, handleSize)
{
    handles << &TLHandle << &TRHandle << &BLHandle << &BRHandle << &LHandle << &THandle << &RHandle << &BHandle;
    setMouseTracking(true);
    setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);

    QTimer::singleShot(50ms, this, &RegionGrabber::init);
    connect(&idleTimer, &QTimer::timeout, this, &RegionGrabber::displayHelp);
    idleTimer.start(3000);
}

RegionGrabber::~RegionGrabber() = default;

void RegionGrabber::init()
{
    pixmap = QApplication::primaryScreen()->grabWindow(0);
    showFullScreen(); // krazy:exclude=qmethods        -- Necessary for proper screenshot capture.
    resize(pixmap.size());
    move(0, 0);
    setCursor(Qt::CrossCursor);
    grabKeyboard();
}

void RegionGrabber::displayHelp()
{
    showHelp = true;
    update();
}

void RegionGrabber::paintEvent(QPaintEvent *e)
{
    Q_UNUSED(e);
    if (grabbing) // grabWindow() should just get the background
        return;

    QPainter painter(this);

    QPalette pal(QToolTip::palette());
    QFont font = QToolTip::font();

    QColor handleColor = pal.color(QPalette::Active, QPalette::Highlight);
    handleColor.setAlpha(160);
    QColor overlayColor(0, 0, 0, 160);
    QColor textColor = pal.color(QPalette::Active, QPalette::Text);
    QColor textBackgroundColor = pal.color(QPalette::Active, QPalette::Base);
    painter.drawPixmap(0, 0, pixmap);
    painter.setFont(font);

    QRect r = selection.normalized().adjusted(0, 0, -1, -1);
    if (!selection.isNull()) {
        QRegion grey(rect());
        grey = grey.subtracted(r);
        painter.setPen(handleColor);
        painter.setBrush(overlayColor);
        painter.setClipRegion(grey);
        painter.drawRect(-1, -1, rect().width() + 1, rect().height() + 1);
        painter.setClipRect(rect());
        painter.setBrush(Qt::NoBrush);
        painter.drawRect(r);
    }

    if (showHelp) {
        painter.setPen(textColor);
        painter.setBrush(textBackgroundColor);
        QString helpText = i18n("Select a region using the mouse. To take the snapshot, press the Enter key. Press Esc to quit.");
        QRect textRect = painter.boundingRect(rect().adjusted(2, 2, -2, -2), Qt::TextWordWrap, helpText);
        textRect.adjust(-2, -2, 4, 2);
        painter.drawRect(textRect);
        textRect.moveTopLeft(QPoint(3, 3));
        painter.drawText(textRect, helpText);
    }

    if (selection.isNull()) {
        return;
    }

    // The grabbed region is everything which is covered by the drawn
    // rectangles (border included). This means that there is no 0px
    // selection, since a 0px wide rectangle will always be drawn as a line.
    QString txt = QStringLiteral("%1x%2").arg(selection.width() == 0 ? 2 : selection.width()).arg(selection.height() == 0 ? 2 : selection.height());
    QRect textRect = painter.boundingRect(rect(), Qt::AlignLeft, txt);
    QRect boundingRect = textRect.adjusted(-4, 0, 0, 0);

    if (textRect.width() < r.width() - 2 * handleSize && textRect.height() < r.height() - 2 * handleSize
        && (r.width() > 100 && r.height() > 100)) { // center, unsuitable for small selections
        boundingRect.moveCenter(r.center());
        textRect.moveCenter(r.center());
    } else if (r.y() - 3 > textRect.height() && r.x() + textRect.width() < rect().right()) { // on top, left aligned
        boundingRect.moveBottomLeft(QPoint(r.x(), r.y() - 3));
        textRect.moveBottomLeft(QPoint(r.x() + 2, r.y() - 3));
    } else if (r.x() - 3 > textRect.width()) { // left, top aligned
        boundingRect.moveTopRight(QPoint(r.x() - 3, r.y()));
        textRect.moveTopRight(QPoint(r.x() - 5, r.y()));
    } else if (r.bottom() + 3 + textRect.height() < rect().bottom() && r.right() > textRect.width()) { // at bottom, right aligned
        boundingRect.moveTopRight(QPoint(r.right(), r.bottom() + 3));
        textRect.moveTopRight(QPoint(r.right() - 2, r.bottom() + 3));
    } else if (r.right() + textRect.width() + 3 < rect().width()) { // right, bottom aligned
        boundingRect.moveBottomLeft(QPoint(r.right() + 3, r.bottom()));
        textRect.moveBottomLeft(QPoint(r.right() + 5, r.bottom()));
    }
    // if the above didn't catch it, you are running on a very tiny screen...
    painter.setPen(textColor);
    painter.setBrush(textBackgroundColor);
    painter.drawRect(boundingRect);
    painter.drawText(textRect, txt);

    if ((r.height() > handleSize * 2 && r.width() > handleSize * 2) || !mouseDown) {
        updateHandles();
        painter.setPen(handleColor);
        handleColor.setAlpha(60);
        painter.setBrush(handleColor);
        painter.drawRects(handleMask().begin(), handleMask().rectCount());
    }
}

void RegionGrabber::resizeEvent(QResizeEvent *e)
{
    Q_UNUSED(e);
    if (selection.isNull())
        return;
    QRect r = selection;
    r.setTopLeft(limitPointToRect(r.topLeft(), rect()));
    r.setBottomRight(limitPointToRect(r.bottomRight(), rect()));
    if (r.width() <= 1 || r.height() <= 1) // this just results in ugly drawing...
        r = QRect();
    selection = r;
}

void RegionGrabber::mousePressEvent(QMouseEvent *e)
{
    showHelp = false;
    idleTimer.stop();
    if (e->button() == Qt::LeftButton) {
        mouseDown = true;
        dragStartPoint = e->pos();
        selectionBeforeDrag = selection;
        if (!selection.contains(e->pos())) {
            newSelection = true;
            selection = QRect();
            showHelp = true;
        } else {
            setCursor(Qt::ClosedHandCursor);
        }
    } else if (e->button() == Qt::RightButton) {
        newSelection = false;
        selection = QRect();
        setCursor(Qt::CrossCursor);
    }
    update();
}

void RegionGrabber::mouseMoveEvent(QMouseEvent *e)
{
    if (mouseDown) {
        if (newSelection) {
            QPoint p = e->pos();
            QRect r = rect();
            selection = QRect(dragStartPoint, limitPointToRect(p, r)).normalized();
        } else if (mouseOverHandle == nullptr) { // moving the whole selection
            QRect r = rect().normalized(), s = selectionBeforeDrag.normalized();
            QPoint p = s.topLeft() + e->pos() - dragStartPoint;
            r.setBottomRight(r.bottomRight() - QPoint(s.width(), s.height()));
            if (!r.isNull() && r.isValid())
                selection.moveTo(limitPointToRect(p, r));
        } else { // dragging a handle
            QRect r = selectionBeforeDrag;
            QPoint offset = e->pos() - dragStartPoint;

            if (mouseOverHandle == &TLHandle || mouseOverHandle == &THandle || mouseOverHandle == &TRHandle) { // dragging one of the top handles
                r.setTop(r.top() + offset.y());
            }

            if (mouseOverHandle == &TLHandle || mouseOverHandle == &LHandle || mouseOverHandle == &BLHandle) { // dragging one of the left handles
                r.setLeft(r.left() + offset.x());
            }

            if (mouseOverHandle == &BLHandle || mouseOverHandle == &BHandle || mouseOverHandle == &BRHandle) { // dragging one of the bottom handles
                r.setBottom(r.bottom() + offset.y());
            }

            if (mouseOverHandle == &TRHandle || mouseOverHandle == &RHandle || mouseOverHandle == &BRHandle) { // dragging one of the right handles
                r.setRight(r.right() + offset.x());
            }
            r = r.normalized();
            r.setTopLeft(limitPointToRect(r.topLeft(), rect()));
            r.setBottomRight(limitPointToRect(r.bottomRight(), rect()));
            selection = r;
        }
        update();
    } else {
        if (selection.isNull())
            return;
        bool found = false;
        for (QRect *r : handles) {
            if (r->contains(e->pos())) {
                mouseOverHandle = r;
                found = true;
                break;
            }
        }
        if (!found) {
            mouseOverHandle = nullptr;
            if (selection.contains(e->pos()))
                setCursor(Qt::OpenHandCursor);
            else
                setCursor(Qt::CrossCursor);
        } else {
            if (mouseOverHandle == &TLHandle || mouseOverHandle == &BRHandle)
                setCursor(Qt::SizeFDiagCursor);
            if (mouseOverHandle == &TRHandle || mouseOverHandle == &BLHandle)
                setCursor(Qt::SizeBDiagCursor);
            if (mouseOverHandle == &LHandle || mouseOverHandle == &RHandle)
                setCursor(Qt::SizeHorCursor);
            if (mouseOverHandle == &THandle || mouseOverHandle == &BHandle)
                setCursor(Qt::SizeVerCursor);
        }
    }
}

void RegionGrabber::mouseReleaseEvent(QMouseEvent *e)
{
    mouseDown = false;
    newSelection = false;
    idleTimer.start();
    if (mouseOverHandle == nullptr && selection.contains(e->pos()))
        setCursor(Qt::OpenHandCursor);
    update();
}

void RegionGrabber::mouseDoubleClickEvent(QMouseEvent *)
{
    grabRect();
}

void RegionGrabber::keyPressEvent(QKeyEvent *e)
{
    if (e->key() == Qt::Key_Escape) {
        Q_EMIT regionGrabbed(QPixmap());
    } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
        grabRect();
    } else {
        e->ignore();
    }
}

void RegionGrabber::grabRect()
{
    QRect r = selection.normalized();
    if (!r.isNull() && r.isValid()) {
        grabbing = true;
        Q_EMIT regionGrabbed(pixmap.copy(r));
    }
}

void RegionGrabber::updateHandles()
{
    QRect r = selection.normalized().adjusted(0, 0, -1, -1);
    int s2 = handleSize / 2;

    TLHandle.moveTopLeft(r.topLeft());
    TRHandle.moveTopRight(r.topRight());
    BLHandle.moveBottomLeft(r.bottomLeft());
    BRHandle.moveBottomRight(r.bottomRight());

    LHandle.moveTopLeft(QPoint(r.x(), r.y() + r.height() / 2 - s2));
    THandle.moveTopLeft(QPoint(r.x() + r.width() / 2 - s2, r.y()));
    RHandle.moveTopRight(QPoint(r.right(), r.y() + r.height() / 2 - s2));
    BHandle.moveBottomLeft(QPoint(r.x() + r.width() / 2 - s2, r.bottom()));
}

QRegion RegionGrabber::handleMask() const
{
    // note: not normalized QRects are bad here, since they will not be drawn
    QRegion mask;
    for (QRect *rect : handles)
        mask += QRegion(*rect);
    return mask;
}

QPoint RegionGrabber::limitPointToRect(const QPoint &p, const QRect &r) const
{
    QPoint q;
    q.setX(p.x() < r.x() ? r.x() : p.x() < r.right() ? p.x() : r.right());
    q.setY(p.y() < r.y() ? r.y() : p.y() < r.bottom() ? p.y() : r.bottom());
    return q;
}

#include "moc_regiongrabber.cpp"
