/*
 *   Copyright 2008 Aaron Seigo <aseigo@kde.org>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as
 *   published by the Free Software Foundation; either version 2,
 *   or (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "panelappletoverlay.h"
#include "panelapplethandle.h"

#include <QApplication>
#include <QGraphicsLinearLayout>
#include <QGraphicsSceneContextMenuEvent>
#include <QPainter>
#include <QTimer>
#include <QAction>
#include <QMenu>

#include <KDebug>
#include <KGlobalSettings>
#include <KIcon>

#include <Plasma/Applet>
#include <Plasma/Containment>
#include <Plasma/Corona>
#include <Plasma/PaintUtils>
#include <Plasma/Theme>
#include <Plasma/View>


PanelAppletHandle *PanelAppletOverlay::s_appletHandle = 0;
int PanelAppletOverlay::s_appletHandleCount = 0;

class AppletMoveSpacer : public QGraphicsWidget
{
public:
    AppletMoveSpacer(Plasma::Applet *applet)
        : QGraphicsWidget(applet->containment()),
          m_applet(applet)
    {
    }

protected:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * widget = 0)
    {
        Q_UNUSED(option)
        Q_UNUSED(widget)

        /*
           results in odd painting corruption
        if (collidesWithItem(m_applet, Qt::IntersectsItemBoundingRect)) {
            painter->fillRect(contentsRect(), Qt::transparent);
            return;
        }
        */

        //TODO: make this a pretty gradient?
        painter->setRenderHint(QPainter::Antialiasing);
        QPainterPath p = Plasma::PaintUtils::roundedRectangle(contentsRect().adjusted(1, 1, -2, -2), 4);
        QColor c = Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor);
        c.setAlphaF(0.3);

        painter->fillPath(p, c);
    }

private:
    QGraphicsWidget *m_applet;
};

PanelAppletOverlay::PanelAppletOverlay(Plasma::Applet *applet, QWidget *parent)
    : QWidget(parent),
      m_applet(applet),
      m_spacer(0),
      m_layout(dynamic_cast<QGraphicsLinearLayout*>(applet->containment()->layout())), // ++assumptions;
      m_index(0),
      m_clickDrag(false)
{
    if (!s_appletHandle) {
        s_appletHandle = new PanelAppletHandle();
    }

    ++s_appletHandleCount;

    connect(s_appletHandle, SIGNAL(mousePressed(Plasma::Applet*,QMouseEvent*)), 
            this, SLOT(handleMousePressed(Plasma::Applet*,QMouseEvent*)));
    connect(s_appletHandle, SIGNAL(mouseMoved(Plasma::Applet*,QMouseEvent*)), 
            this, SLOT(handleMouseMoved(Plasma::Applet*,QMouseEvent*)));
    connect(s_appletHandle, SIGNAL(mouseReleased(Plasma::Applet*,QMouseEvent*)), 
            this, SLOT(handleMouseReleased(Plasma::Applet*,QMouseEvent*)));

    syncIndex();
    syncOrientation();
    syncGeometry();
    setMouseTracking(true);


    connect(m_applet, SIGNAL(destroyed(QObject*)), this, SLOT(appletDestroyed()));
    connect(m_applet, SIGNAL(geometryChanged()), this, SLOT(delaySyncGeometry()));
}

PanelAppletOverlay::~PanelAppletOverlay()
{
    bool mover = mouseGrabber() == this;
    if (mover) {
        kDebug() << "MOVER!" << m_layout << m_index;
        releaseMouse();
        if (m_layout && m_applet) {
            m_layout->insertItem(m_index, m_applet);
        }
    }

    if (m_spacer) {
        if (m_layout) {
            m_layout->removeItem(m_spacer);
        }

        m_spacer->deleteLater();
        m_spacer = 0;
    }

    --s_appletHandleCount;
    if (s_appletHandleCount < 1) {
        delete s_appletHandle;
        s_appletHandle = 0;
        s_appletHandleCount = 0;
    }
}

void PanelAppletOverlay::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)

    QStyleOption op;
    op.initFrom(this);

    bool hovered = op.state & QStyle::State_MouseOver;
    bool mover = mouseGrabber() == this;
    if (!hovered || mover) {
        return;
    }

    QPainter p(this);
    KIcon icon("transform-move");
    int iconSize;
    QRect iconRect;


    if (!m_applet) {
        return;
    }

    if (m_orientation == Qt::Horizontal) {
        iconSize = qMin(qMin(height(), int(m_applet->size().width())), 64);
        iconRect = QRect(rect().center() - QPoint(iconSize / 2, iconSize / 2), QSize(iconSize, iconSize));
    } else {
        iconSize = qMin(qMin(width(), int(m_applet->size().height())), 64);
        iconRect = QRect(rect().center() - QPoint(iconSize / 2, iconSize / 2), QSize(iconSize, iconSize));
    }

    p.drawPixmap(iconRect, icon.pixmap(iconSize, iconSize));
}

void PanelAppletOverlay::mousePressEvent(QMouseEvent *event)
{
    m_lastGlobalPos = event->globalPos();
    //kDebug() << m_clickDrag;
    if (m_clickDrag) {
        setMouseTracking(false);
        m_clickDrag = false;
        m_origin = QPoint();
        return;
    }

    if (!m_applet || event->button() != Qt::LeftButton) {
        return;
    }

    m_clickDrag = false;
    if (!m_spacer) {
        m_spacer = new AppletMoveSpacer(m_applet);
    } else if (m_layout) {
        m_layout->removeItem(m_spacer);
    }

    m_origin = mapToParent(event->pos());
    m_spacer->setMinimumSize(m_applet->geometry().size());
    m_spacer->setMaximumSize(m_applet->geometry().size());
    if (m_layout) {
        m_layout->removeItem(m_applet);
        m_layout->insertItem(m_index, m_spacer);
    }
    m_applet->raise();

    if (m_orientation == Qt::Horizontal) {
        m_offset = geometry().x() - m_origin.x();
    } else {
        m_offset = geometry().y() - m_origin.y();
    }

    m_dragAction = Move;

    const int margin = 9;
    if (m_applet->inherits("PanelSpacer")) {
        if (m_applet->formFactor() == Plasma::Horizontal) {
            if (event->pos().x() < margin) {
                m_dragAction = LeftResize;
            } else if (event->pos().x() > m_applet->size().width() - margin) {
                m_dragAction = RightResize;
            }
        } else if (m_applet->formFactor() == Plasma::Vertical) {
            if (event->pos().y() < margin) {
                m_dragAction = LeftResize;
            } else if (event->pos().y() > m_applet->size().height() - margin) {
                m_dragAction = RightResize;
            }
        }
    }
}

void PanelAppletOverlay::mouseMoveEvent(QMouseEvent *event)
{
    if (!m_layout || !m_applet) {
        //kDebug() << "no layout";
        return;
    }

    const int margin = 9;
    if (m_applet->inherits("PanelSpacer")) {
        if (m_applet->formFactor() == Plasma::Horizontal) {
            if (event->pos().x() < margin || event->pos().x() > m_applet->size().width() - margin) {
                setCursor(Qt::SizeHorCursor);
            } else {
                setCursor(Qt::ArrowCursor);
            }
        } else if (m_applet->formFactor() == Plasma::Vertical) {
            if (event->pos().y() < margin || event->pos().y() > m_applet->size().height() - margin) {
                setCursor(Qt::SizeVerCursor);
            } else {
                setCursor(Qt::ArrowCursor);
            }
        }
    }

    if (!m_clickDrag && !(event->buttons() & Qt::LeftButton)) {
        //kDebug() << "no left button and we aren't click dragging";
        return;
    }

    Plasma::FormFactor f = m_applet->formFactor();


    if (!m_applet->inherits("PanelSpacer") &&
          (((f != Plasma::Horizontal && f != Plasma::Vertical) && rect().intersects(m_applet->rect().toRect())) ||
          ((f == Plasma::Horizontal || f == Plasma::Vertical) && !rect().contains(event->globalPos()))) ) {
        Plasma::View *view = Plasma::View::topLevelViewAt(event->globalPos());
        //kDebug() << "checking view" << view << m_applet->view();

        if (!view) {
            view = dynamic_cast<Plasma::View*>(parent());
        }

        if (!view) {
            return;
        }

        if (view != m_applet->view() && (event->buttons() & Qt::LeftButton)) {
            Plasma::Containment *c = view->containment();
            if (!c) {
                return;
            }

            syncOrientation();
            syncGeometry();

            if (m_spacer) {
                if (m_layout) {
                    m_layout->removeItem(m_spacer);
                }
                m_spacer->deleteLater();
                m_spacer = 0;
            }

            QPointF pos = c->view()->mapFromGlobal(event->globalPos());
            QRectF g = m_applet->geometry();
            pos += QPoint(m_offset, m_offset);
            g.moveTo(pos);
            m_applet->setGeometry(g);
            m_layout = 0;
            c->addApplet(m_applet, pos, true);
            m_applet->flushPendingConstraintsEvents();
            m_applet->setPos(pos);
            releaseMouse();
            emit moved(this);
            return;
        }
    } else if (m_applet->inherits("PanelSpacer") && m_dragAction != Move) {
        if (m_applet->formFactor() == Plasma::Horizontal) {
            if (m_dragAction == LeftResize) {
                int fixedWidth = m_applet->size().width()+(m_lastGlobalPos.x() - event->globalPos().x());
                m_applet->setPos(m_applet->pos().x()-(fixedWidth-m_applet->size().width()), m_applet->pos().y());
                m_applet->setMinimumWidth(fixedWidth);
                m_applet->setMaximumWidth(fixedWidth);
            } else if (m_dragAction == RightResize) {
                int fixedWidth = m_applet->size().width()-(m_lastGlobalPos.x() - event->globalPos().x());
                m_applet->setMinimumWidth(fixedWidth);
                m_applet->setMaximumWidth(fixedWidth);
            }
        } else if (m_applet->formFactor() == Plasma::Vertical) {
            if (m_dragAction == LeftResize) {
                int fixedHeight = m_applet->size().height()+(m_lastGlobalPos.y() - event->globalPos().y());
                m_applet->setPos(m_applet->pos().x(), m_applet->pos().y()-(fixedHeight-m_applet->size().height()));
                m_applet->setMinimumHeight(fixedHeight);
                m_applet->setMaximumHeight(fixedHeight);
            } else if (m_dragAction == RightResize) {
                int fixedHeight = m_applet->size().height()-(m_lastGlobalPos.y() - event->globalPos().y());
                m_applet->setMinimumHeight(fixedHeight);
                m_applet->setMaximumHeight(fixedHeight);
            }
        }
        m_lastGlobalPos = event->globalPos();
        return;
    }

    if (!m_spacer) {
        m_spacer = new AppletMoveSpacer(m_applet);
        m_spacer->setMinimumSize(m_applet->geometry().size());
        m_spacer->setMaximumSize(m_applet->geometry().size());
        if (m_layout) {
            m_layout->removeItem(m_applet);
            m_layout->insertItem(m_index, m_spacer);
        }
    }

    QPoint p = mapToParent(event->pos());
    QRectF g = m_applet->geometry();

    //kDebug() << p << g << "<-- movin'?";
    if (m_orientation == Qt::Horizontal) {
        g.moveLeft(p.x() + m_offset);
    } else {
        g.moveTop(p.y() + m_offset);
    }

    m_applet->setGeometry(g);

    //FIXME: assumption on how panel containment works, presence of a non applet spacer in last position (if they were swapped would be impossible to save and restore)
    if ((m_index > 0 && m_layout->itemAt(m_index - 1)) || m_index == 0) {
        const bool prevIsApplet = dynamic_cast<Plasma::Applet*>(m_layout->itemAt(m_index - 1)) != 0;
        const bool nextIsApplet = dynamic_cast<Plasma::Applet*>(m_layout->itemAt(m_index + 1)) != 0;

        QPointF mousePos = event->pos() + g.topLeft();

        // swap items if we pass completely over the next/previous item or cross
        // more than halfway across it, whichever comes first
        if (m_orientation == Qt::Horizontal) {
            //kDebug() << prevIsApplet << m_prevGeom << g << nextIsApplet << m_nextGeom;
            if (QApplication::layoutDirection() == Qt::RightToLeft) {
                if (prevIsApplet && m_prevGeom.isValid() && mousePos.x() >= m_prevGeom.right()) {
                    swapWithPrevious();
                } else if (nextIsApplet && m_nextGeom.isValid() && mousePos.x() <= m_nextGeom.left()) {
                    swapWithNext();
                }
            } else if (prevIsApplet && m_prevGeom.isValid() && mousePos.x() <= m_prevGeom.left()) {
                swapWithPrevious();
            } else if (nextIsApplet && m_nextGeom.isValid() && mousePos.x() >= m_nextGeom.right()) {
                swapWithNext();
            }

        } else if (prevIsApplet && m_prevGeom.isValid() && mousePos.y() <= m_prevGeom.top()) {
            swapWithPrevious();
        } else if (nextIsApplet && m_nextGeom.isValid() && mousePos.y() >= m_nextGeom.bottom()) {
            swapWithNext();
        }
    }

    m_lastGlobalPos = event->globalPos();
    //kDebug() << "=================================";
}

void PanelAppletOverlay::mouseReleaseEvent(QMouseEvent *event)
{
    Q_UNUSED(event)
    if (!m_spacer || !m_applet) {
        //kDebug() << "releasing as we don't have a spacer";
        releaseMouse();
        setMouseTracking(false);
        return;
    }

    if (!m_origin.isNull()) {
        //kDebug() << m_clickDrag << m_origin << mapToParent(event->pos());
        if (m_orientation == Qt::Horizontal) {
            m_clickDrag = abs(mapToParent(event->pos()).x() - m_origin.x()) < KGlobalSettings::dndEventDelay();
        } else {
            m_clickDrag = abs(mapToParent(event->pos()).y() - m_origin.y()) < KGlobalSettings::dndEventDelay();
        }

        if (m_clickDrag) {
            //kDebug() << "click dragging." << this << mouseGrabber();
            grabMouse();
            setMouseTracking(true);
            event->setAccepted(false);
            return;
        }
    }

    releaseMouse();
    //kDebug() << "released mouse";
    if (m_layout) {
        m_layout->removeItem(m_spacer);
    }

    m_spacer->deleteLater();
    m_spacer = 0;

    if (m_layout) {
        m_layout->insertItem(m_index, m_applet);
    }

    m_applet->setZValue(m_applet->zValue() - 1);
}

void PanelAppletOverlay::handleMousePressed(Plasma::Applet *applet, QMouseEvent *event)
{
    if (applet == m_applet) {
        QMouseEvent ownEvent(event->type(), mapFromGlobal(event->globalPos()), event->globalPos(), event->button(), event->buttons(), event->modifiers());
        mousePressEvent(&ownEvent);
    }
}

void PanelAppletOverlay::handleMouseMoved(Plasma::Applet *applet, QMouseEvent *event)
{
    if (applet == m_applet) {
        QMouseEvent ownEvent(event->type(), mapFromGlobal(event->globalPos()), event->globalPos(), event->button(), event->buttons(), event->modifiers());
        mouseMoveEvent(&ownEvent);
    }
}

void PanelAppletOverlay::handleMouseReleased(Plasma::Applet *applet, QMouseEvent *event)
{
    if (applet == m_applet) {
        QMouseEvent ownEvent(event->type(), mapFromGlobal(event->globalPos()), event->globalPos(), event->button(), event->buttons(), event->modifiers());
        mouseReleaseEvent(&ownEvent);
    }
}

void PanelAppletOverlay::contextMenuEvent(QContextMenuEvent *event)
{
    if (m_applet) {
        Plasma::Containment *c = m_applet->containment();
        if (c) {
            c->showContextMenu(mapToParent(event->pos()), event->globalPos());
        }
    }
}

void PanelAppletOverlay::enterEvent(QEvent *event)
{
    Q_UNUSED(event)
    update();
    s_appletHandle->setApplet(m_applet);
}

void PanelAppletOverlay::leaveEvent(QEvent *event)
{
    setCursor(Qt::ArrowCursor);
    Q_UNUSED(event)
    s_appletHandle->startHideTimeout();
    update();
}

void PanelAppletOverlay::swapWithPrevious()
{
    if (!m_layout) {
        return;
    }

    //kDebug();
    --m_index;

    if (m_index > 0) {
        m_prevGeom = m_layout->itemAt(m_index - 1)->geometry();
    } else {
        m_prevGeom = QRectF();
    }

    m_nextGeom = m_layout->itemAt(m_index + 1)->geometry();
    m_layout->removeItem(m_spacer);
    m_layout->insertItem(m_index, m_spacer);
    emit moved(this);
}

void PanelAppletOverlay::swapWithNext()
{
    if (!m_layout) {
        return;
    }

    //kDebug();
    ++m_index;

    if (m_index < m_layout->count() - 1) {
        m_nextGeom = m_layout->itemAt(m_index + 1)->geometry();
    } else {
        m_nextGeom = QRectF();
    }

    m_prevGeom = m_layout->itemAt(m_index - 1)->geometry();
    m_layout->removeItem(m_spacer);
    m_layout->insertItem(m_index, m_spacer);
    emit moved(this);
}

void PanelAppletOverlay::appletDestroyed()
{
    m_applet = 0;
    emit removedWithApplet(this);
    deleteLater();
}

void PanelAppletOverlay::delaySyncGeometry()
{
    // we need to do this because it gets called in a round-about-way
    // from our own mouseMoveEvent. if we call syncGeometry directly,
    // we end up with a maze of duplicated and confused mouseMoveEvents
    // of which only half are real (the other half being caused by the
    // immediate call to syncGeometry!)
    QTimer::singleShot(0, this, SLOT(syncGeometry()));
}

void PanelAppletOverlay::syncGeometry()
{
    if (!m_layout || !m_applet) {
        return;
    }

    //kDebug();
    setGeometry(m_applet->geometry().toRect());

    if (m_index > 0 && m_layout->itemAt(m_index - 1)) {
        m_prevGeom = m_layout->itemAt(m_index - 1)->geometry();
    } else {
        m_prevGeom = QRectF();
    }

    //kDebug() << m_index << m_layout->count();
    if (m_index < m_layout->count() - 1) {
        m_nextGeom = m_layout->itemAt(m_index + 1)->geometry();
    } else {
        m_nextGeom = QRectF();
    }

    if (m_applet->containment() && m_applet->containment()->corona()) {
        s_appletHandle->move(m_applet->containment()->corona()->popupPosition(m_applet, s_appletHandle->size(), Qt::AlignCenter));
    }
}

void PanelAppletOverlay::syncIndex()
{
    if (!m_layout || !m_applet) {
        m_index = -1;
        return;
    }

    for (int i = 0; i < m_layout->count(); ++i) {
        QGraphicsWidget *w = dynamic_cast<QGraphicsWidget*>(m_layout->itemAt(i));
        if (w == m_applet) {
            m_index = i;
            break;
        }
    }
}

void PanelAppletOverlay::syncOrientation()
{
    if (!m_applet) {
        return;
    }

    m_orientation = m_applet->formFactor() == Plasma::Horizontal ? Qt::Horizontal : Qt::Vertical;
}

Plasma::Applet *PanelAppletOverlay::applet() const
{
    return m_applet;
}

#include "panelappletoverlay.moc"

