/*
 * sound_gstreamer.cxx
 *
 * Sound driver implementation.
 *
 * Portable Windows Library
 *
 * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is Portable Windows Library.
 *
 * The Initial Developer of the Original GSTREAMER Code is
 * Damien Sandras <dsandras@seconix.com>
 *
 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
 * All Rights Reserved.
 *
 * Contributor(s): /
 *
 * $Log: sound_gstreamer.cxx,v $
 */

#pragma implementation "sound_gstreamer.h"

#include <gst/interfaces/propertyprobe.h>
#include <gst/base/gstbasesrc.h>

#include "sound_gstreamer.h"
#include "gstappsrc.h"
#include "gstappbuffer.h"
#include "no_collision_dict.h"

PCREATE_SOUND_PLUGIN(GSTREAMER, PSoundChannelGSTREAMER);

class GSTDevice : public PObject
{
public:

  GSTDevice (PString desc)
    : description(desc) {}

  PString description;

  void PrintOn (ostream &stream) const
  {
    stream << description;
  }
};

static PNoCollisionDictionary<GSTDevice> playerDevices;
static PNoCollisionDictionary<GSTDevice> recorderDevices;
static PMutex devicesMutex;

/* helper functions */

static void
detect_standard_devices ()
{
  recorderDevices.AddNoClash ("beep", new GSTDevice ("audiotestsrc"));
  playerDevices.AddNoClash ("auto", new GSTDevice ("autoaudiosink"));
}

static void
detect_alsa_sources ()
{
  GstElement *elt = NULL;
  GstPropertyProbe *probe = NULL;
  const GParamSpec *pspec = NULL;
  GValueArray *array = NULL;
  gint index = 0;
  GValue *device = NULL;
  gchar *name = NULL;

  elt = gst_element_factory_make ("alsasrc", "test");

  if (elt == NULL) {

    PTRACE (2, "No alsasrc element available");
    return;
  }

  gst_element_set_state (elt, GST_STATE_PAUSED);
  probe = GST_PROPERTY_PROBE (elt);
  pspec = gst_property_probe_get_property (probe, "device");

  array = gst_property_probe_probe_and_get_values (probe, pspec);
  if (array == NULL) {

    PTRACE (2, "No device property to probe");
    return;
  }

  for (index = 0; index < array->n_values; index++) {

    device = g_value_array_get_nth (array, index);
    g_object_set_property (G_OBJECT (elt), "device", device);

    g_object_get (G_OBJECT (elt), "device-name", &name, NULL);
    if (name)
      recorderDevices.AddNoClash (name, new GSTDevice ("alsasrc device=" + PString (g_value_get_string (device))));
    else
      recorderDevices.AddNoClash ("Unnamed", new GSTDevice ("alsasrc device=" + PString (g_value_get_string (device))));
  }
}

static void
detect_alsa_sinks ()
{
  GstElement *elt = NULL;
  GstPropertyProbe *probe = NULL;
  const GParamSpec *pspec = NULL;
  GValueArray *array = NULL;
  gint index = 0;
  GValue *device = NULL;
  gchar *name = NULL;

  elt = gst_element_factory_make ("alsasink", "test");

  if (elt == NULL) {

    PTRACE (2, "No alsasink element available");
    return;
  }

  gst_element_set_state (elt, GST_STATE_PAUSED);
  probe = GST_PROPERTY_PROBE (elt);
  pspec = gst_property_probe_get_property (probe, "device");

  array = gst_property_probe_probe_and_get_values (probe, pspec);
  if (array == NULL) {

    PTRACE (2, "No device property to probe");
    return;
  }

  for (index = 0; index < array->n_values; index++) {

    device = g_value_array_get_nth (array, index);
    g_object_set_property (G_OBJECT (elt), "device", device);

    g_object_get (G_OBJECT (elt), "device-name", &name, NULL);
    if (name)
      playerDevices.AddNoClash (name, new GSTDevice ("alsasink device=" + PString (g_value_get_string (device))));
    else
      playerDevices.AddNoClash ("Unnamed", new GSTDevice ("alsasink device=" + PString (g_value_get_string (device))));
  }
}

static void
detect_alsa_devices ()
{
  detect_alsa_sources ();
  detect_alsa_sinks ();
}

static gboolean
on_message (GstBus *bus,
	    GstMessage *message,
	    gpointer data)
{
  return ((PSoundChannelGSTREAMER *) data)->OnMessage (bus, message);
}

static void
on_handoff_read_from_gstreamer (GstElement *elt,
				GstBuffer *buffer,
				GstPad *pad,
				gpointer data)
{
  ((PSoundChannelGSTREAMER *) data)->OnHandoffReadFromGstreamer (elt, buffer, pad);
}


///////////////////////////////////////////////////////////////////////////////

PSoundChannelGSTREAMER::PSoundChannelGSTREAMER()
{
  myMutex = g_mutex_new ();
  myCond = g_cond_new ();
  pipeline = NULL;
  InitializeGStreamer();
}


PSoundChannelGSTREAMER::PSoundChannelGSTREAMER (const PString &device,
                                                Directions dir,
                                                unsigned numChannels,
                                                unsigned sampleRate,
                                                unsigned bitsPerSample)
{
  myMutex = g_mutex_new ();
  myCond = g_cond_new ();
  pipeline = NULL;
  InitializeGStreamer();
  Open (device, dir, numChannels, sampleRate, bitsPerSample);
}


void PSoundChannelGSTREAMER::InitializeGStreamer()
{
  static BOOL gst_init_done = FALSE;
  int argc = 0;
  char **argv = { NULL };

  if (!gst_init_done)
    gst_init (&argc, &argv);

  gst_init_done = TRUE;
}


PSoundChannelGSTREAMER::~PSoundChannelGSTREAMER()
{
  Close();

  if (pipeline)
    gst_object_unref (pipeline);

  g_mutex_free (myMutex);
  g_cond_free (myCond);
}


PStringArray PSoundChannelGSTREAMER::GetDeviceNames (Directions dir)
{
  PWaitAndSignal m(devicesMutex);

  InitializeGStreamer ();

  playerDevices.RemoveAll ();
  recorderDevices.RemoveAll ();

  detect_standard_devices ();
  detect_alsa_devices ();

  if (dir == Player)
    return playerDevices.GetKeys ();
  else
    return recorderDevices.GetKeys ();
}


PString PSoundChannelGSTREAMER::GetDefaultDevice(Directions dir)
{
  PStringArray devicenames;
  devicenames = PSoundChannelGSTREAMER::GetDeviceNames (dir);

  return devicenames[0];
}


BOOL PSoundChannelGSTREAMER::Open (const PString & _device,
                                   Directions _dir,
                                   unsigned _numChannels,
                                   unsigned _sampleRate,
                                   unsigned _bitsPerSample)
{
  PWaitAndSignal m(devicesMutex);

  GstState current;
  GstStateChangeReturn change;
  GSTDevice device ("");
  GstElement *source = NULL;
  GstElement *filter = NULL;
  GstElement *sink = NULL;
  GError *error = NULL;
  GstBus *bus = NULL;

  Close();

  mNumChannels = _numChannels;
  mSampleRate = _sampleRate;
  mBitsPerSample = _bitsPerSample;
  direction = _dir;
  isInitialised = FALSE;

  if (direction == Player) {

    device = playerDevices[_device];
    sink = gst_parse_launch (device.description, &error);
    if (sink == NULL) {

      if (error) {

	PTRACE (3, error->message);
	g_error_free (error);
      } else {

	PTRACE (3, "GStreamer didn't even set an error!");
      }
      return FALSE;
    }
    source = (GstElement *)g_object_new (GST_TYPE_APP_SRC, "name", "ekiga_source", NULL);
    gst_base_src_set_live (GST_BASE_SRC (source), TRUE);

  } else { /* direction == Recorder */

    device = recorderDevices[_device];

    source = gst_parse_launch (device.description, &error);
    if (source == NULL) {

      if (error) {

	PTRACE (3, error->message);
	g_error_free (error);
      } else {

	PTRACE (3, "GStreamer didn't even set an error!");
      }
      return FALSE;
    }
    sink = gst_element_factory_make ("fakesink", "ekiga_sink");
    g_object_set (G_OBJECT (sink),
                  "signal-handoffs", TRUE,
		  "sync", TRUE,
		  NULL);
    g_signal_connect (sink, "handoff", G_CALLBACK (on_handoff_read_from_gstreamer), this);
  }

  pipeline = gst_pipeline_new ("pipeline");

  filter = gst_element_factory_make ("capsfilter", "ekiga_filter");
  g_object_set (G_OBJECT (filter), "caps",
		gst_caps_new_simple ("audio/x-raw-int",
				     "width", G_TYPE_INT, mBitsPerSample,
				     "depth", G_TYPE_INT, mBitsPerSample,
				     "signed", G_TYPE_BOOLEAN, TRUE,
				     "rate", G_TYPE_INT, mSampleRate,
				     "channels", G_TYPE_INT, mNumChannels,
				     NULL),
		NULL);

  gst_bin_add_many (GST_BIN (pipeline), source, filter, sink, NULL);
  if (!gst_element_link_many (source, filter, sink, NULL)) {

    PTRACE(3, "Couldn't link the elements in the pipeline");
    gst_object_unref (GST_OBJECT (pipeline));
    pipeline = NULL;
    return FALSE;
  }

  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
  gst_bus_add_watch (bus, on_message, this);
  gst_object_unref (bus);

  change = gst_element_set_state (pipeline, GST_STATE_READY);

  /* wait at most 1s before it's done */
  (void)gst_element_get_state (pipeline, &current, NULL, GST_SECOND);

  if (current != GST_STATE_READY) {

    PTRACE(3, "Couldn't put the pipeline to ready");
    gst_element_set_state (pipeline, GST_STATE_NULL);
    (void)gst_element_get_state (pipeline, NULL, NULL, GST_SECOND);
    gst_object_unref (GST_OBJECT (pipeline));
    pipeline = NULL;
    return FALSE;
  }

  /* don't wait since it won't happen as long as we don't push data through */
  (void)gst_element_set_state (pipeline, GST_STATE_PLAYING);

  return TRUE;
}


BOOL PSoundChannelGSTREAMER::Setup()
{
  BOOL no_error = TRUE;

  isInitialised = TRUE;

  return no_error;
}


BOOL PSoundChannelGSTREAMER::Close()
{
  if (!IsOpen ())
    return TRUE;

  gst_element_set_state (pipeline, GST_STATE_NULL);
  (void)gst_element_get_state (pipeline, NULL, NULL, GST_SECOND);

  return TRUE;
}


BOOL PSoundChannelGSTREAMER::Write (const void *buf, PINDEX len)
{
  GstElement *source = NULL;
  GstBuffer *buffer = NULL;
  gpointer data = NULL;

  source = gst_bin_get_by_name (GST_BIN (pipeline), "ekiga_source");
  data = g_malloc (len);
  memcpy (data, buf, len);
  buffer = gst_app_buffer_new (data, len, g_free, data);
  gst_app_src_push_buffer (GST_APP_SRC (source), buffer);

  return TRUE;
}


BOOL PSoundChannelGSTREAMER::Read (void *buf, PINDEX len)
{
  int size;
  char *outgoingBuffer = (char *) buf;

  lastReadCount = 0;

  while (lastReadCount < len) {

    g_mutex_lock (myMutex);
    while (myBuffer == NULL)
      g_cond_wait (myCond, myMutex);

    size = PMIN (GST_BUFFER_SIZE (myBuffer), (len - lastReadCount));
    memcpy ((void *)&outgoingBuffer [lastReadCount],
	    GST_BUFFER_DATA (myBuffer),
	    size);
    lastReadCount += size;
    myBuffer = NULL;
    g_mutex_unlock (myMutex);
  }

  return TRUE;
}


gboolean
PSoundChannelGSTREAMER::OnMessage (GstBus *bus,
				   GstMessage *message)
{
  if ((message->type & GST_MESSAGE_EOS)
      || (message->type & GST_MESSAGE_ERROR)) {

    g_mutex_lock (myMutex);
    PTRACE (2, "gstreamer message eos or error");
    g_mutex_unlock (myMutex);
  }

  return TRUE;
}

void
PSoundChannelGSTREAMER::OnHandoffReadFromGstreamer (GstElement *elt,
						    GstBuffer *buffer,
						    GstPad *pad)
{
  g_mutex_lock (myMutex);

  if (myBuffer == NULL) {

    myBuffer = gst_buffer_copy (buffer);
    g_cond_broadcast (myCond);
  }

  g_mutex_unlock (myMutex);
}


BOOL PSoundChannelGSTREAMER::SetFormat (unsigned numChannels,
					unsigned sampleRate,
					unsigned bitsPerSample)
{
  /* check parameters */
  PAssert((bitsPerSample == 8) || (bitsPerSample == 16), PInvalidParameter);
  PAssert(numChannels >= 1 && numChannels <= 2, PInvalidParameter);

  mNumChannels   = numChannels;
  mSampleRate    = sampleRate;
  mBitsPerSample = bitsPerSample;

  /* mark this channel as uninitialised */
  isInitialised = FALSE;

  return TRUE;
}


unsigned PSoundChannelGSTREAMER::GetChannels()   const
{
  return mNumChannels;
}


unsigned PSoundChannelGSTREAMER::GetSampleRate() const
{
  return mSampleRate;
}


unsigned PSoundChannelGSTREAMER::GetSampleSize() const
{
  return mBitsPerSample;
}


BOOL PSoundChannelGSTREAMER::SetBuffers (PINDEX size, PINDEX count)
{
  //storedPeriods = count;

  //storedSize = size;

  return TRUE;
}


BOOL PSoundChannelGSTREAMER::GetBuffers(PINDEX & size, PINDEX & count)
{
  //size = storedSize;
  //count = storedPeriods;

  return FALSE;
}


BOOL PSoundChannelGSTREAMER::PlaySound(const PSound & sound, BOOL wait)
{

  if (!Write((const BYTE *)sound, sound.GetSize()))
    return FALSE;

  if (wait)
    return WaitForPlayCompletion();

  return TRUE;
}


BOOL PSoundChannelGSTREAMER::PlayFile(const PFilePath & filename, BOOL wait)
{
  BYTE buffer [512];

  PFile file (filename, PFile::ReadOnly);

  if (!file.IsOpen())
    return FALSE;

  for (;;) {

    if (!file.Read (buffer, 512))
      break;

    PINDEX len = file.GetLastReadCount();
    if (len == 0)
      break;
    if (!Write(buffer, len))
      break;
  }

  file.Close();

  if (wait)
    return WaitForPlayCompletion();

  return TRUE;
}


BOOL PSoundChannelGSTREAMER::HasPlayCompleted()
{
  //FIXME

  return TRUE;
}


BOOL PSoundChannelGSTREAMER::WaitForPlayCompletion()
{
  //FIXME

  return TRUE;
}


BOOL PSoundChannelGSTREAMER::RecordSound(PSound & sound)
{
  return FALSE;
}


BOOL PSoundChannelGSTREAMER::RecordFile(const PFilePath & filename)
{
  return FALSE;
}


BOOL PSoundChannelGSTREAMER::StartRecording()
{
  return FALSE;
}


BOOL PSoundChannelGSTREAMER::IsRecordBufferFull()
{
  return TRUE;
}


BOOL PSoundChannelGSTREAMER::AreAllRecordBuffersFull()
{
  return TRUE;
}


BOOL PSoundChannelGSTREAMER::WaitForRecordBufferFull()
{
  return TRUE;
}


BOOL PSoundChannelGSTREAMER::WaitForAllRecordBuffersFull()
{
  return FALSE;
}


BOOL PSoundChannelGSTREAMER::Abort()
{
  return TRUE;
}



BOOL PSoundChannelGSTREAMER::SetVolume (unsigned newVal)
{
  return TRUE;
}


BOOL  PSoundChannelGSTREAMER::GetVolume(unsigned &devVol)
{
  return TRUE;
}


BOOL PSoundChannelGSTREAMER::IsOpen () const
{
  return (pipeline != NULL);
}
