/* GSL - Generic Sound Layer
 * Copyright (C) 2001-2002 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., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
#include "gsldatautils.h"
#include "gsldatacache.h"
#include <errno.h>
#include <unistd.h>


#define	BSIZE		GSL_DATA_HANDLE_PEEK_BUFFER	/* FIXME: global buffer size setting */


/* --- functions --- */
gfloat
gsl_data_peek_value_f (GslDataHandle     *dhandle,
		       GslLong            pos,
		       GslDataPeekBuffer *peekbuf)
{
  if (pos < peekbuf->start || pos >= peekbuf->end)
    {
      GslLong dhandle_length = dhandle->setup.n_values;
      GslLong inc, k, bsize = MIN (GSL_DATA_HANDLE_PEEK_BUFFER, dhandle_length);

      g_return_val_if_fail (pos >= 0 && pos < dhandle_length, 0);

      peekbuf->start = peekbuf->dir > 0 ? pos : peekbuf->dir < 0 ? pos - bsize + 1: pos - bsize / 2;
      peekbuf->end = MIN (peekbuf->start + bsize, dhandle_length);
      peekbuf->start = MAX (peekbuf->start, 0);
      for (k = peekbuf->start; k < peekbuf->end; k += inc)
	{
	  guint n_retries = 5;  /* FIXME: need global retry strategy */

	  do
	    inc = gsl_data_handle_read (dhandle, k, peekbuf->end - k, peekbuf->data + k - peekbuf->start);
	  while (inc < 1 && n_retries-- && GSL_DATA_HANDLE_OPENED (dhandle));
	  if (inc < 1)
	    {   /* pathologic */
	      peekbuf->data[k - peekbuf->start] = 0;
	      inc = 1;
	      gsl_message_send (GSL_MSG_DATA_HANDLE, "PeekBuffer",
				GSL_ERROR_READ_FAILED, "unable to read from data handle (%p)", dhandle);
	    }
	}
    }
  return peekbuf->data[pos - peekbuf->start];
}

gint /* errno */
gsl_data_handle_dump (GslDataHandle    *dhandle,
		      gint	        fd,
		      GslWaveFormatType format,
		      guint             byte_order)
{
  GslLong l, offs = 0;

  g_return_val_if_fail (dhandle != NULL, EINVAL);
  g_return_val_if_fail (GSL_DATA_HANDLE_OPENED (dhandle), EINVAL);
  g_return_val_if_fail (fd >= 0, EINVAL);
  g_return_val_if_fail (format >= GSL_WAVE_FORMAT_UNSIGNED_8 && format <= GSL_WAVE_FORMAT_FLOAT, EINVAL);
  g_return_val_if_fail (byte_order == G_LITTLE_ENDIAN || byte_order == G_BIG_ENDIAN, EINVAL);

  l = dhandle->setup.n_values;
  while (l)
    {
      GslLong retry, j, n = MIN (l, GSL_DATA_HANDLE_PEEK_BUFFER);
      gfloat src[GSL_DATA_HANDLE_PEEK_BUFFER];

      retry = GSL_N_IO_RETRIES;
      do
	n = gsl_data_handle_read (dhandle, offs, n, src);
      while (n < 1 && retry--);
      if (retry < 0)
	return EIO;

      l -= n;
      offs += n;

      n = gsl_conv_from_float_clip (format, byte_order, src, src, n);

      do
	j = write (fd, src, n);
      while (j < 0 && errno == EINTR);
      if (j < 0)
	return errno ? errno : EIO;
    }
  return 0;
}

static void
write_bytes (gint        fd,
	     guint       n_bytes,
	     const void *bytes)
{
  gint errold = errno;
  guint j;

  do
    j = write (fd, bytes, n_bytes);
  while (j < 0 && errno == EINTR);

  if (!errno)
    errno = errold;
}

static void
write_uint32_le (gint    fd,
		 guint32 val)
{
  val = GUINT32_TO_LE (val);
  write_bytes (fd, 4, &val);
}

static void
write_uint16_le (gint    fd,
		 guint16 val)
{
  val = GUINT16_TO_LE (val);
  write_bytes (fd, 2, &val);
}

gint /* errno */
gsl_data_handle_dump_wav (GslDataHandle *dhandle,
			  gint           fd,
			  guint          n_bits,
			  guint          n_channels,
			  guint          sample_freq)
{
  guint data_length, file_length, byte_per_sample, byte_per_second;

  g_return_val_if_fail (dhandle != NULL, EINVAL);
  g_return_val_if_fail (GSL_DATA_HANDLE_OPENED (dhandle), EINVAL);
  g_return_val_if_fail (fd >= 0, EINVAL);
  g_return_val_if_fail (n_bits == 16 || n_bits == 8, EINVAL);
  g_return_val_if_fail (n_channels >= 1, EINVAL);

  data_length = dhandle->setup.n_values * (n_bits == 16 ? 2 : 1);
  file_length = data_length;
  file_length += 4 + 4;				/* 'RIFF' header */
  file_length += 4 + 4 + 2 + 2 + 4 + 4 + 2 + 2;	/* 'fmt ' header */
  file_length += 4 + 4;				/* 'data' header */
  byte_per_sample = (n_bits == 16 ? 2 : 1) * n_channels;
  byte_per_second = byte_per_sample * sample_freq;

  errno = 0;
  write_bytes (fd, 4, "RIFF");		/* main_chunk */
  write_uint32_le (fd, file_length);
  write_bytes (fd, 4, "WAVE");		/* chunk_type */
  write_bytes (fd, 4, "fmt ");		/* sub_chunk */
  write_uint32_le (fd, 16);		/* sub chunk length */
  write_uint16_le (fd, 1);		/* format (1=PCM) */
  write_uint16_le (fd, n_channels);
  write_uint32_le (fd, sample_freq);
  write_uint32_le (fd, byte_per_second);
  write_uint16_le (fd, byte_per_sample);
  write_uint16_le (fd, n_bits);
  write_bytes (fd, 4, "data");		/* data chunk */
  write_uint32_le (fd, data_length);

  if (errno)
    return errno;

  return gsl_data_handle_dump (dhandle, fd,
			       n_bits == 16 ? GSL_WAVE_FORMAT_SIGNED_16 : GSL_WAVE_FORMAT_UNSIGNED_8,
			       G_LITTLE_ENDIAN);
}

gboolean
gsl_data_detect_signal (GslDataHandle *handle,
			GslLong       *sigstart_p,
			GslLong       *sigend_p)
{
  gfloat level_0, level_1, level_2, level_3, level_4;
  gfloat signal_threshold = 16. * 16. * 16.;	/* noise level threshold */
  GslLong k, xcheck = -1, minsamp = -1, maxsamp = -2;
  GslDataPeekBuffer peek_buffer = { +1 /* incremental direction */, 0, };
  
  g_return_val_if_fail (handle != NULL, FALSE);
  g_return_val_if_fail (GSL_DATA_HANDLE_OPENED (handle), FALSE);
  g_return_val_if_fail (sigstart_p || sigend_p, FALSE);

  /* keep open */
  gsl_data_handle_open (handle);

  /* find fadein/fadeout point */
  k = 0;
  level_4 = gsl_data_handle_peek_value (handle, k, &peek_buffer);
  level_4 *= 32768;
  level_0 = level_1 = level_2 = level_3 = level_4;
  for (; k < handle->setup.n_values; k++)
    {
      gfloat mean, needx, current;

      current = gsl_data_handle_peek_value (handle, k, &peek_buffer) * 32768.;
      if (xcheck < 0 && ABS (current) >= 16)
	xcheck = k;
      mean = (level_0 + level_1 + level_2 + level_3 + level_4) / 5;
      needx = (ABS (level_4 + current - (level_0 + level_1 + level_2 + level_3) / 2) *
	       ABS (level_4 - mean) * ABS (current - mean));
      /* shift */
      level_0 = level_1; level_1 = level_2; level_2 = level_3; level_3 = level_4; level_4 = current;
      /* aprox. noise compare */
      if (ABS (needx) > signal_threshold)
	{
	  if (minsamp < 0)
	    minsamp = k;
	  if (maxsamp < k)
	    maxsamp = k;
	}
      /* if (minsamp >= 0 && xcheck >= 0)
       *   break;
       */
    }
  if (xcheck - minsamp > 0)
    g_printerr("###################");
  g_printerr ("active area %ld .. %ld, signal>16 at: %ld\t diff: %ld\n",minsamp,maxsamp,xcheck, xcheck-minsamp);

  /* release open reference */
  gsl_data_handle_close (handle);

  if (sigstart_p)
    *sigstart_p = minsamp;
  if (sigend_p)
    *sigend_p = MAX (-1, maxsamp);

  return maxsamp >= minsamp;
}

GslLong
gsl_data_find_sample (GslDataHandle *dhandle,
		      gfloat         min_value,
		      gfloat         max_value,
		      GslLong        start_offset,
		      gint           direction)
{
  GslDataPeekBuffer peekbuf = { 0, 0, 0, };
  GslLong i;

  g_return_val_if_fail (dhandle != NULL, -1);
  g_return_val_if_fail (direction == -1 || direction == +1, -1);

  if (gsl_data_handle_open (dhandle) != GSL_ERROR_NONE ||
      start_offset >= dhandle->setup.n_values)
    return -1;

  if (start_offset < 0)
    start_offset = dhandle->setup.n_values - 1;

  peekbuf.dir = direction;
  if (min_value <= max_value)
    for (i = start_offset; i < dhandle->setup.n_values && i >= 0; i += direction)
      {
	gfloat val = gsl_data_handle_peek_value (dhandle, i, &peekbuf);

	/* g_print ("(%lu): %f <= %f <= %f\n", i, min_value, val, max_value); */
	if (val >= min_value && val <= max_value)
	  break;
      }
  else
    for (i = start_offset; i < dhandle->setup.n_values && i >= 0; i += direction)
      {
	gfloat val = gsl_data_handle_peek_value (dhandle, i, &peekbuf);

	/* g_print ("(%lu): %f > %f || %f < %f\n", i, val, max_value, val, min_value); */
	if (val > min_value || val < max_value)
	  break;
      }

  gsl_data_handle_close (dhandle);

  return i >= dhandle->setup.n_values ? -1: i;
}

static inline gdouble
tailmatch_score_loop (GslDataHandle *shandle,
		      GslDataHandle *dhandle,
		      GslLong	   start,
		      gdouble	   worst_score)
{
  GslLong l, length = MIN (shandle->setup.n_values, dhandle->setup.n_values);
  gfloat v1[GSL_DATA_HANDLE_PEEK_BUFFER], v2[GSL_DATA_HANDLE_PEEK_BUFFER];
  gdouble score = 0;

  g_assert (start < length);

  for (l = start; l < length; )
    {
      GslLong b = MIN (GSL_DATA_HANDLE_PEEK_BUFFER, length - l);

      b = gsl_data_handle_read (shandle, l, b, v1);
      b = gsl_data_handle_read (dhandle, l, b, v2);
      g_assert (b >= 1);
      l += b;

      while (b--)
	score += (v1[b] - v2[b]) * (v1[b] - v2[b]);

      /* for performance, prematurely abort */
      if (score > worst_score)
	break;
    }
  return score;
}

gboolean
gsl_data_find_tailmatch (GslDataHandle     *dhandle,
			 const GslLoopSpec *lspec,
			 GslLong           *loop_start_p,
			 GslLong           *loop_end_p)
{
  GslDataHandle *shandle;
  GslDataCache *dcache;
  GslLong length, offset, l, lsize, pcount, start = 0, end = 0;
  gdouble pbound, pval, best_score = GSL_MAXLONG;
  
  g_return_val_if_fail (dhandle != NULL, FALSE);
  g_return_val_if_fail (lspec != NULL, FALSE);
  g_return_val_if_fail (loop_start_p != NULL, FALSE);
  g_return_val_if_fail (loop_end_p != NULL, FALSE);
  g_return_val_if_fail (lspec->head_skip >= 0, FALSE);
  g_return_val_if_fail (lspec->tail_cut >= 0, FALSE);
  g_return_val_if_fail (lspec->min_loop >= 1, FALSE);
  g_return_val_if_fail (lspec->max_loop >= lspec->min_loop, FALSE);
  g_return_val_if_fail (lspec->tail_cut >= lspec->max_loop, FALSE);

  if (gsl_data_handle_open (dhandle) != GSL_ERROR_NONE)
    return FALSE;
  length = dhandle->setup.n_values;
  if (lspec->head_skip < length)
    {
      gsl_data_handle_close (dhandle);
      return FALSE;
    }
  offset = lspec->head_skip;
  length -= offset;
  if (lspec->tail_cut < length)
    {
      gsl_data_handle_close (dhandle);
      return FALSE;
    }
  length -= lspec->tail_cut;
  if (lspec->max_loop <= length)
    {
      gsl_data_handle_close (dhandle);
      return FALSE;
    }
  
  dcache = gsl_data_cache_new (dhandle, 1);
  shandle = gsl_data_handle_new_dcached (dcache);
  gsl_data_cache_unref (dcache);
  gsl_data_handle_open (shandle);
  gsl_data_handle_close (dhandle);
  gsl_data_handle_unref (shandle);
  /* at this point, we just hold one open() count on shandle */
  
  pbound = (lspec->max_loop - lspec->min_loop + 1.);
  pbound *= length / 100.;
  pval = 0;
  pcount = 100;

  for (lsize = lspec->min_loop; lsize <= lspec->max_loop; lsize++)
    {
      for (l = length - lsize; l >= 0; l--)
	{
	  GslDataHandle *lhandle = gsl_data_handle_new_looped (shandle, offset + l, offset + l + lsize - 1);
	  gdouble score;
	  
	  gsl_data_handle_open (lhandle);
	  score = tailmatch_score_loop (shandle, lhandle, offset + l, best_score);
	  gsl_data_handle_close (lhandle);
	  gsl_data_handle_unref (lhandle);
	  
	  if (score < best_score)
	    {
	      start = offset + l;
	      end = offset + l + lsize - 1;
	      g_print ("\nimproved: %f < %f: [0x%lx..0x%lx] (%lu)\n", score, best_score, start, end, lsize);
	      best_score = score;
	    }
	  else
	    break;
	}
      if (!pcount--)
	{
	  pcount = 100;
	  pval = lsize - lspec->min_loop;
	  pbound = (lspec->max_loop - lspec->min_loop + 1.);
	  g_print ("\rprocessed: %f%%                  \r", pval / pbound);
	}
    }
  gsl_data_handle_close (shandle);

  g_print ("\nhalted: %f: [0x%lx..0x%lx] (%lu)\n", best_score, start, end, end - start + 1);
  
  *loop_start_p = start;
  *loop_end_p = end;

  return TRUE;
}

/**
 * gsl_data_find_block
 * @handle:   an open GslDataHandle
 * @n_values: amount of values to look for
 * @values:   values to find
 * @epsilon:  maximum difference upon comparisons
 * @RETURNS:  position of values in data handle or -1
 *
 * Find the position of a block of values within a
 * data handle, where all values compare to the reference
 * values with a delta smaller than epsilon.
 */
GslLong
gsl_data_find_block (GslDataHandle *handle,
		     guint          n_values,
		     const gfloat  *values,
		     gfloat         epsilon)
{
  GslDataPeekBuffer pbuf = { +1 /* random access: 0 */ };
  guint i;

  g_return_val_if_fail (handle != NULL, -1);
  g_return_val_if_fail (GSL_DATA_HANDLE_OPENED (handle), -1);

  if (n_values < 1)
    return -1;
  else
    g_return_val_if_fail (values != NULL, -1);

  for (i = 0; i < handle->setup.n_values; i++)
    {
      guint j;

      if (n_values > handle->setup.n_values - i)
	return -1;

      for (j = 0; j < n_values; j++)
	{
	  if (fabs (values[j] - gsl_data_handle_peek_value (handle, i + j, &pbuf)) >= epsilon)
	    break;
	}
      if (j >= n_values)
	return i;
    }
  return -1;
}
