
/*
   SPDX-FileCopyrightText: 2003-2007 Clarence Dang <dang@kde.org>

   SPDX-License-Identifier: BSD-2-Clause
*/

#define DEBUG_KP_TOOL_SELECTION 0

#include "kpAbstractSelectionTool.h"
#include "kpAbstractSelectionToolPrivate.h"
#include "kpLogCategories.h"

#include <KLocalizedString>

#include "commands/kpCommandHistory.h"
#include "commands/kpMacroCommand.h"
#include "commands/tools/selection/kpToolImageSelectionTransparencyCommand.h"
#include "commands/tools/selection/kpToolSelectionCreateCommand.h"
#include "commands/tools/selection/kpToolSelectionDestroyCommand.h"
#include "commands/tools/selection/kpToolSelectionMoveCommand.h"
#include "commands/tools/selection/kpToolSelectionResizeScaleCommand.h"
#include "document/kpDocument.h"
#include "environments/tools/selection/kpToolSelectionEnvironment.h"
#include "kpDefs.h"
#include "layers/selections/image/kpAbstractImageSelection.h"
#include "layers/selections/kpAbstractSelection.h"
#include "views/kpView.h"
#include "views/manager/kpViewManager.h"
#include "widgets/toolbars/kpToolToolBar.h"
#include "widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h"

// private
int kpAbstractSelectionTool::onSelectionResizeHandle() const
{
    kpView *v = viewManager()->viewUnderCursor();
    if (!v) {
        return 0;
    }

    return v->mouseOnSelectionResizeHandle(currentViewPoint());
}

//---------------------------------------------------------------------

// private
void kpAbstractSelectionTool::initResizeScale()
{
    d->currentResizeScaleCommand = nullptr;

    // d->resizeScaleType
}

// private
void kpAbstractSelectionTool::uninitResizeScale()
{
    // (state must be after construction, or after some time after endResizeScale())
    Q_ASSERT(!d->currentResizeScaleCommand);

    // d->resizeScaleType
}

// private
void kpAbstractSelectionTool::beginResizeScale()
{
    // (state must be after construction, or after some time after endResizeScale())
    Q_ASSERT(!d->currentResizeScaleCommand);

    // d->resizeScaleType
}

// private
void kpAbstractSelectionTool::endResizeScale()
{
    // (should have been killed by cancelResizeScale() or endResizeScale())
    Q_ASSERT(!d->currentResizeScaleCommand);

    // d->resizeScaleType
}

//---------------------------------------------------------------------

// private
void kpAbstractSelectionTool::setCursorResizeScale()
{
#if DEBUG_KP_TOOL_SELECTION && 0
    qCDebug(kpLogTools) << "\tonSelectionResizeHandle=" << onSelectionResizeHandle();
#endif
    Qt::CursorShape shape = Qt::ArrowCursor;

    switch (onSelectionResizeHandle()) {
    case (kpView::Top | kpView::Left):
    case (kpView::Bottom | kpView::Right):
        shape = Qt::SizeFDiagCursor;
        break;

    case (kpView::Bottom | kpView::Left):
    case (kpView::Top | kpView::Right):
        shape = Qt::SizeBDiagCursor;
        break;

    case kpView::Top:
    case kpView::Bottom:
        shape = Qt::SizeVerCursor;
        break;

    case kpView::Left:
    case kpView::Right:
        shape = Qt::SizeHorCursor;
        break;
    }

    viewManager()->setCursor(shape);
}

//---------------------------------------------------------------------

// protected virtual
void kpAbstractSelectionTool::setSelectionBorderForBeginDrawResizeScale()
{
    viewManager()->setQueueUpdates();
    {
        viewManager()->setSelectionBorderVisible(true);
        viewManager()->setSelectionBorderFinished(true);
    }
    viewManager()->restoreQueueUpdates();
}

//---------------------------------------------------------------------

// private
void kpAbstractSelectionTool::beginDrawResizeScale()
{
    d->resizeScaleType = onSelectionResizeHandle();

    /*virtual*/ setSelectionBorderForBeginDrawResizeScale();

    setUserMessage(cancelUserMessage());
}

//---------------------------------------------------------------------

// private
void kpAbstractSelectionTool::drawResizeScaleTryKeepAspect(int newWidth,
                                                           int newHeight,
                                                           bool horizontalGripDragged,
                                                           bool verticalGripDragged,
                                                           const kpAbstractSelection &originalSelection,
                                                           int *newWidthOut,
                                                           int *newHeightOut)
{
    const int oldWidth = originalSelection.width(), oldHeight = originalSelection.height();

    // Width changed more than height?  At equality, favor width.
    // Fix width, change height.
    //
    // We use <horizontalGripDragged> and <verticalGripDragged> to prevent
    // e.g. the situation where we've dragged such that newWidth < oldWidth but
    // we're not dragging a vertical grip.  We certainly don't want this
    // code to modify the width - we want to fix the width and change the
    // height.
    if ((horizontalGripDragged ? double(newWidth) / oldWidth : 0) >= (verticalGripDragged ? double(newHeight) / oldHeight : 0)) {
        *newHeightOut = newWidth * oldHeight / oldWidth;
        *newHeightOut = qMax(originalSelection.minimumHeight(), *newHeightOut);
    }
    // Height changed more than width?
    // Fix height, change width.
    else {
        *newWidthOut = newHeight * oldWidth / oldHeight;
        *newWidthOut = qMax(originalSelection.minimumWidth(), *newWidthOut);
    }
}

//---------------------------------------------------------------------

// private
void kpAbstractSelectionTool::drawResizeScaleCalculateNewSelectionPosSize(const kpAbstractSelection &originalSelection,
                                                                          int *newX,
                                                                          int *newY,
                                                                          int *newWidth,
                                                                          int *newHeight)
{
    //
    // Determine new width.
    //

    // Dragging left or right grip?
    // If left, positive X drags decrease width.
    // If right, positive X drags increase width.
    int userXSign = 0;
    if (d->resizeScaleType & kpView::Left) {
        userXSign = -1;
    } else if (d->resizeScaleType & kpView::Right) {
        userXSign = +1;
    }

    // Calculate new width.
    *newWidth = originalSelection.width() + userXSign * (currentPoint().x() - startPoint().x());

    // Don't allow new width to be less than that kind of selection type's
    // minimum.
    *newWidth = qMax(originalSelection.minimumWidth(), *newWidth);

    //
    // Determine new height.
    //

    // Dragging top or bottom grip?
    // If top, positive Y drags decrease height.
    // If bottom, positive Y drags increase height.
    int userYSign = 0;
    if (d->resizeScaleType & kpView::Top) {
        userYSign = -1;
    } else if (d->resizeScaleType & kpView::Bottom) {
        userYSign = +1;
    }

    // Calculate new height.
    *newHeight = originalSelection.height() + userYSign * (currentPoint().y() - startPoint().y());

    // Don't allow new height to be less than that kind of selection type's
    // minimum.
    *newHeight = qMax(originalSelection.minimumHeight(), *newHeight);

    // Keep aspect ratio?
    if (shiftPressed()) {
        drawResizeScaleTryKeepAspect(*newWidth,
                                     *newHeight,
                                     (userXSign != 0) /*X or XY grip dragged*/,
                                     (userYSign != 0) /*Y or XY grip dragged*/,
                                     originalSelection,
                                     newWidth /*ptr*/,
                                     newHeight /*ptr*/);
    }

    *newX = originalSelection.x();
    *newY = originalSelection.y();

    //
    // Adjust x/y to new width/height for left/top resizes.
    //

    if (d->resizeScaleType & kpView::Left) {
        *newX -= (*newWidth - originalSelection.width());
    }

    if (d->resizeScaleType & kpView::Top) {
        *newY -= (*newHeight - originalSelection.height());
    }

#if DEBUG_KP_TOOL_SELECTION && 1
    qCDebug(kpLogTools) << "\t\tnewX=" << *newX << " newY=" << *newY << " newWidth=" << *newWidth << " newHeight=" << *newHeight;
#endif
}

//---------------------------------------------------------------------

// private
void kpAbstractSelectionTool::drawResizeScale(const QPoint &thisPoint, const QRect & /*normalizedRect*/)
{
#if DEBUG_KP_TOOL_SELECTION && 1
    qCDebug(kpLogTools) << "\tresize/scale";
#endif

    kpAbstractSelection *sel = document()->selection();

    if (!d->dragAccepted && thisPoint == startPoint()) {
#if DEBUG_KP_TOOL_SELECTION && 1
        qCDebug(kpLogTools) << "\t\tnop";
#endif

        setUserShapePoints(QPoint(sel->width(), sel->height()));
        return;
    }

    giveContentIfNeeded();

    if (!d->currentResizeScaleCommand) {
        d->currentResizeScaleCommand = new kpToolSelectionResizeScaleCommand(environ()->commandEnvironment());
    }

    const kpAbstractSelection *originalSelection = d->currentResizeScaleCommand->originalSelection();

    // There is nothing illegal about position (-1,-1) but why not.
    int newX = -1, newY = -1, newWidth = 0, newHeight = 0;

    // This should change all of the above values.
    drawResizeScaleCalculateNewSelectionPosSize(*originalSelection, &newX, &newY, &newWidth, &newHeight);

    viewManager()->setFastUpdates();
    {
        d->currentResizeScaleCommand->resizeAndMoveTo(newWidth, newHeight, QPoint(newX, newY), true /*smooth scale delayed*/);
    }
    viewManager()->restoreFastUpdates();

    setUserShapePoints(QPoint(originalSelection->width(), originalSelection->height()), QPoint(newWidth, newHeight), false /*don't set size*/);
    setUserShapeSize(newWidth - originalSelection->width(), newHeight - originalSelection->height());

    d->dragAccepted = true;
}

//---------------------------------------------------------------------

// private
void kpAbstractSelectionTool::cancelResizeScale()
{
#if DEBUG_KP_TOOL_SELECTION
    qCDebug(kpLogTools) << "\twas resize/scale sel - kill";
#endif

    // NOP drag?
    if (!d->currentResizeScaleCommand) {
        return;
    }

#if DEBUG_KP_TOOL_SELECTION
    qCDebug(kpLogTools) << "\t\tundo currentResizeScaleCommand";
#endif
    d->currentResizeScaleCommand->finalize(); // (unneeded but let's be safe)
    d->currentResizeScaleCommand->unexecute();
    delete d->currentResizeScaleCommand;
    d->currentResizeScaleCommand = nullptr;
}

//---------------------------------------------------------------------

// private
void kpAbstractSelectionTool::endDrawResizeScale()
{
    // NOP drag?
    if (!d->currentResizeScaleCommand) {
        return;
    }

    d->currentResizeScaleCommand->finalize();

    addNeedingContentCommand(d->currentResizeScaleCommand);
    d->currentResizeScaleCommand = nullptr;
}

//---------------------------------------------------------------------

// private
QVariant kpAbstractSelectionTool::operationResizeScale(Operation op, const QVariant &data1, const QVariant &data2)
{
    (void)data1;
    (void)data2;

    switch (op) {
    case HaventBegunDrawUserMessage:
        return /*virtual*/ haventBegunDrawUserMessageResizeScale();

    case SetCursor:
        setCursorResizeScale();
        break;

    case BeginDraw:
        beginDrawResizeScale();
        break;

    case Draw:
        drawResizeScale(currentPoint(), normalizedRect());
        break;

    case Cancel:
        cancelResizeScale();
        break;

    case EndDraw:
        endDrawResizeScale();
        break;

    default:
        Q_ASSERT(!"Unhandled operation");
        break;
    }

    return {};
}

//---------------------------------------------------------------------
