// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * player.c - Simple media player based on GStreamer
 *
 * Copyright (C) 2007-2008 Johannes H. Jensen <joh@pseudoberries.com>
 * Copyright (C) 2022 Tasos Sahanidis <code@tasossah.com>
 */

#include <gst/gst.h>

#include "player.h"

/**
 * Create a new media player.
 *
 * @uri				The file to play.
 * @loop			Wether to loop or not.
 * @state_callback	An optional #MediaPlayerStateChangeCallback which will be
 * 					notified when the state of the player changes.
 * @data			Data for the state_callback
 * @error_handler	An optional #MediaPlayerErrorHandler which will be notified
 * 					if an error occurs.
 * @error_data		Data for the error_handler.
 */
MediaPlayer* media_player_new(const gchar* uri, gboolean loop, MediaPlayerStateChangeCallback state_callback, gpointer data, MediaPlayerErrorHandler error_handler, gpointer error_data)
{
    MediaPlayer* player;

    // Initialize struct
    player = g_new(MediaPlayer, 1);

    player->loop = loop;
    player->state = MEDIA_PLAYER_STOPPED;
    player->watch_id = 0;

    player->state_changed = state_callback;
    player->state_changed_data = data;
    player->error_handler = error_handler;
    player->error_handler_data = error_data;

    // Initialize GStreamer
    gst_init(NULL, NULL);

    /* Set up player */
    player->player = gst_element_factory_make("playbin", "player");

    if(!player->player) {
        g_critical("Could not create player. Try running with `GST_DEBUG=WARNING alarm-clock-applet`.");
        g_free(player);
        return NULL;
    }

    // Set uri
    g_object_set(player->player, "uri", uri, NULL);

    return player;
}

/**
 * Free a media player.
 */
void media_player_free(MediaPlayer* player)
{
    g_assert(player);

    if(player->player)
        gst_object_unref(GST_OBJECT(player->player));

    g_free(player);
}

/**
 * Set the uri of player.
 */
void media_player_set_uri(MediaPlayer* player, const gchar* uri)
{
    g_assert(player);

    g_object_set(player->player, "uri", uri, NULL);
}

/**
 * Get the uri of player.
 *
 * Free with g_free()
 */
gchar* media_player_get_uri(MediaPlayer* player)
{
    gchar* uri;

    g_assert(player);

    g_object_get(player->player, "uri", &uri, NULL);

    return uri;
}

/**
 * Set media player state.
 */
void media_player_set_state(MediaPlayer* player, MediaPlayerState state)
{
    g_assert(player);

    MediaPlayerState old = player->state;

    player->state = state;

    // Notify state change handler
    if(old != state && player->state_changed)
        player->state_changed(player, player->state, player->state_changed_data);
}


/**
 * Check for errors & call error handler
 */
static gboolean media_player_bus_check_errors(MediaPlayer* player, GstMessage* message)
{
    //	g_debug ("Got %s message\n", GST_MESSAGE_TYPE_NAME (message));

    switch(GST_MESSAGE_TYPE(message)) {
    case GST_MESSAGE_ERROR:
    {
        GError* err;
        gchar* debug;

        gst_message_parse_error(message, &err, &debug);

        if(player->error_handler)
            player->error_handler(player, err, player->error_handler_data);

        g_error_free(err);
        g_free(debug);

        return FALSE;
        break;
    }
    default:
        break;
    }

    // No errors
    return TRUE;
}

/**
 * GST bus callback.
 */
static gboolean media_player_bus_cb(GstBus* bus, GstMessage* message, MediaPlayer* player)
{
    GstState state;
    //	g_debug ("Got %s message\n", GST_MESSAGE_TYPE_NAME (message));

    if(!media_player_bus_check_errors(player, message)) {
        // There were errors
        media_player_stop(player);

        return FALSE;
    }

    switch(GST_MESSAGE_TYPE(message)) {
    case GST_MESSAGE_ASYNC_DONE:
        g_debug("GST_MESSAGE_ASYNC_DONE");
        gst_element_get_state(player->player, &state, NULL, GST_CLOCK_TIME_NONE);
        if(state == GST_STATE_PAUSED) {
            gst_element_seek(player->player, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_SEGMENT, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
            gst_element_set_state(player->player, GST_STATE_PLAYING);
        }
        break;
    case GST_MESSAGE_SEGMENT_DONE:
        g_debug("GST_MESSAGE_SEGMENT_DONE");
        // End of segment. Do we loop?
        if(player->loop) {
            // Perform a segment seek to the beginning of the stream
            gst_element_seek(player->player, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_SEGMENT, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
        } else {
            // Perform a normal seek so we reach EOS
            gst_element_seek(player->player, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_NONE, 0, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
        }

        break;
    case GST_MESSAGE_EOS:
        g_debug("GST_MESSAGE_EOS");
        media_player_stop(player);
        break;
    default:
        break;
    }

    return TRUE;
}

/**
 * Start media player
 */
void media_player_start(MediaPlayer* player)
{
    GstBus* bus;

    g_assert(player);

    // Attach bus watcher
    bus = gst_pipeline_get_bus(GST_PIPELINE(player->player));
    player->watch_id = gst_bus_add_watch(bus, (GstBusFunc)media_player_bus_cb, player);
    gst_object_unref(bus);

    gst_element_set_state(player->player, GST_STATE_PAUSED);
    media_player_set_state(player, MEDIA_PLAYER_PLAYING);
}

/**
 * Stop player
 */
void media_player_stop(MediaPlayer* player)
{
    g_assert(player);

    if(player->watch_id) {
        g_source_remove(player->watch_id);

        player->watch_id = 0;
    }

    if(player->player != NULL) {
        gst_element_set_state(player->player, GST_STATE_NULL);
    }

    media_player_set_state(player, MEDIA_PLAYER_STOPPED);
}

/*
 * }} Media player
 */
