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

#include "post.h"

#include "mainwindow.h"

Post::Post(ASActivity *activity,
           bool highlightedByFilter,
           bool isStandalone,
           PumpController *pumpController,
           GlobalObject *globalObject,
           QWidget *parent)  :  QFrame(parent)
{
    this->pController = pumpController;
    this->globalObj = globalObject;
    this->standalone = isStandalone;

    this->seeFullImageString = tr("Click the image to see it in full size");
    this->downloadAttachmentString = tr("Click to download the attachment");
    this->postImageIsAnimated = false; // Initialize
    this->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


    leftColumnFrame = new QFrame(this);

    ///////////////////////////////////////////////// Highlighting
    this->highlightType = NoHighlight;
    QStringList highlightColors = globalObj->getColorsList();
    QString highlightPostColor;

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

    if (highlightType != NoHighlight
     && !standalone               // Don't use highlighting when opening as standalone post
     && !pController->currentUserId().isEmpty())
    {
        if (QColor::isValidColor(highlightPostColor))
        {
            // CSS for horizontal gradient from configured color to transparent
            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);

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

    this->unreadPostColor = highlightColors.at(5); // Color #6

    leftColumnLayout = new QVBoxLayout();


    this->activityId = activity->getId(); // FIXME: will be empty in Favorites TL
    this->postId = activity->object()->getId();
    this->postType = activity->object()->getType();
    this->postUrl = activity->object()->getUrl();
    this->postLikesCount = 0;

    // Store relevant likes/comments/shares URLs
    this->postLikesUrl = activity->object()->getLikesUrl();
    this->postCommentsUrl = activity->object()->getCommentsUrl();
    this->postSharesUrl = activity->object()->getSharesUrl();

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


    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();
    }
    this->postAuthorId = authorPerson->getId();
    this->postAuthorName = authorPerson->getNameWithFallback();


    this->postGeneratorString = activity->getGenerator();


    // If it's a standalone post, set window title and restore size
    if (standalone)
    {
        QString windowTitle = tr("Post", "Noun, not verb")
                              + ": "
                              + activity->object()->getTranslatedType(postType)
                              + " - Dianara";
        this->setWindowTitle(windowTitle);
        this->setWindowFlags(Qt::Dialog);
        this->setWindowModality(Qt::WindowModal);

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

    postIsUnread = false;
    postIsDeleted = 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
    postIsSharedLabel = new QLabel(this); // With parent to avoid leaks
                                          // Needs to be initialized even if
                                          // unused, for setFuzzyTimestamps()
    postIsSharedLabel->hide();
    if (activity->isShared())
    {
        postIsSharedLabel->show();
        this->postSharedById = activity->getSharedById();
        postShareTime = activity->getUpdatedAt();

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

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

            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;
        }

        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

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

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


        postIsSharedLabel->setText(postShareInfoString);
        postIsSharedLabel->setWordWrap(true);
        postIsSharedLabel->setOpenExternalLinks(true);
        postIsSharedLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
        postIsSharedLabel->setFont(shareInfoFont);
        postIsSharedLabel->setForegroundRole(QPalette::Text);
        postIsSharedLabel->setBackgroundRole(QPalette::Base);
        postIsSharedLabel->setAutoFillBackground(true);
        postIsSharedLabel->setFrameStyle(QFrame::Panel | QFrame::Raised);
        connect(postIsSharedLabel, SIGNAL(linkHovered(QString)),
                this, SLOT(showHighlightedUrl(QString)));


        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>"
                            + this->postSharedById
                            + "</i>"
                              "</td>"
                              "</tr>"
                              "</table>");


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

        postIsSharedLabel->setToolTip(shareTooltip);
    }


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

    // Different frame if post is ours
    if (postAuthorId == pController->currentUserId())
    {
        postIsOwn = true;
        qDebug() << "Post is our own!";

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


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

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

    // Open post in browser
    openPostInBrowserAction = new QAction(QIcon::fromTheme("internet-web-browser",
                                                           QIcon(":/images/button-download.png")),
                                          tr("Open post in web browser"),
                                          this);
    connect(openPostInBrowserAction, SIGNAL(triggered()),
            this, SLOT(openPostInBrowser()));
    postAuthorAvatarButton->addActionToMenu(openPostInBrowserAction);

    // Copy URL
    copyPostUrlAction = new QAction(QIcon::fromTheme("edit-copy",
                                                     QIcon(":/images/button-download.png")),
                                    tr("Copy post link to clipboard"),
                                    this);
    connect(copyPostUrlAction, SIGNAL(triggered()),
            this, SLOT(copyPostUrlToClipboard()));
    postAuthorAvatarButton->addActionToMenu(copyPostUrlAction);

    // Disable Open and Copy URL if there's no URL
    if (this->postUrl.isEmpty())
    {
        openPostInBrowserAction->setDisabled(true);
        copyPostUrlAction->setDisabled(true);
    }


    // -----
    postAuthorAvatarButton->addSeparatorToMenu();

    // Normalize text colors
    normalizeTextAction = new QAction(QIcon::fromTheme("format-text-color"),
                                      tr("Normalize text colors"),
                                      this);
    connect(normalizeTextAction, SIGNAL(triggered()),
            this, SLOT(normalizeTextFormat()));
    postAuthorAvatarButton->addActionToMenu(normalizeTextAction);


    if (standalone) // own window, add close option to menu
    {
        // -----
        postAuthorAvatarButton->addSeparatorToMenu();

        this->closeAction = new QAction(QIcon::fromTheme("window-close",
                                                         QIcon(":/images/button-close.png")),
                                        tr("&Close"),
                                        this);
        connect(closeAction, SIGNAL(triggered()),
                this, SLOT(close()));
        postAuthorAvatarButton->addActionToMenu(closeAction);
    }


    // End avatar menu

    leftColumnLayout->addWidget(postAuthorAvatarButton, 0, Qt::AlignLeft);


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

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


    leftColumnLayout->addWidget(postAuthorNameLabel);
    leftColumnLayout->addSpacing(1);


    // Post creation time
    postCreatedAtString = 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

    postCreatedAtLabel = new HClabel("", this);
    postCreatedAtLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    postCreatedAtLabel->setFont(detailsFont);
    postCreatedAtLabel->setSizePolicy(QSizePolicy::Ignored,
                                      QSizePolicy::Expanding);
    leftColumnLayout->addWidget(postCreatedAtLabel);

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

    leftColumnLayout->addSpacing(4);


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


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

            leftColumnLayout->addWidget(postToLabel);
            connect(postToLabel, SIGNAL(linkHovered(QString)),
                    this, SLOT(showHighlightedUrl(QString)));
        }

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

            leftColumnLayout->addWidget(postCCLabel);
            connect(postCCLabel, SIGNAL(linkHovered(QString)),
                    this, SLOT(showHighlightedUrl(QString)));
        }
    }

    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
    postLikesCountLabel = new HClabel("", this);
    postLikesCountLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    postLikesCountLabel->setFont(detailsFont);
    postLikesCountLabel->setOpenExternalLinks(true);
    postLikesCountLabel->setSizePolicy(QSizePolicy::Ignored,
                                       QSizePolicy::Expanding);
    connect(postLikesCountLabel, SIGNAL(linkHovered(QString)),
            this, SLOT(showHighlightedUrl(QString)));
    leftColumnLayout->addWidget(postLikesCountLabel);
    postLikesCountLabel->hide();


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


    postSharesCountLabel = new HClabel("", this);
    postSharesCountLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    postSharesCountLabel->setFont(detailsFont);
    postSharesCountLabel->setOpenExternalLinks(true);
    postSharesCountLabel->setSizePolicy(QSizePolicy::Ignored,
                                        QSizePolicy::Expanding);
    connect(postSharesCountLabel, SIGNAL(linkHovered(QString)),
            this, SLOT(showHighlightedUrl(QString)));
    leftColumnLayout->addWidget(postSharesCountLabel);
    postSharesCountLabel->hide();


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

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


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

    // If showing standalone, add a Close button; needed in some environments
    // Button disabled for now; added option to avatar menu
#if 0
    if (standalone)
    {
        this->closeButton = new QPushButton(QIcon::fromTheme("window-close",
                                                             QIcon(":/images/button-close.png")),
                                            tr("&Close"));
        closeButton->setFont(buttonsFont);
        closeButton->setSizePolicy(QSizePolicy::Ignored,
                                   QSizePolicy::Maximum);
        connect(closeButton, SIGNAL(clicked()),
                this, SLOT(close()));

        leftColumnLayout->addWidget(closeButton);
    }
#endif

    leftColumnFrame->setLayout(leftColumnLayout);


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

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

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

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


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


    ///////////////////////////////////// Post text
    postText = new QTextBrowser(this);
    postText->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    postText->setSizePolicy(QSizePolicy::MinimumExpanding,
                            QSizePolicy::MinimumExpanding);
    postText->setMinimumSize(10, 10);
    postText->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
    postText->setOpenLinks(false); // don't open links, manage in openClickedURL()
    postText->setReadOnly(true); // it's default with QTextBrowser, but still...

    QFont postContentsFont;
    postContentsFont.fromString(globalObj->getPostContentsFont());
    postText->setFont(postContentsFont);

    // To help screen readers, enable keyboard interaction here
    // This is disabled for now, since it doesn't really help; hopefully with Qt5
    // things will be better.
    //postText->setTextInteractionFlags(Qt::TextBrowserInteraction
    //                                  | Qt::TextSelectableByKeyboard);
    connect(postText, SIGNAL(anchorClicked(QUrl)),
            this, SLOT(openClickedURL(QUrl)));
    connect(postText, SIGNAL(highlighted(QString)),
            this, SLOT(showHighlightedUrl(QString)));
    rightColumnLayout->addWidget(postText, 1); // No alignment; makes size wrong when standalone


    // Buttons
    // Add like, comment, share and, if post is ours, edit and delete buttons
    buttonsLayout = new QHBoxLayout();
    buttonsLayout->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
    buttonsLayout->setContentsMargins(0, 0, 0, 0);
    buttonsLayout->setMargin(0);
    buttonsLayout->setSpacing(0);


    // Like button
    likeButton = new QPushButton(QIcon::fromTheme("emblem-favorite",
                                                  QIcon(":/images/button-like.png")),
                                 "*like*",
                                 this);
    likeButton->setCheckable(true);
    this->fixLikeButton(activity->object()->isLiked());
    likeButton->setFlat(true);
    likeButton->setFont(buttonsFont);
    connect(likeButton, SIGNAL(clicked(bool)),
            this, SLOT(likePost(bool)));
    buttonsLayout->addWidget(likeButton, 0, Qt::AlignLeft);

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


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

        buttonsLayout->addWidget(shareButton, 0, Qt::AlignLeft);
    }

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

    if (postIsOwn)
    {
        editButton = new QPushButton(QIcon::fromTheme("document-edit",
                                                      QIcon(":/images/button-edit.png")),
                                     tr("Edit"),
                                     this);
        editButton->setToolTip("<b></b>"
                               + tr("Modify this post"));
        editButton->setFlat(true);
        editButton->setFont(buttonsFont);
        connect(editButton, SIGNAL(clicked()),
                this, SLOT(editPost()));
        buttonsLayout->addWidget(editButton, 0, Qt::AlignRight);

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

        buttonsLayout->addWidget(deleteButton, 0, Qt::AlignRight);
    }



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

    this->pendingImagesList.clear(); // Will add the own "post image", and <img>-based ones

    // Get URL of post image, if it's "image" type of post
    if (this->postType == "image")
    {
        postSmallImageUrl = activity->object()->getSmallImageUrl();
        postFullImageUrl = activity->object()->getImageUrl();
        postAttachmentPureUrl = activity->object()->getAttachmentPureUrl();

        if (globalObj->getPostFullImages())
        {
            postImageUrl = postFullImageUrl;
        }
        else
        {
            postImageUrl = postSmallImageUrl;
        }

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

        // Get stored size
        this->postImageSize = QSize(activity->object()->getImageWidth(),
                                    activity->object()->getImageHeight());
    }


    QString attachmentUrl;
    // Get URL of post audio file, if it's "audio" type of post
    if (this->postType == "audio")
    {
        postAudioUrl = activity->object()->getAudioUrl();
        attachmentUrl = postAudioUrl;
        postAttachmentPureUrl = activity->object()->getAttachmentPureUrl();

        qDebug() << "we got AUDIO:" << postAudioUrl;
    }

    // Get URL of post video file, if it's "video" type of post
    if (this->postType == "video")
    {
        postVideoUrl = activity->object()->getVideoUrl();
        attachmentUrl = postVideoUrl;
        postAttachmentPureUrl = activity->object()->getAttachmentPureUrl();

        qDebug() << "we got VIDEO:" << postVideoUrl;
    }

    // Get URL of post general file, if it's "file" type of post
    if (this->postType == "file")
    {
        postFileUrl = activity->object()->getFileUrl();
        attachmentUrl = postFileUrl;
        postAttachmentPureUrl = activity->object()->getAttachmentPureUrl();

        postFileMimeType = activity->object()->getMimeType();
        qDebug() << "we got FILE:" << postFileUrl << "; type:" << postFileMimeType;
    }



    // Button to join/leave the group, if the post is the creation of a group
    if (postType == "group") // FIXME: for now, only JOIN, as we can't know if we're members yet
    {
        this->joinLeaveButton = new QPushButton(QIcon::fromTheme("user-group-new",
                                                                 QIcon(":/images/list-add.png")),
                                                tr("Join Group"),
                                                this);
        connect(joinLeaveButton, SIGNAL(clicked()),
                this, SLOT(joinGroup()));
        rightColumnLayout->addWidget(joinLeaveButton, 0, Qt::AlignCenter);

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


    // Widget to download the attached media, if any
    if (!attachmentUrl.isEmpty())
    {
        QString suggestedFilename = MiscHelpers::getSuggestedFilename(this->postAuthorId,
                                                                      this->postType,
                                                                      this->postTitle,
                                                                      this->postAttachmentPureUrl);
        this->downloadWidget = new DownloadWidget(attachmentUrl,
                                                  suggestedFilename,
                                                  this->pController,
                                                  this);
        rightColumnLayout->addWidget(downloadWidget, 0);
    }


    this->resizesCount = 0;
    // Post resizing takes place in resizeEvent()
    // which will also call setPostContents()


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


    ///////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////// Comments block
    this->commenter = new CommenterBlock(this->pController,
                                         this->globalObj,
                                         this->postAuthorId,
                                         this->standalone,
                                         this);
    connect(commenter, SIGNAL(commentSent(QString)),
            this, SLOT(sendComment(QString)));
    connect(commenter, SIGNAL(commentUpdated(QString,QString)),
            this, SLOT(updateComment(QString,QString)));
    connect(commenter, SIGNAL(allCommentsRequested()),
            this, SLOT(getAllComments()));
    rightColumnLayout->addWidget(commenter, 0);
    rightColumnLayout->addStretch(0); // Push other stuff to the top if there's extra space

    rightColumnFrame->setLayout(rightColumnLayout);

    if (standalone)
    {
        // Set link to "check for comments"
        this->commenter->updateShowAllLink();

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

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

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

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


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

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



    this->updateDataFromActivity(activity);

    qDebug() << "Post created" << this->postId;
}



Post::~Post()
{
    qDebug() << "Post destroyed" << this->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
    this->postUpdatedAtString = object->getUpdatedAt();
    if (this->postUpdatedAtString == this->postCreatedAtString
     || !object->getDeletedTime().isEmpty()) // Edited time useless when deleted
    {
        this->postUpdatedAtString.clear();
    }

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

    if (!this->postGeneratorString.isEmpty())
    {
        dateGeneratorTooltip.append("\n" + tr("Using %1", "1=Program used for "
                                              "posting or sharing")
                                           .arg(this->postGeneratorString));
    }
    if (!postUpdatedAtString.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(postUpdatedAtString));
        postCreatedAtExtendedText.append("<br>"
                                         + modifiedOn);
        dateGeneratorTooltip.append("\n\n"
                                    + modifiedOn);
    }

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


    // Save post title for openClickedURL(), editPost() etc
    this->postTitle = object->getTitle();
    if (!this->postTitle.isEmpty())
    {
        postTitleLabel->setText(postTitle);
        postTitleLabel->show();
    }
    else
    {
        postTitleLabel->hide();
    }

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

    qDebug() << "Post has" << postTextImageList.size() << "images included...";

    pendingImagesList.append(postTextImageList);
    this->getPendingImages();


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


    ///////////////////////////////// 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().toInt();
    if (likesCount > 0)
    {
        this->postLikesCount = likesCount;
        this->setLikesLabel(this->postLikesCount);
        this->setLikes(object->getLastLikesList(),
                       this->postLikesCount);
    }


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

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


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

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

    this->setPostContents();
}



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


void Post::setCommentDeletedFromObject(ASObject *object)
{
    this->commenter->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 (!this->postImageUrl.isEmpty())
    {
        QString imageCachedFilename = MiscHelpers::getCachedImageFilename(postImageUrl);

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

            this->postImageIsAnimated = MiscHelpers::isImageAnimated(imageCachedFilename);

            QString belowMessage;
            if (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 = this->seeFullImageString;
            }

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

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

    // Embedded audio
    if (!this->postAudioUrl.isEmpty())
    {
        postMediaHtml = MiscHelpers::mediaHtmlBase(this->postType,
                                                   ":/images/attached-audio.png",
                                                   this->downloadAttachmentString,
                                                   tr("Attached Audio"));
    }


    // Embedded video
    if (!this->postVideoUrl.isEmpty())
    {
        postMediaHtml = MiscHelpers::mediaHtmlBase(this->postType,
                                                   ":/images/attached-video.png",
                                                   this->downloadAttachmentString,
                                                   tr("Attached Video"));
    }


    // Attached file
    if (!this->postFileUrl.isEmpty())
    {
        postMediaHtml = MiscHelpers::mediaHtmlBase(this->postType,
                                                   ":/images/attached-file.png",
                                                   this->downloadAttachmentString,
                                                   tr("Attached File")
                                                   + ": " + postFileMimeType);
    }



    // Text contents
    QStringList postTextImageList = MiscHelpers::htmlWithReplacedImages(postOriginalText,
                                                                        this->postWidth);
    QString postTextContents = postTextImageList.takeAt(0);

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


    this->setPostHeight();
}


void Post::onResizeOrShow()
{
    this->postWidth = qMax(postText->width() - 80,   // FIXME: use viewport?
                           50);                      // minimum width of 50

    this->setPostContents();


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



/*
 * Set the height of the post, based on the contents
 *
 */
void Post::setPostHeight()
{
    // Minimum height depends on configured avatar size
    int minHeight = qMax(this->globalObj->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 (postIsDeleted)
    {
        minHeight = 20;
    }

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

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

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


void Post::resetResizesCount()
{
    this->resizesCount = 0;
}


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

        connect(pController, SIGNAL(imageStored(QString)),
                this, SLOT(redrawImages(QString)));
        connect(pController, SIGNAL(imageFailed(QString)),
                this, SLOT(onImageFailed(QString)));
    }
}




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


/*
 * 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)
    {
        this->postLikesMap.clear();
        this->postLikesMap = 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)
    {
        this->postLikesCount = likesCount; // use the passed likesCount parameter
    }
    else
    {
        this->postLikesCount = likesList.size(); // show size of actual names list
    }

    this->setLikesLabel(this->postLikesCount);
}


void Post::appendLike(QString actorId, QString actorName, QString actorUrl)
{
    if (!postLikesMap.contains(actorId))
    {
        ASObject::addOnePersonToSimpleMap(actorId, actorName, actorUrl,
                                          &this->postLikesMap);
        ++this->postLikesCount;

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

void Post::removeLike(QString actorId)
{
    if (postLikesMap.contains(actorId))
    {
        this->postLikesMap.remove(actorId);
        --this->postLikesCount;

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

void Post::refreshLikesInfo(int likesListSize, int likesCount)
{
    QString peopleString = ASObject::personStringFromSimpleMap(this->postLikesMap,
                                                               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);
    }

    this->postLikesCountLabel->setExtendedText(likesString);
    this->postLikesCountLabel->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)
        {
            postLikesCountLabel->setBaseText(QString::fromUtf8("\342\231\245 ") // Heart symbol
                                             + tr("1 like"));
        }
        else
        {
            postLikesCountLabel->setBaseText(QString::fromUtf8("\342\231\245 ") // heart symbol
                                             + tr("%1 likes").arg(likesCount));
        }
        postLikesCountLabel->show();
    }
    else
    {
        postLikesCountLabel->clear();
        postLikesCountLabel->hide();
    }
}




/*
 * Return the comments URL for this post
 *
 */
QString Post::commentsURL()
{
    return this->postCommentsUrl;
}


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

    this->commenter->setComments(commentsList, commentCount);
    this->commenter->adjustCommentArea();


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


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

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

    this->setCommentsLabel(this->commenter->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));
        }
        postCommentsCountLabel->setText(countString);
        postCommentsCountLabel->show();
    }
    else
    {
        postCommentsCountLabel->clear();
        postCommentsCountLabel->hide();
    }
}


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

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




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


/*
 * 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);
        }

        this->postSharesCountLabel->setExtendedText(sharesString);
        this->postSharesCountLabel->setToolTip("<b></b>" // So the 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)
        {
            postSharesCountLabel->setBaseText(QString::fromUtf8("\342\231\273 ") // Recycle symbol
                                              + tr("Shared once"));
        }
        else
        {
            postSharesCountLabel->setBaseText(QString::fromUtf8("\342\231\273 ") // Recycle symbol
                                              + tr("Shared %1 times").arg(resharesCount));
        }

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



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

    // CSS for horizontal gradient from configured color to transparent
    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(unreadPostColor);

    if (this->postIsUnread)
    {
        rightColumnFrame->setStyleSheet(css);
        postCreatedAtLabel->setHighlighted(true);
    }
    else
    {
        rightColumnFrame->setStyleSheet("");
        postCreatedAtLabel->setHighlighted(false);
    }

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


void Post::setPostAsNew()
{
    this->postIsUnread = true;
    setPostUnreadStatus();
}


void Post::setPostAsRead(bool informTimeline)
{
    if (postIsUnread)
    {
        this->postIsUnread = false;

        if (informTimeline)
        {
            // Inform the Timeline()
            if (this->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 (this->postIsDeleted)
    {
        // Already set as deleted, ignore
        return;
    }

    /// FIXME, this needs some work
    if (!this->postOriginalText.isEmpty())
    {
        this->postOriginalText.prepend("<hr>"); // --------
    }
    this->postOriginalText.prepend("<center>" + postDeletedTime +
                                   "</center>"); // kinda tmp...
    qDebug() << "This post was deleted on" << postDeletedTime;

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



int Post::getHighlightType()
{
    return this->highlightType;
}

QString Post::getActivityId()
{
    return this->activityId;
}

QString Post::getObjectId()
{
    return this->postId;
}


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

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

    this->postCreatedAtLabel->setBaseText(fuzzyTimestamps);

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

    commenter->updateFuzzyTimestamps();
}


void Post::syncAvatarFollowState()
{
    this->postAuthorAvatarButton->syncFollowState();

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


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

    return false;
}

bool Post::isNew()
{
    return this->postIsUnread;
}



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


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

    this->pController->likePost(this->postId, this->postType,
                                this->postAuthorId, like);

    this->fixLikeButton(like ? "true" : "false");

    connect(pController, SIGNAL(likeSet()),
            this, SLOT(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(QString state)
{
    if (state == "true")
    {
        likeButton->setToolTip("<b></b>"
                               + tr("You like this"));
        likeButton->setText(tr("Unlike"));
        likeButton->setChecked(true);
    }
    else
    {
        likeButton->setToolTip("<b></b>"
                               + tr("Like this post"));
        likeButton->setText(tr("Like"));
        likeButton->setChecked(false);
    }
}


void Post::getAllLikes()
{
    disconnect(pController, SIGNAL(likeSet()),
               this, SLOT(getAllLikes()));

    this->pController->getPostLikes(this->postLikesUrl);
}


/*
 * 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" << this->postTitle << this->postId;

    QString initialText;

    QString selectedText = this->postText->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(this->postAuthorName,
                                              selectedText);
    }
    this->commenter->setFullMode(initialText);

    // Show "Check for comments" link, if it wasn't present, in some form, already
    this->commenter->updateShowAllLink();


    emit commentingOnPost(this->commenter);


    // 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;

    this->pController->addComment(MiscHelpers::cleanupHtml(commentText),
                                  this->postId, this->postType);
}

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


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();
        }
        */


    this->pController->getPostComments(this->postCommentsUrl);
}


/*
 * 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);
    }
}


/*
 * 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();


    int confirmation = QMessageBox::question(this, tr("Share post?"),
                                             tr("Do you want to share %1's post?")
                                              .arg(this->postAuthorName)
                                             + "\n\n\n",
                                             tr("&Yes, share it"), tr("&No"),
                                             "", 1, 1);

    if (confirmation == 0)
    {
        qDebug() << "Sharing this post:" << this->postId;
        this->pController->sharePost(this->postId, this->postType);
    }
    else
    {
        qDebug() << "Confirmation cancelled, not sharing";
    }
}

void Post::unsharePost()
{
    int confirmation = QMessageBox::question(this, tr("Unshare post?"),
                                             tr("Do you want to unshare %1's post?")
                                              .arg(this->postAuthorName),
                                             tr("&Yes, unshare it"), tr("&No"),
                                             "", 1, 1);

    if (confirmation == 0)
    {
        qDebug() << "Unsharing this post:" << this->postId;
        this->pController->unsharePost(this->postId, this->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()
{
    this->globalObj->editPost(this->postId,
                              this->postType,
                              this->postTitle,
                              this->postOriginalText);
    if (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"),
                                             "", 1, 1);
    if (confirmation == 0)
    {
        qDebug() << "Deleting post";
        this->pController->deletePost(this->postId, this->postType);

        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 " << this->postId << "via Post() button";
    this->pController->joinGroup(this->postId);

    this->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...";
        QString suggestedFilename = MiscHelpers::getSuggestedFilename(this->postAuthorId,
                                                                      this->postType,
                                                                      this->postTitle,
                                                                      this->postAttachmentPureUrl);

        ImageViewer *viewer = new ImageViewer(this->postFullImageUrl,
                                              this->postImageSize,
                                              this->postTitle,
                                              suggestedFilename,
                                              this->postImageIsAnimated,
                                              this);
        connect(pController, SIGNAL(imageStored(QString)),
                viewer, SLOT(reloadImage(QString)));
        connect(pController, SIGNAL(imageFailed(QString)),
                viewer, SLOT(onImageFailed(QString)));

        this->pController->enqueueImageForDownload(this->postFullImageUrl);

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



void Post::showHighlightedUrl(QString url)
{
    if (!url.isEmpty())
    {
        if (url.startsWith("image:/")) // Own image:/ URL
        {
            QString statusMessage = this->seeFullImageString
                                    + " ("
                                    + MiscHelpers::resolutionString(postImageSize.width(),
                                                                    postImageSize.height())
                                    + ")";

            this->pController->showTransientMessage(statusMessage);
        }
        else if (url.startsWith("attachment:/"))
        {
            this->pController->showTransientMessage(this->downloadAttachmentString);
        }
        else  // Normal URL
        {
            this->pController->showTransientMessage(url);
            qDebug() << "Highlighted url in post:" << url;
        }
    }
    else
    {
        this->pController->showTransientMessage("");
    }
}




void Post::openPostInBrowser()
{
    QDesktopServices::openUrl(this->postUrl);
}

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

void Post::openParentPost()
{
    qDebug() << "Opening parent post...";

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

    ASActivity *originalPostActivity = new ASActivity(fakeActivityMap,
                                                      pController->currentUserId(),
                                                      this);

    Post *parentPost = new Post(originalPostActivity,
                                false, // Not highlighted
                                true,  // Post is standalone
                                pController,
                                globalObj,
                                this->parentWidget()); // Pass parent widget (Timeline or
                                                       // another Post) instead of
                                                       // 'this', so it won't be killed by reloads
    parentPost->show();
    connect(pController, SIGNAL(commentsReceived(QVariantList,QString)),
            parentPost, SLOT(setAllComments(QVariantList,QString)));
    parentPost->getAllComments();
}


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

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

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

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



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



/*
 * Redraw post contents after receiving downloaded images
 *
 */
void Post::redrawImages(QString imageUrl)
{
    if (pendingImagesList.contains(imageUrl))
    {
        this->pendingImagesList.removeAll(imageUrl);
        // If there are no more images for this post, disconnect
        if (pendingImagesList.isEmpty())
        {
            disconnect(pController, SIGNAL(imageStored(QString)),
                       this, SLOT(redrawImages(QString)));
            disconnect(pController, SIGNAL(imageFailed(QString)),
                       this, SLOT(onImageFailed(QString)));

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

void Post::onImageFailed(QString imageUrl)
{
    if (imageUrl == this->postImageUrl)
    {
        this->postImageFailed = true;
    }

    this->redrawImages(imageUrl);
}



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



void Post::resizeEvent(QResizeEvent *event)
{
    //qDebug() << "Post::resizeEvent()" << event->size();
    if (this->resizesCount > 10) // Don't resize more than 10 times in a row
    {
        qDebug() << "Post::resizeEvent(); Too many resizes, too fast; ignoring!";
        event->ignore();

        return;
    }

    this->onResizeOrShow();


    // Standalone mode doesn't need this kind of protection, and is not re-set by Timeline()
    if (!standalone)
    {
        ++this->resizesCount;
    }

    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 (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)
{
    this->pController->showTransientMessage("");

    event->accept();
}

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

    if (this->commenter->isFullMode())
    {
        // Ask composer to cancel post, which asks the user, unless empty
        this->commenter->getComposer()->cancelPost();
    }

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


    // Check again; if it's still full, it means user cancelled
    if (!this->commenter->isFullMode()) // If not, accepted, so kill the post
    {
        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 (standalone)
    {
        this->onResizeOrShow();
    }

    event->accept();
}
