/* 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 "cslmain.h"

#include "cslconfig.h"
#include "cslprivate.h"
#include "cslutils.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

/**
 * @mainpage CSL - The Common Sound Library
 * This is the API documentation for the Common Sound Library.  A
 * HOWTO and FAQ document is also available
 * <A HREF="../howto.html">here</A>.
 *
 * @author Stefan Westerfeld
 * @author Tim Janik
 *
 */

/* --- typedefs & structures --- */
typedef struct {
  char                  *driver_name;
  CslPcmDriverVTable    *pcm_vtable;
  CslSampleDriverVTable *sample_vtable;
} DriverEntry;


/* --- variables --- */
static const DriverEntry csl_drivers[] = {
  { "arts",	&_csl_arts_pcm_vtable, &_csl_arts_sample_vtable },
  { "oss",	&_csl_oss_pcm_vtable },
};
static const unsigned int n_csl_drivers = sizeof (csl_drivers) / sizeof (csl_drivers[0]);


/* --- functions --- */

/**
 * Return a list of the available CSL back-end drivers.
 *
 * @param n_backends The number of backends found will be written to the variable pointed to.
 * @return A pointer to the start of the list of driver names (null terminated strings).
 * @short Return list of drivers.
 */
const char**
csl_list_drivers (unsigned int *n_backends_p)
{
  static const char *driver_names[n_csl_drivers + 1] = { NULL, };

  /* FIXME: thread-safety issues (theoretically) */

  if (!driver_names[0])
    {
      unsigned int i;

      for (i = 0; i < n_csl_drivers; i++)
	driver_names[i] = csl_drivers[i].driver_name;
      driver_names[i] = NULL;
    }
  if (n_backends_p)
    *n_backends_p = n_csl_drivers;

  return driver_names;
}

static void
driver_shutdown_L (CslDriver *driver)
{
  if (driver->pcm_vtable)
    {
      driver->pcm_vtable->pcm_driver_shutdown (driver);
      driver->pcm_data = NULL;
      driver->pcm_vtable = NULL;
      driver->n_pcm_mappings = 0;
      driver->pcm_mappings = NULL;
    }
  if (driver->sample_vtable)
    {
      driver->sample_vtable->sample_driver_shutdown (driver);
      driver->sample_data = NULL;
      driver->sample_vtable = NULL;
    }
#if 0
  if (driver->mixer_vtable)
    {
      driver->mixer_vtable->mixer_driver_shutdown (driver);
      driver->mixer_data = NULL;
      driver->mixer_vtable = NULL;
    }
#endif
}

static CslErrorType
driver_init_L (CslDriver         *driver,
	       const DriverEntry *entry,
	       CslDriverCaps      required_caps)
{
  CslErrorType error = CSL_ENONE;
  unsigned int i;

  for (i = 0; !error && ((1 << i) & required_caps); i++)
    switch (1 << i)
      {
      case CSL_DRIVER_CAP_PCM:
	error = entry->pcm_vtable ? entry->pcm_vtable->pcm_driver_init (driver)
	    			  : CSL_ENODRIVER;
	break;
      case CSL_DRIVER_CAP_SAMPLE:
	error = entry->sample_vtable ? entry->sample_vtable->sample_driver_init (driver)
	    			     : CSL_ENODRIVER;
	break;
      case CSL_DRIVER_CAP_MIXER:
	error = CSL_ENODRIVER;
	break;
      }
  if (error)
    driver_shutdown_L (driver);

  return error;
}

static void
dummy_nop (CslMutex *mutex)
{
}

/**
 * Call this function to initialize the CSL library. You need to call this to obtain a handle
 * to a driver before performing any driver functions. For non-multithreaded applications you
 * can use @ref csl_driver_init instead.
 *
 * @note The OSS backend looks at the environment variable OSS_DEVICE, and
 * if defined, uses that for the audio device. Otherwise it uses
 * /dev/dsp.
 * @param driver_name Name of the driver desired. Use NULL to have CSL
 * auto-detect a suitable driver. Pass CSL_DRIVER_NAME_ARTS or
 * CSL_DRIVER_NAME_OSS or the string "arts" or "oss" to select a
 * specific driver.
 * @param required_caps A set of driver capabilities that you desire, specified as one or more
 * values of type CslDriverCaps bitwise ORed together.
 * @param mutex A pointer to a variable of type CslMutex which is initialized with the user-defined
 * lock, unlock, and destroy functions for ensuring mutual exclusion. This allows locking to be
 * done in a portable way.
 * @param driver Returns driver (which is a pointer to an opaque type) to be used in subsequent CSL calls.
 * @return Error status
 * @see csl_driver_init
 * @see csl_driver_shutdown
 * @short Initialize the CSL library.
 */
CslErrorType
csl_driver_init_mutex (const char   *driver_name,
		       CslDriverCaps required_caps,
		       CslMutex     *mutex,
		       CslDriver   **driver_p)
{
  static CslMutex dummy_mutex = { NULL, dummy_nop, dummy_nop, NULL };
  CslErrorType error = CSL_ENODRIVER;
  CslDriver *driver;

  csl_return_val_if_fail ((required_caps & CSL_DRIVER_CAP_MASK) != 0, CSL_EINTERN);
  csl_return_val_if_fail (driver_p != NULL, CSL_EINTERN);
  if (!mutex)
    mutex = &dummy_mutex;
  csl_return_val_if_fail (mutex->lock != NULL, CSL_EINTERN);
  csl_return_val_if_fail (mutex->unlock != NULL, CSL_EINTERN);

  driver = csl_new0 (CslDriver, 1);
  driver->mutex = mutex;

  mutex->lock (mutex);

  if (!driver_name)
    {
      unsigned int i;

      for (i = 0; error && i < n_csl_drivers; i++)
	error = driver_init_L (driver, csl_drivers + i, required_caps);
    }
  else
    {
      unsigned int i;

      for (i = 0; i < n_csl_drivers; i++)
	if (strcmp (csl_drivers[i].driver_name, driver_name) == 0)
	  {
	    error = driver_init_L (driver, csl_drivers + i, required_caps);
	    break;
	  }
    }

  mutex->unlock (mutex);

  if (error)
    {
      error = CSL_ENODRIVER;
      csl_free (driver);
      driver = NULL;
      if (mutex->destroy)
	mutex->destroy (mutex);
    }
  *driver_p = driver;

  return error;
}

/**
 * Call this function to initialize the CSL library. You need to call this to obtain a handle
 * to a driver before performing any driver functions. For multithreaded applications you
 * should use @ref csl_driver_init_mutex instead.
 *
 * @note The OSS backend looks at the environment variable OSS_DEVICE, and
 * if defined, uses that for the audio device. Otherwise it uses
 * /dev/dsp.
 * @param driver_name Name of the driver desired. Use NULL to have CSL
 * auto-detect a suitable driver. Pass CSL_DRIVER_NAME_ARTS or
 * CSL_DRIVER_NAME_OSS or the string "arts" or "oss" to select a
 * specific driver.
 * @param driver Returns driver (which is a pointer to an opaque type) to be used in subsequent CSL calls.
 * @return Error status
 * @see csl_driver_init_mutex
 * @see csl_driver_shutdown
 *  @short Initialize the CSL library.
 */
CslErrorType
csl_driver_init (const char *driver_name,
		 CslDriver **driver_p)
{
  return csl_driver_init_mutex (driver_name,
				CSL_DRIVER_CAP_PCM, // FIXME:  | CSL_DRIVER_CAP_MIXER,
				NULL,
				driver_p);
}

/**
 * Shutdown a driver and release any resources associated with it.
 * There is no way of determining if the call was successful. Be
 * sure to close any PCM streams associated with the driver first.
 *
 * @param driver A handle to the driver
 * @see csl_driver_init
 * @see csl_driver_init_mutex
 * @see csl_pcm_close
 * @short Shutdown driver.
 */
void
csl_driver_shutdown (CslDriver *driver)
{
  CslMutex *mutex;

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

  mutex = driver->mutex;

  mutex->lock (mutex);
  driver_shutdown_L (driver);
  mutex->unlock (mutex);

  csl_free (driver);
  if (mutex->destroy)
    mutex->destroy (mutex);
}

/**
 * Return a string describing a CSL error code.
 * @param error_type The error code.
 * @return A string describing the error.
 * @see CslErrorType
 * @short Return error description.
 */
const char*
csl_strerror (CslErrorType error_type)
{
  switch (error_type)
    {
    case CSL_ENONE:			return "Everything went well";
    case CSL_EINTERN:			return "Internal error (please report)";
    case CSL_ENODRIVER:			return "No driver available";
    case CSL_ENOIMPL:			return "Not yet implemented";
    case CSL_EBUSY:			return "Device busy";
    case CSL_EPERMS:			return "Insufficient permissions";
    case CSL_EIO:			return "Device/file I/O error";
    case CSL_EFMTINVAL:			return "Sample format invalid";
    case CSL_EGETCAPS:			return "Failed to query device capabilities";
    case CSL_ECAPSUPPORT:		return "Device capabilities not sufficient";
    case CSL_ESETCAPS:			return "Failed to set device capabilities";
    default:				return "Unknown error";
    }
}

/**
 * Parses the programs command line options and fills in a CslOptions
 * variable.
 *
 * @param options Pointer to options variable in which to store result.
 * @param argv_p Pointer to number of arguments, typically argc in main()
 * @param argv_p Pointer to argument pointer, typically argv in main()
 * @see csl_describe_options
 * @short Parse command line options.
 */
void
csl_options_parse (CslOptions *options,
		   int        *argc_p,
		   char      **argv_p[])
{
  static const CslOptions standard_options = {
    FALSE,	/* float_samples */
    TRUE,	/* signed_samples */
    FALSE,	/* unsigned_samples */
    CSL_BYTE_ORDER != CSL_LITTLE_ENDIAN,	/* big_endian */
    CSL_BYTE_ORDER == CSL_LITTLE_ENDIAN,	/* little_endian */
    16,		/* n_bits */
    2,		/* n_channels */
    44100,	/* rate */
    0,		/* debug_flags */
    /* composed constructs: */
    0,		/* endianess */
    0,		/* pcm_format */
  };
  unsigned int argc;
  char **argv;
  unsigned int i, e;

  csl_return_if_fail (options != NULL);
  csl_return_if_fail (argc_p != NULL);
  csl_return_if_fail (argv_p != NULL);
  csl_return_if_fail (*argc_p >= 0);

  argc = *argc_p;
  argv = *argv_p;
  *options = standard_options;

  for (i = 1; i < argc; i++)
    {
      if (strcmp ("-w", argv[i]) == 0 ||
	  strncmp ("-w=", argv[i], 3) == 0)
	{
	  char *equal = argv[i] + 3;
	  
	  if (*equal == '=')
	    options->n_bits = atoi (equal + 1) >= 10 ? 16 : 8;
	  else if (i + 1 < argc)
	    {
	      options->n_bits = atoi (argv[i + 1]) >= 10 ? 16 : 8;
	      argv[i] = NULL;
	      i += 1;
	    }
	  if (options->n_bits == 8)
	    {
	      options->float_samples = FALSE;
	      options->signed_samples = FALSE;
	      options->unsigned_samples = TRUE;
	    }
	  else /* (options->n_bits == 16) */
	    {
	      options->float_samples = FALSE;
	    }
	  argv[i] = NULL;
	}
      else if (strcmp ("-s", argv[i]) == 0)
	{
	  options->n_bits = 16;
	  options->signed_samples = TRUE;
	  options->unsigned_samples = FALSE;
	  options->float_samples = FALSE;
	  argv[i] = NULL;
	}
      else if (strcmp ("-u", argv[i]) == 0)
	{
	  /* options->n_bits = 16 or 8; */
	  options->signed_samples = FALSE;
	  options->unsigned_samples = TRUE;
	  options->float_samples = FALSE;
	  argv[i] = NULL;
	}
      else if (strcmp ("-F", argv[i]) == 0)
	{
	  options->n_bits = 32;
	  options->signed_samples = FALSE;
	  options->unsigned_samples = FALSE;
	  options->float_samples = TRUE;
	  argv[i] = NULL;
	}
      else if (strcmp ("-B", argv[i]) == 0)
	{
	  options->big_endian = TRUE;
	  options->little_endian = FALSE;
	  argv[i] = NULL;
	}
      else if (strcmp ("-L", argv[i]) == 0)
	{
	  options->big_endian = FALSE;
	  options->little_endian = TRUE;
	  argv[i] = NULL;
	}
      else if (strcmp ("-r", argv[i]) == 0 ||
	       strncmp ("-r=", argv[i], 3) == 0)
	{
	  char *equal = argv[i] + 3;
	  
	  if (*equal == '=')
	    options->rate = atoi (equal + 1);
	  else if (i + 1 < argc)
	    {
	      options->rate = atoi (argv[i + 1]);
	      argv[i] = NULL;
	      i += 1;
	    }
	  if (options->rate < 1000)
	    options->rate = 8000;
	  argv[i] = NULL;
	}
      else if (strcmp ("-c", argv[i]) == 0 ||
	       strncmp ("-c=", argv[i], 3) == 0)
	{
	  char *equal = argv[i] + 3;
	  
	  if (*equal == '=')
	    options->n_channels = atoi (equal + 1);
	  else if (i + 1 < argc)
	    {
	      options->n_channels = atoi (argv[i + 1]);
	      argv[i] = NULL;
	      i += 1;
	    }
	  if (options->n_channels < 1)
	    options->n_channels = 1;
	  argv[i] = NULL;
	}
      else if (strcmp ("-D", argv[i]) == 0 ||
	       strncmp ("-D=", argv[i], 3) == 0)
	{
	  char *equal = argv[i] + 3;
	  
	  if (*equal == '=')
	    options->debug_flags = atoi (equal + 1);
	  else if (i + 1 < argc)
	    {
	      options->debug_flags = atoi (argv[i + 1]);
	      argv[i] = NULL;
	      i += 1;
	    }
	  argv[i] = NULL;
	}
    }

  /* sanity check options and compose constructs */
  options->big_endian = !options->little_endian;
  if (options->n_bits == 32)
    {
      options->float_samples = TRUE;
      options->signed_samples = FALSE;
      options->unsigned_samples = FALSE;
      options->pcm_format = CSL_PCM_FORMAT_SIZE_32;
    }
  else if (options->n_bits == 16)
    {
      options->unsigned_samples = !options->signed_samples;
      options->float_samples = FALSE;
      options->pcm_format = CSL_PCM_FORMAT_SIZE_16;
    }
  else /* (options->n_bits == 8) */
    {
      options->n_bits = 8;
      options->unsigned_samples = TRUE;
      options->signed_samples = FALSE;
      options->float_samples = FALSE;
      options->pcm_format = CSL_PCM_FORMAT_SIZE_8;
    }
  options->endianess = options->little_endian ? CSL_LITTLE_ENDIAN : CSL_BIG_ENDIAN;
  options->pcm_format |= options->little_endian ? CSL_PCM_FORMAT_ENDIAN_LE : CSL_PCM_FORMAT_ENDIAN_BE;
  if (options->signed_samples)
    options->pcm_format |= CSL_PCM_FORMAT_ENCODING_LINEAR_SIGNED;
  else if (options->unsigned_samples)
    options->pcm_format |= CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED;
  else /* (options->float_samples) */
    options->pcm_format |= CSL_PCM_FORMAT_ENCODING_FLOAT;
  if (options->debug_flags)
    options->debug_flags = (1 << MAX (options->debug_flags, 31)) - 1;

  /* resort argc/argv */
  e = 0;
  for (i = 1; i < argc; i++)
    {
      if (e)
	{
	  if (argv[i])
	    {
	      argv[e++] = argv[i];
	      argv[i] = NULL;
	    }
	}
      else if (!argv[i])
	e = i;
    }
  if (e)
    *argc_p = e;
}

/**
 * Outputs a usage string describing the command line options
 * supported by csl_parse_options. Typical output is:
 * <pre>
 * -B, -L          big/little endian
 * -s, -u          signed/unsigned samples
 * -F              float samples (32bit)
 * -w [8|16]       number of bits (width is 16 or 8)
 * -c <n_channels> number of channels
 * -r <rate>       sampling frequency
 * -D <level>      debugging level
 * </pre>
 *
 * @param indent Number of spaces to pad before the output
 * to support formatting in a usage message.
 * @return String describing the options.
 * @see csl_parse_options
 * @see csl_describe_pcm_format
 * @short Describe command line options.  */
char*
csl_options_describe (unsigned int indent)
{
  static const char *blurb =
    (
     "-B, -L          big/little endian\n"
     "-s, -u          signed/unsigned samples\n"
     "-F              float samples (32bit)\n"
     "-w [8|16]       number of bits (width is 16 or 8)\n"
     "-c <n_channels> number of channels\n"
     "-r <rate>       sampling frequency\n"
     "-D <level>      debugging level"
     );
  unsigned int i, n_newlines, length = strlen (blurb);
  char *s, *buffer;

  n_newlines = 0;
  for (i = 0; i < length; i++)
    if (blurb[i] == '\n')
      n_newlines++;

  buffer = csl_new (char, indent + n_newlines * indent + length + 1);
  s = buffer;
  for (i = 0; i < indent; i++)
    *s++ = ' ';
  for (i = 0; i < length; i++)
    {
      *s++ = blurb[i];
      if (blurb[i] == '\n')
	{
	  unsigned int n;
	  
	  for (n = 0; n < indent; n++)
	    *s++ = ' ';
	}
    }
  *s++ = 0;

  return buffer;
}

/**
 * This function returns a debug string describing the CSL options.
 *
 * @param options Point to CslOptions variable.
 * @return A string describing the options. The strings is dynamically
 * allocated and must be freed using @ref csl_free when no longer needed.
 * @short Dump CSL options.
 */
char*
csl_options_dump (CslOptions *options)
{
  char buffer[11 * 1024];	/* make this big enough (lines * 1k) */
  char *b, *str;
  
  csl_return_val_if_fail (options != NULL, NULL);

  /* create debug string of the option contents
   * sprintf() doesn't reliably return the number of written
   * characters on all platforms, so use strlen() along the way
   */
  b = buffer;
  sprintf (b, "        float? %d\n", options->float_samples);
  b += strlen (b);
  sprintf (b, "       signed? %d\n", options->signed_samples);
  b += strlen (b);
  sprintf (b, "     unsigned? %d\n", options->unsigned_samples);
  b += strlen (b);
  sprintf (b, "   big endian? %d\n", options->big_endian);
  b += strlen (b);
  sprintf (b, "little endian? %d\n", options->little_endian);
  b += strlen (b);
  sprintf (b, "         bits: %d\n", options->n_bits);
  b += strlen (b);
  sprintf (b, "     channels: %d\n", options->n_channels);
  b += strlen (b);
  sprintf (b, "         rate: %d\n", options->rate);
  b += strlen (b);
  sprintf (b, "  debug flags: %d\n", options->debug_flags);
  b += strlen (b);
  sprintf (b, "    endianess: %s\n", options->endianess == CSL_LITTLE_ENDIAN ? "little" : "big");
  b += strlen (b);
  str = csl_describe_pcm_format (options->pcm_format);
  sprintf (b, "   PCM format: %s\n", str);
  csl_free (str);

  return csl_strdup (buffer);
}

/**
 * Given a PCM format value, return a string describing the selected
 * format (e.g. "16bit, little-endian, signed")
 *
 * @param pcm_format The PCM format
 * @return A newly allocated text string describing the format.
 * @short Describe a PCM format.
 */
char*
csl_describe_pcm_format (unsigned int format)
{
  char *s, buffer[1024];

  s = buffer;
  switch (format & CSL_PCM_FORMAT_SIZE_MASK)
    {
    case CSL_PCM_FORMAT_SIZE_8:				sprintf (s, "8bit");	break;
    case CSL_PCM_FORMAT_SIZE_16:			sprintf (s, "16bit");	break;
    case CSL_PCM_FORMAT_SIZE_32:			sprintf (s, "32bit");	break;
    default:						sprintf (s, "??bit");	break;
    }
  s += strlen (s);
  switch (format & CSL_PCM_FORMAT_ENDIAN_MASK)
    {
    case CSL_PCM_FORMAT_ENDIAN_BE:			sprintf (s, ", big-endian");	break;
    case CSL_PCM_FORMAT_ENDIAN_LE:			sprintf (s, ", little-endian");	break;
    }
  s += strlen (s);
  switch (format & CSL_PCM_FORMAT_ENCODING_MASK)
    {
    case CSL_PCM_FORMAT_ENCODING_LINEAR_SIGNED:		sprintf (s, ", signed");	break;
    case CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED:	sprintf (s, ", unsigned");	break;
    case CSL_PCM_FORMAT_ENCODING_FLOAT:			sprintf (s, ", float");		break;
    }
  return csl_strdup (buffer);
}

#if 0
void
usage (void)
{
  fprintf (stderr, "Usage: xplay [options...] [files...]\n");
  fprintf (stderr, "Options:\n");
  fprintf (stderr, "       -b, -l	big/little endian\n");
  fprintf (stderr, "       -s, -u	signed/unsigned samples\n");
  fprintf (stderr, "       -s, -u	signed/unsigned samples\n");
  fprintf (stderr, "       -r <rate>	sampling frequency\n");
  fprintf (stderr, "       -c <n>	number of channels\n");
  fprintf (stderr, "Encoding:\n");
  fprintf (stderr, "       -w [8|16]	6/16 bit samples\n");
  fprintf (stderr, "       --mu-law	\n");
  fprintf (stderr, "       --a-law	\n");
  fprintf (stderr, "       --ima-adpcm	\n");
  fprintf (stderr, "       --mpeg	\n");
  fprintf (stderr, "       --ac3	\n");
  argv[i] = NULL;
  exit (0);
}
#endif
/* vim:ts=8:sw=2:sts=2
 */
