/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 * Copyright (C) 2007 Collabora Ltd.  All rights reserved.
 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * aint with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "config.h"

#if ENABLE(VIDEO)

#include "MediaPlayerPrivateGStreamer.h"
#include "VideoSinkGStreamer.h"

#include "CString.h"
#include "GraphicsContext.h"
#include "IntRect.h"
#include "KURL.h"
#include "MIMETypeRegistry.h"
#include "MediaPlayer.h"
#include "NotImplemented.h"
#include "ScrollView.h"
#include "Widget.h"

#include <gdk/gdkx.h>
#include <gst/base/gstbasesrc.h>
#include <gst/gst.h>
#include <gst/interfaces/mixer.h>
#include <gst/interfaces/xoverlay.h>
#include <gst/video/video.h>
#include <libgnomevfs/gnome-vfs.h>
#include <limits>
#include <math.h>

using namespace std;

namespace WebCore {

gboolean mediaPlayerPrivateErrorCallback(GstBus* bus, GstMessage* message, gpointer data)
{
    if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_ERROR)
    {
        GError* err;
        gchar* debug;

        gst_message_parse_error(message, &err, &debug);
        if (err->code == 3) {
            LOG_VERBOSE(Media, "File not found");
            MediaPlayerPrivate* mp = reinterpret_cast<MediaPlayerPrivate*>(data);
            if (mp)
                mp->loadingFailed();
        } else {
            LOG_VERBOSE(Media, "Error: %d, %s", err->code,  err->message);
            g_error_free(err);
            g_free(debug);
        }
    }
    return true;
}

gboolean mediaPlayerPrivateEOSCallback(GstBus* bus, GstMessage* message, gpointer data)
{
    if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_EOS)
    {
        LOG_VERBOSE(Media, "End of Stream");
        MediaPlayerPrivate* mp = reinterpret_cast<MediaPlayerPrivate*>(data);
        mp->didEnd();
    }
    return true;
}

gboolean mediaPlayerPrivateStateCallback(GstBus* bus, GstMessage* message, gpointer data)
{
    if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_STATE_CHANGED)
    {
        MediaPlayerPrivate* mp = reinterpret_cast<MediaPlayerPrivate*>(data);
        mp->updateStates();
    }
    return true;
}

gboolean mediaPlayerPrivateBufferingCallback(GstBus* bus, GstMessage* message, gpointer data)
{
    if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_BUFFERING)
    {
        gint percent = 0;
        gst_message_parse_buffering(message, &percent);
        LOG_VERBOSE(Media, "Buffering %d", percent);
    }
    return true;
}

MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
    : m_player(player)
    , m_playBin(0)
    , m_videoSink(0)
    , m_source(0)
    , m_rate(1.0f)
    , m_endTime(numeric_limits<float>::infinity())
    , m_isEndReached(false)
    , m_volume(0.5f)
    , m_networkState(MediaPlayer::Empty)
    , m_readyState(MediaPlayer::DataUnavailable)
    , m_startedPlaying(false)
    , m_isStreaming(false)
    , m_rect(IntRect())
    , m_visible(true)
{

    static bool gstInitialized = false;
    // FIXME: We should pass the arguments from the command line
    if (!gstInitialized) {
        gst_init(0, NULL);
        gstInitialized = true;
    }

    // FIXME: The size shouldn't be fixed here, this is just a quick hack.
    m_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 640, 480);
}

MediaPlayerPrivate::~MediaPlayerPrivate()
{
    if (m_surface)
        cairo_surface_destroy(m_surface);

    if (m_playBin) {
        gst_element_set_state(m_playBin, GST_STATE_NULL);
        gst_object_unref(GST_OBJECT(m_playBin));
    }
}

void MediaPlayerPrivate::load(String url)
{
    LOG_VERBOSE(Media, "Load %s", url.utf8().data());
    if (m_networkState != MediaPlayer::Loading) {
        m_networkState = MediaPlayer::Loading;
        m_player->networkStateChanged();
    }
    if (m_readyState != MediaPlayer::DataUnavailable) {
        m_readyState = MediaPlayer::DataUnavailable;
        m_player->readyStateChanged();
    }

    createGSTPlayBin(url);
    pause();
}

void MediaPlayerPrivate::play()
{
    LOG_VERBOSE(Media, "Play");
    // When end reached, rewind for Test video-seek-past-end-playing
    if (m_isEndReached)
        seek(0);
    m_isEndReached = false;

    gst_element_set_state(m_playBin, GST_STATE_PLAYING);
    m_startedPlaying = true;
}

void MediaPlayerPrivate::pause()
{
    LOG_VERBOSE(Media, "Pause");
    gst_element_set_state(m_playBin, GST_STATE_PAUSED);
    m_startedPlaying = false;
}

float MediaPlayerPrivate::duration()
{
    if (!m_playBin)
        return 0.0;

    GstFormat fmt = GST_FORMAT_TIME;
    gint64 len = 0;

    if (gst_element_query_duration(m_playBin, &fmt, &len))
        LOG_VERBOSE(Media, "Duration: %" GST_TIME_FORMAT, GST_TIME_ARGS(len));
    else
        LOG_VERBOSE(Media, "Duration query failed ");

    if ((GstClockTime)len == GST_CLOCK_TIME_NONE) {
        m_isStreaming = true;
        return numeric_limits<float>::infinity();
    }
    return (float) (len / 1000000000.0);
    // FIXME: handle 3.14.9.5 properly
}

float MediaPlayerPrivate::currentTime() const
{
    if (!m_playBin)
        return 0;
    // Necessary as sometimes, gstreamer return 0:00 at the EOS
    if (m_isEndReached)
        return m_endTime;

    float ret;

    GstQuery* query = gst_query_new_position(GST_FORMAT_TIME);
    if (gst_element_query(m_playBin, query)) {
        gint64 position;
        gst_query_parse_position(query, NULL, &position);
        ret = (float) (position / 1000000000.0);
        LOG_VERBOSE(Media, "Position %" GST_TIME_FORMAT, GST_TIME_ARGS(position));
    } else {
        LOG_VERBOSE(Media, "Position query failed...");
        ret = 0.0;
    }
    gst_query_unref(query);

    return ret;
}

void MediaPlayerPrivate::seek(float time)
{
    GstClockTime sec = (GstClockTime)(time * GST_SECOND);

    if (!m_playBin)
        return;

    if (m_isStreaming)
        return;

    LOG_VERBOSE(Media, "Seek: %" GST_TIME_FORMAT, GST_TIME_ARGS(sec));
    // FIXME: What happens when the seeked position is not available?
    if (!gst_element_seek( m_playBin, m_rate,
            GST_FORMAT_TIME,
            (GstSeekFlags)(GST_SEEK_FLAG_FLUSH),
            GST_SEEK_TYPE_SET, sec,
            GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
        LOG_VERBOSE(Media, "Seek to %f failed", time);
}

void MediaPlayerPrivate::setEndTime(float time)
{
    if (!m_playBin)
        return;
    if (m_isStreaming)
        return;
    if (m_endTime != time) {
        m_endTime = time;
        GstClockTime start = (GstClockTime)(currentTime() * GST_SECOND);
        GstClockTime end   = (GstClockTime)(time * GST_SECOND);
        LOG_VERBOSE(Media, "setEndTime: %" GST_TIME_FORMAT, GST_TIME_ARGS(end));
        // FIXME: What happens when the seeked position is not available?
        if (!gst_element_seek(m_playBin, m_rate,
                GST_FORMAT_TIME,
                (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE),
                GST_SEEK_TYPE_SET, start,
                GST_SEEK_TYPE_SET, end ))
            LOG_VERBOSE(Media, "Seek to %f failed", time);
    }
}

void MediaPlayerPrivate::startEndPointTimerIfNeeded()
{
    notImplemented();
}

void MediaPlayerPrivate::cancelSeek()
{
    notImplemented();
}

void MediaPlayerPrivate::endPointTimerFired(Timer<MediaPlayerPrivate>*)
{
    notImplemented();
}

bool MediaPlayerPrivate::paused() const
{
    return !m_startedPlaying;
}

bool MediaPlayerPrivate::seeking() const
{
    return false;
}

// Returns the size of the video
IntSize MediaPlayerPrivate::naturalSize()
{
    if (!hasVideo())
        return IntSize();

    int x = 0, y = 0;
    if (GstPad* pad = gst_element_get_static_pad(m_videoSink, "sink")) {
        gst_video_get_size(GST_PAD(pad), &x, &y);
        gst_object_unref(GST_OBJECT(pad));
    }

    return IntSize(x, y);
}

bool MediaPlayerPrivate::hasVideo()
{
    gint currentVideo = -1;
    if (m_playBin)
        g_object_get(G_OBJECT(m_playBin), "current-video", &currentVideo, NULL);
    return currentVideo > -1;
}

void MediaPlayerPrivate::setVolume(float volume)
{
    m_volume = volume;
    LOG_VERBOSE(Media, "Volume to %f", volume);
    setMuted(false);
}

void MediaPlayerPrivate::setMuted(bool b)
{
    if (!m_playBin)
        return;

    if (b) {
        g_object_get(G_OBJECT(m_playBin), "volume", &m_volume, NULL);
        g_object_set(G_OBJECT(m_playBin), "volume", (double)0.0, NULL);
    } else {
        g_object_set(G_OBJECT(m_playBin), "volume", m_volume, NULL);
    }
}

void MediaPlayerPrivate::setRate(float rate)
{
    if (rate == 0.0) {
        gst_element_set_state(m_playBin, GST_STATE_PAUSED);
        return;
    }
    if (m_isStreaming)
        return;

    m_rate = rate;
    LOG_VERBOSE(Media, "Set Rate to %f", rate);
    if (!gst_element_seek(m_playBin, rate,
            GST_FORMAT_TIME,
            (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE),
            GST_SEEK_TYPE_SET, (GstClockTime) (currentTime() * GST_SECOND),
            GST_SEEK_TYPE_SET, (GstClockTime) (m_endTime * GST_SECOND)))
        LOG_VERBOSE(Media, "Set Rate to %f failed", rate);
}

int MediaPlayerPrivate::dataRate() const
{
    notImplemented();
    return 1;
}

MediaPlayer::NetworkState MediaPlayerPrivate::networkState()
{
    return m_networkState;
}

MediaPlayer::ReadyState MediaPlayerPrivate::readyState()
{
    return m_readyState;
}

float MediaPlayerPrivate::maxTimeBuffered()
{
    notImplemented();
    LOG_VERBOSE(Media, "maxTimeBuffered");
    // rtsp streams are not buffered
    return m_isStreaming ? 0 : maxTimeLoaded();
}

float MediaPlayerPrivate::maxTimeSeekable()
{
    // TODO
    LOG_VERBOSE(Media, "maxTimeSeekable");
    if (m_isStreaming)
        return numeric_limits<float>::infinity();
    // infinite duration means live stream
    return maxTimeLoaded();
}

float MediaPlayerPrivate::maxTimeLoaded()
{
    // TODO
    LOG_VERBOSE(Media, "maxTimeLoaded");
    notImplemented();
    return duration();
}

unsigned MediaPlayerPrivate::bytesLoaded()
{
    notImplemented();
    LOG_VERBOSE(Media, "bytesLoaded");
    /*if (!m_playBin)
        return 0;
    float dur = duration();
    float maxTime = maxTimeLoaded();
    if (!dur)
        return 0;*/
    return 1;//totalBytes() * maxTime / dur;
}

bool MediaPlayerPrivate::totalBytesKnown()
{
    notImplemented();
    LOG_VERBOSE(Media, "totalBytesKnown");
    return totalBytes() > 0;
}

unsigned MediaPlayerPrivate::totalBytes()
{
    notImplemented();
    LOG_VERBOSE(Media, "totalBytes");
    if (!m_playBin)
        return 0;

    if (!m_source)
        return 0;

    // Do something with m_source to get the total bytes of the media

    return 100;
}

void MediaPlayerPrivate::cancelLoad()
{
    notImplemented();
}

void MediaPlayerPrivate::updateStates()
{
    // There is no (known) way to get such level of information about
    // the state of GStreamer, therefore, when in PAUSED state,
    // we are sure we can display the first frame and go to play

    MediaPlayer::NetworkState oldNetworkState = m_networkState;
    MediaPlayer::ReadyState oldReadyState = m_readyState;
    GstState state;
    GstState pending;

    if (!m_playBin)
        return;

    GstStateChangeReturn ret = gst_element_get_state (m_playBin,
        &state, &pending, 250 * GST_NSECOND);

    switch(ret) {
    case GST_STATE_CHANGE_SUCCESS:
        LOG_VERBOSE(Media, "State: %s, pending: %s",
            gst_element_state_get_name(state),
            gst_element_state_get_name(pending));

        if (state == GST_STATE_READY) {
            m_readyState = MediaPlayer::CanPlayThrough;
        } else if (state == GST_STATE_PAUSED) {
            m_readyState = MediaPlayer::CanPlayThrough;
        }
        if (m_networkState < MediaPlayer::Loaded)
            m_networkState = MediaPlayer::Loaded;

        g_object_get(m_playBin, "source", &m_source, NULL);
        if (!m_source)
            LOG_VERBOSE(Media, "m_source is NULL");
        break;
    case GST_STATE_CHANGE_ASYNC:
        LOG_VERBOSE(Media, "Async: State: %s, pending: %s",
            gst_element_state_get_name(state),
            gst_element_state_get_name(pending));
        // Change in progress
        return;
        break;
    case GST_STATE_CHANGE_NO_PREROLL:
        LOG_VERBOSE(Media, "No preroll: State: %s, pending: %s",
            gst_element_state_get_name(state),
            gst_element_state_get_name(pending));
        if (state == GST_STATE_READY) {
            m_readyState = MediaPlayer::CanPlay;
        } else if (state == GST_STATE_PAUSED) {
            m_readyState = MediaPlayer::CanPlay;
        }
        if (m_networkState < MediaPlayer::LoadedMetaData)
            m_networkState = MediaPlayer::LoadedMetaData;
        break;
    default:
        LOG_VERBOSE(Media, "Else : %d", ret);
        break;
    }

    if (seeking())
        m_readyState = MediaPlayer::DataUnavailable;

    if (m_networkState != oldNetworkState) {
        LOG_VERBOSE(Media, "Network State Changed from %u to %u",
            oldNetworkState, m_networkState);
        m_player->networkStateChanged();
    }
    if (m_readyState != oldReadyState) {
        LOG_VERBOSE(Media, "Ready State Changed from %u to %u",
            oldReadyState, m_readyState);
        m_player->readyStateChanged();
    }
}

void MediaPlayerPrivate::loadStateChanged()
{
    updateStates();
}

void MediaPlayerPrivate::rateChanged()
{
    updateStates();
}

void MediaPlayerPrivate::sizeChanged()
{
    notImplemented();
}

void MediaPlayerPrivate::timeChanged()
{
    updateStates();
    m_player->timeChanged();
}

void MediaPlayerPrivate::volumeChanged()
{
    m_player->volumeChanged();
}

void MediaPlayerPrivate::didEnd()
{
    m_isEndReached = true;
    pause();
    timeChanged();
}

void MediaPlayerPrivate::loadingFailed()
{
    if (m_networkState != MediaPlayer::LoadFailed) {
        m_networkState = MediaPlayer::LoadFailed;
        m_player->networkStateChanged();
    }
    if (m_readyState != MediaPlayer::DataUnavailable) {
        m_readyState = MediaPlayer::DataUnavailable;
        m_player->readyStateChanged();
    }
}

void MediaPlayerPrivate::setRect(const IntRect& rect)
{
    m_rect = rect;
}

void MediaPlayerPrivate::setVisible(bool visible)
{
    m_visible = visible;
}

void MediaPlayerPrivate::repaint()
{
    m_player->repaint();
}

void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& rect)
{
    if (context->paintingDisabled())
        return;

    if (!m_visible)
        return;

    //TODO: m_rect vs rect?
    cairo_t* cr = context->platformContext();

    cairo_save(cr);
    cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
    cairo_translate(cr, rect.x(), rect.y());
    cairo_rectangle(cr, 0, 0, rect.width(), rect.height());
    cairo_set_source_surface(cr, m_surface, 0, 0);
    cairo_fill(cr);
    cairo_restore(cr);
}

void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types)
{
    // FIXME: do the real thing
    notImplemented();
    types.add(String("video/x-theora+ogg"));
}

void MediaPlayerPrivate::createGSTPlayBin(String url)
{
    ASSERT(!m_playBin);
    m_playBin = gst_element_factory_make("playbin", "play");

    GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(m_playBin));
    gst_bus_add_signal_watch(bus);
    g_signal_connect(bus, "message::error", G_CALLBACK(mediaPlayerPrivateErrorCallback), this);
    g_signal_connect(bus, "message::eos", G_CALLBACK(mediaPlayerPrivateEOSCallback), this);
    g_signal_connect(bus, "message::state-changed", G_CALLBACK(mediaPlayerPrivateStateCallback), this);
    g_signal_connect(bus, "message::buffering", G_CALLBACK(mediaPlayerPrivateBufferingCallback), this);
    gst_object_unref(bus);

    g_object_set(G_OBJECT(m_playBin), "uri", url.utf8().data(), NULL);

    GstElement* audioSink = gst_element_factory_make("gconfaudiosink", NULL);
    m_videoSink = webkit_video_sink_new(m_surface);

    g_object_set(m_playBin, "audio-sink", audioSink, NULL);
    g_object_set(m_playBin, "video-sink", m_videoSink, NULL);

    setVolume(m_volume);
}

}

#endif

