/* GSL - Generic Sound Layer
 * Copyright (C) 2001-2002 Tim Janik and Stefan Westerfeld
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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        "gslloader.h"

#include        "gslcommon.h"
#include        "gsldatahandle.h"
#include        "gslmagic.h"
#include        <string.h>


/* --- variables --- */
static GslLoader *gsl_loader_list = NULL;
static GslRing   *gsl_magic_list = NULL;


/* --- functions --- */
static GslLoader*
loader_find_by_name (const gchar *name)
{
  GslLoader *loader;

  for (loader = gsl_loader_list; loader != NULL; loader = loader->next)
    if (strcmp (name, loader->name) == 0)
      return loader;
  return NULL;
}

void
gsl_loader_register (GslLoader *loader)
{
  g_return_if_fail (loader != NULL);
  g_return_if_fail (loader->name != NULL);
  g_return_if_fail (loader->extensions || loader->mime_types || loader->magic_specs);
  g_return_if_fail (loader_find_by_name (loader->name) == NULL);
  g_return_if_fail (loader->next == NULL);
  g_return_if_fail (loader->load_file_info != NULL);
  g_return_if_fail (loader->free_file_info != NULL);
  g_return_if_fail (loader->load_wave_dsc != NULL);
  g_return_if_fail (loader->free_wave_dsc != NULL);
  g_return_if_fail (loader->create_chunk_handle != NULL);

  loader->next = gsl_loader_list;
  gsl_loader_list = loader;

  if (loader->magic_specs)
    {
      GslMagic *magic;
      guint i, j;

      for (i = 0; loader->magic_specs[i]; i++)
	{
	  if (loader->extensions)
	    for (j = 0; loader->extensions[j]; j++)
	      {
		magic = gsl_magic_create (loader, loader->priority,
					  loader->extensions[j], loader->magic_specs[i]);
		gsl_magic_list = gsl_ring_append (gsl_magic_list, magic);
	      }
	  else
	    {
	      magic = gsl_magic_create (loader, loader->priority,
					NULL, loader->magic_specs[i]);
	      gsl_magic_list = gsl_ring_append (gsl_magic_list, magic);
	    }
	}
    }
}

GslLoader*
gsl_loader_match (const gchar *file_name)
{
  GslMagic *magic;

  g_return_val_if_fail (file_name != NULL, NULL);

  magic = gsl_magic_list_match_file (gsl_magic_list, file_name);
  if (magic)
    return magic->data;

  return NULL;
}

GslWaveFileInfo*
gsl_wave_file_info_load (const gchar  *file_name,
			 GslErrorType *error_p)
{
  GslWaveFileInfo *finfo = NULL;
  GslErrorType error = GSL_ERROR_NONE;
  GslLoader *loader;

  if (error_p)
    *error_p = GSL_ERROR_INTERNAL;
  g_return_val_if_fail (file_name != NULL, NULL);

  loader = gsl_loader_match (file_name);
  if (loader)
    {
      finfo = loader->load_file_info (loader->data, file_name, &error);
      if (error && finfo)
	{
	  /* loaders shouldn't do this */
	  loader->free_file_info (loader->data, finfo);
	  finfo = NULL;
	}
      if (!finfo && !error)
	error = GSL_ERROR_FILE_EMPTY;	/* FIXME: try next loader */
      if (finfo)
	{
	  if (finfo->n_waves > 0)
	    {
	      guint i;

	      g_return_val_if_fail (finfo->loader == NULL, NULL);
	      g_return_val_if_fail (finfo->file_name == NULL, NULL);

	      for (i = 0; i < finfo->n_waves; i++)
		g_return_val_if_fail (finfo->waves[i].name != NULL, NULL);

	      finfo->file_name = g_strdup (file_name);
	      finfo->loader = loader;
	      finfo->ref_count = 1;
	    }
	  else
	    {
	      loader->free_file_info (loader->data, finfo);
	      finfo = NULL;
	      error = GSL_ERROR_FILE_EMPTY;   /* FIXME: try next loader */
	    }
	}
    }
  else /* no loader match */
    {
      /* try to provide appropriate error code */
      error = gsl_check_file (file_name, "rf");
      if (!error)
	error = GSL_ERROR_FORMAT_UNKNOWN;
    }

  if (error_p)
    *error_p = error;

  return finfo;
}

void
gsl_wave_file_info_unref (GslWaveFileInfo *wave_file_info)
{
  g_return_if_fail (wave_file_info != NULL);
  g_return_if_fail (wave_file_info->ref_count > 0);

  wave_file_info->ref_count--;
  if (!wave_file_info->ref_count)
    {
      GslLoader *loader = wave_file_info->loader;

      g_free (wave_file_info->file_name);
      wave_file_info->file_name = NULL;
      wave_file_info->loader = NULL;

      loader->free_file_info (loader->data, wave_file_info);
    }
}

GslWaveFileInfo*
gsl_wave_file_info_ref (GslWaveFileInfo *wave_file_info)
{
  g_return_val_if_fail (wave_file_info != NULL, NULL);
  g_return_val_if_fail (wave_file_info->ref_count > 0, NULL);

  wave_file_info->ref_count++;

  return wave_file_info;
}

GslWaveDsc*
gsl_wave_dsc_load (GslWaveFileInfo *wave_file_info,
		   guint            nth_wave,
		   GslErrorType    *error_p)
{
  GslErrorType error = GSL_ERROR_NONE;
  GslWaveDsc *wdsc;
  GslLoader *loader;

  if (error_p)
    *error_p = GSL_ERROR_INTERNAL;
  g_return_val_if_fail (wave_file_info != NULL, NULL);
  g_return_val_if_fail (wave_file_info->loader != NULL, NULL);
  g_return_val_if_fail (nth_wave < wave_file_info->n_waves, NULL);

  loader = wave_file_info->loader;
  wdsc = loader->load_wave_dsc (loader->data, wave_file_info, nth_wave,&error);

  if (error && wdsc)
    {
      /* loaders shouldn't do this */
      loader->free_wave_dsc (loader->data, wdsc);
      wdsc = NULL;
    }
  if (!wdsc && !error)
    error = GSL_ERROR_FILE_EMPTY;
  if (wdsc)
    {
      if (wdsc->n_chunks > 0)
	{
	  g_return_val_if_fail (wdsc->file_info == NULL, NULL);
	  g_return_val_if_fail (wdsc->name && strcmp (wdsc->name, wave_file_info->waves[nth_wave].name) == 0, NULL);

	  wdsc->file_info = wave_file_info;
	  gsl_wave_file_info_ref (wave_file_info);
	}
      else
	{
	  loader->free_wave_dsc (loader->data, wdsc);
	  wdsc = NULL;
	  error = GSL_ERROR_FILE_EMPTY;
	}
    }

  if (error_p)
    *error_p = error;

  return wdsc;
}

void
gsl_wave_dsc_free (GslWaveDsc *wave_dsc)
{
  GslWaveFileInfo *file_info;

  g_return_if_fail (wave_dsc != NULL);
  g_return_if_fail (wave_dsc->file_info != NULL);

  file_info = wave_dsc->file_info;
  wave_dsc->file_info = NULL;

  file_info->loader->free_wave_dsc (file_info->loader->data, wave_dsc);

  gsl_wave_file_info_unref (file_info);
}

GslDataHandle*
gsl_wave_handle_create (GslWaveDsc   *wave_dsc,
			guint	      nth_chunk,
			GslErrorType *error_p)
{
  GslErrorType error = GSL_ERROR_NONE;
  GslDataHandle *dhandle;
  GslLoader *loader;

  if (error_p)
    *error_p = GSL_ERROR_INTERNAL;
  g_return_val_if_fail (wave_dsc != NULL, NULL);
  g_return_val_if_fail (wave_dsc->file_info != NULL, NULL);
  g_return_val_if_fail (nth_chunk < wave_dsc->n_chunks, NULL);

  loader = wave_dsc->file_info->loader;

  dhandle = loader->create_chunk_handle (loader->data,
					 wave_dsc,
					 nth_chunk,
					 &error);
  if (error && dhandle)
    {
      /* loaders shouldn't do this */
      gsl_data_handle_unref (dhandle);
      dhandle = NULL;
    }
  if (!dhandle && !error)
    error = GSL_ERROR_FORMAT_INVALID;

  if (error_p)
    *error_p = error;

  return dhandle;
}

GslWaveChunk*
gsl_wave_chunk_create (GslWaveDsc   *wave_dsc,
		       guint         nth_chunk,
		       GslErrorType *error_p)
{
  GslDataHandle *dhandle;
  GslDataCache *dcache;
  GslWaveChunk *wchunk;

  if (error_p)
    *error_p = GSL_ERROR_INTERNAL;
  g_return_val_if_fail (wave_dsc != NULL, NULL);
  g_return_val_if_fail (nth_chunk < wave_dsc->n_chunks, NULL);

  dhandle = gsl_wave_handle_create (wave_dsc, nth_chunk, error_p);
  if (!dhandle)
    return NULL;

  if (error_p)
    *error_p = GSL_ERROR_IO;

  /* FIXME: we essentially create a dcache for each wchunk here ;( */

  dcache = gsl_data_cache_from_dhandle (dhandle, gsl_get_config ()->wave_chunk_padding * wave_dsc->n_channels);
  gsl_data_handle_unref (dhandle);
  if (!dcache)
    return NULL;
  /* dcache keeps dhandle alive */

  wchunk = gsl_wave_chunk_new (dcache,
			       wave_dsc->chunks[nth_chunk].osc_freq,
			       wave_dsc->chunks[nth_chunk].mix_freq,
			       wave_dsc->chunks[nth_chunk].loop_type,
			       wave_dsc->chunks[nth_chunk].loop_start,
			       wave_dsc->chunks[nth_chunk].loop_end,
			       wave_dsc->chunks[nth_chunk].loop_count);
  gsl_data_cache_unref (dcache);

  if (error_p && wchunk)
    *error_p = GSL_ERROR_NONE;

  return wchunk;
}
