/* CSL - Common Sound Layer
 * Copyright (C) 2000-2001 Stefan Westerfeld and Tim Janik
 *
 * 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 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., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include	"cslpcm.h"

#include	"cslutils.h"
#include	"cslprivate.h"
#include	<string.h>


/* --- functions --- */
static CslErrorType
pcm_open_internal (CslDriver       *driver,
		   const char      *role,
		   unsigned int     rate,
		   unsigned int     n_channels,
		   CslPcmFormatType format,
		   CslBool          readable,
		   CslPcmStream   **stream_p)
{
  CslErrorType error;
  CslPcmStream *stream = NULL;

  csl_return_val_if_fail (driver != NULL, CSL_EINTERN);
  csl_return_val_if_fail (driver->pcm_vtable != NULL, CSL_EINTERN);
  csl_return_val_if_fail (role != NULL, CSL_EINTERN);
  csl_return_val_if_fail (stream_p != NULL, CSL_EINTERN);

  DRIVER_LOCK (driver);

  error = driver->pcm_vtable->stream_init (driver,
					   role,
					   rate,
					   n_channels,
					   format,
					   readable,
					   &stream);
  if (error)
    {
      if (stream)
	driver->pcm_vtable->stream_destroy (stream);
      *stream_p = NULL;
    }
  else
    *stream_p = stream;

  DRIVER_UNLOCK (driver);

  return error;
}

/**
 * Open a PCM stream for output, specifying a driver and data format.
 *
 * @param driver A pointer to a previously opened CSL driver.
 * @param role A, identifier string used by the underlying driver.
 * The role must be constant for a stream, should not be
 * internationalized, and should should be different for different
 * kinds of streams For eexample, role coul be assigned to something
 * like "quake", "noatun", or "quake-sounds". This argument is
 * mandatory.
 * @param rate Sampling rate in samples/second.
 * @param n_channels Number of channels.
 * @param format PCM data format.
 * @param stream_p A pointer to write the opened stream.
 * @return Error code indicating success or failure.
 * @see csl_pcm_open_input
 * @see csl_pcm_close
 * @short Open PCM input stream.
 */
CslErrorType
csl_pcm_open_output (CslDriver       *driver,
		     const char      *role,
		     unsigned int     rate,
		     unsigned int     n_channels,
		     CslPcmFormatType format,
		     CslPcmStream   **stream_p)
{
  return pcm_open_internal (driver,
			    role,
			    rate,
			    n_channels,
			    format,
			    FALSE,
			    stream_p);
}

/**
 * Open a PCM stream for input, specifying a driver and data format.
 *
 * @param driver A pointer to a previously opened CSL driver.
 * @param role A, identifier string that can be used by the underlying driver
 * (see description under csl_pcm_open_output).
 * @param rate Sampling rate in samples/second.
 * @param n_channels Number of channels.
 * @param format PCM data format.
 * @param stream_p A pointer to write the opened stream.
 * @return Error code indicating success or failure.
 * @see csl_pcm_open_input
 * @see csl_pcm_close
 * @short Open PCM output stream.
 */
CslErrorType
csl_pcm_open_input (CslDriver       *driver,
		    const char      *role,
		    unsigned int     rate,
		    unsigned int     n_channels,
		    CslPcmFormatType format,
		    CslPcmStream   **stream_p)
{
  return pcm_open_internal (driver,
			    role,
			    rate,
			    n_channels,
			    format,
			    TRUE,
			    stream_p);
}

/**
 * Return format information about a PCM stream.
 *
 * @param stream A previously opened stream.
 * @return The stream format information.
 * @short Return stream format.
 */
CslPcmFormatType
csl_pcm_get_format (CslPcmStream *stream)
{
  csl_return_val_if_fail (stream != NULL, 0);
  csl_return_val_if_fail (stream->driver != NULL, 0);

  return stream->format;
}

/**
 * Close a PCM input or output stream.
 *
 * @param stream The stream to be closed.
 * @see csl_pcm_open_output
 * @see csl_pcm_open_input
 * @short Close PCM stream.
 */
void
csl_pcm_close (CslPcmStream *stream)
{
  CslDriver *driver;

  csl_return_if_fail (stream != NULL);
  csl_return_if_fail (stream->driver != NULL);

  driver = stream->driver;
  DRIVER_LOCK (driver);
  driver->pcm_vtable->stream_destroy (stream);
  DRIVER_UNLOCK (driver);
}

/**
 * Read bytes from a PCM input stream.
 *
 * @param stream The PCM stream to read.
 * @param n_bytes Number of bytes to read.
 * @param bytes Pointer to buffer to store the data read.
 * @return Returns the number of bytes read or -1 if an error occurred.
 * @see csl_pcm_write
 * @short Read from a stream.
 */
int
csl_pcm_read (CslPcmStream    *stream,
	      unsigned int     n_bytes,
	      void            *bytes)
{
  CslDriver *driver;
  CslErrorType error;
  
  csl_return_val_if_fail (stream != NULL, CSL_EINTERN);
  csl_return_val_if_fail (stream->driver != NULL, CSL_EINTERN);
  csl_return_val_if_fail (stream->readable == TRUE, CSL_EINTERN);

  stream->accessed = TRUE;
  if (n_bytes)
    {
      csl_return_val_if_fail (bytes != NULL, CSL_EINTERN);
      
      driver = stream->driver;
      DRIVER_LOCK (driver);
      error = driver->pcm_vtable->read (stream, n_bytes, bytes);
      DRIVER_UNLOCK (driver);
    }
  else
    error = CSL_ENONE;

  return error;
}

/**
 * Write bytes to a PCM output stream.
 *
 * @param stream The PCM stream to write.
 * @param n_bytes Number of bytes to write.
 * @param bytes Pointer to buffer containing the data to be written.
 * @return Returns the number of bytes written or -1 if an error occurred.
 * @see csl_pcm_read
 * @short Write to a stream.
 */
int
csl_pcm_write (CslPcmStream    *stream,
	       unsigned int     n_bytes,
	       void            *bytes)
{
  CslDriver *driver;
  CslErrorType error;

  csl_return_val_if_fail (stream != NULL, CSL_EINTERN);
  csl_return_val_if_fail (stream->driver != NULL, CSL_EINTERN);
  csl_return_val_if_fail (stream->writable == TRUE, CSL_EINTERN);

  stream->accessed = TRUE;
  if (n_bytes)
    {
      driver = stream->driver;
      DRIVER_LOCK (driver);
      error = driver->pcm_vtable->write (stream, n_bytes, bytes);
      DRIVER_UNLOCK (driver);
    }
  else
    error = CSL_ENONE;

  return error;
}

/**
 * Obtains status information about a PCM stream.
 *
 * @param stream The PCM stream to query.
 * @param status A pointer to variable to write the status information.
 * @return Error status code.
 * @short Get stream status.
 */
CslErrorType
csl_pcm_get_status (CslPcmStream *stream,
		    CslPcmStatus *status)
{
  CslDriver *driver;
  CslErrorType error;

  csl_return_val_if_fail (stream != NULL, CSL_EINTERN);
  csl_return_val_if_fail (stream->driver != NULL, CSL_EINTERN);
  csl_return_val_if_fail (status != NULL, CSL_EINTERN);

  driver = stream->driver;
  DRIVER_LOCK (driver);
  error = driver->pcm_vtable->update_status (stream);
  status->rate = stream->rate;
  status->n_channels = stream->n_channels;
  status->format = stream->format;
  switch (status->format & CSL_PCM_FORMAT_SIZE_MASK)
    {
    case CSL_PCM_FORMAT_SIZE_8:
      status->n_bits = 8;
      break;
    case CSL_PCM_FORMAT_SIZE_16:
      status->n_bits = 16;
      break;
    case CSL_PCM_FORMAT_SIZE_32:
      if (status->format & CSL_PCM_FORMAT_ENCODING_FLOAT)
	status->n_bits = 0;
      else
	status->n_bits = 32;
      break;
    default:
      csl_error ("stream->format(%u) is junk", stream->format);
      break;
    }
  status->buffer_size = stream->buffer_size;
  status->n_bytes_available = stream->n_bytes_available;
  if (stream->packet_mode)
    {
      status->packet_size = stream->packet.packet_size;
      status->n_buffer_packets = stream->packet.n_total_packets;
      status->n_packets_available = stream->packet.n_packets_available;
    }
  else
    {
      status->packet_size = 0;
      status->n_buffer_packets = 0;
      status->n_packets_available = 0;
    }
  DRIVER_UNLOCK (driver);

  return error;
}

/**
 * Cancel any pending output written to an output stream.  After a
 * call to csl_pcm_write, data may still be stored in buffers. Calling
 * this function will remove as many of these bytes as possible,
 * i.e. try to stop playing immediately, cancelling already written
 * bytes.
 *
 * @warning Not yet implemented by all backend drivers.
 * @param stream The PCM stream to flush.
 * @return Error status code.
 * @short Flush PCM data.
 */
CslErrorType
csl_pcm_flush (CslPcmStream *stream)
{
  CslDriver *driver;
  CslErrorType error;

  csl_return_val_if_fail (stream != NULL, CSL_EINTERN);
  csl_return_val_if_fail (stream->driver != NULL, CSL_EINTERN);

  driver = stream->driver;
  DRIVER_LOCK (driver);
  error = driver->pcm_vtable->flush (stream);
  DRIVER_UNLOCK (driver);

  return error;
}

/**
 * Ensure PCM data has been written to an output device. After a call
 * to csl_pcm_write, data may still be stored in buffers. Calling this
 * function ensures that it has actually been written to the audio
 * output device.
 *
 * @warning Not yet implemented by backend drivers.
 * @param stream The PCM stream to sync.
 * @return Error status code.
 * @short Sync PCM data.
 */
CslErrorType
csl_pcm_sync (CslPcmStream *stream)
{
  CslDriver *driver;
  CslErrorType error;

  csl_return_val_if_fail (stream != NULL, CSL_EINTERN);
  csl_return_val_if_fail (stream->driver != NULL, CSL_EINTERN);

  driver = stream->driver;
  DRIVER_LOCK (driver);
  error = driver->pcm_vtable->sync (stream);
  DRIVER_UNLOCK (driver);

  return error;
}

/**
 * Associate a title string with the stream.  The title may be shown
 * to the end user to describe the stream. The title should be
 * internationalized (in UTF-8 format) and can change. If you don't
 * assign a title, it will default to the role parameter for the
 * stream.
 *
 * @param title String to use as the title.
 * @param stream The PCM stream.
 * @return Error status code
 * @see csl_pcm_dup_title
 * @short Set stream title.
 */
CslErrorType
csl_pcm_set_title (CslPcmStream *stream,
		   const char   *title)
{
  CslDriver *driver;
  CslErrorType error;

  csl_return_val_if_fail (stream != NULL, CSL_EINTERN);
  csl_return_val_if_fail (stream->driver != NULL, CSL_EINTERN);
  csl_return_val_if_fail (title != NULL, CSL_EINTERN);

  driver = stream->driver;
  DRIVER_LOCK (driver);
  if (!stream->title || strcmp (stream->title, title) != 0)
    error = driver->pcm_vtable->set_title (stream, title);
  else
    error = CSL_ENONE;
  DRIVER_UNLOCK (driver);

  return error;
}

/**
 * Return the title string associated with a stream.
 *
 * @param stream The PCM stream.
 * @return The stream name. This is a dynamically allocated string
 * which needs to be freed using csl_free when no longer needed.
 * @see csl_pcm_set_title
 * @short Get stream title.
 */
char*
csl_pcm_dup_title (CslPcmStream *stream)
{
  CslDriver *driver;
  char *title;

  csl_return_val_if_fail (stream != NULL, NULL);
  csl_return_val_if_fail (stream->driver != NULL, NULL);

  driver = stream->driver;
  DRIVER_LOCK (driver);
  title = csl_strdup (stream->title ? stream->title : stream->role);
  DRIVER_UNLOCK (driver);

  return title;
}

/**
 * Put the @ref CslPcmStream into streaming mode, or, if already in
 * streaming mode, alter the buffer and watermark settings.
 * For writable streams, the watermark indicates
 * the amount of bytes that should be writable before csl_select()
 * flags this stream as writable.
 * For readable streams, this means the amount of bytes that should
 * be readable before csl_select() flags this stream as readable.
 * The resulting values strongly depend on the driver implementation
 * and should be queried after invoking this function with
 * csl_pcm_get_stream_settings().
 *
 * @param stream          The PCM stream to change
 * @param buffer_size     The buffer size in bytes for streaming mode
 * @param byte_watermark  The fill state watermark into buffer 
 * @return Whether setting stream parameters succeeded.
 *
 * @short Put PCM stream to streaming mode, set buffer settings.
 **/
CslErrorType
csl_pcm_set_stream_mode (CslPcmStream *stream,
			 unsigned int  buffer_size,
			 unsigned int  byte_watermark)
{
  CslDriver *driver;
  CslErrorType error;

  csl_return_val_if_fail (stream != NULL, CSL_EINTERN);
  csl_return_val_if_fail (stream->driver != NULL, CSL_EINTERN);

  driver = stream->driver;
  DRIVER_LOCK (driver);
  error = driver->pcm_vtable->set_stream_mode (stream, buffer_size, byte_watermark);
  DRIVER_UNLOCK (driver);

  return error;
}

/**
 * Put the @ref CslPcmStream into packet mode, or, if already in
 * packet mode, alter the buffer and watermark settings.
 * For writable streams, the watermark indicates
 * the amount of packets that should be writable before csl_select()
 * flags this stream as writable.
 * For readable streams, this means the amount of packets that should
 * be readable before csl_select() flags this stream as readable.
 * The resulting values strongly depend on the driver implementation
 * and should be queried after invoking this function with
 * csl_pcm_get_packet_settings().
 *
 * @param n_packets         Number of packets to be kept in buffer
 * @param packet_size       Size of a packet in bytes
 * @param packet_watermark  The fill state watermark in number of packets
 * @returns Whether setting packet mode parameters succeeded.
 *
 * @short Put PCM stream to packet mode, set buffer settings.
 **/
CslErrorType
csl_pcm_set_packet_mode (CslPcmStream *stream,
			 unsigned int  n_packets,
			 unsigned int  packet_size,
			 unsigned int  packet_watermark)
{
  CslDriver *driver;
  CslErrorType error;

  csl_return_val_if_fail (stream != NULL, CSL_EINTERN);
  csl_return_val_if_fail (stream->driver != NULL, CSL_EINTERN);

  driver = stream->driver;
  DRIVER_LOCK (driver);
  /* backend needs to make sure n_packets && packet_size are set correctly */
  error = driver->pcm_vtable->set_packet_mode (stream, n_packets, packet_size, packet_watermark);
  DRIVER_UNLOCK (driver);

  return error;
}

/**
 * Get the current stream settings for buffer size and watermark.
 *
 * @param stream The PCM stream.
 * @param buffer_size_p Pointer to variable to store returned buffer size.
 * @param byte_watermark_p Pointer to variable to store watermark setting.
 * @return Error status code
 * @short Get PCM stream settings.
 */
void
csl_pcm_get_stream_settings (CslPcmStream   *stream,
			     unsigned int   *buffer_size_p,
			     unsigned int   *byte_watermark_p)
{
  CslDriver *driver;

  csl_return_if_fail (stream != NULL);
  csl_return_if_fail (stream->driver != NULL);
  csl_return_if_fail (stream->stream_mode == TRUE);

  driver = stream->driver;
  DRIVER_LOCK (driver);
  if (buffer_size_p)
    *buffer_size_p = stream->buffer_size;
  if (byte_watermark_p)
    *byte_watermark_p = stream->buffer_watermark;
  DRIVER_UNLOCK (driver);
}

/**
 * Get the current stream settings for number of packets, size, and watermark.
 *
 * @param stream The PCM stream.
 * @param n_packets_p Pointer to variable to store number of packets.
 * @param packet_size_p Pointer to variable to store packet size, in bytes.
 * @param packet_watermark_p Pointer to variable to store watermark setting.
 * @return Error status code
 * @short Get PCM stream packet settings.
 */
void
csl_pcm_get_packet_settings (CslPcmStream   *stream,
			     unsigned int   *n_packets_p,
			     unsigned int   *packet_size_p,
			     unsigned int   *packet_watermark_p)
{
  CslDriver *driver;

  csl_return_if_fail (stream != NULL);
  csl_return_if_fail (stream->driver != NULL);
  csl_return_if_fail (stream->packet_mode == TRUE);

  driver = stream->driver;
  DRIVER_LOCK (driver);
  if (n_packets_p)
    *n_packets_p = stream->packet.n_total_packets;
  if (packet_size_p)
    *packet_size_p = stream->packet.packet_size;
  if (packet_watermark_p)
    *packet_watermark_p = stream->packet.packet_watermark;
  DRIVER_UNLOCK (driver);
}

#if 0		/* --- proposed --- */
/**
 * List the mapping of channel names to channel numbers. These are the
 * global channel names supported by a driver, and are fixed. You can
 * dynamically assign channel names to PCM streams using
 * csl_pcm_set_channel_mapping.
 *
 * @param driver The CSL driver (note, not a PCM stream).
 * @param n_maps_p Pointer to variable in which to return the number
 * of channel mapping strings returned.
 * @return A pointer to a list of strings containing the names
 * associated with each channel number.
 * @see csl_pcm_set_channel_mapping
 * @see csl_pcm_dup_channel_mapping
 * @short List channel mappings.
 */
char**
csl_pcm_list_channel_mappings (CslDriver    *driver,
			       unsigned int *n_maps_p)
{
  csl_return_val_if_fail (n_maps_p != NULL, NULL);

  /* FIXME: for drivers like aRts this should invoke a driver function */

  *n_maps_p = driver->n_pcm_mappings;

  /* FIXME: dup the output */
  return driver->pcm_mappings;
}

/**
 * Set the mapping of a channel name to channel number.
 *
 * @warning This is not yet implemented and will always return
 * an error code of CSL_ENONE.
 *
 * @param driver The PCM stream.
 * @param channel Channel number to set.
 * @param mapping String containing name for channel, either
 * a literal string or a constant such as CSL_PCM_CHANNEL_FRONT_LEFT.
 * @return Error return code.
 * @see csl_pcm_list_channel_mappings
 * @see csl_pcm_dup_channel_mapping
 * @short Set channel mapping.
 */
CslErrorType
csl_pcm_set_channel_mapping (CslPcmStream *stream,
			     unsigned int  channel,
			     const char   *mapping)
{
  /* FIXME */
  return CSL_EINTERN;
}

/**
 * Return the mapping of a channel name to channel number.
 *
 * @warning This is not yet implemented and will always return NULL.
 *
 * @param driver The PCM stream.
 * @param channel Channel number to use.
 * @return The channel name. This is a dynamically allocated string
 * which needs to be freed using csl_free when no longer needed.
 * @see csl_pcm_list_channel_mappings
 * @see csl_pcm_set_channel_mapping
 * @short Duplicate channel mapping.
 */
char*
csl_pcm_dup_channel_mapping (CslPcmStream *stream,
			     unsigned int  channel)
{
  /* FIXME */
  return NULL;
}
#endif	/* proposed channel mappings */
