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


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

    m_itemIsNew = false;
    m_haveTargetAvatar = false;

    // This sizePolicy prevents cut messages, and the huge space at the end
    // of the feed, after clicking "Older Activities" several times
    this->setSizePolicy(QSizePolicy::Minimum,
                        QSizePolicy::Preferred);

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

    // Store activity ID so MinorFeed can reconstruct "next" url's
    m_activityId = activity->getId();

    QFont mainFont;
    mainFont.setPointSize(mainFont.pointSize() - 2);

    QString authorId = activity->author()->getId();
    m_itemIsOwn = (authorId == m_pumpController->currentUserId());


    m_avatarButton = new AvatarButton(activity->author(),
                                      m_pumpController,
                                      m_globalObject,
                                      m_globalObject->getMfAvatarSize(),
                                      this);


    m_createdAtTimestamp = activity->getCreatedAt();
    QString timestampTooltip = "<b>"
                               + Timestamp::localTimeDate(m_createdAtTimestamp)
                               + "</b>";

    QString generator = activity->getGenerator();
    if (!generator.isEmpty())
    {
        timestampTooltip.append("<br>"
                                + tr("Using %1",
                                     "Application used to generate this activity")
                                  .arg(generator));
    }

    const QString toField = activity->getToString();
    const QString ccField = activity->getCcString();
    if (!toField.isEmpty() || !ccField.isEmpty())
    {
        timestampTooltip.append("<br>"); // Extra separation before To or Cc

        if (!toField.isEmpty())
        {
            timestampTooltip.append("<br>"
                                    + tr("To: %1",
                                         "1=people to whom this activity was sent")
                                      .arg(toField));
        }
        if (!ccField.isEmpty())
        {
            timestampTooltip.append("<br>"
                                    + tr("Cc: %1",
                                         "1=people to whom this activity was sent as CC")
                                      .arg(ccField));
        }
    }

    m_timestampLabel = new QLabel(this);
    mainFont.setBold(true);
    m_timestampLabel->setFont(mainFont);
    m_timestampLabel->setWordWrap(true);
    m_timestampLabel->setToolTip(timestampTooltip.trimmed());
    m_timestampLabel->setAlignment(Qt::AlignCenter);
    m_timestampLabel->setAutoFillBackground(true);
    m_timestampLabel->setForegroundRole(QPalette::Text);
    m_timestampLabel->setBackgroundRole(QPalette::Base);
    m_timestampLabel->setFrameStyle(QFrame::Panel | QFrame::Raised);
    this->setFuzzyTimeStamp();


    // The description itself, like "JaneDoe updated a note",
    m_activityDescriptionLabel = new QLabel(this); // To be filled later
    QFont descriptionFont;
    descriptionFont.fromString(m_globalObject->getMinorFeedFont());
    m_activityDescriptionLabel->setFont(descriptionFont);
    m_activityDescriptionLabel->setWordWrap(true);
    m_activityDescriptionLabel->setOpenExternalLinks(true);
    m_activityDescriptionLabel->setAlignment(Qt::AlignTop);
    m_activityDescriptionLabel->setSizePolicy(QSizePolicy::Ignored,
                                              QSizePolicy::MinimumExpanding);
    connect(m_activityDescriptionLabel, &QLabel::linkHovered,
            this, &MinorFeedItem::showUrlInfo);

    // Tooltip contents
    QString activityTooltip = activity->generateTooltip();
    if (!activityTooltip.isEmpty())
    {
        // Set the activity info as tooltip for description label
        m_activityDescriptionLabel->setToolTip(activityTooltip);

        // Set a different mouse cursor for the description label, as a hint to wait for tooltip info
        m_activityDescriptionLabel->setCursor(Qt::WhatsThisCursor);
    }


    m_originalObjectMap = activity->object()->getOriginalObject();

    m_inReplyToMap = activity->object()->getInReplyTo();
    QString inReplyToAuthorId = ASPerson::cleanupId(m_inReplyToMap
                                                    .value("author").toMap()
                                                    .value("id").toString());


    // Highlight this item if it's about the user, with different colors
    m_itemHighlightType = -1; // False

    // We are in the recipient list of the activity
    if (activity->getRecipientsIdList().contains(m_pumpController->currentUserId()))
    {
        m_itemHighlightType = 0;
    }

    // Activity is in reply to something made by us
    if (inReplyToAuthorId == m_pumpController->currentUserId())
    {
        m_itemHighlightType = 1;
    }

    // We are the object; someone added us, etc.
    if (activity->object()->getId() == m_pumpController->currentUserId())
    {
        m_itemHighlightType = 2;
    }

    // The object is ours; someone favorited our note, or something
    if (activity->object()->author()->getId() == m_pumpController->currentUserId())
    {
        m_itemHighlightType = 3;
    }

    //// Special case: highlighting the item because there's a filter rule for it
    if (highlightedByFilter && m_itemHighlightType == -1) // Only if there's no other reason for HL
    {
        m_itemHighlightType = 4;
    }


    if (m_itemHighlightType != -1)
    {
        // Unless the user ID is also empty!
        if (!m_pumpController->currentUserId().isEmpty())
        {
            QString highlightItemColor = m_globalObject->getColor(m_itemHighlightType);

            if (QColor::isValidColor(highlightItemColor)) // Valid color
            {
                // CSS for horizontal gradient from configured color to transparent
                QString css = QString("MinorFeedItem "
                                      "{ background-color: "
                                      "qlineargradient(spread:pad, "
                                      "x1:0, y1:0, x2:1, y2:0, "
                                      "stop:0 %1, stop:1 rgba(0, 0, 0, 0)); "
                                      "}")
                              .arg(highlightItemColor);

                this->setStyleSheet(css);
            }
            else // If there's no valid color, highlight with a border
            {
                this->setFrameStyle(QFrame::Panel);
            }
        }
    }


    // Set the activity description with optional snippet
    QString activityDescription = activity->getContent();


    // Add a snippet if configured to do so
    if (m_globalObject->getMinorFeedSnippetsType() != 3)    // 3=Never
    {
        if (m_globalObject->getMinorFeedSnippetsType() == 2 // 2=Always
         || m_itemHighlightType != -1)       // 0/1/2 and it's highlighted
        {
            if (m_globalObject->getMinorFeedSnippetsType() > 0  // Always or any highlighted
             || authorId != m_pumpController->currentUserId())   // or highlighted but not ours (0)
            {
                // FIXME 1.4.x: fix this spaghetti mess!!
                // TODO: option to not show when the object of the activity is ours
                // i.e. "JohnDoe liked a note", our note

                int snippetCharLimit;
                if (m_itemHighlightType == -1)
                {
                    snippetCharLimit = m_globalObject->getSnippetsCharLimit();
                }
                else
                {
                    snippetCharLimit = m_globalObject->getSnippetsCharLimitHl();
                }

                QString snippet = activity->generateSnippet(snippetCharLimit);
                if (!snippet.isEmpty())
                {
                    activityDescription.append(QStringLiteral("&nbsp;<hr>")
                                               + snippet);
                }
            }
        }
    }

    m_activityDescriptionLabel->setText(activityDescription);




    //////////////////////////////////////////////////////// Layout
    m_leftLayout = new QVBoxLayout();
    m_leftLayout->setAlignment(Qt::AlignTop);
    m_leftLayout->setContentsMargins(1, 0, 1, 0);
    m_leftLayout->setSpacing(1);
    m_leftLayout->addWidget(m_avatarButton, 0, Qt::AlignTop | Qt::AlignLeft);
    m_leftLayout->addStretch(0);



    // Original post available (inReplyTo) or object available (note, image...)
    if (!m_inReplyToMap.isEmpty()
        || (ASObject::canDisplayObject(activity->object()->getType())
            && activity->object()->getDeletedTime().isEmpty())
       )
    {
        m_openButton = new QPushButton(QStringLiteral("+"), this);
        m_openButton->setSizePolicy(QSizePolicy::Ignored,
                                    QSizePolicy::Maximum);
        m_openButton->setToolTip("<b></b>"
                                 + tr("Open referenced post"));
        connect(m_openButton, &QAbstractButton::clicked,
                this, &MinorFeedItem::openOriginalPost);
        m_leftLayout->addWidget(m_openButton, 0, Qt::AlignHCenter);
    }


    m_rightLowerLayout = new QHBoxLayout();
    m_rightLowerLayout->setContentsMargins(0, 0, 0, 0);
    m_rightLowerLayout->addWidget(m_activityDescriptionLabel, 1);
    // This may also contain an AvatarButton for an "object person"

    m_rightLayout = new QVBoxLayout();
    m_rightLayout->setAlignment(Qt::AlignTop);
    m_rightLayout->addWidget(m_timestampLabel);
    m_rightLayout->addLayout(m_rightLowerLayout);

    // If the object is a person, such as someone following someone else, add an AvatarButton for them
    if (activity->object()->getType() == QStringLiteral("person"))
    {
        ASPerson *personObject = activity->personObject();
        if (personObject->getId() != authorId) // Avoid cases like "JohnDoe updated JohnDoe"
        {
            m_haveTargetAvatar = true;
            m_targetAvatarButton = new AvatarButton(personObject,
                                                    m_pumpController,
                                                    m_globalObject,
                                                    m_globalObject->getMfAvatarSize(), // 28,28
                                                    this);
            m_rightLowerLayout->addWidget(m_targetAvatarButton, 0, Qt::AlignBottom);
        }
    }



    m_topLayout = new QHBoxLayout();
    m_topLayout->setContentsMargins(0, 0, 0, 0);

    int iconSpacing = 0;
    m_verbIconType = m_globalObject->getMfIconType();

    if (m_verbIconType != 0)
    {
        m_verbIcons = MiscHelpers::iconsForActivity(activity->getVerb());

        // Handle the different styles to prepare the paintEvent()
        m_verbIconSize = m_globalObject->getMfAvatarSize().width();
        m_verbIconSizeSubtle = m_verbIconSize * 1.4;

        if (m_verbIconType == 1 || m_verbIconType == 3)
        {
            iconSpacing = m_verbIconSize;
        }
        else
        {
            iconSpacing = m_verbIconSizeSubtle / 2;
        }
    }

    if (m_itemIsOwn) // For our items, text first
    {
        m_topLayout->addLayout(m_rightLayout, 20);

        if (m_verbIconType < 3) // 1 and 2 are icon-first (and this is reversed)
        {
            m_topLayout->addLayout(m_leftLayout, 1);
            m_topLayout->addSpacing(iconSpacing);
        }
        else                  // 3 and 4 are avatar-first
        {
            m_topLayout->addSpacing(iconSpacing);
            m_topLayout->addLayout(m_leftLayout, 1);
        }
    }
    else // Normal item, not ours, layout with avatar/icon first
    {
        if (m_verbIconType < 3) // 1 and 2 are icon-first
        {
            m_topLayout->addSpacing(iconSpacing);
            m_topLayout->addLayout(m_leftLayout, 1);
        }
        else
        {
            m_topLayout->addLayout(m_leftLayout, 1);
            m_topLayout->addSpacing(iconSpacing);
        }

        m_topLayout->addLayout(m_rightLayout, 20);
    }


    m_mainLayout = new QVBoxLayout();
    m_mainLayout->setContentsMargins(2, 1, 2, 4);
    m_mainLayout->addLayout(m_topLayout);

    if (highlightedByFilter) // Show which filtering rules caused this item to be highlighted
    {
        QVariantMap filterMatches = activity->getFilterMatches();
        if (filterMatches.value("filterAction").toInt() == FilterChecker::Highlight)
        {
            m_filterMatchesWidget = new FilterMatchesWidget(filterMatches,
                                                            this);
            m_mainLayout->addWidget(m_filterMatchesWidget);
            // FIXME -- TODO: make this widget optional
        }
    }

    this->setLayout(m_mainLayout);

    qDebug() << "MinorFeedItem created";
}



MinorFeedItem::~MinorFeedItem()
{
    qDebug() << "MinorFeedItem destroyed";
}


/*
 * Pseudo-highlight for new items
 *
 */
void MinorFeedItem::setItemAsNew(bool isNew, bool informFeed)
{
    m_itemIsNew = isNew;

    if (m_itemIsNew)
    {
        this->setAutoFillBackground(true);
        this->setBackgroundRole(QPalette::Mid);

        m_timestampLabel->setStyleSheet("QLabel "
                                        "{ background-color: "
                                        "      qlineargradient(spread:pad,"
                                        "      x1:0, y1:0, x2:1, y2:0,"
                                        "      stop:0   palette(highlight),"
                                        "      stop:0.2 palette(base),"
                                        "      stop:0.8 palette(base),"
                                        "      stop:1   palette(highlight));"
                                        "}"
                                        "QLabel:hover "
                                        "{ color: palette(highlighted-text);   "
                                        "  background-color: palette(highlight)"
                                        "}");
    }
    else
    {
        this->setAutoFillBackground(false);
        this->setBackgroundRole(QPalette::Window);

        m_timestampLabel->setStyleSheet("QLabel:hover "
                                        "{ color: palette(highlighted-text);   "
                                        "  background-color: palette(highlight)"
                                        "}");

        if (informFeed)
        {
            bool wasHighlighted = false;
            if (m_itemHighlightType != -1)
            {
                wasHighlighted = true;
            }

            emit itemRead(wasHighlighted);
        }
    }
}

bool MinorFeedItem::isNew()
{
    return m_itemIsNew;
}


/*
 * Set/Update the fuzzy timestamp
 *
 * This will be called from time to time
 *
 */
void MinorFeedItem::setFuzzyTimeStamp()
{
    m_timestampLabel->setText(Timestamp::fuzzyTime(m_createdAtTimestamp));
}


void MinorFeedItem::syncAvatarFollowState()
{
    m_avatarButton->syncFollowState();
    if (m_haveTargetAvatar)
    {
        m_targetAvatarButton->syncFollowState();
    }
}


int MinorFeedItem::getItemHighlightType()
{
    return m_itemHighlightType;
}

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



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



void MinorFeedItem::openOriginalPost()
{
    // Create a fake activity for the object
    QVariantMap originalPostMap;
    if (!m_inReplyToMap.isEmpty())
    {
        originalPostMap.insert("object", m_inReplyToMap);
        originalPostMap.insert("actor",  m_inReplyToMap.value("author")
                                                       .toMap());
    }
    else
    {
        originalPostMap.insert("object", m_originalObjectMap);
        originalPostMap.insert("actor",  m_originalObjectMap.value("author")
                                                            .toMap());
    }

    ASActivity *originalPostActivity = new ASActivity(originalPostMap,
                                                      m_pumpController->currentUserId(),
                                                      this);

    Post *referencedPost = new Post(originalPostActivity,
                                    false, // Not highlighted
                                    true,  // Post is standalone
                                    m_pumpController,
                                    m_globalObject,
                                    nullptr); // Make it an independent window
    referencedPost->show();
    connect(m_pumpController, &PumpController::commentsReceived,
            referencedPost, &Post::setAllComments);
    referencedPost->requestCommenterComments();
}



void MinorFeedItem::showUrlInfo(QString url)
{
    if (!url.isEmpty())
    {
        m_pumpController->showTransientMessage(url);

        qDebug() << "Link hovered in Minor Feed:" << url;
    }
    else
    {
        m_pumpController->showTransientMessage(QString());
    }
}


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


/*
 * On mouse click in any part of the item, set it as read
 *
 */
void MinorFeedItem::mousePressEvent(QMouseEvent *event)
{
    if (m_itemIsNew)
    {
        this->setItemAsNew(false, // Mark as not new
                           true); // Inform the feed

        // Avoid flickering of the "new" effect later
        m_timestampLabel->repaint();
    }

    event->accept();
}


/*
 * Ensure URL info in statusbar is hidden when the mouse leaves the item
 *
 */
void MinorFeedItem::leaveEvent(QEvent *event)
{
    m_pumpController->showTransientMessage(QString());

    event->accept();
}


void MinorFeedItem::paintEvent(QPaintEvent *event)
{
    if (m_verbIconType == 0 || m_verbIcons.size() < 2)
    {
        // Set not to show activity icons, or icon definitions are wrong
        event->ignore();
        return;
    }

    //////////// FIXME - TODO -- This needs a lot of optimizing all around

    QIcon icon = QIcon::fromTheme(m_verbIcons.first(),
                                  QIcon(m_verbIcons.last()));

    bool subtle = false;
    if (m_verbIconType == 2 || m_verbIconType == 4)
    {
        subtle = true;
    }

    QPixmap iconPixmap;
    int iconOffset = 0;
    if (subtle)
    {
        iconPixmap = icon.pixmap(m_verbIconSizeSubtle, m_verbIconSizeSubtle);
        if (m_verbIconType == 4) // icon-after, subtle
        {
            iconOffset =  m_verbIconSize / 2; // Based on original icon size
        }
    }
    else
    {
        iconPixmap = icon.pixmap(m_verbIconSize, m_verbIconSize);
        if (m_verbIconType == 3) // icon-after
        {
            iconOffset = m_verbIconSize + m_topLayout->spacing();
        }
    }


    int iconPos;
    if (m_itemIsOwn)
    {
        iconPos = this->width() - iconPixmap.width() - iconOffset;
    }
    else
    {
        iconPos = 0 + iconOffset;
    }

    QPainter painter(this);
    if (subtle)
    {
        painter.setOpacity(0.3);
    }

    painter.drawPixmap(iconPos, 0, iconPixmap);

    event->accept();
}
