/***************************************************************************
 *   Copyright (C) 2005-2015 by the Quassel Project                        *
 *   devel@quassel-irc.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 of the License, or     *
 *   (at your option) version 3.                                           *
 *                                                                         *
 *   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 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.         *
 ***************************************************************************/

#ifndef CHATSCENE_H_
#define CHATSCENE_H_

#include <QAbstractItemModel>
#include <QClipboard>
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QSet>
#include <QTimer>
#include <QUrl>

#include "chatlinemodel.h"
#include "messagefilter.h"

class AbstractUiMsg;
class ChatItem;
class ChatLine;
class ChatView;
class ColumnHandleItem;
class MarkerLineItem;
class WebPreviewItem;

class QGraphicsSceneMouseEvent;

class ChatScene : public QGraphicsScene
{
    Q_OBJECT

public:
    enum CutoffMode {
        CutoffLeft,
        CutoffRight
    };

    enum ItemType {
        ChatLineType = QGraphicsItem::UserType + 1,
        ChatItemType,
        TimestampChatItemType,
        SenderChatItemType,
        ContentsChatItemType,
        SearchHighlightType,
        WebPreviewType,
        ColumnHandleType,
        MarkerLineType
    };

    enum ClickMode {
        NoClick,
        DragStartClick,
        SingleClick,
        DoubleClick,
        TripleClick
    };

    ChatScene(QAbstractItemModel *model, const QString &idString, qreal width, ChatView *parent);
    virtual ~ChatScene();

    inline QAbstractItemModel *model() const { return _model; }
    inline MessageFilter *filter() const { return qobject_cast<MessageFilter *>(_model); }
    inline QString idString() const { return _idString; }

    int rowByScenePos(qreal y) const;
    inline int rowByScenePos(const QPointF &pos) const { return rowByScenePos(pos.y()); }
    ChatLineModel::ColumnType columnByScenePos(qreal x) const;
    inline ChatLineModel::ColumnType columnByScenePos(const QPointF &pos) const { return columnByScenePos(pos.x()); }

    ChatView *chatView() const;
    ChatItem *chatItemAt(const QPointF &pos) const;
    inline ChatLine *chatLine(int row) const { return (row < _lines.count()) ? _lines.value(row) : 0; }
    inline ChatLine *chatLine(const QModelIndex &index) const { return _lines.value(index.row()); }

    //! Find the ChatLine belonging to a MsgId
    /** Searches for the ChatLine belonging to a MsgId. If there are more than one ChatLine with the same msgId,
     *  the first one is returned.
     *  Note that this method performs a binary search, hence it has as complexity of O(log n).
     *  If matchExact is false, and we don't have an exact match for the given msgId, we return the visible line right
     *  above the requested one.
     *  \param msgId      The message ID to look for
     *  \param matchExact Whether we find only exact matches
     *  \param ignoreDayChange Whether we ignore day change messages
     *  \return The ChatLine corresponding to the given MsgId
     */
    ChatLine *chatLine(MsgId msgId, bool matchExact = true, bool ignoreDayChange = true) const;

    inline ChatLine *lastLine() const { return _lines.count() ? _lines.last() : 0; }

    inline MarkerLineItem *markerLine() const { return _markerLine; }

    inline bool isSingleBufferScene() const { return _singleBufferId.isValid(); }
    inline BufferId singleBufferId() const { return _singleBufferId; }
    bool containsBuffer(const BufferId &id) const;

    ColumnHandleItem *firstColumnHandle() const;
    ColumnHandleItem *secondColumnHandle() const;

    inline CutoffMode senderCutoffMode() const { return _cutoffMode; }
    inline void setSenderCutoffMode(CutoffMode mode) { _cutoffMode = mode; }

    QString selection() const;
    bool hasSelection() const;
    bool hasGlobalSelection() const;
    bool isPosOverSelection(const QPointF &) const;
    bool isGloballySelecting() const;
    void initiateDrag(QWidget *source);

    bool isScrollingAllowed() const;

public slots:
    void updateForViewport(qreal width, qreal height);
    void setWidth(qreal width);
    void layout(int start, int end, qreal width);

    void resetColumnWidths();

    void setMarkerLineVisible(bool visible = true);
    void setMarkerLine(MsgId msgId = MsgId());
    void jumpToMarkerLine(bool requestBacklog);

    // these are used by the chatitems to notify the scene and manage selections
    void setSelectingItem(ChatItem *item);
    ChatItem *selectingItem() const { return _selectingItem; }
    void startGlobalSelection(ChatItem *item, const QPointF &itemPos);
    void clearGlobalSelection();
    void clearSelection();
    void selectionToClipboard(QClipboard::Mode = QClipboard::Clipboard);
    void stringToClipboard(const QString &str, QClipboard::Mode = QClipboard::Clipboard);

    void webSearchOnSelection();

    void requestBacklog();

#ifdef HAVE_WEBKIT
    void loadWebPreview(ChatItem *parentItem, const QUrl &url, const QRectF &urlRect);
    void clearWebPreview(ChatItem *parentItem = 0);
#endif

signals:
    void lastLineChanged(QGraphicsItem *item, qreal offset);
    void layoutChanged(); // indicates changes to the scenerect due to resizing of the contentsitems
    void mouseMoveWhileSelecting(const QPointF &scenePos);

protected:
    virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent *contextMenuEvent);
    virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent);
    virtual void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent);
    virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent);
    virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvent);
    virtual void handleClick(Qt::MouseButton button, const QPointF &scenePos);

protected slots:
    void rowsInserted(const QModelIndex &, int, int);
    void rowsAboutToBeRemoved(const QModelIndex &, int, int);
    void dataChanged(const QModelIndex &, const QModelIndex &);

private slots:
    void firstHandlePositionChanged(qreal xpos);
    void secondHandlePositionChanged(qreal xpos);
#ifdef HAVE_WEBKIT
    void webPreviewNextStep();
#endif
    void showWebPreviewChanged();

    void rowsRemoved();

    void clickTimeout();

private:
    void setHandleXLimits();
    void updateSelection(const QPointF &pos);

    ChatView *_chatView;
    QString _idString;
    QAbstractItemModel *_model;
    QList<ChatLine *> _lines;
    BufferId _singleBufferId;

    // calls to QChatScene::sceneRect() are very expensive. As we manage the scenerect ourselves
    // we store the size in a member variable.
    QRectF _sceneRect;
    int _firstLineRow; // the first row to display (aka: not a daychange msg)
    void updateSceneRect(qreal width);
    inline void updateSceneRect() { updateSceneRect(_sceneRect.width()); }
    void updateSceneRect(const QRectF &rect);
    qreal _viewportHeight;

    MarkerLineItem *_markerLine;
    bool _markerLineVisible, _markerLineValid, _markerLineJumpPending;

    ColumnHandleItem *_firstColHandle, *_secondColHandle;
    qreal _firstColHandlePos, _secondColHandlePos;
    int _defaultFirstColHandlePos, _defaultSecondColHandlePos;
    CutoffMode _cutoffMode;

    ChatItem *_selectingItem;
    int _selectionStartCol, _selectionMinCol;
    int _selectionStart;
    int _selectionEnd;
    int _firstSelectionRow;
    bool _isSelecting;

    QTimer _clickTimer;
    ClickMode _clickMode;
    QPointF _clickPos;
    bool _clickHandled;
    bool _leftButtonPressed;

    bool _showWebPreview;

    static const int _webSearchSelectionTextMaxVisible = 24;

#ifdef HAVE_WEBKIT
    struct WebPreview {
        enum PreviewState {
            NoPreview,
            NewPreview,
            DelayPreview,
            ShowPreview,
            HidePreview
        };
        ChatItem *parentItem;
        QGraphicsItem *previewItem;
        QUrl url;
        QRectF urlRect;
        PreviewState previewState;
        QTimer timer;
        WebPreview() : parentItem(0), previewItem(0), previewState(NoPreview) {}
    };
    WebPreview webPreview;
#endif // HAVE_WEBKIT
};


#endif
