#include "gstwestonimagesrc.h"
#include "gst/gstinfo.h"
#include <string.h>

GST_DEBUG_CATEGORY_STATIC(gst_westonimagesrc_debug);
#define GST_CAT_DEFAULT gst_westonimagesrc_debug

static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS("text/plain; charset=utf-8"));

enum { PROP_0, PROP_FRAMERATE, N_PROPERTIES };

static gboolean gst_westonimagesrc_start(GstBaseSrc *basesrc);
static gboolean gst_westonimagesrc_stop(GstBaseSrc *basesrc);
static GstFlowReturn gst_westonimagesrc_create(GstBaseSrc *basesrc, guint64 offset, guint size, GstBuffer **buf);
static gboolean gst_westonimagesrc_unlock(GstBaseSrc *basesrc);
static gboolean gst_westonimagesrc_unlock_stop(GstBaseSrc *basesrc);

static void gst_westonimagesrc_class_init(GstWestonImageSrcClass *klass);
static void gst_westonimagesrc_init(GstWestonImageSrc *westonimagesrc);
static void gst_westonimagesrc_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
static void gst_westonimagesrc_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);

G_DEFINE_TYPE(GstWestonImageSrc, gst_westonimagesrc, GST_TYPE_BASE_SRC);

static void gst_westonimagesrc_class_init(GstWestonImageSrcClass *klass) {
  GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
  GstElementClass *gstelement_class = GST_ELEMENT_CLASS(klass);
  GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS(klass);

  gobject_class->set_property = gst_westonimagesrc_set_property;
  gobject_class->get_property = gst_westonimagesrc_get_property;

  gstbasesrc_class->start = GST_DEBUG_FUNCPTR(gst_westonimagesrc_start);
  gstbasesrc_class->stop = GST_DEBUG_FUNCPTR(gst_westonimagesrc_stop);
  gstbasesrc_class->create = GST_DEBUG_FUNCPTR(gst_westonimagesrc_create);
  gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR(gst_westonimagesrc_unlock);
  gstbasesrc_class->unlock_stop = GST_DEBUG_FUNCPTR(gst_westonimagesrc_unlock_stop);

  // Регистрация свойств
  g_object_class_install_property(
      gobject_class, PROP_FRAMERATE,
      g_param_spec_double("framerate", "Frame rate", "Number of hello world messages per second", 0.f, 24.f, 1.f, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  gst_element_class_set_static_metadata(gstelement_class, "WestonImageSrc World Source", "Source", "Generates westonimagesrc world messages", "Your Name <your.email@example.com>");
  gst_element_class_add_static_pad_template(gstelement_class, &src_template);
  GST_DEBUG_CATEGORY_INIT(gst_westonimagesrc_debug, "westonimagesrc", 0, "WestonImageSrc World Plugin");
}

static void gst_westonimagesrc_init(GstWestonImageSrc *westonimagesrc) {
  gst_base_src_set_live(GST_BASE_SRC(westonimagesrc), TRUE);
  gst_base_src_set_format(GST_BASE_SRC(westonimagesrc), GST_FORMAT_TIME);
  gst_base_src_set_blocksize(GST_BASE_SRC(westonimagesrc), 0);

  westonimagesrc->running = FALSE;
  westonimagesrc->timeout_id = 0;
  westonimagesrc->offset = 0;
  westonimagesrc->framerate = 1.f;

  g_mutex_init(&westonimagesrc->lock);
  g_cond_init(&westonimagesrc->cond);
}

static void gst_westonimagesrc_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
  GstWestonImageSrc *westonimagesrc = GST_WESTONIMAGESRC(object);

  GST_OBJECT_LOCK(westonimagesrc);

  switch (prop_id) {
  case PROP_FRAMERATE: {
    gdouble new_framerate = g_value_get_double(value);

    if (new_framerate != westonimagesrc->framerate) {
      westonimagesrc->framerate = new_framerate;
      g_object_notify(object, "framerate");
    }
    break;
  }

  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    break;
  }

  GST_OBJECT_UNLOCK(westonimagesrc);
}

static void gst_westonimagesrc_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
  GstWestonImageSrc *westonimagesrc = GST_WESTONIMAGESRC(object);
  GST_OBJECT_LOCK(westonimagesrc);

  switch (prop_id) {
  case PROP_FRAMERATE:
    g_value_set_double(value, westonimagesrc->framerate);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    break;
  }

  GST_OBJECT_UNLOCK(westonimagesrc);
}

static gboolean gst_westonimagesrc_start(GstBaseSrc *basesrc) {
  GstWestonImageSrc *westonimagesrc = GST_WESTONIMAGESRC(basesrc);

  g_mutex_lock(&westonimagesrc->lock);
  westonimagesrc->running = TRUE;
  westonimagesrc->offset = 0;
  g_mutex_unlock(&westonimagesrc->lock);

  GST_DEBUG_OBJECT(westonimagesrc, "started");
  return TRUE;
}

static gboolean gst_westonimagesrc_stop(GstBaseSrc *basesrc) {
  GstWestonImageSrc *westonimagesrc = GST_WESTONIMAGESRC(basesrc);

  g_mutex_lock(&westonimagesrc->lock);
  westonimagesrc->running = FALSE;
  g_cond_signal(&westonimagesrc->cond);
  g_mutex_unlock(&westonimagesrc->lock);

  GST_DEBUG_OBJECT(westonimagesrc, "stopped");
  return TRUE;
}

static GstFlowReturn gst_westonimagesrc_create(GstBaseSrc *basesrc, guint64 offset, guint size, GstBuffer **buf) {
  GstWestonImageSrc *westonimagesrc = GST_WESTONIMAGESRC(basesrc);
  GstMapInfo map;
  gchar *message;
  gint message_len;
  GstClockTime timestamp;
  GstBuffer *buffer;

  g_mutex_lock(&westonimagesrc->lock);

  while (westonimagesrc->running) {
    GstClockTime wait_until = g_get_monotonic_time() + (1 / westonimagesrc->framerate) * G_TIME_SPAN_SECOND;

    if (g_cond_wait_until(&westonimagesrc->cond, &westonimagesrc->lock, wait_until)) {
      if (!westonimagesrc->running) {
        g_mutex_unlock(&westonimagesrc->lock);
        return GST_FLOW_FLUSHING;
      }
    }

    if (!westonimagesrc->running) {
      g_mutex_unlock(&westonimagesrc->lock);
      return GST_FLOW_FLUSHING;
    }

    message = g_strdup_printf("westonimagesrc world #%lu\n", (gulong)westonimagesrc->offset);
    message_len = strlen(message);

    buffer = gst_buffer_new_allocate(NULL, message_len, NULL);
    gst_buffer_map(buffer, &map, GST_MAP_WRITE);
    memcpy(map.data, message, message_len);
    gst_buffer_unmap(buffer, &map);
    g_free(message);

    timestamp = westonimagesrc->offset * GST_SECOND;
    GST_BUFFER_PTS(buffer) = timestamp;
    GST_BUFFER_DURATION(buffer) = GST_SECOND;
    GST_BUFFER_OFFSET(buffer) = westonimagesrc->offset;
    GST_BUFFER_OFFSET_END(buffer) = westonimagesrc->offset + 1;

    westonimagesrc->offset++;

    *buf = buffer;
    g_mutex_unlock(&westonimagesrc->lock);

    GST_DEBUG_OBJECT(westonimagesrc, "created buffer %" G_GUINT64_FORMAT, westonimagesrc->offset - 1);
    return GST_FLOW_OK;
  }

  g_mutex_unlock(&westonimagesrc->lock);
  return GST_FLOW_FLUSHING;
}

static gboolean gst_westonimagesrc_unlock(GstBaseSrc *basesrc) {
  GstWestonImageSrc *westonimagesrc = GST_WESTONIMAGESRC(basesrc);

  GST_DEBUG_OBJECT(westonimagesrc, "unlock");
  g_mutex_lock(&westonimagesrc->lock);
  westonimagesrc->running = FALSE;
  g_cond_signal(&westonimagesrc->cond);
  g_mutex_unlock(&westonimagesrc->lock);

  return TRUE;
}

static gboolean gst_westonimagesrc_unlock_stop(GstBaseSrc *basesrc) {
  GstWestonImageSrc *westonimagesrc = GST_WESTONIMAGESRC(basesrc);

  GST_DEBUG_OBJECT(westonimagesrc, "unlock stop");
  g_mutex_lock(&westonimagesrc->lock);
  westonimagesrc->running = TRUE;
  g_mutex_unlock(&westonimagesrc->lock);

  return TRUE;
}

static gboolean plugin_init(GstPlugin *plugin) { return gst_element_register(plugin, "westonimagesrc", GST_RANK_NONE, GST_TYPE_WESTONIMAGESRC); }

#ifndef PACKAGE
#  define PACKAGE "westonimagesrc"
#endif

GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR, westonimagesrc, "WestonImageSrc World source plugin", plugin_init, "1.0", "LGPL", "GStreamer", "http://gstreamer.net/")
