/* gstreamermm - a C++ wrapper for gstreamer
 *
 * Copyright 2008-2009 The gstreamermm Development Team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <gst/audio/gstringbuffer.h>
#include <gstreamermm/caps.h>
#include <gstreamermm/object.h>
#include <gstreamermm/format.h>
#include <glibmm/arrayhandle.h>
#include <memory>

_DEFS(gstreamermm,gst)

namespace Gst
{

_WRAP_ENUM(RingBufferSegState, GstRingBufferSegState)
_WRAP_ENUM(RingBufferState, GstRingBufferState)
_WRAP_ENUM(BufferFormat, GstBufferFormat)
_WRAP_ENUM(BufferFormatType, GstBufferFormatType)

/** A class containing the format specification of a Gst::RingBuffer.
 * Gst::RingBufferSpec contains the format specification of a Gst::RingBuffer.
 * The "in" members should be specified by the caller of the
 * Gst::RingBuffer::acquire() method while the "in/out" members may be set by
 * the caller but are also modifiable by Gst::RingBuffer::acquire().  The
 * "out" members are generated as a result of the call to
 * Gst::RingBuffer::acquire().
 * @see Gst::RingBuffer::acquire().
 */
class RingBufferSpec
{
    _CLASS_GENERIC(RingBufferSpec, GstRingBufferSpec)
public:
  /** Default constructor.
   * @throw std::runtime_error if memory is unavailable for the new
   * Gst::RingBufferSpec.
   */
  RingBufferSpec();

  /** Fully construct a Gst::RingBufferSpec.  Only the "(in)" parameters are
   * required.  The "(in/out)" parameters are optional and may be modified by
   * the call to the Gst::RingBuffer::acquire() method.
   * @param caps The caps of the buffer (in).
   * @param type The sample type (in/out).
   * @param format The sample format (in/out).
   * @param sign The sample sign (in/out).
   * @param bigend The endianness of the samples (in/out).
   * @param width The width of the samples (in/out).
   * @param depth The depth of the samples (in/out).
   * @param rate The sample rate (in/out).
   * @param channels The number of channels (in/out).
   * @param latency_time The latency in microseconds (in/out).
   * @param buffer_time The total buffer size in microseconds (in/out).
   * @param segsize The size of one segment in bytes (in/out).
   * @param segtotal The total number of segments (in/out).
   * @param seglatency Number of segments queued in the lower level device,
   * defaults to @a segtotal in the C API (in/out).
   *
   * @throw std::runtime_error if memory is unavailable for the new
   * Gst::RingBufferSpec.
   */
  RingBufferSpec(const Glib::RefPtr<Gst::Caps>& caps,
    Gst::BufferFormatType type = Gst::BUFTYPE_LINEAR,
    Gst::BufferFormat format = Gst::UNKNOWN, bool sign = false,
    bool bigend = false, int width = 0, int depth = 0, int rate = 0,
    int channels = 0, guint64 latency_time = 0, guint64 buffer_time = 0,
    int segsize = 0, int segtotal = 0, int seglatency = 0);

  /// Construct a Gst::RingBufferSpec from a GstRingBufferSpec.
  explicit RingBufferSpec(GstRingBufferSpec& castitem,
    bool take_ownership = false);

  /** Copy constructor.
   *
   * @throw std::runtime_error if memory is unavailable for the new
   * Gst::RingBufferSpec.
   */
  RingBufferSpec(const RingBufferSpec& other);

  /// Assignment operator.
  RingBufferSpec& operator=(const RingBufferSpec& other);

  /// Destructor.
  virtual ~RingBufferSpec();

  void swap(RingBufferSpec& other);

  /// Gets the underlying gobject.
  GstRingBufferSpec* gobj() { return m_spec; };

  /// Gets the underlying gobject.
  const GstRingBufferSpec* gobj() const { return m_spec; };

  /** Get the caps of the buffer (in). */
  _MEMBER_GET_GOBJECT(caps, caps, Gst::Caps, GstCaps*)

  /** Set the caps of the buffer (in). */
  _MEMBER_SET_GOBJECT(caps, caps, Gst::Caps, GstCaps*)

  /** Get the sample type (in/out).
   */
  _MEMBER_GET(type, type, Gst::BufferFormatType, GstBufferFormatType)

  /** Set the sample type (in/out).
   */
  _MEMBER_SET(type, type, Gst::BufferFormatType, GstBufferFormatType)

  /** Get the sample format (in/out).
   */
  _MEMBER_GET(format, format, Gst::BufferFormat, GstBufferFormat)

  /** Set the sample format (in/out).
   */
  _MEMBER_SET(format, format, Gst::BufferFormat, GstBufferFormat)

  /** Get the sample sign (in/out).
   */
  _MEMBER_GET(sign, sign, bool, gboolean)

  /** Set the sample sign (in/out).
   */
  _MEMBER_SET(sign, sign, bool, gboolean)

  /** Get the endianness of the samples (in/out).
   */
  _MEMBER_GET(bigend, bigend, bool, gboolean)

  /** Set the endianness of the samples (in/out).
   */
  _MEMBER_SET(bigend, bigend, bool, gboolean)

  /** Get the width of the samples (in/out).
   */
  _MEMBER_GET(width, width, int, gint)

  /** Set the width of the samples (in/out).
   */
  _MEMBER_SET(width, width, int, gint)

  /** Get the depth of the samples (in/out).
   */
  _MEMBER_GET(depth, depth, int, gint)

  /** Set the depth of the samples (in/out).
   */
  _MEMBER_SET(depth, depth, int, gint)

  /** Get the samplerate (in/out).
   */
  _MEMBER_GET(rate, rate, int, gint)

  /** Set the samplerate (in/out).
   */
  _MEMBER_SET(rate, rate, int, gint)

  /** Get the number of channels (in/out).
   */
  _MEMBER_GET(channels, channels, int, gint)

  /** Set the number of channels (in/out).
   */
  _MEMBER_SET(channels, channels, int, gint)

  /** Get the latency in microseconds (in/out).
   */
  _MEMBER_GET(latency_time, latency_time, guint64, guint64)

  /** Set the latency in microseconds (in/out).
   */
  _MEMBER_SET(latency_time, latency_time, guint64, guint64)

  /** Get the total buffer size in microseconds (in/out).
   */
  _MEMBER_GET(buffer_time, buffer_time, guint64, guint64)

  /** Set the total buffer size in microseconds (in/out).
   */
  _MEMBER_SET(buffer_time, buffer_time, guint64, guint64)

  /** Get the size of one segment in bytes (in/out).
   */
  _MEMBER_GET(segsize, segsize, int, gint)

  /** Set the size of one segment in bytes (in/out).
   */
  _MEMBER_SET(segsize, segsize, int, gint)

  /** Get the total number of segments (in/out).
   */
  _MEMBER_GET(segtotal, segtotal, int, gint)

  /** Set the total number of segments (in/out).
   */
  _MEMBER_SET(segtotal, segtotal, int, gint)

  /** Get the number of segments queued in the lower level device, defaults to
   * segtotal (in/out).
   */
  _MEMBER_GET(seglatency, seglatency, int, gint)

  /** Set the number of segments queued in the lower level device, defaults to
   * segtotal (in/out).
   */
  _MEMBER_SET(seglatency, seglatency, int, gint)

  /** Get the number of bytes of one sample (out).  This is set by the call to
   * Gst::RingBuffer::acquire().
   */
  _MEMBER_GET(bytes_per_sample, bytes_per_sample, int, gint)

  /** Set the number of bytes of one sample (out).  This is set by the call to
   * Gst::RingBuffer::acquire() and should probably not be set.
   */
  _MEMBER_SET(bytes_per_sample, bytes_per_sample, int, gint)

// This constant is obtained from the "silence_sample" member in the
// GstRingBufferSpec structure definition.
#define GSTREAMERMM_RING_BUFFER_SPEC_SILENCE_SAMPLE 32

#m4 _CONVERSION(`guint8*', `Glib::ArrayHandle<guint8>', `Glib::ArrayHandle<guint8>($3, GSTREAMERMM_RING_BUFFER_SPEC_SILENCE_SAMPLE, Glib::OWNERSHIP_NONE)')
  /** Get the bytes representing one sample of silence (out).  This is set by
   * the call to Gst::RingBuffer::acquire().
   */
  _MEMBER_GET(silence_sample, silence_sample, Glib::ArrayHandle<guint8>, guint8*)

  /** Sets the bytes representing one sample of silence (out).  This is set by
   * the call to Gst::RingBuffer::acquire() and probably should not be set.
   */
  void set_silence_sample(const Glib::ArrayHandle<guint8>& silence_sample);

protected:
#ifndef DOXYGEN_SHOULD_SKIP_THIS
  GstRingBufferSpec* m_spec;
  // Tells whether the m_spec member should be freed upon destruction.
  bool take_ownership;
#endif /* DOXYGEN_SHOULD_SKIP_THIS */
};

/** A base class for audio ringbuffer implementations.
 * This object is the base class for audio ringbuffers used by the base audio
 * source and sink classes.
 *
 * The ringbuffer abstracts a circular buffer of data. One reader and one
 * writer can operate on the data from different threads in a lockfree manner.
 * The base class is sufficiently flexible to be used as an abstraction for
 * DMA based ringbuffers as well as a pure software implementations.
 *
 * Last reviewed on 2006-02-02 (0.10.4).
 * @ingroup GstBaseClasses
 */
class RingBuffer : public Gst::Object
{
  _CLASS_GOBJECT(RingBuffer, GstRingBuffer, GST_RING_BUFFER, Gst::Object, GstObject)

public:
  /** For example,
   * bool on_fill(const Glib::RefPtr<Gst::RingBuffer>& rbuf,
   * const std::vector<guint8>& data, guint len);.
   * This slot is set with set_fill_slot() and is called to fill the memory at
   * data with len bytes of samples.
   */
  typedef sigc::slot<void, const Glib::ArrayHandle<guint8>&, guint> SlotFill;

  //TODO: _MEMBER_GET(cond, cond, Glib::Cond, GCond*)

  /** Sets the given fill slot on the buffer. The slot will be called every
   * time a segment has been written to a device.
   *
   * MT safe.
   *
   * @param slot The fill slot to set.
   */
  void set_fill_slot(const SlotFill& slot);
  _IGNORE(gst_ring_buffer_set_callback)

  _WRAP_METHOD(bool acquire(Gst::RingBufferSpec& spec), gst_ring_buffer_acquire)
  _WRAP_METHOD(bool release(), gst_ring_buffer_release)
  _WRAP_METHOD(bool is_acquired() const, gst_ring_buffer_is_acquired)
  _WRAP_METHOD(bool activate(bool active), gst_ring_buffer_activate)
  _WRAP_METHOD(bool is_active() const, gst_ring_buffer_is_active)
  _WRAP_METHOD(bool start(), gst_ring_buffer_start)
  _WRAP_METHOD(bool pause(), gst_ring_buffer_pause)
  _WRAP_METHOD(bool stop(), gst_ring_buffer_stop)
  _WRAP_METHOD(guint get_delay() const, gst_ring_buffer_delay)
  _WRAP_METHOD(guint64 get_samples_done() const, gst_ring_buffer_samples_done)
  _WRAP_METHOD(void set_sample(guint64 sample), gst_ring_buffer_set_sample)

#m4 _CONVERSION(`const Glib::ArrayHandle<guchar>&', `guchar*', `const_cast<guchar*>($3.data())')
  _WRAP_METHOD(guint commit(guint64 sample, const Glib::ArrayHandle<guchar>& data, guint len), gst_ring_buffer_commit)
  _WRAP_METHOD(guint commit(guint64& sample, const Glib::ArrayHandle<guchar>& data, int in_samples, int out_samples, int& accum), gst_ring_buffer_commit_full)

  _WRAP_METHOD(bool convert(Gst::Format src_fmt, gint64 src_val, Gst::Format dest_fmt, gint64& dest_val) const, gst_ring_buffer_convert)

  _WRAP_METHOD_DOCS_ONLY(gst_ring_buffer_prepare_read)
  bool prepare_read(int& segment, std::vector<guint8>& readptr, int& len);

  _WRAP_METHOD(guint read(guint64 sample, const Glib::ArrayHandle<guchar>& data, guint len), gst_ring_buffer_read)

  _WRAP_METHOD(void clear(int segment), gst_ring_buffer_clear)
  _WRAP_METHOD(void clear_all(), gst_ring_buffer_clear_all)
  _WRAP_METHOD(void advance(guint advance), gst_ring_buffer_advance)
  _WRAP_METHOD(bool close_device(), gst_ring_buffer_close_device)
  _WRAP_METHOD(bool open_device(), gst_ring_buffer_open_device)
  _WRAP_METHOD(bool device_is_open() const, gst_ring_buffer_device_is_open)
  _WRAP_METHOD(void set_may_start(bool allowed), gst_ring_buffer_may_start)
  _WRAP_METHOD(static bool parse_caps(Gst::RingBufferSpec&, const Glib::RefPtr<Gst::Caps>& caps), gst_ring_buffer_parse_caps)
  _WRAP_METHOD(void set_flushing(bool flushing), gst_ring_buffer_set_flushing)

  /** Virtual function to open the device.  Don't set any params or allocate
   * anything.
   */
  _WRAP_VFUNC(bool open_device(), "open_device")

  /** Virtual function to allocate the resources for the ring buffer using the
   * given spec.
   */
  virtual bool acquire_vfunc(Gst::RingBufferSpec& spec);

  /** Virtual function to free resources of the ring buffer.
   */
  _WRAP_VFUNC(bool release(), "release")

  /** Virtual function to close the device.
   */
  _WRAP_VFUNC(bool close_device(), "close_device")

  /** Virtual function to start processing of samples.
   */
  _WRAP_VFUNC(bool start(), "start")

  /** Virtual function to pause processing of samples.
   */
  _WRAP_VFUNC(bool pause(), "pause")

  /** Virtual function to resume processing of samples after pause.
   */
  _WRAP_VFUNC(bool resume(), "resume")

  /** Virtual function to stop processing of samples.
   */
  _WRAP_VFUNC(bool stop(), "stop")

  /** Virtual function to get number of samples queued in device.
   */
  _WRAP_VFUNC(guint delay(), "delay")

  /** Virtual function to activate the thread that starts pulling and
   * monitoring the consumed segments in the device. Since 0.10.22.
   */
  _WRAP_VFUNC(bool activate(bool active), "activate")

  /** Virtual function to write samples into the ring buffer.
   */
  virtual guint commit_vfunc(guint64& sample, const Glib::ArrayHandle<guchar>& data, int in_samples, int out_samples, int& accum);

  /** Virtual function to clear the entire ringbuffer Since 0.10.24.
   */
  _WRAP_VFUNC(void clear_all(), "clear_all")

protected:
#m4begin
  _PUSH(SECTION_PCC_CLASS_INIT_VFUNCS)
  klass->acquire = &acquire_vfunc_callback;
  klass->commit = &commit_vfunc_callback;
  _SECTION(SECTION_PH_VFUNCS)
  static gboolean acquire_vfunc_callback(GstRingBuffer* self, GstRingBufferSpec* spec);
  static guint commit_vfunc_callback(GstRingBuffer* self, guint64* sample, guchar* data, gint in_samples, gint out_samples, gint* accum);
  _POP()
#m4end

#ifndef DOXYGEN_SHOULD_SKIP_THIS
private:
  std::auto_ptr<SlotFill> m_slot;
#endif
};

} // namespace Gst
