/*
 * 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 "sound_gstreamer.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_alsa_devices ()
{
  playerDevices.AddNoClash ("default", new GSTDevice ("alsasink"));
  recorderDevices.AddNoClash ("beep", new GSTDevice ("audiotestsrc"));
  recorderDevices.AddNoClash ("default", new GSTDevice ("alsasrc"));
}

static void
cb_write_handoff (GstElement *elt,
                  GstBuffer *buffer,
                  GstPad *pad,
                  gpointer user_data)
{
  ((PSoundChannelGSTREAMER *) user_data)->OnWriteHandoff (elt, buffer, pad);
}


static void
cb_read_handoff (GstElement *elt,
                 GstBuffer *buffer,
                 GstPad *pad,
                 gpointer user_data)
{
  ((PSoundChannelGSTREAMER *) user_data)->OnReadHandoff (elt, buffer, pad);
}


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

PSoundChannelGSTREAMER::PSoundChannelGSTREAMER()
{
  Construct();
}


PSoundChannelGSTREAMER::PSoundChannelGSTREAMER (const PString &device,
                                                Directions dir,
                                                unsigned numChannels,
                                                unsigned sampleRate,
                                                unsigned bitsPerSample)
{
  Construct();
  Open (device, dir, numChannels, sampleRate, bitsPerSample);
}


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

  pipeline = NULL;


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

  dataBuf = (gchar *) malloc (76800);
  memset (dataBuf, '0', 76800);
}


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


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

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

  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)
{
  GstState current;
  GSTDevice device ("");
  GstElement *source = NULL;
  GstElement *filter = NULL;
  GstElement *sink = NULL;
  GError *error = NULL;

  Close();

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

  if (direction == Player) {

    device = playerDevices[_device];
    cout << "Open with " << mBitsPerSample << " "
	 << mSampleRate << " "
	 << mNumChannels << " player\n";
    cout << "device : " << device.description << "\n";
    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 = gst_element_factory_make ("fakesrc", "ekiga_source");
    g_object_set (G_OBJECT (source),
		  "signal-handoffs", TRUE,
		  "sizemax", 480,
		  "sizetype", 2,
		  NULL);
    g_signal_connect (source, "handoff", G_CALLBACK (cb_write_handoff), this);
    dataBufReady = FALSE;

  } else { /* direction == Recorder */

    device = recorderDevices[_device];
    cout << "Open with " << mBitsPerSample << " "
	 << mSampleRate << " "
	 << mNumChannels << " recorder\n";
    cout << "device : " << device.description << "\n";

    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,
		  NULL);
    g_signal_connect (sink, "handoff", G_CALLBACK (cb_read_handoff), this);

    dataBufReady = TRUE;
  }

  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);
  gst_element_link_many (source, filter, sink, NULL);
  (void)gst_element_set_state (pipeline, GST_STATE_PLAYING);

  (void)gst_element_get_state (pipeline,
			       &current,
			       NULL,
			       GST_SECOND);

  if (current != GST_STATE_PLAYING) {

    PTRACE(3, "Couldn't put the pipeline to play");
    gst_object_unref (GST_OBJECT (pipeline));
    pipeline = NULL;
    return FALSE;
  }

  return TRUE;
}


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

  isInitialised = TRUE;

  return no_error;
}


BOOL PSoundChannelGSTREAMER::Close()
{
  if (pipeline) {

    gst_element_set_state (pipeline, GST_STATE_NULL);
    g_object_unref (pipeline);
  }

  pipeline = NULL;

  return TRUE;
}


BOOL PSoundChannelGSTREAMER::Write (const void *buf, PINDEX len)
{
  int bufPos = 0;
  char *incomingBuffer = (char *) buf;

  while (bufPos < len) {

    if (dataBufReady == FALSE) {

      PWaitAndSignal m(bufferMutex);
      memset (dataBuf, '0', 480); //FIXME we hardcode 480 but we shouldn't.
      memcpy (dataBuf, (const void *) &incomingBuffer [bufPos], PMIN (480, (len - bufPos)));
      bufPos += 480;
      dataBufReady = TRUE;
    }
  }

  lastWriteCount += len;
  return TRUE;
}


void
PSoundChannelGSTREAMER::OnWriteHandoff (GstElement *elt,
                                        GstBuffer *buffer,
                                        GstPad *pad)
{
  cout << "OnWriteHandoff\n";

  if (dataBufReady == TRUE) {

    PWaitAndSignal m(bufferMutex);
    memcpy (GST_BUFFER_DATA (buffer), (const void *) & dataBuf [0], 480);
    memset (dataBuf, '0', 480);
    dataBufReady = FALSE;
    printf ("Wrote 480 in a buffer of %d\n", GST_BUFFER_SIZE (buffer));
  }
}


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

  while (bufPos < len) {

    if (dataBufReady == FALSE) {

      PWaitAndSignal m(bufferMutex);
      memset (outgoingBuffer, 0, 480);
      memcpy ((void *) &outgoingBuffer [bufPos], dataBuf, PMIN (480, (len - bufPos)));
      bufPos += 480;
      dataBufReady = TRUE;
      //printf ("To Transmit (in buffer) %d\n", len);
    }
  }

  lastReadCount += len;

  return TRUE;
}


void
PSoundChannelGSTREAMER::OnReadHandoff (GstElement *elt,
                                       GstBuffer *buffer,
                                       GstPad *pad)
{
  cout << "OnReadHandoff\n";
  if (dataBufReady == TRUE) {

    PWaitAndSignal m(bufferMutex);
    memset (dataBuf, '0', 480);
    memcpy ((void *) & dataBuf [0], GST_BUFFER_DATA (buffer), 480);
    dataBufReady = FALSE;
    printf ("Wrote 480 in a buffer of size %d\n", GST_BUFFER_SIZE (buffer));
  }
}


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;

  printf ("End of PlaySound\n");

  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 TRUE;
}
