/*
 * itemviews.cpp
 *
 * Copyright (c) 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 "itemviews.h"
#include "knewsticker.h"

#include "settings.h"

#include <QCursor>
#include <QFontMetrics>
#include <QTimeLine>
#include <QTimer>

#include <KIconLoader>
#include <KLocale>
#include <KMimeType>

NewsItemView::NewsItemView( KNewsTicker *parent )
    : QObject( 0 ),
    QGraphicsRectItem( parent ),
    m_newsTicker( parent )
{
}

const QList<NewsItem> &NewsItemView::items() const
{
    return m_items;
}

void NewsItemView::setItems( const QList<NewsItem> &items )
{
    m_items = items;
}

void NewsItemView::reloadSettings()
{
}

HyperlinkItem::HyperlinkItem( const QString &text, const QUrl &url,
                                QGraphicsItem *parentItem )
    : QObject(), QGraphicsSimpleTextItem( text, parentItem ),
    m_url( url )
{
    setFont( Settings::font() );
    setBrush( Settings::color() );
    setCursor( Qt::PointingHandCursor );
    setAcceptedMouseButtons( Qt::LeftButton );
    setAcceptsHoverEvents( true );
}

void HyperlinkItem::hoverEnterEvent( QGraphicsSceneHoverEvent * )
{
    if ( m_url.isEmpty() ) {
        return;
    }

    QFont f = font();
    f.setUnderline( true );
    setFont( f );
    setBrush( Qt::red );
}

void HyperlinkItem::hoverLeaveEvent( QGraphicsSceneHoverEvent * )
{
    if ( m_url.isEmpty() ) {
        return;
    }

    QFont f = font();
    f.setUnderline( false );
    setFont( f );
    setBrush( Settings::color() );
}

void HyperlinkItem::mouseReleaseEvent( QGraphicsSceneMouseEvent * )
{
    if ( m_url.isEmpty() ) {
        return;
    }

    emit activated( m_url.toString() );
}

SeparatorItem::SeparatorItem( QGraphicsItem *parentItem )
    : QGraphicsSimpleTextItem( " +++ ", parentItem )
{
    setBrush( Settings::color() );
    setFont( Settings::font() );
}

NewsTickerItem::NewsTickerItem( const QString &text, const QUrl &url,
                                const QString &description,
                                QGraphicsItem *parentItem )
    : QGraphicsItemGroup( parentItem )
{
    setHandlesChildEvents( false );

    qreal xpos = 0;

    QGraphicsItem *pi = 0;
    const QString favIcon = KMimeType::favIconForUrl( url );
    if ( !favIcon.isEmpty() ) {
        pi = new QGraphicsPixmapItem( SmallIcon( favIcon ), this );
        addToGroup( pi );
        xpos += boundingRect().width() + 8;
    }

    m_headlineItem = new HyperlinkItem( text, url, this );
    m_headlineItem->setPos( xpos, 0 );
    m_headlineItem->setToolTip( description );
    addToGroup( m_headlineItem );

    m_separatorItem = new SeparatorItem( this );
    m_separatorItem->setPos( boundingRect().width(), 0 );
    addToGroup( m_separatorItem );

    if ( pi ) {
        pi->setPos( 0, ( boundingRect().height() - pi->boundingRect().height() ) / 2 );
    }
}

ScrollingItemView::ScrollingItemView( KNewsTicker *parent )
    : NewsItemView( parent ),
    m_steppingWidth( 1.0 )
{
    m_scrollTimer = new QTimer( this );
    connect( m_scrollTimer, SIGNAL( timeout() ),
             this, SLOT( advance() ) );
    configureScrollTimer();
    m_scrollTimer->start();
}

void ScrollingItemView::setItems( const QList<NewsItem> &items )
{
    NewsItemView::setItems( items );
    relayoutItems();
}

void ScrollingItemView::relayoutItems()
{
    qDeleteAll( m_graphicsItems );
    m_graphicsItems.clear();

    if ( items().empty() ) {
        return;
    }

    qreal ypos = ( boundingRect().height() - QFontMetrics( Settings::font() ).height() ) / 2;
    qreal xpos = 0;
    QList<NewsItem>::ConstIterator it, end = items().end();
    for ( it = items().begin(); it != end; ++it ) {
        addItemToLayout( *it, &xpos, &ypos );
    }

    /* In case none of the available items were added to the scroll text (this
     * can happen if all of them have been read), add a filler item which tells
     * the user that no unread news are available.
     */
    if ( m_graphicsItems.isEmpty() ) {
        NewsItem item;
        item.text = i18n( "No unread news available" );

        addItemToLayout( item, &xpos, &ypos );
        const qreal firstItemWidth = m_graphicsItems.first()->boundingRect().width();
        while ( xpos < boundingRect().width() + firstItemWidth ) { // XXX
            addItemToLayout( item, &xpos, &ypos );
        }
        return;
    }

    const qreal firstItemWidth = m_graphicsItems.first()->boundingRect().width();

    it = items().begin();
    while ( xpos < boundingRect().width() + firstItemWidth ) { // XXX
        addItemToLayout( *it, &xpos, &ypos );

        if ( ++it == items().end() ) {
            it = items().begin();
        }
    }
}

void ScrollingItemView::addItemToLayout( const NewsItem &item, qreal *xpos, qreal *ypos )
{
    if ( newsTicker()->hideArticle( item.url ) ) {
        return;
    }

    NewsTickerItem *i = new NewsTickerItem( item.text, item.url, item.description,
                                            this );
    connect( i->headlineItem(), SIGNAL( activated( const QString & ) ),
             this, SIGNAL( itemActivated( const QString & ) ) );
    i->setPos( *xpos, *ypos );
    m_graphicsItems.append( i );
    *xpos += i->boundingRect().width();
}

void ScrollingItemView::advance()
{
    if ( m_graphicsItems.isEmpty() ) {
        return;
    }

    const qreal ypos = ( boundingRect().height() - QFontMetrics( Settings::font() ).height() ) / 2; // XXX

    /* In case an item scrolled out to the left, take it out of the m_graphicsItems list
     * and insert it at the end again. Also, move it's position to the very end (determine
     * the end by adding up the widths of the remaining items).
     */
    NewsTickerItem *firstItem = m_graphicsItems.first();
    while ( firstItem->x() + firstItem->boundingRect().width() < 0 ) {
        m_graphicsItems.erase( m_graphicsItems.begin() );

        /* If we just scrolled an item out of view which we read already,
         * don't show it again in case the user configured this.
         */
        if ( newsTicker()->hideArticle( firstItem->headlineItem()->url() ) ) {
            delete firstItem;
        } else {
            qreal xpos = 0;
            foreach ( QGraphicsItem *item, m_graphicsItems ) {
                xpos += item->boundingRect().width();
            }
            firstItem->setPos( xpos, ypos );
            m_graphicsItems.append( firstItem );
        }
        firstItem = m_graphicsItems.first();
    }

    foreach ( NewsTickerItem *item, m_graphicsItems ) {
        item->moveBy( m_steppingWidth * -1, 0 );
    }
}

void ScrollingItemView::configureScrollTimer()
{
    if ( Settings::scrollingSpeed() < 25 ) {
        m_scrollTimer->setInterval( 25 );
        m_steppingWidth = 25.0 / Settings::scrollingSpeed();
    } else {
        m_scrollTimer->setInterval( Settings::scrollingSpeed() );
        m_steppingWidth = 1.0;
    }
}

void ScrollingItemView::reloadSettings()
{
    foreach ( NewsTickerItem *item, m_graphicsItems ) {
        item->headlineItem()->setBrush( Settings::color() );
        item->separatorItem()->setBrush( Settings::color() );
    }
    configureScrollTimer();
}

PagingItemView::PagingItemView( KNewsTicker *parent )
    : NewsItemView( parent ),
    m_needToReloadSettings( false )
{
    m_linkItem = new HyperlinkItem( QString(), QUrl(), this );

    QTimeLine *moveInTimer = new QTimeLine( 800 );
    connect( moveInTimer, SIGNAL( finished() ),
             this, SLOT( itemMovedIn() ) );
    moveInTimer->setCurveShape( QTimeLine::EaseOutCurve );
    moveInTimer->setFrameRange( 0, 100 );

    m_moveInAnimation = new QGraphicsItemAnimation;
    m_moveInAnimation->setItem( m_linkItem );
    m_moveInAnimation->setTimeLine( moveInTimer );

    QTimeLine *moveOutTimer = new QTimeLine( 800 );
    connect( moveOutTimer, SIGNAL( finished() ),
             this, SLOT( itemMovedOut() ) );
    moveOutTimer->setCurveShape( QTimeLine::EaseInCurve );
    moveOutTimer->setFrameRange( 0, 100 );

    m_moveOutAnimation = new QGraphicsItemAnimation;
    m_moveOutAnimation->setItem( m_linkItem );
    m_moveOutAnimation->setTimeLine( moveOutTimer );
}

PagingItemView::~PagingItemView()
{
    m_moveInAnimation->timeLine()->stop();
    m_moveOutAnimation->timeLine()->stop();
}

void PagingItemView::updateMoveAnimations()
{
    m_moveInAnimation->clear();
    m_moveOutAnimation->clear();
    switch ( Settings::pagingDirection() ) {
        case Settings::EnumPagingDirection::Upwards:
        case Settings::EnumPagingDirection::Downwards: {
            const qreal itemHeight = m_linkItem->boundingRect().height();
            const qreal height = boundingRect().height();
            const qreal xpos = ( boundingRect().width() - m_linkItem->boundingRect().width() ) / 2;

            const qreal topY = -itemHeight;
            const qreal centerY = ( height - itemHeight ) / 2;
            const qreal bottomY = height;

            if ( Settings::pagingDirection() == Settings::EnumPagingDirection::Upwards ) {
                for ( qreal y = bottomY; y > centerY; --y ) {
                    const qreal step = double( bottomY - y ) / ( bottomY - centerY );
                    m_moveInAnimation->setPosAt( step, QPointF( xpos, y ) );
                }

                for ( qreal y = centerY; y > topY; --y ) {
                    const qreal step = double( centerY - y ) / ( centerY - topY );
                    m_moveOutAnimation->setPosAt( step, QPointF( xpos, y ) );
                }
            } else {
                for ( qreal y = topY; y < centerY; ++y ) {
                    const qreal step = double( y - topY ) / ( centerY - topY );
                    m_moveInAnimation->setPosAt( step, QPointF( xpos, y ) );
                }

                for ( qreal y = centerY; y < bottomY; ++y ) {
                    const qreal step = double( y - centerY ) / ( bottomY - centerY );
                    m_moveOutAnimation->setPosAt( step, QPointF( xpos, y ) );
                }
            }
        }
        break;

        case Settings::EnumPagingDirection::Leftwards:
        case Settings::EnumPagingDirection::Rightwards: {
            const qreal itemWidth = m_linkItem->boundingRect().width();
            const qreal width = boundingRect().width();
            const qreal ypos = ( boundingRect().height() - m_linkItem->boundingRect().height() ) / 2;

            const qreal leftX = -itemWidth;
            const qreal centerX = ( width - itemWidth ) / 2;
            const qreal rightX = width;

            if ( Settings::pagingDirection() == Settings::EnumPagingDirection::Leftwards ) {
                for ( qreal x = rightX; x > centerX; --x ) {
                    const qreal step = double( rightX - x ) / ( rightX - centerX );
                    m_moveInAnimation->setPosAt( step, QPointF( x, ypos ) );
                }

                for ( qreal x = centerX; x > leftX; --x ) {
                    const qreal step = double( centerX - x ) / ( centerX - leftX );
                    m_moveOutAnimation->setPosAt( step, QPointF( x, ypos ) );
                }
            } else {
                for ( qreal x = leftX; x < centerX; ++x ) {
                    const qreal step = double( x - leftX ) / ( centerX - leftX );
                    m_moveInAnimation->setPosAt( step, QPointF( x, ypos ) );
                }

                for ( qreal x = centerX; x < rightX; ++x ) {
                    const qreal step = double( x - centerX ) / ( rightX - centerX );
                    m_moveOutAnimation->setPosAt( step, QPointF( x, ypos ) );
                }
            }
        }
        break;

        case Settings::EnumPagingDirection::COUNT:
        // XXX Ahem; generated by kconfig_compiler for internal reasons.
        break;
    }
}

void PagingItemView::setItems( const QList<NewsItem> &items_ )
{
    NewsItemView::setItems( items_ );
    m_currentItem = items().begin();
    updateLinkItem();
    updateMoveAnimations();
    moveItemIn();
}

void PagingItemView::moveItemIn()
{
    m_moveInAnimation->reset();
    m_moveInAnimation->timeLine()->start();
}

void PagingItemView::itemMovedIn()
{
    if ( !items().isEmpty() ) {
        QTimer::singleShot( Settings::pagingInterval() * 1000, this, SLOT( moveItemOut() ) );
    }
}

void PagingItemView::moveItemOut()
{
    m_moveOutAnimation->reset();
    m_moveOutAnimation->timeLine()->start();
}

void PagingItemView::itemMovedOut()
{
    if ( m_currentItem == items().end() || ++m_currentItem == items().end() ) {
        m_currentItem = items().begin();
    }
    updateLinkItem();
    if ( m_needToReloadSettings ) {
        m_linkItem->setBrush( Settings::color() );
        m_linkItem->setFont( Settings::font() );
        m_needToReloadSettings = false;
    }
    updateMoveAnimations();
    moveItemIn();
}

void PagingItemView::reloadSettings()
{
    m_needToReloadSettings = true;
}

void PagingItemView::updateLinkItem()
{
    if ( m_currentItem == items().end() ) {
        m_linkItem->setText( i18n( "No unread news available" ) );
        m_linkItem->setUrl( QString() );
        m_linkItem->setToolTip( QString() );
    } else {
        m_linkItem->setText( ( *m_currentItem ).text );
        m_linkItem->setUrl( ( *m_currentItem ).url );
        m_linkItem->setToolTip( ( *m_currentItem ).description );
    }
}

#include "itemviews.moc"

