/*
 * knewsticker.cpp
 *
 * Copyright (c) 2000, 2001, 2007 Frerich Raabe <raabe@kde.org>
 *
 * 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. For licensing and distribution details, check the
 * accompanying file 'COPYING'.
 */
#include "knewsticker.h"
#include "newsfeedmanager.h"
#include "settings.h"
#include "itemviews.h"
#include "settingsdialog.h"

#include <kconfigdialog.h>
#include <kiconloader.h>
#include <kmimetype.h>
#include <syndication/item.h>

#include <QDesktopServices>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>
#include <QMessageBox>
#include <QSignalMapper>
#include <QTimer>

using namespace Syndication;

static QString unescape( const QString &s )
{
    QString t = s;
    t.replace( QLatin1String( "&amp;" ), QLatin1String( "&" ) );
    t.replace( QLatin1String( "&quot;" ), QLatin1String( "'" ) );
    t.replace( QLatin1String( "&apos;" ), QLatin1String( "\"" ) );
    t.replace( QLatin1String( "&lt;" ), QLatin1String( "<" ) );
    t.replace( QLatin1String( "&gt;" ), QLatin1String( ">" ) );
    return t;
}

KNewsTicker::KNewsTicker( QObject *parent, const QVariantList &args )
    : Plasma::Applet( parent, args ),
    m_signalMapper( 0 ),
    m_itemView( 0 ),
    m_settingsDialog( 0 )
{
    setBackgroundHints( DefaultBackground );

    setupItemView();
    connect( NewsFeedManager::self(), SIGNAL( feedLoaded( const QUrl & ) ),
             this, SLOT( feedLoaded( const QUrl & ) ) );
    connect( NewsFeedManager::self(), SIGNAL( updateFinished() ),
             this, SLOT( feedUpdateFinished() ) );

    NewsFeedManager::self()->setSubscriptions( Settings::feedUrls() );

    m_updateTimer = new QTimer( this );
    connect( m_updateTimer, SIGNAL( timeout() ),
             this, SLOT( updateFeeds() ) );
    m_updateTimer->start( Settings::updateInterval() * 60 * 1000 );

    setHasConfigurationInterface( true );

    updateFeeds();
}

KNewsTicker::~KNewsTicker()
{
    delete m_settingsDialog;
}

void KNewsTicker::showConfigurationInterface()
{
    if ( !m_settingsDialog ) {
        m_settingsDialog = new SettingsDialog;
        connect( m_settingsDialog, SIGNAL( settingsChanged( const QString & ) ),
                 this, SLOT( settingsChanged( const QString & ) ) );
        connect( m_settingsDialog, SIGNAL( accepted() ),
                 this, SLOT( settingsAccepted() ) );
    }
    m_settingsDialog->show();
}

void KNewsTicker::settingsChanged( const QString & /* dialogName */ )
{
    reloadSettings();
}

void KNewsTicker::settingsAccepted()
{
    Settings::setFeedUrls( m_settingsDialog->feedUrls() );
    NewsFeedManager::self()->setSubscriptions( Settings::feedUrls() );
    updateFeeds();
    reloadSettings();
}

QSizeF KNewsTicker::contentSizeHint() const
{
    return m_itemView->boundingRect().size();
}

QList<QAction *> KNewsTicker::contextActions()
{
    QList<QAction *> actions;

    delete m_signalMapper;
    m_signalMapper = new QSignalMapper( this );
    connect( m_signalMapper, SIGNAL( mapped( const QString & ) ),
             this, SLOT( openFeedItem( const QString & ) ) );

    QList<FeedPtr> availableFeeds = NewsFeedManager::self()->availableFeeds().values();
    foreach ( FeedPtr feed, availableFeeds ) {
        QMenu *feedMenu = new QMenu;
        QList<ItemPtr> items = feed->items();
        foreach ( ItemPtr item, items ) {
            QString title = unescape( item->title() );
            title.replace( "&", "&amp;" );
            QAction *itemAction = feedMenu->addAction( title,
                                                       m_signalMapper, SLOT( map() ) );

            m_signalMapper->setMapping( itemAction, item->link() );
        }

        QAction *feedAction = new QAction( feed->title(), 0 );
        const QString favIcon = KMimeType::favIconForUrl( feed->link() );
        if ( !favIcon.isEmpty() ) {
            feedAction->setIcon( SmallIcon( favIcon ) );
        }
        feedAction->setMenu( feedMenu );
        actions.append( feedAction );
    }

    return actions;
}

bool KNewsTicker::hideArticle( const QUrl &url )
{
    return Settings::hideReadArticles() && m_readArticles.contains( url );
}

void KNewsTicker::feedLoaded( const QUrl &url )
{
    FeedPtr feed = NewsFeedManager::self()->availableFeeds()[ url ];
    foreach ( ItemPtr item, feed->items() ) {
        NewsItem i;
        i.text = unescape( item->title() );
        i.url = item->link();
        i.description = unescape( item->description() );
        m_items.append( i );
    }
}

void KNewsTicker::feedUpdateFinished()
{
    /* Filter out unneded entries in our m_readArticles set; first find out
     * which URLs are shown after the update. Then, create a new list of read
     * articles in which only those articles get added which are available
     * and read.
     */
    QSet<QUrl> allUrls;
    foreach ( const NewsItem &item, m_items ) {
        allUrls.insert( item.url );
    }

    QSet<QUrl> activeReadArticles;
    foreach ( const QUrl &readUrl, m_readArticles ) {
        if ( allUrls.contains( readUrl ) ) {
            activeReadArticles.insert( readUrl );
        }
    }
    m_readArticles = activeReadArticles;

    m_itemView->setItems( m_items );
}

void KNewsTicker::updateFeeds()
{
    m_items.clear();
    NewsFeedManager::self()->update();
}

void KNewsTicker::openFeedItem( const QString &url )
{
    QDesktopServices::openUrl( url );
    if ( !m_readArticles.contains( url ) ) {
        m_readArticles.insert( url );
    }
}

void KNewsTicker::reloadSettings()
{
    setupItemView();
    m_updateTimer->setInterval( Settings::updateInterval() * 60 * 1000 );
}

void KNewsTicker::setupItemView()
{
    delete m_itemView;
    m_itemView = 0;
    switch ( Settings::displayStyle() ) {
        case Settings::EnumDisplayStyle::ScrollingText:
            m_itemView = new ScrollingItemView( this );
            break;
        case Settings::EnumDisplayStyle::PagedText:
            m_itemView = new PagingItemView( this );
            break;
        case Settings::EnumDisplayStyle::COUNT:
            // Just used internally by KConfigXT
            Q_ASSERT(false);
            break;
    }
    connect( m_itemView, SIGNAL( itemActivated( const QString & ) ),
             this, SLOT( openFeedItem( const QString & ) ) );
    m_itemView->setRect( 0, 0, 512, QFontMetrics( Settings::font() ).height() * 2 );
    m_itemView->reloadSettings();
    m_itemView->setItems( m_items );
}

K_EXPORT_PLASMA_APPLET(knewsticker, KNewsTicker)

#include "knewsticker.moc"
