/*
 *   This file is part of Dianara
 *   Copyright 2012-2016  JanKusanagi JRR <jancoding@gmx.com>
 *
 *   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) 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 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 "commenterblock.h"

CommenterBlock::CommenterBlock(PumpController *pumpController,
                               GlobalObject *globalObject,
                               QString parentAuthorId,
                               bool parentStandalone,
                               QWidget *parent) : QWidget(parent)
{
    this->pController = pumpController;
    this->globalObj = globalObject;
    this->parentPostAuthorId = parentAuthorId;
    this->parentPostStandalone = parentStandalone;

    this->editingMode = false;
    this->currentCommentCount = 0;
    this->allCommentsShown = false;

    this->reloadCommentsString = QString::fromUtf8("\342\206\273") // Reload symbol
                                 + " &nbsp;"
                                 + tr("Reload comments");
    this->showAllCommentsLinkLabel = new QLabel("<a href=\"showall://\">"
                                                + reloadCommentsString
                                                + "</a>",
                                                this);
    showAllCommentsLinkLabel->setContextMenuPolicy(Qt::NoContextMenu);
    QFont showAllFont;
    showAllFont.setPointSize(showAllFont.pointSize() - 3);
    showAllCommentsLinkLabel->setFont(showAllFont);
    connect(showAllCommentsLinkLabel, SIGNAL(linkActivated(QString)),
            this, SLOT(requestAllComments()));

    this->setContentsMargins(0, 0, 0, 0);

    commentsLayout = new QVBoxLayout();
    commentsLayout->setContentsMargins(0, 0, 0, 0);
    commentsLayout->setSpacing(1);

    commentsWidget = new QWidget(this);
    commentsWidget->setContentsMargins(0, 0, 0, 0);
    QSizePolicy sizePolicy;
    sizePolicy.setHeightForWidth(false);
    sizePolicy.setWidthForHeight(false);
    sizePolicy.setHorizontalPolicy(QSizePolicy::Minimum);
    sizePolicy.setVerticalPolicy(QSizePolicy::Maximum);
    commentsWidget->setSizePolicy(sizePolicy);
    commentsWidget->setLayout(commentsLayout);

    this->commentsScrollArea = new QScrollArea(this);
    //commentsScrollArea->setFrameStyle(QFrame::NoFrame);
    commentsScrollArea->setContentsMargins(0, 0, 0, 0);
    //commentsScrollArea->setSizePolicy(sizePolicy);
    commentsScrollArea->setSizePolicy(QSizePolicy::Minimum,
                                      QSizePolicy::Preferred);
    commentsScrollArea->setWidget(commentsWidget);
    commentsScrollArea->setWidgetResizable(true);

    this->getAllCommentsTimer = new QTimer(this);
    getAllCommentsTimer->setSingleShot(true);
    connect(getAllCommentsTimer, SIGNAL(timeout()),
            this, SLOT(requestAllComments()));

    // Hide these until setComments() is called, if there are any comments
    showAllCommentsLinkLabel->hide();
    commentsScrollArea->hide();

    scrollToBottomTimer = new QTimer(this);
    scrollToBottomTimer->setSingleShot(true);
    connect(scrollToBottomTimer, SIGNAL(timeout()),
            this, SLOT(scrollCommentsToBottom()));



    this->commentComposer = new Composer(this->globalObj,
                                         false, // forPublisher = false
                                         this);

    this->commentComposer->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    this->commentComposer->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    this->commentComposer->setSizePolicy(QSizePolicy::MinimumExpanding,
                                         QSizePolicy::MinimumExpanding);

    connect(commentComposer, SIGNAL(editingFinished()),
            this, SLOT(sendComment()));
    connect(commentComposer, SIGNAL(editingCancelled()),
            this, SLOT(setMinimumMode()));



    // Formatting/Tools button exported from Composer
    this->toolsButton = commentComposer->getToolsButton();


    // Info label about sending status
    this->statusInfoLabel = new QLabel(this);
    statusInfoLabel->setAlignment(Qt::AlignCenter);
    statusInfoLabel->setWordWrap(true);
    showAllFont.setPointSize(showAllFont.pointSize() + 1);
    statusInfoLabel->setFont(showAllFont);


    this->commentButton = new QPushButton(QIcon::fromTheme("mail-send",
                                                           QIcon(":/images/button-post.png")),
                                          tr("Comment",
                                             "Infinitive verb"),
                                          this);
    commentButton->setToolTip("<b></b>"
                              + tr("You can press Control+Enter to send "
                                   "the comment with the keyboard"));
    connect(commentButton, SIGNAL(clicked()),
            this, SLOT(sendComment()));

    this->cancelButton = new QPushButton(QIcon::fromTheme("dialog-cancel",
                                                          QIcon(":/images/button-cancel.png")),
                                         tr("Cancel"),
                                         this);
    cancelButton->setToolTip("<b></b>"
                             + tr("Press ESC to cancel the comment "
                                  "if there is no text"));
    connect(cancelButton, SIGNAL(clicked()),
            commentComposer, SLOT(cancelPost()));


    bottomLayout = new QGridLayout();
    bottomLayout->setContentsMargins(0, 0, 0, 0);
    bottomLayout->setSpacing(1);
    bottomLayout->addWidget(commentComposer,        0, 0, 8, 3);
    bottomLayout->addWidget(toolsButton,            0, 3, 1, 1);
    bottomLayout->addWidget(statusInfoLabel,        1, 3, 5, 1, Qt::AlignCenter);
    bottomLayout->addWidget(commentButton,          6, 3, 1, 1);
    bottomLayout->addWidget(cancelButton,           7, 3, 1, 1);

    mainLayout = new QVBoxLayout();
    mainLayout->setContentsMargins(0, 0, 0, 0);
    mainLayout->setSpacing(1);
    mainLayout->addWidget(showAllCommentsLinkLabel, 0, Qt::AlignRight | Qt::AlignTop);
    mainLayout->addWidget(commentsScrollArea,       0);
    mainLayout->addLayout(bottomLayout,             0);
    this->setLayout(mainLayout);

    this->setMinimumMode();

    qDebug() << "Commenter created";
}


CommenterBlock::~CommenterBlock()
{
    qDebug() << "Commenter destroyed";
}



void CommenterBlock::clearComments()
{
    foreach (Comment *comment, commentsInBlock)
    {
        comment->deleteLater();
    }

    commentsInBlock.clear();
    this->currentCommentCount = 0;
}



void CommenterBlock::setComments(QVariantList commentsList, int commentCount)
{
    if (commentCount > 0)
    {
        // If some comments are actually included, clear first
        if (commentsList.length() > 0)
        {
            this->clearComments();
        }

        this->currentCommentCount = commentCount;
        if (currentCommentCount > commentsList.size())
        {
            allCommentsShown = false;
        }
        else
        {
            allCommentsShown = true;
        }
        this->updateShowAllLink();


        foreach (QVariant commentVariant, commentsList)
        {
            ASObject *commentObject = new ASObject(commentVariant.toMap(),
                                                   this);
            this->appendComment(commentObject);
        }

        this->commentsScrollArea->show();

        // Move scrollbar to the bottom
        this->scrollCommentsToBottom(); // First, try

        // Then, some msecs later, try again, in case there was no scrollbar before,
        // and the first try didn't work
        // Trying immediately first avoids a flicker-like effect sometimes
        scrollToBottomTimer->start(500);
    }
}


/*
 * Add a single comment to the layout
 *
 */
void CommenterBlock::appendComment(ASObject *object, bool justOne)
{
    foreach (Comment *previousComment, this->commentsInBlock)
    {
        if (previousComment->getObjectId() == object->getId())
        {
            return; // Comment is already present, so abort
        }
    }

    Comment *comment = new Comment(this->pController,
                                   this->globalObj,
                                   object,
                                   this);
    connect(comment, SIGNAL(commentQuoteRequested(QString)),
            this, SLOT(quoteComment(QString)));
    connect(comment, SIGNAL(commentEditRequested(QString,QString)),
            this, SLOT(editComment(QString,QString)));



    // Highlight if the comment was made by the author of the parent post
    if (this->globalObj->getPostHLAuthorComments()) // if configured to do so
    {
        if (object->author()->getId() == this->parentPostAuthorId)
        {
            comment->setHint(this->globalObj->getColor(4)); // HL filtering color
        }
    }

    // Or highlight if this is our own comment, also optional
    if (this->globalObj->getPostHLOwnComments())
    {
        if (object->author()->getId() == this->pController->currentUserId())
        {
            comment->setHint(this->globalObj->getColor(3)); // Your own posts color
        }
    }


    // Add the comment at the end if it's just one (via MinorFeed), or at
    // the top when it's part of a full list, since those lists come in reverse
    this->commentsInBlock.append(comment); // First, keep track of it

    if (justOne)
    {
        this->commentsLayout->addWidget(comment);
        ++this->currentCommentCount;

        // Check if we need to show "reload comments" or "show all X comments"
        if (this->currentCommentCount == commentsInBlock.size())
        {
            this->allCommentsShown = true;
        }
        this->updateShowAllLink();

        // Showing this is needed if there were 0 comments before
        this->commentsScrollArea->show();

        scrollToBottomTimer->start(500);
    }
    else  // called from setComments()
    {
        this->commentsLayout->insertWidget(0, comment);
    }
}


void CommenterBlock::updateCommentFromObject(ASObject *object)
{
    foreach (Comment *comment, this->commentsInBlock)
    {
        if (comment->getObjectId() == object->getId())
        {
            comment->updateDataFromObject(object);
        }
    }

    // FIXME: this needs some checking...
    commentsWidget->setMinimumHeight(10);
    scrollToBottomTimer->start(500);

    this->adjustCommentsHeight();
    this->adjustCommentArea();
}


void CommenterBlock::setCommentDeletedFromObject(ASObject *object)
{
    foreach (Comment *comment, this->commentsInBlock)
    {
        if (comment->getObjectId() == object->getId())
        {
            comment->setCommentDeleted(object->getDeletedOnString());
        }
    }

    // FIXME: this needs some checking... and is copied from previous function
    commentsWidget->setMinimumHeight(10);
    scrollToBottomTimer->start(500);

    this->adjustCommentsHeight();
    this->adjustCommentArea();
}


void CommenterBlock::updateFuzzyTimestamps()
{
    foreach (Comment *comment, this->commentsInBlock)
    {
        comment->setFuzzyTimestamps();
    }
}

void CommenterBlock::updateAvatarFollowStates()
{
    foreach (Comment *comment, this->commentsInBlock)
    {
        comment->syncAvatarFollowState();
    }
}


void CommenterBlock::updateShowAllLink()
{
    QString showAllString;
    if (allCommentsShown)
    {
        showAllString = this->reloadCommentsString;
    }
    else
    {
        if (this->currentCommentCount == 0)
        {
            showAllString = QString::fromUtf8("\342\206\273") // Reload symbol
                            + " &nbsp;" // Another option, magnifying glass: &#128270;
                            + tr("Check for comments");
        }
        else
        {
            showAllString = tr("Show all %1 comments")
                            .arg(this->currentCommentCount);
        }
    }

    // Small trick to avoid a bug where the link stops having appropriate mouse pointer
    this->showAllCommentsLinkLabel->clear();
    this->showAllCommentsLinkLabel->hide();

    this->showAllCommentsLinkLabel->setText("<a href=\"showall://\">"
                                            + showAllString
                                            + "</a>");
    this->showAllCommentsLinkLabel->show();
}


/*
 * Disable the "check for comments" link when there's no valid comment URL,
 * by setting a clear message instead. Simply disabling the widget wouldn't
 * make the link look disabled.
 *
 */
void CommenterBlock::disableShowAllLink()
{
    this->showAllCommentsLinkLabel->setText(tr("Comments are not available"));
}



void CommenterBlock::adjustCommentsWidth()
{
    //commentsWidget->setMaximumWidth(commentsScrollArea->viewport()->width() - 2);
}

void CommenterBlock::adjustCommentsHeight()
{
    if (this->commentsScrollArea->isVisible())
    {
        this->commentsScrollArea->hide();
        // this->commentsWidget->ensurePolished(); // Disabled for 1.3.1; using classic resize stuff
        this->commentsScrollArea->show();
    }
}


void CommenterBlock::adjustCommentArea()
{
    this->commentsWidget->ensurePolished(); // So .height() is valid
    int commentsWidgetHeight = qMax(this->commentsWidget->height(),
                                    32); // 32 px min! (size of avatar)
    int goodScrollAreaHeight = qMin(commentsWidgetHeight,
                                    globalObj->getTimelineHeight()); // Not bigger than the window
    goodScrollAreaHeight += commentsScrollArea->frameWidth() * 2; // Account for the frame

    if (!parentPostStandalone)
    {
        // Minimum is set only when the post is not open as standalone
        this->commentsScrollArea->setMinimumHeight(goodScrollAreaHeight);
    }
    this->commentsScrollArea->setMaximumHeight(goodScrollAreaHeight);
}


void CommenterBlock::redrawComments()
{
    foreach (Comment *comment, this->commentsInBlock)
    {
        comment->setCommentContents();
    }
}


bool CommenterBlock::isFullMode()
{
    return this->fullMode;
}


int CommenterBlock::getCommentCount()
{
    return this->currentCommentCount;
}


Composer *CommenterBlock::getComposer()
{
    return this->commentComposer;
}



/*****************************************************************************/
/*********************************** SLOTS ***********************************/
/*****************************************************************************/



void CommenterBlock::setMinimumMode()
{
    this->commentComposer->hide();
    this->toolsButton->hide();
    statusInfoLabel->clear();
    this->statusInfoLabel->hide();
    this->commentButton->hide();
    this->cancelButton->hide();

    // Clear formatting options like bold or italic
    this->commentComposer->setCurrentCharFormat(QTextCharFormat());

    // Clear "editing mode", restore stuff
    if (editingMode)
    {
        this->editingMode = false;
        this->editingCommentId.clear();

        this->commentButton->setText(tr("Comment",  // Button text back to "Comment" as usual
                                        "Infinitive verb")); // With exact same comment, so it's 1 entry
    }

    this->fullMode = false;
}



void CommenterBlock::setFullMode(QString initialText)
{
    this->commentComposer->show();
    this->toolsButton->show();
    this->statusInfoLabel->show();
    this->commentButton->show();
    this->cancelButton->show();

    this->commentComposer->setFocus();
    if (!initialText.isEmpty())
    {
        this->commentComposer->append(initialText);
        // Alternative way, would insert anywhere, has other problems
        // this->commentComposer->insertHtml(initialText);

        // Delete extra newline after the quote
        this->commentComposer->moveCursor(QTextCursor::End); // Otherwise could delete other characters
        this->commentComposer->textCursor().deletePreviousChar();
    }

    this->fullMode = true;
}



void CommenterBlock::quoteComment(QString content)
{
    this->setFullMode(content); // FIXME: connect to setFullMode(QString) directly?
}


void CommenterBlock::editComment(QString id, QString content)
{
    // Avoid destroying a comment currently being composed!
    if (editingMode || fullMode)
    {
        QMessageBox::warning(this, tr("Error: Already composing"),
                             tr("You can't edit a comment at this time, "
                                "because another comment is already being composed."));
        return;
    }

    this->editingMode = true;
    this->editingCommentId = id;
    setFullMode();

    this->commentComposer->setHtml(content);


    this->commentButton->setText("Update");
    this->statusInfoLabel->setText(tr("Editing comment"));
}



void CommenterBlock::requestAllComments()
{
    emit allCommentsRequested(); // FIXME 1.3.x: could connect() to this signal directly
}



/*
 * Called when commentPosted() is emmited by PumpController,
 * which means comment posted (or updated) successfully.
 *
 */
void CommenterBlock::onPostingCommentOk()
{
    // Clear info message
    this->statusInfoLabel->clear();

    // Comment was added successfully, so we can re-enable things
    this->setEnabled(true);

    // Erase the text from the comment box...
    this->commentComposer->erase();

    // Show the comment area, even if empty, in case comments are not reloaded ok
    this->showAllCommentsLinkLabel->show();
    this->commentsScrollArea->show();

    // and since we're done posting the comment, hide this
    setMinimumMode();


    disconnect(pController, SIGNAL(commentPosted()),
               this, SLOT(onPostingCommentOk()));
    disconnect(pController, SIGNAL(commentPostingFailed()),
               this, SLOT(onPostingCommentFailed()));


    this->getAllCommentsTimer->start(3000); // Request comments after a delay
}


/*
 * Executed when commentPostingFailed() signal is received from PumpController
 *
 */
void CommenterBlock::onPostingCommentFailed()
{
    qDebug() << "Posting the comment failed, re-enabling Commenter";

    // Alert about the error
    this->statusInfoLabel->setText(tr("Posting comment failed.\n\nTry again."));

    // Re-enable things, so user can try again
    this->setEnabled(true);
    this->commentComposer->setFocus();

    disconnect(pController, SIGNAL(commentPostingFailed()),
               this, SLOT(onPostingCommentFailed()));
    disconnect(pController, SIGNAL(commentPosted()),
               this, SLOT(onPostingCommentOk()));
}




void CommenterBlock::sendComment()
{
    qDebug() << "Commenter character count:"
             << commentComposer->textCursor().document()->characterCount();

    // If there's some text in the comment, send it
    if (commentComposer->textCursor().document()->characterCount() > 1)
    {
        connect(pController, SIGNAL(commentPosted()),
                this, SLOT(onPostingCommentOk()));
        connect(pController, SIGNAL(commentPostingFailed()),
                this, SLOT(onPostingCommentFailed()));

        if (!editingMode)
        {
            this->statusInfoLabel->setText(tr("Sending comment..."));
            emit commentSent(commentComposer->toHtml());
        }
        else
        {
            this->statusInfoLabel->setText(tr("Updating comment..."));
            emit commentUpdated(editingCommentId,
                                commentComposer->toHtml());
        }

        this->setDisabled(true);
    }
    else
    {
        this->statusInfoLabel->setText(tr("Comment is empty."));
        qDebug() << "Can't post, comment is empty";
    }
}

/*
 * Called by the QTimer
 *
 */
void CommenterBlock::scrollCommentsToBottom()
{
    this->adjustCommentsWidth();
    this->adjustCommentArea();

    commentsScrollArea->verticalScrollBar()->triggerAction(QScrollBar::SliderToMaximum);
}



/*****************************************************************************/
/********************************* PROTECTED *********************************/
/*****************************************************************************/



void CommenterBlock::resizeEvent(QResizeEvent *event)
{
    //qDebug() << "CommenterBlock::resizeEvent()"
    //         << event->oldSize() << ">" << event->size();

    this->adjustCommentsWidth();

    this->redrawComments();


    //this->commentsWidget->adjustSize();
    //this->commentsScrollArea->adjustSize();
    this->adjustCommentArea();

    event->accept();
}

