/***************************************************************************
 *   Copyright (C) 2008 by Pino Toscano <pino@kde.org>                     *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

#include "videowidget.h"

// qt/kde includes
#include <qaction.h>
#include <qcoreapplication.h>
#include <qdir.h>
#include <qevent.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qmenu.h>
#include <qstackedlayout.h>
#include <qtoolbar.h>
#include <qtoolbutton.h>
#include <qwidgetaction.h>

#include <QIcon>
#include <KLocalizedString>

#include <phonon/mediaobject.h>
#include <phonon/seekslider.h>
#include <phonon/videoplayer.h>

#include "core/annotations.h"
#include "core/area.h"
#include "core/document.h"
#include "core/movie.h"
#include "snapshottaker.h"

static QAction* createToolBarButtonWithWidgetPopup( QToolBar* toolBar, QWidget *widget, const QIcon &icon )
{
    QToolButton *button = new QToolButton( toolBar );
    QAction *action = toolBar->addWidget( button );
    button->setAutoRaise( true );
    button->setIcon( icon );
    button->setPopupMode( QToolButton::InstantPopup );
    QMenu *menu = new QMenu( button );
    button->setMenu( menu );
    QWidgetAction *widgetAction = new QWidgetAction( menu );
    QWidget *dummy = new QWidget( menu );
    widgetAction->setDefaultWidget( dummy );
    QVBoxLayout *dummyLayout = new QVBoxLayout( dummy );
    dummyLayout->setContentsMargins( 5, 5, 5, 5 );
    dummyLayout->addWidget( widget );
    menu->addAction( widgetAction );
    return action;
}

/* Private storage. */
class VideoWidget::Private
{
public:
    Private( Okular::Movie *m, Okular::Document *doc, VideoWidget *qq )
        : q( qq ), movie( m ), document( doc ), player( nullptr ), loaded( false )
    {
    }

    ~Private()
    {
        if ( player )
            player->stop();
    }

    enum PlayPauseMode { PlayMode, PauseMode };

    void load();
    void setupPlayPauseAction( PlayPauseMode mode );
    void setPosterImage( const QImage& );
    void takeSnapshot();
    void videoStopped();
    void stateChanged(Phonon::State newState);

    // slots
    void finished();
    void playOrPause();

    VideoWidget *q;
    Okular::Movie *movie;
    Okular::Document *document;
    Okular::NormalizedRect geom;
    Phonon::VideoPlayer *player;
    Phonon::SeekSlider *seekSlider;
    QToolBar *controlBar;
    QAction *playPauseAction;
    QAction *stopAction;
    QAction *seekSliderAction;
    QAction *seekSliderMenuAction;
    QStackedLayout *pageLayout;
    QLabel *posterImagePage;
    bool loaded : 1;
    double repetitionsLeft;
};

static QUrl urlFromUrlString(const QString &url, Okular::Document *document)
{
    QUrl newurl;
    if ( url.startsWith( QLatin1Char( '/' ) ) )
    {
        newurl = QUrl::fromLocalFile( url );
    }
    else
    {
        newurl = QUrl( url );
        if ( newurl.isRelative() )
        {
            newurl = document->currentDocument().adjusted(QUrl::RemoveFilename);
            newurl.setPath( newurl.path() + url );
        }
    }
    return newurl;
}

void VideoWidget::Private::load()
{
    repetitionsLeft = movie->playRepetitions();
    if ( loaded )
        return;

    loaded = true;

    player->load( urlFromUrlString( movie->url(), document ) );

    connect( player->mediaObject(), &Phonon::MediaObject::stateChanged,
             q, [this](Phonon::State s) { stateChanged( s ); } );

    seekSlider->setEnabled( true );
}

void VideoWidget::Private::setupPlayPauseAction( PlayPauseMode mode )
{
    if ( mode == PlayMode )
    {
        playPauseAction->setIcon( QIcon::fromTheme( QStringLiteral("media-playback-start") ) );
        playPauseAction->setText( i18nc( "start the movie playback", "Play" ) );
    }
    else if ( mode == PauseMode )
    {
        playPauseAction->setIcon( QIcon::fromTheme( QStringLiteral("media-playback-pause") ) );
        playPauseAction->setText( i18nc( "pause the movie playback", "Pause" ) );
    }
}

void VideoWidget::Private::takeSnapshot()
{
    const QUrl url = urlFromUrlString( movie->url(), document );
    SnapshotTaker *taker = new SnapshotTaker( url, q );

    q->connect( taker, &SnapshotTaker::finished, q, [this]( const QImage &image ) { setPosterImage( image ); } );
}

void VideoWidget::Private::videoStopped()
{
    if ( movie->showPosterImage() )
        pageLayout->setCurrentIndex( 1 );
    else
        q->hide();
}

void VideoWidget::Private::finished()
{
    switch ( movie->playMode() )
    {
        case Okular::Movie::PlayLimited:
        case Okular::Movie::PlayOpen:
            repetitionsLeft -= 1.0;
            if ( repetitionsLeft < 1e-5 ) { // allow for some calculation error
                // playback has ended
                stopAction->setEnabled( false );
                setupPlayPauseAction( PlayMode );
                if ( movie->playMode() == Okular::Movie::PlayLimited )
                    controlBar->setVisible( false );
                videoStopped();
            } else
                // not done yet, repeat
                // if repetitionsLeft is less than 1, we are supposed to stop midway, but not even Adobe reader does this
                player->play();
            break;
        case Okular::Movie::PlayRepeat:
            // repeat the playback
            player->play();
            break;
        case Okular::Movie::PlayPalindrome:
            // FIXME we should play backward, but we cannot
            player->play();
            break;
    }
}

void VideoWidget::Private::playOrPause()
{
    if ( player->isPlaying() )
    {
        player->pause();
        setupPlayPauseAction( PlayMode );
    }
    else
    {
        q->play();
    }
}

void VideoWidget::Private::setPosterImage( const QImage &image )
{
    if ( !image.isNull() )
    {
        // cache the snapshot image
        movie->setPosterImage( image );
    }

    posterImagePage->setPixmap( QPixmap::fromImage( image ) );
}

void VideoWidget::Private::stateChanged( Phonon::State newState )
{
    if ( newState == Phonon::PlayingState )
        pageLayout->setCurrentIndex( 0 );
}

VideoWidget::VideoWidget( const Okular::Annotation *annotation, Okular::Movie *movie, Okular::Document *document, QWidget *parent )
    : QWidget( parent ), d( new Private( movie, document, this ) )
{
    // do not propagate the mouse events to the parent widget;
    // they should be tied to this widget, not spread around...
    setAttribute( Qt::WA_NoMousePropagation );

    // Setup player page
    QWidget *playerPage = new QWidget( this );

    QVBoxLayout *mainlay = new QVBoxLayout( playerPage );
    mainlay->setContentsMargins( 0, 0, 0, 0 );
    mainlay->setSpacing( 0 );

    d->player = new Phonon::VideoPlayer( Phonon::NoCategory, playerPage );
    d->player->installEventFilter( playerPage );
    mainlay->addWidget( d->player );

    d->controlBar = new QToolBar( playerPage );
    d->controlBar->setIconSize( QSize( 16, 16 ) );
    d->controlBar->setAutoFillBackground( true );
    mainlay->addWidget( d->controlBar );

    d->playPauseAction = new QAction( d->controlBar );
    d->controlBar->addAction( d->playPauseAction );
    d->setupPlayPauseAction( Private::PlayMode );
    d->stopAction = d->controlBar->addAction(
        QIcon::fromTheme( QStringLiteral("media-playback-stop") ),
        i18nc( "stop the movie playback", "Stop" ),
        this, SLOT(stop()) );
    d->stopAction->setEnabled( false );
    d->controlBar->addSeparator();
    d->seekSlider = new Phonon::SeekSlider( d->player->mediaObject(), d->controlBar );
    d->seekSliderAction = d->controlBar->addWidget( d->seekSlider );
    d->seekSlider->setEnabled( false );

    Phonon::SeekSlider *verticalSeekSlider = new Phonon::SeekSlider( d->player->mediaObject(), nullptr );
    verticalSeekSlider->setMaximumHeight( 100 );
    d->seekSliderMenuAction = createToolBarButtonWithWidgetPopup(
        d->controlBar, verticalSeekSlider, QIcon::fromTheme( QStringLiteral("player-time") ) );
    d->seekSliderMenuAction->setVisible( false );

    d->controlBar->setVisible( movie->showControls() );

    connect( d->player, &Phonon::VideoPlayer::finished, this, [this] { d->finished(); } );
    connect( d->playPauseAction, &QAction::triggered, this, [this] { d->playOrPause(); } );

    d->geom = annotation->transformedBoundingRectangle();

    // Setup poster image page
    d->posterImagePage = new QLabel;
    d->posterImagePage->setScaledContents( true );
    d->posterImagePage->installEventFilter( this );
    d->posterImagePage->setCursor( Qt::PointingHandCursor );

    d->pageLayout = new QStackedLayout( this );
    d->pageLayout->setMargin( 0 );
    d->pageLayout->setSpacing( 0 );
    d->pageLayout->addWidget( playerPage );
    d->pageLayout->addWidget( d->posterImagePage );


    if ( movie->showPosterImage() )
    {
        d->pageLayout->setCurrentIndex( 1 );

        const QImage posterImage = movie->posterImage();
        if ( posterImage.isNull() )
        {
            d->takeSnapshot();
        }
        else
        {
            d->setPosterImage( posterImage );
        }
    }
    else
    {
        d->pageLayout->setCurrentIndex( 0 );
    }
}

VideoWidget::~VideoWidget()
{
    delete d;
}

void VideoWidget::setNormGeometry( const Okular::NormalizedRect &rect )
{
    d->geom = rect;
}

Okular::NormalizedRect VideoWidget::normGeometry() const
{
    return d->geom;
}

bool VideoWidget::isPlaying() const
{
    return d->player->isPlaying();
}

void VideoWidget::pageInitialized()
{
    hide();
}

void VideoWidget::pageEntered()
{
    if ( d->movie->showPosterImage() )
    {
        d->pageLayout->setCurrentIndex( 1 );
        show();
    }

    if ( d->movie->autoPlay() )
    {
        show();
        QMetaObject::invokeMethod(this, "play", Qt::QueuedConnection);
    }
}

void VideoWidget::pageLeft()
{
    d->player->stop();
    d->videoStopped();

    hide();
}

void VideoWidget::play()
{
    d->controlBar->setVisible( d->movie->showControls() );
    d->load();
    // if d->repetitionsLeft is less than 1, we are supposed to stop midway, but not even Adobe reader does this
    d->player->play();
    d->stopAction->setEnabled( true );
    d->setupPlayPauseAction( Private::PauseMode );
}

void VideoWidget::stop()
{
    d->player->stop();
    d->stopAction->setEnabled( false );
    d->setupPlayPauseAction( Private::PlayMode );
}

void VideoWidget::pause()
{
    d->player->pause();
    d->setupPlayPauseAction( Private::PlayMode );
}

bool VideoWidget::eventFilter( QObject * object, QEvent * event )
{
    if ( object == d->player || object == d->posterImagePage )
    {
        switch ( event->type() )
        {
            case QEvent::MouseButtonPress:
            {
                QMouseEvent * me = static_cast< QMouseEvent * >( event );
                if ( me->button() == Qt::LeftButton )
                {
                    if ( !d->player->isPlaying() )
                    {
                        play();
                    }
                    event->accept();
                }
                break;
            }
            case QEvent::Wheel:
            {
                if ( object == d->posterImagePage )
                {
                    QWheelEvent * we = static_cast< QWheelEvent * >( event );

                    // forward wheel events to parent widget
                    QWheelEvent *copy = new QWheelEvent( we->pos(), we->globalPos(), we->angleDelta().y(), we->buttons(), we->modifiers(), we->orientation() );
                    QCoreApplication::postEvent( parentWidget(), copy );
                }
                break;
            }
            default: ;
        }
    }

    return false;
}

bool VideoWidget::event( QEvent * event )
{
    switch ( event->type() )
    {
        case QEvent::ToolTip:
            // "eat" the help events (= tooltips), avoid parent widgets receiving them
            event->accept();
            return true;
            break;
        default: ;
    }

    return QWidget::event( event );
}

void VideoWidget::resizeEvent( QResizeEvent * event )
{
    const QSize &s = event->size();
    int usedSpace = d->seekSlider->geometry().left() + d->seekSlider->iconSize().width();
    // try to give the slider at least 30px of space
    if ( s.width() < ( usedSpace + 30 ) )
    {
        d->seekSliderAction->setVisible( false );
        d->seekSliderMenuAction->setVisible( true );
    }
    else
    {
        d->seekSliderAction->setVisible( true );
        d->seekSliderMenuAction->setVisible( false );
    }
}

#include "moc_videowidget.cpp"
