/*
 *   This file is part of Dianara
 *   Copyright 2012-2019  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 parentId,
                               QString parentAuthorId,
                               bool parentStandalone,
                               QWidget *parent) : QWidget(parent)
{
    m_pumpController = pumpController;
    m_globalObject = globalObject;
    m_parentPostId = parentId;
    m_parentPostAuthorId = parentAuthorId;
    m_parentPostStandalone = parentStandalone;

    m_editingMode = false;
    m_currentCommentCount = 0;
    m_allCommentsShown = false;

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

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

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

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

    m_commentsScrollArea = new QScrollArea(this);
    m_commentsScrollArea->setContentsMargins(0, 0, 0, 0);
    m_commentsScrollArea->setSizePolicy(QSizePolicy::Minimum,
                                        QSizePolicy::Preferred);
    m_commentsScrollArea->setWidget(m_commentsWidget);
    m_commentsScrollArea->setWidgetResizable(true);

    m_getAllCommentsTimer = new QTimer(this);
    m_getAllCommentsTimer->setSingleShot(true);
    connect(m_getAllCommentsTimer, &QTimer::timeout,
            this, &CommenterBlock::requestAllComments);

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

    m_scrollToBottomTimer = new QTimer(this);
    m_scrollToBottomTimer->setSingleShot(true);
    connect(m_scrollToBottomTimer, &QTimer::timeout,
            this, &CommenterBlock::scrollCommentsToBottom);



    m_commentComposer = new Composer(m_globalObject,
                                     false, // forPublisher = false
                                     this);

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

    connect(m_commentComposer, &Composer::editingFinished,
            this, &CommenterBlock::sendComment);
    connect(m_commentComposer, &Composer::editingCancelled,
            this, &CommenterBlock::setMinimumMode);



    // Formatting/Tools button exported from Composer
    m_toolsButton = m_commentComposer->getToolsButton();

    // Optional character picker button, also taken from Composer
#ifdef HAVE_KCHARSELECT
    m_charPickerButton = m_commentComposer->getCharPickerButton();
#endif

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


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

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

    // Layout for optional character picker button, in one row with the Format button
    m_topButtonsLayout = new QHBoxLayout();
    m_topButtonsLayout->setContentsMargins(0, 0, 0, 0);
#ifdef HAVE_KCHARSELECT
    m_topButtonsLayout->addWidget(m_charPickerButton);
    m_topButtonsLayout->addSpacing(4);
#endif
    m_topButtonsLayout->addWidget(m_toolsButton);

    m_bottomLayout = new QGridLayout();
    m_bottomLayout->setContentsMargins(0, 0, 0, 0);
    m_bottomLayout->setSpacing(1);
    m_bottomLayout->addWidget(m_commentComposer,        0, 0, 8, 3);
    m_bottomLayout->addLayout(m_topButtonsLayout,       0, 3, 1, 1);
    m_bottomLayout->addWidget(m_statusInfoLabel,        1, 3, 5, 1, Qt::AlignCenter);
    m_bottomLayout->addWidget(m_commentButton,          6, 3, 1, 1);
    m_bottomLayout->addWidget(m_cancelButton,           7, 3, 1, 1);

    m_mainLayout = new QVBoxLayout();
    m_mainLayout->setContentsMargins(0, 0, 0, 0);
    m_mainLayout->setSpacing(1);
    m_mainLayout->addWidget(m_showAllCommentsLinkLabel, 0, Qt::AlignRight | Qt::AlignTop);
    m_mainLayout->addWidget(m_commentsScrollArea,       0);
    m_mainLayout->addLayout(m_bottomLayout,             0);
    this->setLayout(m_mainLayout);

    this->setMinimumMode();

    qDebug() << "Commenter created";
}


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



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

    m_commentsInBlock.clear();
    m_currentCommentCount = 0;
}



void CommenterBlock::setComments(QVariantList commentsList, int commentCount)
{
    m_reloadErrorString.clear(); // No error to show!
    disconnect(m_pumpController, &PumpController::commentsNotReceived,
               this, &CommenterBlock::onCommentsNotReceived);


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

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


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

        m_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
        m_scrollToBottomTimer->start(500);
    }
    else
    {
        // For cases where 0 comments were received, after specifically reloading
        this->updateShowAllLink();
    }
}


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

    Comment *comment = new Comment(m_pumpController,
                                   m_globalObject,
                                   object,
                                   this);
    connect(comment, &Comment::commentQuoteRequested,
            this, &CommenterBlock::quoteComment);
    connect(comment, &Comment::commentEditRequested,
            this, &CommenterBlock::editComment);



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

    // Or highlight if this is our own comment, also optional
    if (m_globalObject->getPostHLOwnComments())
    {
        if (object->author()->getId() == m_pumpController->currentUserId())
        {
            comment->setHint(m_globalObject->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
    m_commentsInBlock.append(comment); // First, keep track of it

    if (justOne)
    {
        m_commentsLayout->addWidget(comment);
        ++m_currentCommentCount;

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

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

        m_scrollToBottomTimer->start(500);
    }
    else  // Called from setComments()
    {
        m_commentsLayout->insertWidget(0, comment);
    }
}


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

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

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


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

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

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



void CommenterBlock::updateShowAllLink()
{
    QString showAllString;

    if (m_allCommentsShown)
    {
        showAllString = m_reloadCommentsString;
    }
    else
    {
        if (m_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(m_currentCommentCount);
        }
    }


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

    m_showAllCommentsLinkLabel->setText(m_reloadErrorString
                                        + "<a href=\"showall://\">"
                                        + showAllString
                                        + "</a>");
    m_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()
{
    m_showAllCommentsLinkLabel->setText(tr("Comments are not available"));
}



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

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


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

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


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

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


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


bool CommenterBlock::isFullMode()
{
    return m_fullMode;
}


void CommenterBlock::toggleWidgetsWhileSending(bool widgetsEnabled)
{
    m_showAllCommentsLinkLabel->setEnabled(widgetsEnabled);
    m_commentComposer->setEnabled(widgetsEnabled);
    m_toolsButton->setEnabled(widgetsEnabled);
#ifdef HAVE_KCHARSELECT
    m_charPickerButton->setEnabled(widgetsEnabled);
#endif
    m_commentButton->setEnabled(widgetsEnabled);
}


int CommenterBlock::getCommentCount()
{
    return m_currentCommentCount;
}


Composer *CommenterBlock::getComposer()
{
    return m_commentComposer;
}



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



void CommenterBlock::setMinimumMode()
{
    m_commentComposer->hide();
    m_toolsButton->hide();
#ifdef HAVE_KCHARSELECT
    m_charPickerButton->hide();
#endif
    m_statusInfoLabel->clear();
    m_statusInfoLabel->hide();
    m_commentButton->hide();
    m_cancelButton->hide();

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

    // Clear "editing mode", restore stuff
    if (m_editingMode)
    {
        m_editingMode = false;
        m_editingCommentId.clear();

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

    m_fullMode = false;

    // Re-enable stuff just in case a POST request never finished and comment was cancelled
    this->toggleWidgetsWhileSending(true);
}



void CommenterBlock::setFullMode(QString initialText)
{
    m_commentComposer->show();
    m_toolsButton->show();
#ifdef HAVE_KCHARSELECT
    m_charPickerButton->show();
#endif
    m_statusInfoLabel->show();
    m_commentButton->show();
    m_cancelButton->show();

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

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

    m_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 (m_editingMode || m_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;
    }

    m_editingMode = true;
    m_editingCommentId = id;
    setFullMode();

    m_commentComposer->setHtml(content);
    m_commentComposer->moveCursor(QTextCursor::End);


    m_commentButton->setText("Update");
    m_statusInfoLabel->setText(tr("Editing comment"));
}



void CommenterBlock::requestAllComments()
{
    // Show feedback in place, regardless of statusbar message
    m_showAllCommentsLinkLabel->setText("<b></b>&sim; &nbsp;" // ~
                                        + tr("Loading comments..."));

    connect(m_pumpController, &PumpController::commentsNotReceived,
            this, &CommenterBlock::onCommentsNotReceived);

    emit allCommentsRequested();
}



/*
 * Called when commentPosted() is emmited by PumpController,
 * which means comment posted (or updated) successfully.
 *
 */
void CommenterBlock::onPostingCommentOk(QString postId)
{
    if (postId != m_parentPostId)
    {
        // Not related to this post...
        return;
    }


    // Clear info message
    m_statusInfoLabel->clear();

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

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

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

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


    disconnect(m_pumpController, &PumpController::commentPosted,
               this, &CommenterBlock::onPostingCommentOk);
    disconnect(m_pumpController, &PumpController::commentPostingFailed,
               this, &CommenterBlock::onPostingCommentFailed);


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


/*
 * Executed when commentPostingFailed() signal is received from PumpController
 *
 */
void CommenterBlock::onPostingCommentFailed(QString postId)
{
    if (postId != m_parentPostId)
    {
        // Not related to this post...
        return;
    }


    qDebug() << "Posting the comment failed, re-enabling Commenter";

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

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

    disconnect(m_pumpController, &PumpController::commentPostingFailed,
               this, &CommenterBlock::onPostingCommentFailed);
    disconnect(m_pumpController, &PumpController::commentPosted,
               this, &CommenterBlock::onPostingCommentOk);
}


void CommenterBlock::onCommentsNotReceived(QString id)
{
    if (id == m_parentPostId)
    {
        m_reloadErrorString = tr("An error occurred") + " &mdash; ";

        this->updateShowAllLink();
    }
}




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

    // If there's some text in the comment, send it
    if (m_commentComposer->textCursor().document()->characterCount() > 1)
    {
        connect(m_pumpController, &PumpController::commentPosted,
                this, &CommenterBlock::onPostingCommentOk);
        connect(m_pumpController, &PumpController::commentPostingFailed,
                this, &CommenterBlock::onPostingCommentFailed);

        if (!m_editingMode)
        {
            m_statusInfoLabel->setText(tr("Sending comment..."));
            emit commentSent(m_commentComposer->toHtml());
        }
        else
        {
            m_statusInfoLabel->setText(tr("Updating comment..."));
            emit commentUpdated(m_editingCommentId,
                                m_commentComposer->toHtml());
        }

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

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

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



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



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

    this->adjustCommentsWidth();

    this->redrawComments();


    //m_commentsWidget->adjustSize();
    //m_commentsScrollArea->adjustSize();
    this->adjustCommentArea();

    event->accept();
}

