/* libSoX effect: Pad With Silence   (c) 2006 robs@users.sourceforge.net
 *
 * 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.1 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 "sox_i.h"

typedef struct {
  unsigned npads;     /* Number of pads requested */
  struct {
    char * str;       /* Command-line argument to parse for this pad */
    uint64_t start; /* Start padding when in_pos equals this */
    uint64_t pad;   /* Number of samples to pad */
    uint64_t align; /* Align output to multiple of this */
  } * pads;

  uint64_t in_pos;  /* Number of samples read from the input stream */
  uint64_t out_pos; /* Number of samples written to the output stream */
  unsigned pads_pos;  /* Number of pads completed so far */
  uint64_t pad_pos; /* Number of samples through the current pad */
} priv_t;

static uint64_t pad_align(uint64_t pos, uint64_t align)
{
  return (align - pos % align) % align;
}

static int parse(sox_effect_t * effp, char * * argv, sox_rate_t rate)
{
  priv_t * p = (priv_t *)effp->priv;
  char const * next;
  unsigned i;
  uint64_t last_seen = 0;
  const uint64_t in_length = argv ? 0 :
    (effp->in_signal.length != SOX_UNKNOWN_LEN ?
     effp->in_signal.length / effp->in_signal.channels : SOX_UNKNOWN_LEN);
  uint64_t pad_len = 0;
  uint64_t *arg;
  char *str;

  for (i = 0; i < p->npads; ++i) {
    if (argv) /* 1st parse only */
      p->pads[i].str = lsx_strdup(argv[i]);
    str = p->pads[i].str;
    if (*str == '%') {
      str++;
      arg = &p->pads[i].align;
    } else {
      arg = &p->pads[i].pad;
    }
    next = lsx_parsesamples(rate, str, arg, 't');
    if (next == NULL) {
      lsx_fail("cannot parse length `%s'", str);
      return SOX_EOF;
    }
    if (*next == '\0')
      p->pads[i].start = i? in_length : 0;
    else {
      char const *pos;
      if (*next != '@') break;
      next = lsx_parseposition(rate, (pos=next+1), argv ? NULL : &p->pads[i].start,
               last_seen, in_length, '=');
      if (next == NULL || *next != '\0') {
        lsx_fail("cannot parse position `%s'", pos);
        return SOX_EOF;
      }
      last_seen = p->pads[i].start;
      if (p->pads[i].start == SOX_UNKNOWN_LEN)
        p->pads[i].start = UINT64_MAX; /* currently the same value, but ... */
    }
    if (!argv) {
      if (p->pads[i].align && p->pads[i].start != UINT64_MAX) {
        p->pads[i].pad =
          pad_align(p->pads[i].start + pad_len, p->pads[i].align);
        p->pads[i].align = 0;
      }

      pad_len += p->pads[i].pad;

      /* Do this check only during the second pass when the actual
         sample rate is known, otherwise it might fail on valid
         commands like
           pad 1@0.5 1@30000s
         if the rate is, e.g., 48k. */
      if (i > 0 && p->pads[i].start <= p->pads[i-1].start) {
        lsx_fail("positions must be in ascending order");
        return SOX_EOF;
      }
    }
  }
  return SOX_SUCCESS;
}

static int create_pad(sox_effect_t * effp, int argc, char * * argv)
{
  priv_t * p = (priv_t *)effp->priv;
  --argc, ++argv;
  p->npads = argc;
  lsx_vcalloc(p->pads, p->npads);
  return parse(effp, argv, 1e5); /* No rate yet; parse with dummy */
}

static int start_pad(sox_effect_t * effp)
{
  priv_t * p = (priv_t *)effp->priv;
  unsigned i;

  /* Re-parse now rate is known */
  if (parse(effp, 0, effp->in_signal.rate) != SOX_SUCCESS)
    return SOX_EOF;

  if ((effp->out_signal.length = effp->in_signal.length) != SOX_UNKNOWN_LEN) {
    for (i = 0; i < p->npads; ++i)
      effp->out_signal.length +=
        p->pads[i].pad * effp->in_signal.channels;

    /* Check that the last pad position (except for "at the end")
       is within bounds. */
    i = p->npads;
    if (i > 0 && p->pads[i-1].start == UINT64_MAX)
      i--;
    if (i > 0 &&
        p->pads[i-1].start * effp->in_signal.channels
          > effp->in_signal.length)
    {
      lsx_fail("position is after end of audio");
      return SOX_EOF;
    }
  }

  p->in_pos = p->out_pos = p->pad_pos = p->pads_pos = 0;
  for (i = 0; i < p->npads; ++i)
    if (p->pads[i].pad || p->pads[i].align)
      return SOX_SUCCESS;
  return SOX_EFF_NULL;
}

static int flow_pad(sox_effect_t * effp, const sox_sample_t * ibuf,
    sox_sample_t * obuf, size_t * isamp, size_t * osamp)
{
  priv_t * p = (priv_t *)effp->priv;
  size_t c, idone = 0, odone = 0;
  *isamp /= effp->in_signal.channels;
  *osamp /= effp->in_signal.channels;

  do {
    /* Copying: */
    for (; idone < *isamp && odone < *osamp && !(p->pads_pos != p->npads && p->in_pos == p->pads[p->pads_pos].start); ++idone, ++odone, ++p->in_pos)
      for (c = 0; c < effp->in_signal.channels; ++c) *obuf++ = *ibuf++;

    /* Padding: */
    if (p->pads_pos != p->npads && p->in_pos == p->pads[p->pads_pos].start) {
      for (; odone < *osamp && p->pad_pos < p->pads[p->pads_pos].pad; ++odone, ++p->pad_pos)
        for (c = 0; c < effp->in_signal.channels; ++c) *obuf++ = 0;
      if (p->pad_pos == p->pads[p->pads_pos].pad) { /* Move to next pad? */
        ++p->pads_pos;
        p->pad_pos = 0;
      }
    }
  } while (idone < *isamp && odone < *osamp);

  p->out_pos += odone;

  *isamp = idone * effp->in_signal.channels;
  *osamp = odone * effp->in_signal.channels;
  return SOX_SUCCESS;
}

static int drain_pad(sox_effect_t * effp, sox_sample_t * obuf, size_t * osamp)
{
  priv_t * p = (priv_t *)effp->priv;
  static size_t isamp = 0;
  if (p->pads_pos != p->npads && p->in_pos != p->pads[p->pads_pos].start) {
    if (p->pads[p->pads_pos].align)
      p->pads[p->pads_pos].pad =
        pad_align(p->pads[p->pads_pos].align, p->pads[p->pads_pos].align);
    p->in_pos = UINT64_MAX;  /* Invoke the final pad (with no given start) */
  }
  return flow_pad(effp, 0, obuf, &isamp, osamp);
}

static int stop_pad(sox_effect_t * effp)
{
  priv_t * p = (priv_t *)effp->priv;
  if (p->pads_pos != p->npads)
    lsx_warn("input audio is too short; pads not applied: %u", p->npads-p->pads_pos);
  return SOX_SUCCESS;
}

static int kill_pad(sox_effect_t * effp)
{
  priv_t * p = (priv_t *)effp->priv;
  unsigned i;
  for (i = 0; i < p->npads; ++i)
    free(p->pads[i].str);
  free(p->pads);
  return SOX_SUCCESS;
}

sox_effect_handler_t const * lsx_pad_effect_fn(void)
{
  static const char usage[] = "{[%]length[@position]}";
  static sox_effect_handler_t handler = {
    "pad", usage, SOX_EFF_MCHAN|SOX_EFF_LENGTH|SOX_EFF_MODIFY,
    create_pad, start_pad, flow_pad, drain_pad, stop_pad, kill_pad,
    sizeof(priv_t),
    NULL,
  };
  return &handler;
}
