/*
 *   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 "post.h"

#include "mainwindow.h"

Post::Post(ASActivity *activity,
           bool highlightedByFilter,
           bool isStandalone,
           PumpController *pumpController,
           GlobalObject *globalObject,
           QWidget *parent)  :  QFrame(parent)
{
    m_pumpController = pumpController;
    m_globalObject = globalObject;
    m_standalone = isStandalone;

    m_seeFullImageString = tr("Click the image to see it in full size");
    m_downloadAttachmentString = tr("Click to download the attachment");
    m_postImageIsAnimated = false; // Initialize
    m_postImageFailed = false;
    this->setSizePolicy(QSizePolicy::MinimumExpanding,
                        QSizePolicy::Minimum);
    this->setMinimumSize(30, 30); // Ensure something's visible at all times

    activity->setParent(this); // Reparent the passed activity


    m_leftColumnFrame = new QFrame(this);

    ///////////////////////////////////////////////// Highlighting
    const QStringList highlightColors = m_globalObject->getColorsList();
    m_highlightType = NoHighlight;
    QString highlightPostColor;

    // Post is adressed to us
    if (activity->getRecipientsIdList().contains(m_pumpController->currentUserId()))
    {
        m_highlightType = MessageForUserHighlight;
        highlightPostColor = highlightColors.at(0); // Color #1 in config
    }    
    else if (activity->author()->getId() == m_pumpController->currentUserId() // Post/activity is ours
          || activity->object()->author()->getId() == m_pumpController->currentUserId())
    {
        m_highlightType = OwnMessageHighlight;
        highlightPostColor = highlightColors.at(3); // Color #4 in the config
    }
    else if (highlightedByFilter) // Highlight by filtering rules
    {
        m_highlightType = FilterRulesHighlight;
        highlightPostColor = highlightColors.at(4); // Color #5
    }

    if (m_highlightType != NoHighlight
     && !m_standalone           // Don't use highlighting when opening as standalone post
     && !m_pumpController->currentUserId().isEmpty())
    {
        if (QColor::isValidColor(highlightPostColor))
        {
            // CSS for horizontal gradient from configured color to transparent
            const QString css = QString("QFrame#LeftFrame "
                                        "{ background-color: "
                                        "  qlineargradient(spread:pad, "
                                        "  x1:0, y1:0, x2:1, y2:0, "
                                        "  stop:0 %1, stop:1 rgba(0, 0, 0, 0)); "
                                        "}")
                                .arg(highlightPostColor);

            m_leftColumnFrame->setObjectName("LeftFrame");
            m_leftColumnFrame->setStyleSheet(css);
        }
        else
        {
            m_leftColumnFrame->setFrameStyle(QFrame::Panel);
        }
    }

    m_unreadPostColor = highlightColors.at(5); // Color #6

    m_leftColumnLayout = new QVBoxLayout();


    m_activityId = activity->getId(); // FIXME: will be empty in Favorites TL
    m_postId = activity->object()->getId();
    m_postType = activity->object()->getType();
    m_postUrl = activity->object()->getUrl();
    m_likesCount = 0;

    // Store relevant likes/comments/shares URLs
    m_likesUrl = activity->object()->getLikesUrl();
    m_commentsUrl = activity->object()->getCommentsUrl();
    m_sharesUrl = activity->object()->getSharesUrl();

    // Add comments URL to "ever seen" list, only needed if post is on another server
    if (activity->object()->hasProxiedUrls())
    {
        m_pumpController->addCommentUrlToSeenList(m_postId,
                                                  m_commentsUrl);
    }


    ASPerson *authorPerson;
    // Having ID means the object has proper author data
    if (!activity->object()->author()->getId().isEmpty())
    {
        authorPerson = activity->object()->author();
    }
    else // No ID means post author data is the activity's author data
    {    // This is a workaround, because pump.io doesn't give object author
         // if the activity is by the same user
        authorPerson = activity->author();
    }
    m_postAuthorId = authorPerson->getId();
    m_postAuthorName = authorPerson->getNameWithFallback();

    m_generatorString = activity->getGenerator();


    // If it's a standalone post, set window title and restore size
    if (m_standalone)
    {
        this->setWindowTitle(tr("Post", "Noun, not verb")
                             + " ("
                             + activity->object()->getTranslatedType(m_postType)
                             + ") - "
                             + activity->object()->author()->getNameWithFallback()
                             + " - Dianara");
        this->setWindowIcon(QIcon::fromTheme(QStringLiteral("mail-message"),
                                             QIcon(":/images/button-edit.png")));
        this->setWindowFlags(Qt::Window);
        this->setWindowModality(Qt::NonModal);

        // Restore size
        this->setMinimumSize(420, 360);
        QSettings settings;
        this->resize(settings.value("Post/postSize",
                                    QSize(600, 440)).toSize());

        connect(m_globalObject, &GlobalObject::programShuttingDown,
                this, &QWidget::close);
    }

    m_postIsUnread = false;
    m_postIsDeleted = false;
    m_haveDownloadWidget = false;


    QFont detailsFont;
    detailsFont.setPointSize(detailsFont.pointSize() - 1); // FIXME: check size first


    // Is the post a reshare? Indicate who shared it with a wide label at the top
    m_shareInfoLabel = new QLabel(this); // With parent to avoid leaks
                                         // Needs to be initialized even if
                                         // unused, for setFuzzyTimestamps()
    QString sharedById;
    if (activity->isShared())
    {
        sharedById = activity->getSharedById();
        m_postShareTime = activity->getUpdatedAt();

        const QString sharedByName = activity->getSharedByName();
        const QString sharedByAvatar =
              MiscHelpers::getCachedAvatarFilename(activity->getSharedByAvatar());
        m_postShareInfoString = QString::fromUtf8("\342\231\272 "  // Recycling symbol
                                                  "&nbsp; &nbsp; ");

        QFont shareInfoFont;
        // If 'extended share info' is enabled, show avatar and use bigger font
        if (m_globalObject->getPostExtendedShares())
        {
            // Share info font is standard size

            m_postShareInfoString.append("<img align=\"middle\" src=\""
                                         + sharedByAvatar
                                         + "\" height=\"32\" /> "
                                         "&nbsp; &nbsp; ");

            // FIXME/TODO: show client used?
        }
        else
        {
            // Small font, like in other details
            shareInfoFont = detailsFont;
        }

        m_postShareInfoString.append(tr("Via %1")
                                     .arg("<a href=\""
                                          + activity->author()->getUrl()
                                          + "\">" + sharedByName + "</a> - "));

        // Fuzzy time is added here, in setFuzzyTimestamps(), along with To/Cc info

        m_postSharedToCcString.clear();
        if (!activity->getToString().isEmpty())
        {
            m_postSharedToCcString.append(" - " + tr("To") + ": "
                                          + activity->getToString());
        }

        if (!activity->getCcString().isEmpty())
        {
            m_postSharedToCcString.append(" - " + tr("Cc") + ": "
                                          + activity->getCcString());
        }


        m_shareInfoLabel->setText(m_postShareInfoString);
        m_shareInfoLabel->setWordWrap(true);
        m_shareInfoLabel->setOpenExternalLinks(true);
        m_shareInfoLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
        m_shareInfoLabel->setFont(shareInfoFont);
        m_shareInfoLabel->setForegroundRole(QPalette::Text);
        m_shareInfoLabel->setBackgroundRole(QPalette::Base);
        m_shareInfoLabel->setAutoFillBackground(true);
        m_shareInfoLabel->setFrameStyle(QFrame::Panel | QFrame::Raised);
        connect(m_shareInfoLabel, &QLabel::linkHovered,
                this, &Post::showHighlightedUrl);


        QString shareTooltip = "<table cellpadding=8>"
                               "<tr>"
                               "<td>"
                               "<img align=\"left\" src=\""
                               + sharedByAvatar
                               + "\" width=\"96\" />"
                                 "</td>";
        shareTooltip.append("<td><b>"
                            + sharedByName
                            + "</b><br>");
        shareTooltip.append("<i>"
                            + sharedById
                            + "</i>"
                              "</td>"
                              "</tr>"
                              "</table>");


        const QString exactShareTime = Timestamp::localTimeDate(m_postShareTime);
        shareTooltip.append("<hr>"
                            + tr("Shared on %1").arg(exactShareTime));
        if (!m_generatorString.isEmpty())
        {
            shareTooltip.append("<br>"
                                + tr("Using %1",
                                     "1=Program used for posting or sharing")
                                  .arg(m_generatorString));
            m_generatorString.clear(); // So it's not used in the post timestamp tooltip!
        }

        m_shareInfoLabel->setToolTip(shareTooltip);
    }
    else  // Activity is not 'share'
    {
        m_shareInfoLabel->hide();
    }


    /////////////////////////////////////////////////// Left column, post Meta info

    // Different frame if post is ours
    if (m_postAuthorId == m_pumpController->currentUserId())
    {
        m_postIsOwn = true;
        qDebug() << "Post is our own!";

        this->setFrameStyle(QFrame::Panel | QFrame::Sunken);
    }
    else
    {
        m_postIsOwn = false;
        this->setFrameStyle(QFrame::Box | QFrame::Raised);
    }


    // Author avatar
    QSize avatarSize = m_globalObject->getPostAvatarSize();
    if (!activity->object()->getDeletedTime().isEmpty())
    {
        // If the post was initially deleted, use a small avatar
        avatarSize = QSize(32,32);
    }
    m_postAuthorAvatarButton = new AvatarButton(authorPerson,
                                                m_pumpController,
                                                m_globalObject,
                                                avatarSize,
                                                this);

    ///////////// Add extra options to the avatar menu
    m_postAuthorAvatarButton->addSeparatorToMenu();

    // Open post in browser
    m_openInBrowserAction = new QAction(QIcon::fromTheme(QStringLiteral("internet-web-browser"),
                                                         QIcon(":/images/button-download.png")),
                                        tr("Open post in web browser"),
                                        this);
    connect(m_openInBrowserAction, &QAction::triggered,
            this, &Post::openPostInBrowser);
    m_postAuthorAvatarButton->addActionToMenu(m_openInBrowserAction);

    // Copy URL
    m_copyPostUrlAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy"),
                                                       QIcon(":/images/button-download.png")),
                                      tr("Copy post link to clipboard"),
                                      this);
    connect(m_copyPostUrlAction, &QAction::triggered,
            this, &Post::copyPostUrlToClipboard);
    m_postAuthorAvatarButton->addActionToMenu(m_copyPostUrlAction);

    // Disable Open and Copy URL if there's no URL
    if (m_postUrl.isEmpty())
    {
        m_openInBrowserAction->setDisabled(true);
        m_copyPostUrlAction->setDisabled(true);
    }


    // -----
    m_postAuthorAvatarButton->addSeparatorToMenu();

    // Normalize text colors
    m_normalizeTextAction = new QAction(QIcon::fromTheme("format-text-color"),
                                        tr("Normalize text colors"),
                                        this);
    connect(m_normalizeTextAction, &QAction::triggered,
            this, &Post::normalizeTextFormat);
    m_postAuthorAvatarButton->addActionToMenu(m_normalizeTextAction);


    if (m_standalone) // Own window, so add close option to menu
    {
        // -----
        m_postAuthorAvatarButton->addSeparatorToMenu();

        /*
         * 1.3.5: This will probably not be needed anymore for any environment
         *        since Posts are now windows, not dialogs, but still...
         */
        m_closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close"),
                                                     QIcon(":/images/button-close.png")),
                                    tr("&Close"),
                                    this);
        connect(m_closeAction, &QAction::triggered,
                this, &QWidget::close);
        m_postAuthorAvatarButton->addActionToMenu(m_closeAction);
    }


    // End avatar menu

    m_leftColumnLayout->addWidget(m_postAuthorAvatarButton, 0, Qt::AlignLeft);


    QFont authorFont;
    authorFont.setBold(true);
    if (m_postIsOwn) // Another visual hint when the post is our own
    {
        authorFont.setItalic(true);
    }

    // Author name
    m_authorLabel = new QLabel(m_postAuthorName, this);
    m_authorLabel->setTextFormat(Qt::PlainText);
    m_authorLabel->setWordWrap(true); ///////////////// FIXME: make optional
    m_authorLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    m_authorLabel->setFont(authorFont);
    // Ensure a certain width for the metadata column, by setting a minimum width for this label
    m_authorLabel->setMinimumWidth(90); // FIXME: use font metrics for some chars instead


    m_leftColumnLayout->addWidget(m_authorLabel);
    m_leftColumnLayout->addSpacing(1);


    // Post creation time
    m_timeCreatedAtString = activity->object()->getCreatedAt();
    // Timestamp format is "Combined date and time in UTC", like
    // "2012-02-07T01:32:02Z" as per ISO8601 http://en.wikipedia.org/wiki/ISO_8601

    m_timestampLabel = new HClabel(QString(), this);
    m_timestampLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    m_timestampLabel->setFont(detailsFont);
    m_timestampLabel->setSizePolicy(QSizePolicy::Ignored,
                                    QSizePolicy::Expanding);
    m_leftColumnLayout->addWidget(m_timestampLabel);

    // Show generator app, if enabled to display directly
    if (!m_generatorString.isEmpty()
     && m_globalObject->getPostShowExtraInfo())
    {
        //QString::fromUtf8("\342\232\231 ") // Gear symbol
        m_generatorLabel = new QLabel(QString::fromUtf8("\342\214\250 ") // Keyboard symbol
                                      + m_generatorString,
                                      this);
        m_generatorLabel->setFont(detailsFont);
        m_generatorLabel->setSizePolicy(QSizePolicy::Ignored,
                                        QSizePolicy::Minimum);
        m_leftColumnLayout->addWidget(m_generatorLabel);
    }

    m_leftColumnLayout->addSpacing(4);


    // Location information, if any
    m_locationLabel = new HClabel(QString(), this);
    m_locationLabel->setFont(detailsFont);
    m_locationLabel->setSizePolicy(QSizePolicy::Ignored,
                                   QSizePolicy::Expanding);
    m_leftColumnLayout->addWidget(m_locationLabel);
    m_leftColumnLayout->addSpacing(2);
    m_locationLabel->hide();


    if (!activity->isShared())  // Because when shared, To/Cc are shown with share info
    {
        if (!activity->getToString().isEmpty())
        {
            m_toLabel = new QLabel(tr("To") + ": "
                                   + activity->getToString()
                                   + " &nbsp;", // Small hack to try to ensure full visibility
                                   this);
            m_toLabel->setWordWrap(true);
            m_toLabel->setOpenExternalLinks(true);
            m_toLabel->setSizePolicy(QSizePolicy::Ignored,
                                     QSizePolicy::Expanding);
            m_toLabel->setFont(detailsFont);

            m_leftColumnLayout->addWidget(m_toLabel);
            connect(m_toLabel, &QLabel::linkHovered,
                    this, &Post::showHighlightedUrl);
        }

        if (!activity->getCcString().isEmpty())
        {
            m_ccLabel = new QLabel(tr("Cc") + ": "
                                   + activity->getCcString()
                                   + " &nbsp;",
                                   this);
            m_ccLabel->setWordWrap(true);
            m_ccLabel->setOpenExternalLinks(true);
            m_ccLabel->setSizePolicy(QSizePolicy::Ignored,
                                     QSizePolicy::Expanding);
            m_ccLabel->setFont(detailsFont);

            m_leftColumnLayout->addWidget(m_ccLabel);
            connect(m_ccLabel, &QLabel::linkHovered,
                    this, &Post::showHighlightedUrl);
        }
    }

    m_leftColumnLayout->addSpacing(4);


    // Set these labels with parent=this, since we're gonna hide them right away
    // They'll be reparented to the layout, but not having a parent before that
    // would cause visual glitches
    m_likesCountLabel = new HClabel(QString(), this);
    m_likesCountLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    m_likesCountLabel->setFont(detailsFont);
    m_likesCountLabel->setOpenExternalLinks(true);
    m_likesCountLabel->setSizePolicy(QSizePolicy::Ignored,
                                     QSizePolicy::Expanding);
    connect(m_likesCountLabel, &QLabel::linkHovered,
            this, &Post::showHighlightedUrl);
    m_leftColumnLayout->addWidget(m_likesCountLabel);
    m_likesCountLabel->hide();


    m_commentsCountLabel = new QLabel(this);
    m_commentsCountLabel->setWordWrap(true);
    m_commentsCountLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    m_commentsCountLabel->setFont(detailsFont);
    m_leftColumnLayout->addWidget(m_commentsCountLabel);
    m_commentsCountLabel->hide();


    m_sharesCountLabel = new HClabel(QString(), this);
    m_sharesCountLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    m_sharesCountLabel->setFont(detailsFont);
    m_sharesCountLabel->setOpenExternalLinks(true);
    m_sharesCountLabel->setSizePolicy(QSizePolicy::Ignored,
                                      QSizePolicy::Expanding);
    connect(m_sharesCountLabel, &QLabel::linkHovered,
            this, &Post::showHighlightedUrl);
    m_leftColumnLayout->addWidget(m_sharesCountLabel);
    m_sharesCountLabel->hide();


    // Try to use all remaining space, aligning
    // all previous widgets nicely at the top
    // leftColumnLayout->setAlignment(Qt::AlignTop) caused glitches
    m_leftColumnLayout->addStretch(1);

    QFont buttonsFont;
    buttonsFont.setPointSize(buttonsFont.pointSize() - 1);


    // Check if the object is in reply to something (a shared comment, for instance)
    m_postParentMap = activity->object()->getInReplyTo();
    if (!m_postParentMap.isEmpty())
    {
        m_openParentButton = new QPushButton(QIcon::fromTheme(QStringLiteral("go-up-search"),
                                                              QIcon(":/images/button-parent.png")),
                                             tr("Parent",
                                                "As in 'Open the parent post'. "
                                                "Try to use the shortest word!"),
                                             this);
        //m_openParentButton->setFlat(true);
        m_openParentButton->setFont(buttonsFont);
        m_openParentButton->setSizePolicy(QSizePolicy::Ignored,
                                          QSizePolicy::Maximum);
        m_openParentButton->setToolTip("<b></b>"
                                       + tr("Open the parent post, to which "
                                            "this one replies"));
        connect(m_openParentButton, &QAbstractButton::clicked,
                this, &Post::openParentPost);
        m_leftColumnLayout->addWidget(m_openParentButton);
    }


    m_leftColumnFrame->setLayout(m_leftColumnLayout);


    //////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////// Right column, content

    m_rightColumnFrame = new QFrame(this);
    m_rightColumnFrame->setObjectName("RightFrame"); // For CSS changes later

    m_rightColumnLayout = new QVBoxLayout();
    m_rightColumnLayout->setAlignment(Qt::AlignTop);

    ///////////////////////////////////// Title
    m_titleLabel = new QLabel(this);
    m_titleLabel->setWordWrap(true);
    QFont postTitleFont;
    postTitleFont.fromString(m_globalObject->getPostTitleFont());
    m_titleLabel->setFont(postTitleFont);
    m_rightColumnLayout->addWidget(m_titleLabel, 0, Qt::AlignTop);


    ///////////////////////////////////// Summary
    const QString postSummary = activity->object()->getSummary();
    if (!postSummary.isEmpty())
    {
        m_summaryLabel = new QLabel(this);
        m_summaryLabel->setWordWrap(true);
        m_summaryLabel->setFont(detailsFont);
        m_summaryLabel->setText(postSummary);
        m_rightColumnLayout->addWidget(m_summaryLabel, 0);
    }


    ///////////////////////////////////// Post body, the text contents
    m_bodyTextBrowser = new QTextBrowser(this);
    m_bodyTextBrowser->setSizePolicy(QSizePolicy::MinimumExpanding,
                                     QSizePolicy::MinimumExpanding);
    m_bodyTextBrowser->setMinimumSize(10, 10);
    m_bodyTextBrowser->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
    m_bodyTextBrowser->setOpenLinks(false); // Manage links in openClickedUrl()
    m_bodyTextBrowser->setReadOnly(true); // It's default with QTextBrowser, but still...

    QFont contentsFont;
    contentsFont.fromString(m_globalObject->getPostContentsFont());
    m_bodyTextBrowser->setFont(contentsFont);

    // To help screen readers, enable keyboard interaction here
    // This is disabled for now, since it doesn't really help; hopefully with Qt5
    // things can be better.
    //m_bodyTextBrowser->setTextInteractionFlags(Qt::TextBrowserInteraction
    //                                           | Qt::TextSelectableByKeyboard);
    connect(m_bodyTextBrowser, &QTextBrowser::anchorClicked,
            this, &Post::openClickedUrl);
    connect(m_bodyTextBrowser, SIGNAL(highlighted(QString)), // Old-style due to overload
            this, SLOT(showHighlightedUrl(QString)));
    m_rightColumnLayout->addWidget(m_bodyTextBrowser, 1);
                                   // No alignment; makes size wrong when standalone

    // Buttons
    // Add like, comment, share and, if post is ours, edit and delete buttons
    m_buttonsLayout = new QHBoxLayout();
    m_buttonsLayout->setContentsMargins(0, 0, 0, 0);
    m_buttonsLayout->setMargin(0);
    m_buttonsLayout->setSpacing(0);


    // Like button
    m_likeButton = new QPushButton(QIcon::fromTheme(QStringLiteral("emblem-favorite"),
                                                    QIcon(":/images/button-like.png")),
                                   QString(),
                                   this);
    m_likeButton->setCheckable(true);
    this->fixLikeButton(activity->object()->isLiked());
    m_likeButton->setFlat(true);
    m_likeButton->setFont(buttonsFont);
    connect(m_likeButton, &QAbstractButton::clicked,
            this, &Post::likePost);
    m_buttonsLayout->addWidget(m_likeButton, 0, Qt::AlignLeft);

    // Only add Comment and Share buttons if it's NOT a comment
    if (m_postType != QStringLiteral("comment")) // (in Favorites timeline or via share)
    {
        // Comment (reply) button
        m_commentButton = new QPushButton(QIcon::fromTheme(QStringLiteral("mail-reply-sender"),
                                                           QIcon(":/images/button-comment.png")),
                                          tr("Comment",
                                             "verb, for the comment button"),
                                          this);
        m_commentButton->setToolTip(tr("Reply to this post.")
                                    + "<br><br>"
                                    + tr("If you select some text, "
                                         "it will be quoted."));
        m_commentButton->setFlat(true);
        m_commentButton->setFont(buttonsFont);
        connect(m_commentButton, &QAbstractButton::clicked,
                this, &Post::commentOnPost);
        m_buttonsLayout->addWidget(m_commentButton, 0, Qt::AlignLeft);


        // Share button
        m_shareButton = new QPushButton(QIcon::fromTheme(QStringLiteral("mail-forward"),
                                                         QIcon(":/images/button-share.png")),
                                        QString(),
                                        this);
        // Note: Gwenview includes 'document-share' icon
        m_shareButton->setFlat(true);
        m_shareButton->setFont(buttonsFont);
        if (sharedById.isEmpty()
            || (m_pumpController->currentUserId() != sharedById))
        {
            m_shareButton->setText(tr("Share"));
            m_shareButton->setToolTip("<b></b>"
                                      + tr("Share this post with your contacts"));
            connect(m_shareButton, &QAbstractButton::clicked,
                    this, &Post::sharePost);
        }
        else // Shared by us!
        {
            m_shareButton->setText(tr("Unshare"));
            m_shareButton->setToolTip("<b></b>"
                                      + tr("Unshare this post"));
            connect(m_shareButton, &QAbstractButton::clicked,
                    this, &Post::unsharePost);
            m_shareButton->setDisabled(true);
            // FIXME: disabled for 1.2.x, since it doesn't really work
        }

        m_buttonsLayout->addWidget(m_shareButton, 0, Qt::AlignLeft);
    }

    m_buttonsLayout->addStretch(1); // so the (optional) Edit and Delete buttons get separated

    if (m_postIsOwn)
    {
        m_editButton = new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit"),
                                                        QIcon(":/images/button-edit.png")),
                                       tr("Edit"),
                                       this);
        m_editButton->setToolTip("<b></b>"
                                 + tr("Modify this post"));
        m_editButton->setFlat(true);
        m_editButton->setFont(buttonsFont);
        connect(m_editButton, &QAbstractButton::clicked,
                this, &Post::editPost);
        m_buttonsLayout->addWidget(m_editButton, 0, Qt::AlignRight);

        m_deleteButton = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-delete"),
                                                          QIcon(":/images/button-delete.png")),
                                         tr("Delete"),
                                         this);
        m_deleteButton->setToolTip("<b></b>"
                                   + tr("Erase this post"));
        m_deleteButton->setFlat(true);
        m_deleteButton->setFont(buttonsFont);
        connect(m_deleteButton, &QAbstractButton::clicked,
                this, &Post::deletePost);

        m_buttonsLayout->addWidget(m_deleteButton, 0, Qt::AlignRight);
    }



    /******************************************************************/

    m_pendingImagesList.clear(); // Will add own "post image", and <img>-based ones
    QString attachmentUrl;

    // Get URL of post image, if it's "image" type of post
    if (m_postType == QStringLiteral("image"))
    {
        m_postFullImageUrl = activity->object()->getImageUrl();
        m_postAttachmentPureUrl = activity->object()->getAttachmentPureUrl();

        if (m_globalObject->getPostFullImages())
        {
            m_postImageUrl = m_postFullImageUrl;
        }
        else
        {
            m_postImageUrl = activity->object()->getSmallImageUrl();
        }

        m_pendingImagesList.append(m_postImageUrl);
        // TMP/FIXME: optionally autodownload the full size version
        //            when using thumbnails in the timeline?

        // Get stored size
        m_postImageSize = QSize(activity->object()->getImageWidth(),
                                activity->object()->getImageHeight());
    }
    // Get URL of post audio file, if it's "audio" type of post
    else if (m_postType == QStringLiteral("audio"))
    {
        m_postAudioUrl = activity->object()->getAudioUrl();
        attachmentUrl = m_postAudioUrl;
        m_postAttachmentPureUrl = activity->object()->getAttachmentPureUrl();

        qDebug() << "We got AUDIO:" << m_postAudioUrl;
    }
    // Get URL of post video file, if it's "video" type of post
    else if (m_postType == QStringLiteral("video"))
    {
        m_postVideoUrl = activity->object()->getVideoUrl();
        attachmentUrl = m_postVideoUrl;
        m_postAttachmentPureUrl = activity->object()->getAttachmentPureUrl();

        qDebug() << "We got VIDEO:" << m_postVideoUrl;
    }
    // Get URL of post general file, if it's "file" type of post
    else if (m_postType == QStringLiteral("file"))
    {
        m_postFileUrl = activity->object()->getFileUrl();
        attachmentUrl = m_postFileUrl;
        m_postAttachmentPureUrl = activity->object()->getAttachmentPureUrl();

        m_postFileMimeType = activity->object()->getMimeType();
        qDebug() << "We got FILE:" << m_postFileUrl
                 << "; type:" << m_postFileMimeType;
    }
    // Button to join/leave the group, if the post is the creation of a group
    else if (m_postType == QStringLiteral("group"))
    {
        // FIXME: for now, only JOIN, as we can't know if we're members yet
        m_joinLeaveButton = new QPushButton(QIcon::fromTheme(QStringLiteral("user-group-new"),
                                                             QIcon(":/images/list-add.png")),
                                            tr("Join Group"),
                                            this);
        connect(m_joinLeaveButton, &QAbstractButton::clicked,
                this, &Post::joinGroup);
        m_rightColumnLayout->addWidget(m_joinLeaveButton, 0, Qt::AlignCenter);

        m_groupInfoLabel = new QLabel(tr("%1 members in the group")
                                      .arg(activity->object()->getMemberCount()),
                                      this);
#ifdef GROUPSUPPORT
        m_groupInfoLabel->setText(m_groupInfoLabel->text()
                                  + "<br><a href=\""
                                  + activity->object()->getId()
                                  + "\">-GROUP ID-</a>");
#endif
        m_groupInfoLabel->setWordWrap(true);
        m_groupInfoLabel->setFont(detailsFont);
        m_groupInfoLabel->setAlignment(Qt::AlignCenter);
        m_rightColumnLayout->addWidget(m_groupInfoLabel);
    }


    // Widget to download the attached media, if any
    if (!attachmentUrl.isEmpty())
    {
        m_downloadWidget = new DownloadWidget(attachmentUrl,
                                              m_pumpController,
                                              this);
        m_haveDownloadWidget = true;

        m_rightColumnLayout->addWidget(m_downloadWidget, 0);
    }


    // FIXME: button creation should be moved here
    m_rightColumnLayout->addLayout(m_buttonsLayout, 0);


    // Show which filtering rules caused this post to be highlighted
    const QVariantMap filterMatches = activity->getFilterMatches();
    if (filterMatches.value("filterAction").toInt() == FilterChecker::Highlight)
    {
        m_filterMatchesWidget = new FilterMatchesWidget(filterMatches, this);
        m_rightColumnLayout->addWidget(m_filterMatchesWidget);
        // FIXME -- TODO: make this widget optional + way to hide it later
    }


    ///////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////// Comments block
    m_commenterBlock = new CommenterBlock(m_pumpController,
                                          m_globalObject,
                                          m_postId,
                                          m_postAuthorId,
                                          m_standalone,
                                          this);
    connect(m_commenterBlock, &CommenterBlock::commentSent,
            this, &Post::sendComment);
    connect(m_commenterBlock, &CommenterBlock::commentUpdated,
            this, &Post::updateComment);
    connect(m_commenterBlock, &CommenterBlock::allCommentsRequested,
            this, &Post::getAllComments);

    m_rightColumnLayout->addWidget(m_commenterBlock, 0);
    m_rightColumnLayout->addStretch(0); // Push other stuff to the top if there's extra space

    m_rightColumnFrame->setLayout(m_rightColumnLayout);

    if (m_standalone)
    {
        // Set link to "check for comments"
        m_commenterBlock->updateShowAllLink();

        this->updateBestCommentsUrl();
        // If we still don't have a valid URL from our host, proxyURL or not..
        if (!this->canGetAllComments())
        {
            m_commenterBlock->disableShowAllLink();
        }
    }

    m_mainLayout = new QHBoxLayout();
    m_mainLayout->setContentsMargins(0, 0, 0, 0);   // Minimal margins
    m_mainLayout->setSpacing(1);
    m_mainLayout->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    m_mainLayout->addWidget(m_leftColumnFrame,  3);  // stretch 3/17
    m_mainLayout->addWidget(m_rightColumnFrame, 14); // stretch 14/17

    if (activity->isShared())
    {
        if (m_globalObject->getPostExtendedShares())
        {
            // Add a vertical hint on left side, a line and a recycling symbol
            m_shareHintLabel = new QLabel(QString::fromUtf8("\342\231\272"),
                                          this);
            m_shareHintLabel->setToolTip(m_shareInfoLabel->toolTip());
            m_shareHintLabel->setAlignment(Qt::AlignBottom);
            m_shareHintLabel->setFrameStyle(QFrame::VLine);
            m_mainLayout->insertWidget(0, m_shareHintLabel);
        }

        m_outerLayout = new QVBoxLayout();
        m_outerLayout->setContentsMargins(0, 0, 0, 0);
        m_outerLayout->setSpacing(0);
        m_outerLayout->setAlignment(Qt::AlignTop);
        m_outerLayout->addWidget(m_shareInfoLabel, 0); // Ensure it's small
        m_outerLayout->addLayout(m_mainLayout,     10);
        this->setLayout(m_outerLayout);
    }
    else
    {
        this->setLayout(m_mainLayout);
    }


    // Set read status
    this->setPostUnreadStatus();

    // Set timestamps; Doing this here, after CommenterBlock has been initialized too
    this->setFuzzyTimestamps();


    m_resizeTimer = new QTimer(this);
    m_resizeTimer->setSingleShot(true);
    connect(m_resizeTimer, &QTimer::timeout,
            this, &Post::delayedResize);


    this->updateDataFromActivity(activity);

    qDebug() << "Post created" << m_postId;
}



Post::~Post()
{
    qDebug() << "Post destroyed" << m_postId;
}



/*
 * Fill in or replace data such as audience, generator,
 * or object data from activity data
 *
 */
void Post::updateDataFromActivity(ASActivity *activity)
{
    qDebug() << "Updating Post() data from activity...";

    // set To/CC..... TODO

    this->updateDataFromObject(activity->object());
}

/*
 * Fill in or replace data such as timestamps, title, contents,
 * number of likes and shares, etc, from object data
 *
 */
void Post::updateDataFromObject(ASObject *object)
{
    qDebug() << "Updating Post() data from object...";


    //////////////////////////////////////////////////////////////// Metadata
    // Post update time, if any
    m_timeUpdatedAtString = object->getUpdatedAt();
    if (m_timeUpdatedAtString == m_timeCreatedAtString
     || !object->getDeletedTime().isEmpty()) // Edited time useless when deleted
    {
        m_timeUpdatedAtString.clear();
    }

    QString postCreatedAtExtendedText = tr("Posted on %1", "1=Date")
                                        .arg(Timestamp::localTimeDate(m_timeCreatedAtString));
    QString dateGeneratorTooltip = tr("Type",
                                      "As in: type of object") + ": "
                                   + object->getTranslatedType(m_postType)
                                   + "\n"
                                   + postCreatedAtExtendedText;

    if (!m_generatorString.isEmpty())
    {
        dateGeneratorTooltip.append("\n" + tr("Using %1", "1=Program used for "
                                              "posting or sharing")
                                           .arg(m_generatorString));
    }
    if (!m_timeUpdatedAtString.isEmpty())
    {
        // Using \n instead of <br> because I don't want this tooltip to be HTML (wrapped)
        QString modifiedOn = tr("Modified on %1")
                             .arg(Timestamp::localTimeDate(m_timeUpdatedAtString));
        postCreatedAtExtendedText.append("<br>"
                                         + modifiedOn);
        dateGeneratorTooltip.append("\n\n"
                                    + modifiedOn);
    }

    m_timestampLabel->setExtendedText(postCreatedAtExtendedText);
    m_timestampLabel->setToolTip(dateGeneratorTooltip);
    this->setFuzzyTimestamps();


    // Post title, also used in openClickedUrl(), editPost() etc
    m_postTitle = object->getTitle();
    if (!m_postTitle.isEmpty())
    {
        m_titleLabel->setText(m_postTitle);
        m_titleLabel->show();
    }
    else
    {
        m_titleLabel->hide();
    }

    // Raw HTML of main content
    m_postOriginalText = object->getContent(); // Save it for later...
    QStringList srcImageList = MiscHelpers::htmlWithReplacedImages(m_postOriginalText,
                                                                   m_bodyTextBrowser->width());
    srcImageList.removeFirst(); // First is the HTML itself

    qDebug() << "Post has" << srcImageList.size() << " <img src> images included...";

    m_pendingImagesList.append(srcImageList);
    this->getPendingImages();


    // Location information
    const QString location = object->getLocationName();
    if (!location.isEmpty())
    {
        m_locationLabel->setBaseText(tr("In")
                                     + ": <b>" + location + "</b>");
        m_locationLabel->setExtendedText(object->getLocationTooltip());
        m_locationLabel->setToolTip("<font size=\"+6\">"      // Position indicator symbol
                                    + QString::fromUtf8("\342\214\226 ")
                                    + "</font>"
                                    + object->getLocationTooltip());
        m_locationLabel->show();
    }
    else
    {
        m_locationLabel->hide();
    }

    if (m_haveDownloadWidget)
    {
        const QString newName = MiscHelpers::getSuggestedFilename(m_postAuthorId,
                                                                  m_postType,
                                                                  m_postTitle,
                                                                  m_postAttachmentPureUrl,
                                                                  m_postFileMimeType);
        m_downloadWidget->updateSuggestedFilename(newName);
    }


    ///////////////////////////////// Likes - Comments - Shares information

    /* FIXME: this needs some serious work.
     *
     * Separation between what's initially set and what can be
     * updated later from minor feed information
     *
    */

    // Set the initial likes, comments and shares (4 most recent)
    int likesCount = object->getLikesCount();
    if (likesCount > 0)
    {
        m_likesCount = likesCount;
        this->setLikesLabel(m_likesCount);
        this->setLikes(object->getLastLikesList(),
                       m_likesCount);
    }


    int commentsCount = object->getCommentsCount();
    if (commentsCount > 0
     && !m_standalone) // Don't set initial comments in standalone mode
    {                  // It's unnecessary and messes up comment area size
        this->setCommentsLabel(commentsCount);
        m_commenterBlock->setComments(object->getLastCommentsList(),
                                      commentsCount);
    }

    const int sharesCount = object->getSharesCount();
    this->setSharesLabel(sharesCount);
    this->setShares(object->getLastSharesList(),
                    sharesCount);


    // If there's a "deleted" key, show minimalistic version of the post indicating it
    const QString postDeletedTime = object->getDeletedOnString();
    if (!postDeletedTime.isEmpty())
    {
        this->setPostDeleted(postDeletedTime);

        // If the setting to show deleted posts if off, hide
        if (!m_globalObject->getShowDeleted())
        {
            // FIXME: this also hides posts that were not deleted when added to the timeline
            this->hide();
        }
    }

    this->setPostContents();
}



void Post::updateCommentFromObject(ASObject *object)
{
    m_commenterBlock->updateCommentFromObject(object);
}


void Post::setCommentDeletedFromObject(ASObject *object)
{
    m_commenterBlock->setCommentDeletedFromObject(object);
}





/*
 * Set the post contents, and trigger a vertical resize
 *
 */
void Post::setPostContents()
{
    //qDebug() << "Post::setPostContents()" << this->postId;
    QString postMediaHtml;

    // Embedded image
    int imageWidth;
    if (!m_postImageUrl.isEmpty())
    {
        QString imageCachedFilename = MiscHelpers::getCachedImageFilename(m_postImageUrl);

        if (QFile::exists(imageCachedFilename))
        {
            // If the image is wider than the post space, make it smaller
            imageWidth = qMin(MiscHelpers::getImageWidth(imageCachedFilename),
                              m_postWidth);

            m_postImageIsAnimated = MiscHelpers::isImageAnimated(imageCachedFilename);

            QString belowMessage;
            if (m_postImageIsAnimated)
            {
                belowMessage = "<big>"
                               + QString::fromUtf8("\342\226\266") // Play-like symbol
                               + " </big> "
                               + tr("Image is animated. Click on it to play.")
                               + " <big> "
                               + QString::fromUtf8("\342\232\231") // Gear symbol
                               + "</big>";
            }
            else
            {
                belowMessage = m_seeFullImageString;
            }

            const QString resolution = tr("Size",
                                          "Image size (resolution)") + ": "
                                       + MiscHelpers::resolutionString(m_postImageSize.width(),
                                                                       m_postImageSize.height());

            postMediaHtml = MiscHelpers::mediaHtmlBase(m_postType,
                                                       imageCachedFilename,
                                                       resolution,
                                                       belowMessage,
                                                       imageWidth);
        }
        else // use placeholder image while it loads, or if it failed...
        {
            QString imageLoadingString;
            QString placeholderFilename;
            if (m_postImageFailed)
            {
                imageLoadingString = tr("Couldn't load image!");
                placeholderFilename = QStringLiteral("image-missing.png");
            }
            else
            {
                imageLoadingString = tr("Loading image...");
                placeholderFilename = QStringLiteral("image-loading.png");
            }
            postMediaHtml = "<br>"
                            "<div align=center>"
                            "<img src=\":/images/" + placeholderFilename + "\" />"
                            "<br><br><b>" + imageLoadingString + "</b>"
                            "</div>"
                            "<hr><br>";
        }
    }

    // Embedded audio
    if (!m_postAudioUrl.isEmpty())
    {
        postMediaHtml = MiscHelpers::mediaHtmlBase(m_postType,
                                                   ":/images/attached-audio.png",
                                                   m_downloadAttachmentString,
                                                   tr("Attached Audio"));
    }


    // Embedded video
    if (!m_postVideoUrl.isEmpty())
    {
        postMediaHtml = MiscHelpers::mediaHtmlBase(m_postType,
                                                   ":/images/attached-video.png",
                                                   m_downloadAttachmentString,
                                                   tr("Attached Video"));
    }


    // Attached file
    if (!m_postFileUrl.isEmpty())
    {
        QByteArray iconDataUri;

        QMimeDatabase mimeDb;
        QMimeType mimeType = mimeDb.mimeTypeForName(m_postFileMimeType);
        const QString mimeIcon = mimeType.iconName();

        if (QIcon::hasThemeIcon(mimeIcon)) // Insert icon from theme as HTML data
        {
            QPixmap mimePixmap = QIcon::fromTheme(mimeIcon).pixmap(128, 128);
            QBuffer mimeBuffer;
            mimeBuffer.open(QIODevice::WriteOnly);
            mimePixmap.save(&mimeBuffer, "PNG");

            // Compose a "Data URI" with the icon-as-png data
            iconDataUri = "data:image/png;base64,"
                        + mimeBuffer.buffer().toBase64();

            mimeBuffer.close();
        }
        else
        {
            iconDataUri = ":/images/attached-file.png";
        }

        postMediaHtml = MiscHelpers::mediaHtmlBase(m_postType,
                                                   iconDataUri,
                                                   m_downloadAttachmentString,
                                                   tr("Attached File")
                                                   + QString(": %1 (%2)")
                                                     .arg(mimeType.comment(),
                                                          m_postFileMimeType));
    }



    // Text contents
    QString postTextContents = MiscHelpers::htmlWithReplacedImages(m_postOriginalText,
                                                                   m_postWidth)
                                           .takeFirst();

    // Add the text content of the post
    m_bodyTextBrowser->setHtml(postMediaHtml + postTextContents);

    this->setPostHeight();
}


void Post::onResizeOrShow()
{
    m_postWidth = qMax(m_bodyTextBrowser->width() - 80, // FIXME: use viewport?
                       50);                             // Minimum width of 50

    this->setPostContents();


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



/*
 * Set the height of the post, based on the contents
 *
 */
void Post::setPostHeight()
{
    // Minimum height depends on configured avatar size
    int minHeight = qMax(m_globalObject->getPostAvatarSize().height(),
                         64); // Absolute minimum of 64, even if avatar is 32x32

    // Unless the post is deleted, in which case it can be smaller
    if (m_postIsDeleted)
    {
        minHeight = 20;
    }

    int height = qMax(m_bodyTextBrowser->document()->size().toSize().height()
                      + 12,        // +12px error margin
                      minHeight);

    // Don't allow a post to be too tall, either
    height = qMin(height, m_globalObject->getTimelineHeight());

    if (m_standalone)
    {
        m_bodyTextBrowser->setMinimumHeight(minHeight);
    }
    else    // Only force a specific height if the post is NOT standalone
    {
        m_bodyTextBrowser->setMinimumHeight(height);
        m_bodyTextBrowser->setMaximumHeight(height);
    }
}



/*
 * Download post image, and img-tag-based images parsed from the contents
 *
 */
void Post::getPendingImages()
{
    if (!m_pendingImagesList.isEmpty())
    {
        foreach (const QString imageUrl, m_pendingImagesList)
        {
            m_pumpController->enqueueImageForDownload(imageUrl);
        }

        connect(m_pumpController, &PumpController::imageStored,
                this, &Post::redrawImages);
        connect(m_pumpController, &PumpController::imageFailed,
                this, &Post::onImageFailed);
    }
}



/*
 * Return the likes/favorites URL for this post
 *
 */
QString Post::likesUrl()
{
    return m_likesUrl;
}


/*
 * Update the tooltip/extended info in "%NUMBER likes" with the names
 * of the people who liked the post
 *
 */
void Post::setLikes(QVariantList likesList, int likesCount)
{
    if (likesList.size() > 0)
    {
        m_likesMap.clear();
        m_likesMap = ASObject::simplePersonMapFromList(likesList);

        this->refreshLikesInfo(likesList.size(), likesCount);
    }

    // TMP/FIXME this can set the number to lower than initially set
    // if called with the "initial" up-to-4 likes list that comes with the post
    if (likesCount != -1)
    {
        m_likesCount = likesCount; // Use the passed likesCount parameter
    }
    else
    {
        m_likesCount = likesList.size(); // Show size of actual names list
    }

    this->setLikesLabel(m_likesCount);
}


void Post::appendLike(QString actorId, QString actorName, QString actorUrl)
{
    if (!m_likesMap.contains(actorId))
    {
        ASObject::addOnePersonToSimpleMap(actorId, actorName, actorUrl,
                                          &m_likesMap);
        ++m_likesCount;

        this->refreshLikesInfo(m_likesCount, m_likesCount); // FIXME?
        this->setLikesLabel(m_likesCount);
    }
}

void Post::removeLike(QString actorId)
{
    if (m_likesMap.contains(actorId))
    {
        m_likesMap.remove(actorId);
        --m_likesCount;

        this->refreshLikesInfo(m_likesCount, m_likesCount); // FIXME?
        this->setLikesLabel(m_likesCount);
    }
}

void Post::refreshLikesInfo(int likesListSize, int likesCount)
{
    QString peopleString = ASObject::personStringFromSimpleMap(m_likesMap,
                                                               likesCount);
    QString likesString;
    if (likesListSize == 1)
    {
        likesString = tr("%1 likes this",
                         "One person").arg(peopleString);
    }
    else
    {
        likesString = tr("%1 like this",
                         "More than one person").arg(peopleString);
    }

    m_likesCountLabel->setExtendedText(likesString);
    m_likesCountLabel->setToolTip("<b></b>" // Turn the tooltip into rich text
                                  + likesString);
}


/*
 * Update the "$NUMBER likes" label in left side
 *
 */
void Post::setLikesLabel(int likesCount)
{
    if (likesCount > 0)
    {
        if (likesCount == 1)
        {
            m_likesCountLabel->setBaseText(QString::fromUtf8("\342\231\245 ") // Heart symbol
                                           + tr("1 like"));
        }
        else
        {
            m_likesCountLabel->setBaseText(QString::fromUtf8("\342\231\245 ") // Heart symbol
                                           + tr("%1 likes").arg(likesCount));
        }
        m_likesCountLabel->show();
    }
    else
    {
        m_likesCountLabel->clear();
        m_likesCountLabel->hide();
    }
}




/*
 * Return the comments URL for this post
 *
 */
QString Post::commentsUrl()
{
    return m_commentsUrl;
}


/*
 * Ask the Commenter to set new comments
 *
 */
void Post::setComments(QVariantList commentsList)
{
    const int commentCount = commentsList.size();

    m_commenterBlock->setComments(commentsList, commentCount);
    m_commenterBlock->adjustCommentArea();


    // Update number of comments in left side counter
    this->setCommentsLabel(commentCount);
}


void Post::appendComment(ASObject *comment)
{
    m_commenterBlock->appendComment(comment,
                                    true);   // Just this one

    m_commenterBlock->adjustCommentsHeight();
    m_commenterBlock->adjustCommentArea();

    this->setCommentsLabel(m_commenterBlock->getCommentCount());
}


/*
 * Update the "NUMBER comments" label in left side
 *
 */
void Post::setCommentsLabel(int commentsCount)
{
    if (commentsCount != 0)
    {
        QString countString = QString::fromUtf8("\342\234\215 "); // writing hand
        if (commentsCount == 1)
        {
            countString.append(tr("1 comment"));

        }
        else
        {
            countString.append(tr("%1 comments").arg(commentsCount));
        }
        m_commentsCountLabel->setText(countString);
        m_commentsCountLabel->show();
    }
    else
    {
        m_commentsCountLabel->clear();
        m_commentsCountLabel->hide();
    }
}


void Post::updateBestCommentsUrl()
{
    // If comments URL is in another server and not proxyed...
    if (!m_pumpController->urlIsInOurHost(m_commentsUrl))
    {
        // Try to find the post in the 'ever seen' list
        const QString newCommentsUrl = m_pumpController->commentsUrlForPost(m_postId);

        if (!newCommentsUrl.isEmpty())
        {
            qDebug() << "Found matching post; Updating comments URL...";
            m_commentsUrl = newCommentsUrl;
        }
    }
}



/*
 * Return the shares URL for this post,
 * list of people who reshared it
 *
 */
QString Post::sharesUrl()
{
    return m_sharesUrl;
}


/*
 * Update the tooltip for "%NUMBER shares" with names
 *
 */
void Post::setShares(QVariantList sharesList, int sharesCount)
{
    if (sharesList.size() > 0)
    {
        QString peopleString = ASObject::personStringFromList(sharesList,
                                                              sharesCount);

        QString sharesString;
        if (sharesList.size() == 1)
        {
            sharesString = tr("%1 shared this",
                              "%1 = One person name").arg(peopleString);
        }
        else
        {
            sharesString = tr("%1 shared this",
                              "%1 = Names for more than one person").arg(peopleString);
        }

        m_sharesCountLabel->setExtendedText(sharesString);
        m_sharesCountLabel->setToolTip("<b></b>" // So tooltip is rich text, wordwrapped
                                       + sharesString);
    }

    // TMP/FIXME this can set the number to lower than initially set
    // if called with the "initial" up-to-4 shares list that comes with the post
    if (sharesCount != -1)
    {
        this->setSharesLabel(sharesCount); // use the passed sharesCount parameter
    }
    else
    {
        this->setSharesLabel(sharesList.size()); // show size of actual names list
    }
}


void Post::setSharesLabel(int resharesCount)
{
    if (resharesCount != 0)
    {
        if (resharesCount == 1)
        {
            m_sharesCountLabel->setBaseText(QString::fromUtf8("\342\231\273 ") // Recycle symbol
                                            + tr("Shared once"));
        }
        else
        {
            m_sharesCountLabel->setBaseText(QString::fromUtf8("\342\231\273 ") // Recycle symbol
                                            + tr("Shared %1 times").arg(resharesCount));
        }

        m_sharesCountLabel->show();
    }
    else
    {
        m_sharesCountLabel->clear();
        m_sharesCountLabel->hide();
    }
}



/*
 * Set or unset the visual hint indicating if the post is unread
 *
 */
void Post::setPostUnreadStatus()
{
    if (!QColor::isValidColor(m_unreadPostColor))
    {
        m_unreadPostColor = "palette(highlight)";
    }

    // CSS for horizontal gradient from configured color to transparent
    const QString css = QString("QFrame#RightFrame "
                                "{ background-color: "
                                "  qlineargradient(spread:pad, "
                                "  x1:0, y1:0, x2:1, y2:0,  "
                                "  stop:0 rgba(0, 0, 0, 0), "
                                "  stop:1 %1); "
                                "}").arg(m_unreadPostColor);

    if (m_postIsUnread)
    {
        m_rightColumnFrame->setStyleSheet(css);
        m_timestampLabel->setHighlighted(true);
    }
    else
    {
        m_rightColumnFrame->setStyleSheet(QString());
        m_timestampLabel->setHighlighted(false);
    }

    // Avoid flickering effect later
    m_timestampLabel->repaint();
}


void Post::setPostAsNew()
{
    m_postIsUnread = true;
    setPostUnreadStatus();
}


void Post::setPostAsRead(bool informTimeline)
{
    if (m_postIsUnread)
    {
        m_postIsUnread = false;

        if (informTimeline)
        {
            // Inform the Timeline()
            if (m_highlightType == NoHighlight)
            {
                emit postRead(false);
            }
            else
            {
                emit postRead(true); // Say it's marked as read, and was highlighted
            }
        }

        setPostUnreadStatus();
    }
}


void Post::setPostDeleted(QString postDeletedTime)
{
    if (m_postIsDeleted)
    {
        // Already set as deleted, ignore
        return;
    }

    if (!m_postOriginalText.isEmpty())
    {
        m_postOriginalText.prepend("<hr />"); // --------
    }
    m_postOriginalText.prepend("<center>" + postDeletedTime + "</center>");
    qDebug() << "This post was deleted on" << postDeletedTime;

    // Cancel a possible comment being composed
    // Otherwise this disabled post would block timeline updates forever
    if (this->isBeingCommented())
    {
        m_commenterBlock->setMinimumMode();
    }

    m_postIsDeleted = true;
    this->setDisabled(true);
    this->setPostContents();
}



int Post::getHighlightType()
{
    return m_highlightType;
}

QString Post::getActivityId()
{
    return m_activityId;
}

QString Post::getObjectId()
{
    return m_postId;
}


void Post::setFuzzyTimestamps()
{
    QString fuzzyTimestamps = Timestamp::fuzzyTime(m_timeCreatedAtString);

    if (!m_timeUpdatedAtString.isEmpty())
    {
        fuzzyTimestamps.append(QStringLiteral("<br>")
                               + tr("Edited: %1")
                                 .arg(Timestamp::fuzzyTime(m_timeUpdatedAtString)));
    }

    m_timestampLabel->setBaseText(fuzzyTimestamps);

    // Update "share time" on share info too!
    if (!m_postShareInfoString.isEmpty())
    {
        QString shareLabelString = m_postShareInfoString;
        shareLabelString.append(Timestamp::fuzzyTime(m_postShareTime));
        shareLabelString.append(m_postSharedToCcString);
        shareLabelString.append(QString::fromUtf8(" &nbsp; &nbsp; "
                                                  "\342\231\272")); // Recycle symbol, again
        m_shareInfoLabel->setText(shareLabelString);
    }

    m_commenterBlock->updateFuzzyTimestamps();
}


void Post::syncAvatarFollowState()
{
    m_postAuthorAvatarButton->syncFollowState();

    // Ask CommenterBlock to update its avatars too
    m_commenterBlock->updateAvatarFollowStates();
}


bool Post::isBeingCommented()
{
    if (m_commenterBlock->isFullMode())
    {
        return true;
    }

    return false;
}


bool Post::isNew()
{
    return m_postIsUnread;
}



////////////////////////////////////////////////////////////////////////////
////////////////////////////////// SLOTS ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////


/*
 * Like (favorite) a post
 *
 */
void Post::likePost(bool like)
{
    qDebug() << "Post::likePost()" << (like ? "like" : "unlike");

    m_pumpController->likePost(m_postId,
                               m_postType,
                               m_postAuthorId,
                               like);

    this->fixLikeButton(like); // FIXME: this should wait until like is ack'd

    // FIXME: this shoud use an object ID
    connect(m_pumpController, &PumpController::likeSet,
            this, &Post::getAllLikes);


    // Clicking 'Like' doesn't generate a mousePressEvent, so mark as read here
    this->setPostAsRead();
}

/*
 * Set the right labels and tooltips to the like button, depending on its state
 *
 */
void Post::fixLikeButton(bool isLiked)
{
    if (isLiked)
    {
        m_likeButton->setToolTip("<b></b>"
                                 + tr("You like this"));
        m_likeButton->setText(tr("Unlike"));
    }
    else
    {
        m_likeButton->setToolTip("<b></b>"
                                 + tr("Like this post"));
        m_likeButton->setText(tr("Like"));
    }

    m_likeButton->setChecked(isLiked);
}


void Post::getAllLikes()
{
    disconnect(m_pumpController, &PumpController::likeSet,
               this, &Post::getAllLikes);

    m_pumpController->getPostLikes(m_likesUrl);
}


/*
 * Make the commenter widget visible, so user can type the comment
 *
 * If some text was selected, insert it as quoted text
 *
 */
void Post::commentOnPost()
{
    qDebug() << "Commenting on post" << m_postTitle << m_postId;

    QString initialText;

    QString selectedText = m_bodyTextBrowser->textCursor().selectedText();
    if (!selectedText.isEmpty())
    {
        // Selected text has literal < and >, so convert to HTML entities
        selectedText.replace("<", "&lt;");
        selectedText.replace(">", "&gt;");
        selectedText.prepend("[...] ");
        selectedText.append(" [...]");
        initialText = MiscHelpers::quotedText(m_postAuthorName,
                                              selectedText);
    }
    m_commenterBlock->setFullMode(initialText);

    // Show "Check for comments" link, if it wasn't present, in some form, already
    if (this->canGetAllComments()) // As long as we can actually get them
    {
        m_commenterBlock->updateShowAllLink();
    }


    emit commentingOnPost(m_commenterBlock);


    // Clicking 'Comment' doesn't generate a mousePressEvent, so mark as read here
    this->setPostAsRead();
}


/*
 * The actual sending of the comment to the Pump controller
 *
 */
void Post::sendComment(QString commentText)
{
    qDebug() << "About to publish this comment:" << commentText;

    m_pumpController->addComment(MiscHelpers::cleanupHtml(commentText),
                                 m_postId,
                                 m_postType);
}


void Post::updateComment(QString commentId, QString commentText)
{
    m_pumpController->updateComment(commentId,
                                    MiscHelpers::cleanupHtml(commentText),
                                    m_postId);
}


void Post::requestCommenterComments()
{
    if (this->canGetAllComments())
    {
        m_commenterBlock->requestAllComments();
    }
}


void Post::getAllComments()
{
    this->updateBestCommentsUrl();

        /* // Keeping this for the future
         * // to be used to copy over comments directly (TBD)

        // Try to find this same post in one of the major timelines
        qDebug() << "Looking for this post in the major timelines, for comments";

        MainWindow *mainWindow = (MainWindow *)this->globalObj->parent(); // Kinda risky
        bool postFoundOk = false;

        Post *alternatePost = mainWindow->findPostInTimelines(this->postId,
                                                              &postFoundOk);
        if (postFoundOk)
        {
            qDebug() << "Found the post; Updating comments Url...";
            this->postCommentsUrl = alternatePost->commentsUrl();
        }
        */


    m_pumpController->getPostComments(m_commentsUrl, m_postId);
}


/*
 * Set all comments received from signal, when post is a separate window,
 * and not handled by Timeline()
 *
 */
void Post::setAllComments(QVariantList commentsList, QString originatingPostUrl)
{
    QString originatingPostCleanUrl = originatingPostUrl.split("?").first();
    if (this->commentsUrl() == originatingPostCleanUrl)
    {
        this->setComments(commentsList);
    }
}


bool Post::canGetAllComments()
{
    return m_pumpController->urlIsInOurHost(m_commentsUrl);
}


/*
 * Share a post.
 *
 * TODO: Turn this into a custom dialog allowing to select
 *       custom recipients for the 'share' activity.
 *
 */
void Post::sharePost()
{
    // Clicking 'Share' doesn't generate a mousePressEvent, so mark as read here
    this->setPostAsRead();

    QString shareConfirmString;
    if (!m_postIsOwn)
    {
        shareConfirmString = tr("Do you want to share %1's post?")
                             .arg(m_postAuthorName);
    }
    else
    {
        shareConfirmString = tr("Are you sure you want to share "
                                "your own post?");
    }


    const int confirmation = QMessageBox::question(this, tr("Share post?"),
                                                   shareConfirmString
                                                   + "\n\n\n",
                                                   tr("&Yes, share it"),
                                                   tr("&No"),
                                                   QString(), 1, 1);
    if (confirmation == 0)
    {
        qDebug() << "Sharing this post:" << m_postId;
        m_pumpController->sharePost(m_postId, m_postType);
    }
    else
    {
        qDebug() << "Confirmation cancelled, not sharing";
    }
}

void Post::unsharePost()
{
    const int confirmation = QMessageBox::question(this,
                                                   tr("Unshare post?"),
                                                   tr("Do you want to unshare %1's post?")
                                                   .arg(m_postAuthorName),
                                                   tr("&Yes, unshare it"),
                                                   tr("&No"),
                                                   QString(), 1, 1);
    if (confirmation == 0)
    {
        qDebug() << "Unsharing this post:" << m_postId;
        m_pumpController->unsharePost(m_postId, m_postType);

        this->setDisabled(true); // Disable the widget, to let user know it's been unshared
    }
    else
    {
        qDebug() << "Confirmation cancelled, will not unshare";
    }
}


/*
 * Set the Publisher in editing mode with this post's contents
 *
 */
void Post::editPost()
{
    m_globalObject->editPost(m_postId,
                             m_postType,
                             m_postTitle,
                             m_postOriginalText);
    if (m_standalone)
    {
        this->close();
    }
}



/*
 * Delete a post
 *
 */
void Post::deletePost()
{
    int confirmation = QMessageBox::question(this,
                                             tr("WARNING: Delete post?"),
                                             tr("Are you sure you want to "
                                                "delete this post?"),
                                             tr("&Yes, delete it"), tr("&No"),
                                             QString(), 1, 1);
    if (confirmation == 0)
    {
        qDebug() << "Deleting post";
        m_pumpController->deletePost(m_postId, m_postType);

        const QString timeNow = QDateTime::currentDateTimeUtc()
                                          .toString(Qt::ISODate);
        this->setPostDeleted(ASObject::makeDeletedOnString(timeNow));
    }
    else
    {
        qDebug() << "Confirmation cancelled, not deleting the post";
    }
}


void Post::joinGroup()
{
    qDebug() << "Joining group " << m_postId << "via Post() button";
    m_pumpController->joinGroup(m_postId);

    m_joinLeaveButton->setDisabled(true); // FIXME; make it turn into "Leave", etc.
}



void Post::openClickedUrl(QUrl url)
{
    qDebug() << "Anchor URL clicked:" << url.toString();

    if (url.scheme() == "image") // Use our own viewer, or maybe a configured external viewer
    {
        qDebug() << "Opening this image in our own viewer...";
        const QString suggestedName = MiscHelpers::getSuggestedFilename(m_postAuthorId,
                                                                        m_postType,
                                                                        m_postTitle,
                                                                        m_postAttachmentPureUrl);

        ImageViewer *viewer = new ImageViewer(m_postFullImageUrl,
                                              m_postImageSize,
                                              m_postTitle,
                                              suggestedName,
                                              m_postImageIsAnimated,
                                              nullptr); // Independent window
        connect(m_pumpController, &PumpController::imageStored,
                viewer, &ImageViewer::reloadImage);
        connect(m_pumpController, &PumpController::imageFailed,
                viewer, &ImageViewer::onImageFailed);

        m_pumpController->enqueueImageForDownload(m_postFullImageUrl);

        viewer->show();
    }
    else if (url.scheme() == "attachment")
    {
        m_downloadWidget->downloadAttachment();
    }
    else
    {
        qDebug() << "Opening this link in browser";
        MiscHelpers::openUrl(url, this);
    }
}



void Post::showHighlightedUrl(QString url)
{
    if (!url.isEmpty())
    {
        if (url.startsWith(QStringLiteral("image:/"))) // Own image:/ URL
        {
            const QString statusMessage = m_seeFullImageString
                                          + " ("
                                          + MiscHelpers::resolutionString(m_postImageSize.width(),
                                                                          m_postImageSize.height())
                                          + ")";

            m_pumpController->showTransientMessage(statusMessage);
        }
        else if (url.startsWith(QStringLiteral("attachment:/")))
        {
            m_pumpController->showTransientMessage(m_downloadAttachmentString);
        }
        else  // Normal URL
        {
            m_pumpController->showTransientMessage(url);
            qDebug() << "Highlighted url in post:" << url;
        }
    }
    else
    {
        m_pumpController->showTransientMessage(QString());
    }
}




void Post::openPostInBrowser()
{
    MiscHelpers::openUrl(m_postUrl, this);
}

void Post::copyPostUrlToClipboard()
{
    QApplication::clipboard()->setText(m_postUrl);
}

void Post::openParentPost()
{
    qDebug() << "Opening parent post..."
             << m_postParentMap.value("id").toString();

    // Create a fake activity for the parent post
    QVariantMap fakeActivityMap;
    fakeActivityMap.insert("object", m_postParentMap);
    fakeActivityMap.insert("actor",  m_postParentMap.value("author").toMap());

    ASActivity *fakeActivity = new ASActivity(fakeActivityMap,
                                              m_pumpController->currentUserId(),
                                              this);

    Post *parentPost = new Post(fakeActivity,
                                false, // Not highlighted
                                true,  // Post is standalone
                                m_pumpController,
                                m_globalObject,
                                nullptr); // Independent window
    parentPost->show();
    connect(m_pumpController, &PumpController::commentsReceived,
            parentPost, &Post::setAllComments);
    parentPost->requestCommenterComments();
}


/*
 * Normalize text colors; Used from menu when a post
 * has white text with white background, or similar
 *
 */
void Post::normalizeTextFormat()
{
    m_bodyTextBrowser->selectAll();

    // Set default text color for all text
    m_bodyTextBrowser->setTextColor(qApp->palette().windowText().color());
    m_bodyTextBrowser->setTextBackgroundColor(QColor(Qt::transparent));

    // Take care of background colors
    QTextBlockFormat blockFormat = m_bodyTextBrowser->textCursor().blockFormat();
    blockFormat.setBackground(QBrush());
    m_bodyTextBrowser->textCursor().setBlockFormat(blockFormat);

    // Select 'none'
    m_bodyTextBrowser->moveCursor(QTextCursor::Start);
    m_bodyTextBrowser->textCursor().select(QTextCursor::WordUnderCursor);
}



/*
 * Trigger a resizeEvent, which will call
 * setPostContents() and setPostHeight()
 *
 */
void Post::triggerResize()
{
    this->resize(this->width() - 1,
                 this->height() - 1);
}


void Post::delayedResize()
{
    this->onResizeOrShow();
}



/*
 * Redraw post contents after receiving downloaded images
 *
 */
void Post::redrawImages(QString imageUrl)
{
    if (m_pendingImagesList.contains(imageUrl))
    {
        m_pendingImagesList.removeAll(imageUrl);
        // If there are no more images for this post, disconnect
        if (m_pendingImagesList.isEmpty())
        {
            disconnect(m_pumpController, &PumpController::imageStored,
                       this, &Post::redrawImages);
            disconnect(m_pumpController, &PumpController::imageFailed,
                       this, &Post::onImageFailed);

            // Trigger resizeEvent(), with setPostContents(), etc.
            triggerResize();
        }
    }
}

void Post::onImageFailed(QString imageUrl)
{
    if (imageUrl == m_postImageUrl)
    {
        m_postImageFailed = true;
    }

    // Tentatively redraw anyway, in case other <img src> images failed
    this->redrawImages(imageUrl);
}



////////////////////////////////////////////////////////////////////////////
//////////////////////////////// PROTECTED /////////////////////////////////
////////////////////////////////////////////////////////////////////////////



void Post::resizeEvent(QResizeEvent *event)
{
    m_resizeTimer->stop();
    m_resizeTimer->start(200); // Schedule content resize

    event->accept();
}



/*
 * On mouse click in any part of the post, set it as read
 *
 */
void Post::mousePressEvent(QMouseEvent *event)
{
    setPostAsRead();

    event->accept();
}

void Post::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Escape)
    {
        event->accept();

        if (m_standalone)
        {
            this->close();
        }
    }
    else
    {
        event->ignore();
    }
}


/*
 * Ensure we change back the highlighted URL message when the mouse leaves the post
 *
 */
void Post::leaveEvent(QEvent *event)
{
    m_pumpController->showTransientMessage(QString());

    event->accept();
}

/*
 * closeEvent, needed when posts are opened in separate window
 *
 */
void Post::closeEvent(QCloseEvent *event)
{
    qDebug() << "Post::closeEvent()";
    if (m_standalone)
    {
        QSettings settings;
        if (settings.isWritable())
        {
            settings.setValue("Post/postSize", this->size());
        }
    }

    if (m_commenterBlock->isFullMode()
     && !m_globalObject->isProgramShuttingDown())
    {
        // Ask composer to cancel post, which asks the user (unless empty)
        m_commenterBlock->getComposer()->cancelPost();

        /*
         * TODO / FIXME: avoid asking for confirmation only when the program
         *               is shutting down due to environment's request
         */
    }

    event->ignore(); // Event will be ignored anyway


    // Check again; if it's still full, it means user cancelled
    if (!m_commenterBlock->isFullMode() // If not, user accepted, so kill the post
      || m_globalObject->isProgramShuttingDown()) // Kill it either way if shutting down
    {
        this->hide();
        this->deleteLater();
    }
}

/*
 * Needed when a post is shown as a standalone window,
 * to ensure images are properly resized
 *
 */
void Post::showEvent(QShowEvent *event)
{
    if (m_standalone)
    {
        this->onResizeOrShow();
    }

    event->accept();
}
