// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "controllerwidget.h"
#include <controls.h>

#include <QtWidgets>
#include <QWindow>
#include <QBackingStore>
#include <QPaintDevice>
#include <QPainter>
#include <QRandomGenerator>
#include <QResizeEvent>

CoordinateControl::CoordinateControl(const QString &sep) : m_x(new QSpinBox), m_y(new QSpinBox)
{
    m_x->setMinimum(-2000);
    m_x->setMaximum(2000);
    connect(m_x, SIGNAL(valueChanged(int)), this, SLOT(spinBoxChanged()));
    m_y->setMinimum(-2000);
    m_y->setMaximum(2000);
    connect(m_y, SIGNAL(valueChanged(int)), this, SLOT(spinBoxChanged()));
    QHBoxLayout *l = new QHBoxLayout(this);
    l->setSpacing(2);
    l->addWidget(m_x);
    l->addWidget(new QLabel(sep));
    l->addWidget(m_y);
}

void CoordinateControl::setCoordinates(int x, int y)
{
    m_x->blockSignals(true);
    m_y->blockSignals(true);
    m_x->setValue(x);
    m_y->setValue(y);
    m_x->blockSignals(false);
    m_y->blockSignals(false);
}

QPair<int, int> CoordinateControl::coordinates() const
{
    return QPair<int, int>(m_x->value(), m_y->value());
}

void CoordinateControl::spinBoxChanged()
{
    const int x = m_x->value();
    const int y = m_y->value();
    emit pointValueChanged(QPoint(x, y));
    emit sizeValueChanged(QSize(x, y));
}

RectControl::RectControl()
    : m_point(new CoordinateControl(QLatin1String("+")))
    , m_size(new CoordinateControl(QLatin1String("x")))
{
    QHBoxLayout *l = new QHBoxLayout(this);
    l->setSpacing(0);
    l->setMargin(ControlLayoutMargin);
    connect(m_point, SIGNAL(pointValueChanged(QPoint)), this, SLOT(handleChanged()));
    connect(m_point, SIGNAL(pointValueChanged(QPoint)), this, SIGNAL(positionChanged(QPoint)));
    l->addWidget(m_point);
    l->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
    connect(m_size, SIGNAL(sizeValueChanged(QSize)), this, SLOT(handleChanged()));
    connect(m_size, SIGNAL(sizeValueChanged(QSize)), this, SIGNAL(sizeChanged(QSize)));
    l->addWidget(m_size);
}

void RectControl::setRectValue(const QRect &r)
{
    m_point->setPointValue(r.topLeft());
    m_size->setSizeValue(r.size());
}

QRect RectControl::rectValue() const
{
    return QRect(m_point->pointValue(), m_size->sizeValue());
}

void RectControl::handleChanged()
{
    emit changed(rectValue());
}

BaseWindowControl::BaseWindowControl(QObject *w)
    : m_layout(new QGridLayout(this))
    , m_object(w)
    , m_geometry(new RectControl)
    , m_framePosition(new CoordinateControl(QLatin1String("x")))
    , m_moveEventLabel(new QLabel(tr("Move events")))
    , m_resizeEventLabel(new QLabel(tr("Resize events")))
    , m_mouseEventLabel(new QLabel(tr("Mouse events")))
    , m_moveCount(0)
    , m_resizeCount(0)
{
    m_object->installEventFilter(this);
    m_geometry->setTitle(tr("Geometry"));
    int row = 0;
    m_layout->addWidget(m_geometry, row, 0, 1, 2);
    m_layout->setMargin(ControlLayoutMargin);
    QGroupBox *frameGB = new QGroupBox(tr("Frame"));
    QVBoxLayout *frameL = new QVBoxLayout(frameGB);
    frameL->setSpacing(0);
    frameL->setMargin(ControlLayoutMargin);
    frameL->addWidget(m_framePosition);
    m_layout->addWidget(frameGB, row, 2);

    QGroupBox *eventGroupBox = new QGroupBox(tr("Events"));
    QVBoxLayout *l = new QVBoxLayout(eventGroupBox);
    l->setSpacing(0);
    l->setMargin(ControlLayoutMargin);
    l->addWidget(m_moveEventLabel);
    l->addWidget(m_resizeEventLabel);
    l->addWidget(m_mouseEventLabel);
    m_layout->addWidget(eventGroupBox, ++row, 2);

    connect(m_geometry, SIGNAL(positionChanged(QPoint)), this, SLOT(posChanged(QPoint)));
    connect(m_geometry, SIGNAL(sizeChanged(QSize)), this, SLOT(sizeChanged(QSize)));
    connect(m_framePosition, SIGNAL(pointValueChanged(QPoint)), this, SLOT(framePosChanged(QPoint)));
}

bool BaseWindowControl::eventFilter(QObject *, QEvent *e)
{
    switch (e->type()) {
    case QEvent::Resize: {
        const QResizeEvent *re = static_cast<const QResizeEvent *>(e);
        m_resizeEventLabel->setText(tr("Resize %1x%2 (#%3)")
                                    .arg(re->size().width()).arg(re->size().height())
                                    .arg(++m_resizeCount));
        refresh();
    }
        break;
    case QEvent::Move: {
        const QMoveEvent *me = static_cast<const QMoveEvent *>(e);
        m_moveEventLabel->setText(tr("Move %1,%2 (#%3)")
                                  .arg(me->pos().x()).arg(me->pos().y())
                                  .arg(++m_moveCount));
        refresh();
    }
        break;
    case QEvent::MouseMove: {
        const QMouseEvent *me = static_cast<const QMouseEvent *>(e);
        const QPoint pos = me->pos();
        QPoint globalPos = objectMapToGlobal(m_object, pos);
        m_mouseEventLabel->setText(tr("Mouse: %1,%2 Global: %3,%4 ").
                                   arg(pos.x()).arg(pos.y()).arg(globalPos.x()).arg(globalPos.y()));
    }
        break;
    case QEvent::WindowStateChange:
        refresh();
    default:
        break;
    }
    return false;
}

void BaseWindowControl::posChanged(const QPoint &p)
{
    QRect geom = objectGeometry(m_object);
    geom.moveTopLeft(p);
    setObjectGeometry(m_object, geom);
}

void BaseWindowControl::sizeChanged(const QSize &s)
{
    QRect geom = objectGeometry(m_object);
    geom.setSize(s);
    setObjectGeometry(m_object, geom);
}

void BaseWindowControl::framePosChanged(const QPoint &p)
{
    setObjectFramePosition(m_object, p);
}

void BaseWindowControl::refresh()
{
    m_geometry->setRectValue(objectGeometry(m_object));
    m_framePosition->setPointValue(objectFramePosition(m_object));
}

// A control for a QWidget
class WidgetWindowControl : public BaseWindowControl
{
    Q_OBJECT
public:
    explicit WidgetWindowControl(QWidget *w);

    virtual void refresh();

private slots:
    void statesChanged();

private:
    virtual QRect objectGeometry(const QObject *o) const
        { return static_cast<const QWidget *>(o)->geometry(); }
    virtual void setObjectGeometry(QObject *o, const QRect &r) const
        { static_cast<QWidget *>(o)->setGeometry(r); }
    virtual QPoint objectFramePosition(const QObject *o) const
        { return static_cast<const QWidget *>(o)->pos(); }
    virtual void setObjectFramePosition(QObject *o, const QPoint &p) const
        { static_cast<QWidget *>(o)->move(p); }
    virtual QPoint objectMapToGlobal(const QObject *o, const QPoint &p) const
        { return static_cast<const QWidget *>(o)->mapToGlobal(p); }
    virtual Qt::WindowFlags objectWindowFlags(const QObject *o) const
        { return static_cast<const QWidget *>(o)->windowFlags(); }
    virtual void setObjectWindowFlags(QObject *o, Qt::WindowFlags f);

    WindowStatesControl *m_statesControl;
};

WidgetWindowControl::WidgetWindowControl(QWidget *w )
    : BaseWindowControl(w)
    , m_statesControl(new WindowStatesControl)
{
    setTitle(w->windowTitle());
    m_layout->addWidget(m_statesControl, 2, 0);
    connect(m_statesControl, SIGNAL(changed()), this, SLOT(statesChanged()));
}

void WidgetWindowControl::setObjectWindowFlags(QObject *o, Qt::WindowFlags f)
{
    QWidget *w = static_cast<QWidget *>(o);
    const bool visible = w->isVisible();
    w->setWindowFlags(f); // hides.
    if (visible)
        w->show();
}

void WidgetWindowControl::refresh()
{
    const QWidget *w = static_cast<const QWidget *>(m_object);
    m_statesControl->setVisibleValue(w->isVisible());
    m_statesControl->setStates(w->windowState());
    BaseWindowControl::refresh();
}

void WidgetWindowControl::statesChanged()
{
    QWidget *w = static_cast<QWidget *>(m_object);
    w->setVisible(m_statesControl->visibleValue());
    w->setWindowState(m_statesControl->states());
}

// Test window drawing diagonal lines
class Window : public QWindow
{
public:
    explicit Window(QWindow *parent = nullptr)
        : QWindow(parent)
        , m_backingStore(new QBackingStore(this))
        , m_color(Qt::GlobalColor(QRandomGenerator::global()->bounded(18)))
    {
        setObjectName(QStringLiteral("window"));
        setTitle(tr("TestWindow"));
        setProperty("_q_platform_MacUseNSWindow", QVariant(true));
    }

protected:
    void mouseMoveEvent(QMouseEvent * ev);
    void mousePressEvent(QMouseEvent * ev);
    void mouseReleaseEvent(QMouseEvent * e);
    void exposeEvent(QExposeEvent *)
        { render(); }

private:
    QBackingStore *m_backingStore;
    Qt::GlobalColor m_color;
    QPoint m_mouseDownPosition;
    void render();
};

void Window::mouseMoveEvent(QMouseEvent * ev)
{
    if (m_mouseDownPosition.isNull())
        return;

    QPoint delta = ev->pos() - m_mouseDownPosition;
    QPoint newPosition = position() + delta;
    setPosition(newPosition);
//    qDebug() << "diff" << delta << newPosition;
}

void Window::mousePressEvent(QMouseEvent * ev)
{
    m_mouseDownPosition = ev->pos();
}

void Window::mouseReleaseEvent(QMouseEvent *)
{
    m_mouseDownPosition = QPoint();
}

void Window::render()
{
    QRect rect(QPoint(), geometry().size());
    m_backingStore->resize(rect.size());
    m_backingStore->beginPaint(rect);
    if (!rect.size().isEmpty()) {
        QPaintDevice *device = m_backingStore->paintDevice();
        QPainter p(device);
        p.fillRect(rect, m_color);
        p.drawLine(0, 0, rect.width(), rect.height());
        p.drawLine(0, rect.height(), rect.width(), 0);
    }
    m_backingStore->endPaint();
    m_backingStore->flush(rect);
}

// A control for a QWindow
class WindowControl : public BaseWindowControl
{
    Q_OBJECT
public:
    explicit WindowControl(QWindow *w);

    virtual void refresh();

private slots:
    void showWindow();
    void hideWindow();
    void raiseWindow();
    void lowerWindow();
    void toggleAttachWindow();
    void addChildWindow();
private:
    virtual QRect objectGeometry(const QObject *o) const
        { return static_cast<const QWindow *>(o)->geometry(); }
    virtual void setObjectGeometry(QObject *o, const QRect &r) const
        { static_cast<QWindow *>(o)->setGeometry(r); }
    virtual QPoint objectFramePosition(const QObject *o) const
        { return static_cast<const QWindow *>(o)->framePosition(); }
    virtual void setObjectFramePosition(QObject *o, const QPoint &p) const
        { static_cast<QWindow *>(o)->setFramePosition(p); }
    virtual QPoint objectMapToGlobal(const QObject *o, const QPoint &p) const
        { return static_cast<const QWindow *>(o)->mapToGlobal(p); }
    virtual void setObjectWindowFlags(QObject *o, Qt::WindowFlags f)
        { static_cast<QWindow *>(o)->setFlags(f); }

    WindowStatesControl *m_statesControl;
    QWindow *m_window;
    QWindow *m_detachedParent; // set when this window is detached. This is the window we should re-attach to.
};

WindowControl::WindowControl(QWindow *w )
    : BaseWindowControl(w)
    , m_statesControl(new WindowStatesControl)
    , m_window(w)
    , m_detachedParent(0)
{
    setTitle(w->title());

    QPushButton *button = new QPushButton("Show Window");
    connect(button, SIGNAL(clicked()), SLOT(showWindow()));
    m_layout->addWidget(button, 1, 0);

    button = new QPushButton("hide Window");
    connect(button, SIGNAL(clicked()), SLOT(hideWindow()));
    m_layout->addWidget(button, 1, 1);

    button = new QPushButton("Rase Window");
    connect(button, SIGNAL(clicked()), SLOT(raiseWindow()));
    m_layout->addWidget(button, 2, 0);

    button = new QPushButton("Lower Window");
    connect(button, SIGNAL(clicked()), SLOT(lowerWindow()));
    m_layout->addWidget(button, 2, 1);

    button = new QPushButton("Toggle attach");
    connect(button, SIGNAL(clicked()), SLOT(toggleAttachWindow()));
    m_layout->addWidget(button, 3, 0);

    button = new QPushButton("Add Child Window");
    connect(button, SIGNAL(clicked()), SLOT(addChildWindow()));
    m_layout->addWidget(button, 3, 1);
}

void WindowControl::refresh()
{
    BaseWindowControl::refresh();
}

void WindowControl::showWindow()
{
    m_window->show();
}

void WindowControl::hideWindow()
{
    m_window->hide();
}

void WindowControl::raiseWindow()
{
    m_window->raise();
}

void WindowControl::lowerWindow()
{
    m_window->lower();
}

void WindowControl::toggleAttachWindow()
{
    if (m_detachedParent) {
        m_window->hide();
        m_window->setParent(m_detachedParent);
        m_window->show();
        m_detachedParent = 0;
    } else {
        m_detachedParent = m_window->parent();
        m_window->hide();
        m_window->setParent(0);
        m_window->show();
    }
}

void WindowControl::addChildWindow()
{
    qDebug() << "WindowControl::addChildWindow";
    Window *childWindow = new Window(m_window);

    QRect childGeometry(50, 50, 40, 40);
    childWindow->setGeometry(childGeometry);
    WindowControl *control = new WindowControl(childWindow);
    control->show();
}

ControllerWidget::ControllerWidget(QWidget *parent)
    : QMainWindow(parent)
    , m_testWindow(new Window)
{
    QMenu *fileMenu = menuBar()->addMenu(tr("File"));
    QAction *exitAction = fileMenu->addAction(tr("Exit"));
    exitAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q));
    connect(exitAction, SIGNAL(triggered()), qApp, SLOT(quit()));

    QString title = QLatin1String("Child Window Geometry test, (Qt ");
    title += QLatin1String(QT_VERSION_STR);
    title += QLatin1String(", ");
    title += qApp->platformName();
    title += QLatin1Char(')');
    setWindowTitle(title);

    int x = 100;
    int y = 100;
    const QStringList args = QApplication::arguments();
    const int offsetArgIndex = args.indexOf(QLatin1String("-offset"));
    if (offsetArgIndex >=0 && offsetArgIndex < args.size() - 1)
        y += args.at(offsetArgIndex + 1).toInt();

    move(x, y);

    x += 800;

    x += 300;
    m_testWindow->setFlags(Qt::Window | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint
                                 | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint
                                 | Qt::WindowTitleHint | Qt::WindowFullscreenButtonHint);
    m_testWindow->setFramePosition(QPoint(x, y));
    m_testWindow->resize(200, 200);
    m_testWindow->setTitle(tr("TestWindow"));

    QWidget *central = new QWidget ;
    QVBoxLayout *l = new QVBoxLayout(central);

    const QString labelText = tr(
        "<html><head/><body><p>This example lets you control the geometry"
        " of QWindows and child QWindows");

    l->addWidget(new QLabel(labelText));

    WindowControl *windowControl = new WindowControl(m_testWindow.data());
    l->addWidget(windowControl);

    setCentralWidget(central);
}

ControllerWidget::~ControllerWidget()
{


}

#include "controllerwidget.moc"
