/***************************************************************************
 * tracelist.c:
 *
 * Routines to handle TraceList and related structures.
 *
 * Written by Chad Trabant, IRIS Data Management Center
 *
 * modified: 2015.108
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "libmseed.h"

MSTraceSeg *mstl_msr2seg (MSRecord *msr, hptime_t endtime);
MSTraceSeg *mstl_addmsrtoseg (MSTraceSeg *seg, MSRecord *msr, hptime_t endtime, flag whence);
MSTraceSeg *mstl_addsegtoseg (MSTraceSeg *seg1, MSTraceSeg *seg2);

/***************************************************************************
 * mstl_init:
 *
 * Initialize and return a MSTraceList struct, allocating memory if
 * needed.  If the supplied MSTraceList is not NULL any associated
 * memory it will be freed including data at prvtptr pointers.
 *
 * Returns a pointer to a MSTraceList struct on success or NULL on error.
 ***************************************************************************/
MSTraceList *
mstl_init (MSTraceList *mstl)
{
  if (mstl)
  {
    mstl_free (&mstl, 1);
  }

  mstl = (MSTraceList *)malloc (sizeof (MSTraceList));

  if (mstl == NULL)
  {
    ms_log (2, "mstl_init(): Cannot allocate memory\n");
    return NULL;
  }

  memset (mstl, 0, sizeof (MSTraceList));

  return mstl;
} /* End of mstl_init() */

/***************************************************************************
 * mstl_free:
 *
 * Free all memory associated with a MSTraceList struct and set the
 * pointer to 0.
 *
 * If the freeprvtptr flag is true any private pointer data will also
 * be freed when present.
 ***************************************************************************/
void
mstl_free (MSTraceList **ppmstl, flag freeprvtptr)
{
  MSTraceID *id       = 0;
  MSTraceID *nextid   = 0;
  MSTraceSeg *seg     = 0;
  MSTraceSeg *nextseg = 0;

  if (!ppmstl)
    return;

  if (*ppmstl)
  {
    /* Free any associated traces */
    id = (*ppmstl)->traces;
    while (id)
    {
      nextid = id->next;

      /* Free any associated trace segments */
      seg = id->first;
      while (seg)
      {
        nextseg = seg->next;

        /* Free private pointer data if present and requested*/
        if (freeprvtptr && seg->prvtptr)
          free (seg->prvtptr);

        /* Free data array if allocated */
        if (seg->datasamples)
          free (seg->datasamples);

        free (seg);
        seg = nextseg;
      }

      /* Free private pointer data if present and requested*/
      if (freeprvtptr && id->prvtptr)
        free (id->prvtptr);

      free (id);
      id = nextid;
    }

    free (*ppmstl);

    *ppmstl = NULL;
  }

  return;
} /* End of mstl_free() */

/***************************************************************************
 * mstl_addmsr:
 *
 * Add data coverage from an MSRecord to a MSTraceList by searching the
 * list for the appropriate MSTraceID and MSTraceSeg and either adding
 * data to it or creating a new MStraceID and/or MSTraceSeg if needed.
 *
 * If the dataquality flag is true the data quality bytes must also
 * match otherwise they are ignored.
 *
 * If the autoheal flag is true extra processing is invoked to conjoin
 * trace segments that fit together after the MSRecord coverage is
 * added.  For segments that are removed, any memory at the prvtptr
 * will be freed.
 *
 * An MSTraceList is always maintained with the MSTraceIDs in
 * descending alphanumeric order.  MSTraceIDs are always maintained
 * with MSTraceSegs in data time time order.
 *
 * Return a pointer to the MSTraceSeg updated or 0 on error.
 ***************************************************************************/
MSTraceSeg *
mstl_addmsr (MSTraceList *mstl, MSRecord *msr, flag dataquality,
             flag autoheal, double timetol, double sampratetol)
{
  MSTraceID *id       = 0;
  MSTraceID *searchid = 0;
  MSTraceID *ltid     = 0;

  MSTraceSeg *seg       = 0;
  MSTraceSeg *searchseg = 0;
  MSTraceSeg *segbefore = 0;
  MSTraceSeg *segafter  = 0;
  MSTraceSeg *followseg = 0;

  hptime_t endtime;
  hptime_t pregap;
  hptime_t postgap;
  hptime_t lastgap;
  hptime_t firstgap;
  hptime_t hpdelta;
  hptime_t hptimetol  = 0;
  hptime_t nhptimetol = 0;

  char srcname[45];
  char *s1, *s2;
  flag whence;
  flag lastratecheck;
  flag firstratecheck;
  int mag;
  int cmp;
  int ltmag;
  int ltcmp;

  if (!mstl || !msr)
    return 0;

  /* Calculate end time for MSRecord */
  if ((endtime = msr_endtime (msr)) == HPTERROR)
  {
    ms_log (2, "mstl_addmsr(): Error calculating record end time\n");
    return 0;
  }

  /* Generate source name string */
  if (!msr_srcname (msr, srcname, dataquality))
  {
    ms_log (2, "mstl_addmsr(): Error generating srcname for MSRecord\n");
    return 0;
  }

  /* Search for matching trace ID starting with last accessed ID and
     then looping through the trace ID list. */
  if (mstl->last)
  {
    s1 = mstl->last->srcname;
    s2 = srcname;
    while (*s1 == *s2++)
    {
      if (*s1++ == '\0')
        break;
    }
    cmp = (*s1 - *--s2);

    if (!cmp)
    {
      id = mstl->last;
    }
    else
    {
      /* Loop through trace ID list searching for a match, simultaneously
         track the source name which is closest but less than the MSRecord
         to allow for later insertion with sort order. */
      searchid = mstl->traces;
      ltcmp    = 0;
      ltmag    = 0;
      while (searchid)
      {
        /* Compare source names */
        s1  = searchid->srcname;
        s2  = srcname;
        mag = 0;
        while (*s1 == *s2++)
        {
          mag++;
          if (*s1++ == '\0')
            break;
        }
        cmp = (*s1 - *--s2);

        /* If source names did not match track closest "less than" value
           and continue searching. */
        if (cmp != 0)
        {
          if (cmp < 0)
          {
            if ((ltcmp == 0 || cmp >= ltcmp) && mag >= ltmag)
            {
              ltcmp = cmp;
              ltmag = mag;
              ltid  = searchid;
            }
            else if (mag > ltmag)
            {
              ltcmp = cmp;
              ltmag = mag;
              ltid  = searchid;
            }
          }

          searchid = searchid->next;
          continue;
        }

        /* If we made it this far we found a match */
        id = searchid;
        break;
      }
    }
  } /* Done searching for match in trace ID list */

  /* If no matching ID was found create new MSTraceID and MSTraceSeg entries */
  if (!id)
  {
    if (!(id = (MSTraceID *)calloc (1, sizeof (MSTraceID))))
    {
      ms_log (2, "mstl_addmsr(): Error allocating memory\n");
      return 0;
    }

    /* Populate MSTraceID */
    strcpy (id->network, msr->network);
    strcpy (id->station, msr->station);
    strcpy (id->location, msr->location);
    strcpy (id->channel, msr->channel);
    id->dataquality = msr->dataquality;
    strcpy (id->srcname, srcname);

    id->earliest    = msr->starttime;
    id->latest      = endtime;
    id->numsegments = 1;

    if (!(seg = mstl_msr2seg (msr, endtime)))
    {
      return 0;
    }
    id->first = id->last = seg;

    /* Add new MSTraceID to MSTraceList */
    if (!mstl->traces || !ltid)
    {
      id->next     = mstl->traces;
      mstl->traces = id;
    }
    else
    {
      id->next   = ltid->next;
      ltid->next = id;
    }

    mstl->numtraces++;
  }
  /* Add data coverage to the matching MSTraceID */
  else
  {
    /* Calculate high-precision sample period */
    hpdelta = (hptime_t) ((msr->samprate) ? (HPTMODULUS / msr->samprate) : 0.0);

    /* Calculate high-precision time tolerance */
    if (timetol == -1.0)
      hptimetol = (hptime_t) (0.5 * hpdelta); /* Default time tolerance is 1/2 sample period */
    else if (timetol >= 0.0)
      hptimetol = (hptime_t) (timetol * HPTMODULUS);

    nhptimetol = (hptimetol) ? -hptimetol : 0;

    /* last/firstgap are negative when the record overlaps the trace
     * segment and positive when there is a time gap. */

    /* Gap relative to the last segment */
    lastgap = msr->starttime - id->last->endtime - hpdelta;

    /* Gap relative to the first segment */
    firstgap = id->first->starttime - endtime - hpdelta;

    /* Sample rate tolerance checks for first and last segments */
    if (sampratetol == -1.0)
    {
      lastratecheck  = MS_ISRATETOLERABLE (msr->samprate, id->last->samprate);
      firstratecheck = MS_ISRATETOLERABLE (msr->samprate, id->first->samprate);
    }
    else
    {
      lastratecheck  = (ms_dabs (msr->samprate - id->last->samprate) > sampratetol) ? 0 : 1;
      firstratecheck = (ms_dabs (msr->samprate - id->first->samprate) > sampratetol) ? 0 : 1;
    }

    /* Search first for the simple scenarios in order of likelihood:
     * - Record fits at end of last segment
     * - Record fits after all coverage
     * - Record fits before all coverage
     * - Record fits at beginning of first segment
     *
     * If none of those scenarios are true search the complete segment list.
     */

    /* Record coverage fits at end of last segment */
    if (lastgap <= hptimetol && lastgap >= nhptimetol && lastratecheck)
    {
      if (!mstl_addmsrtoseg (id->last, msr, endtime, 1))
        return 0;

      seg = id->last;

      if (endtime > id->latest)
        id->latest = endtime;
    }
    /* Record coverage is after all other coverage */
    else if ((msr->starttime - hpdelta - hptimetol) > id->latest)
    {
      if (!(seg = mstl_msr2seg (msr, endtime)))
        return 0;

      /* Add to end of list */
      id->last->next = seg;
      seg->prev      = id->last;
      id->last       = seg;
      id->numsegments++;

      if (endtime > id->latest)
        id->latest = endtime;
    }
    /* Record coverage is before all other coverage */
    else if ((endtime + hpdelta + hptimetol) < id->earliest)
    {
      if (!(seg = mstl_msr2seg (msr, endtime)))
        return 0;

      /* Add to beginning of list */
      id->first->prev = seg;
      seg->next       = id->first;
      id->first       = seg;
      id->numsegments++;

      if (msr->starttime < id->earliest)
        id->earliest = msr->starttime;
    }
    /* Record coverage fits at beginning of first segment */
    else if (firstgap <= hptimetol && firstgap >= nhptimetol && firstratecheck)
    {
      if (!mstl_addmsrtoseg (id->first, msr, endtime, 2))
        return 0;

      seg = id->first;

      if (msr->starttime < id->earliest)
        id->earliest = msr->starttime;
    }
    /* Search complete segment list for matches */
    else
    {
      searchseg = id->first;
      segbefore = 0; /* Find segment that record fits before */
      segafter  = 0; /* Find segment that record fits after */
      followseg = 0; /* Track segment that record follows in time order */
      while (searchseg)
      {
        if (msr->starttime > searchseg->starttime)
          followseg = searchseg;

        whence = 0;

        postgap = msr->starttime - searchseg->endtime - hpdelta;
        if (!segbefore && postgap <= hptimetol && postgap >= nhptimetol)
          whence = 1;

        pregap = searchseg->starttime - endtime - hpdelta;
        if (!segafter && pregap <= hptimetol && pregap >= nhptimetol)
          whence = 2;

        if (!whence)
        {
          searchseg = searchseg->next;
          continue;
        }

        if (sampratetol == -1.0)
        {
          if (!MS_ISRATETOLERABLE (msr->samprate, searchseg->samprate))
          {
            searchseg = searchseg->next;
            continue;
          }
        }
        else
        {
          if (ms_dabs (msr->samprate - searchseg->samprate) > sampratetol)
          {
            searchseg = searchseg->next;
            continue;
          }
        }

        if (whence == 1)
          segbefore = searchseg;
        else
          segafter = searchseg;

        /* Done searching if not autohealing */
        if (!autoheal)
          break;

        /* Done searching if both before and after segments are found */
        if (segbefore && segafter)
          break;

        searchseg = searchseg->next;
      } /* Done looping through segments */

      /* Add MSRecord coverage to end of segment before */
      if (segbefore)
      {
        if (!mstl_addmsrtoseg (segbefore, msr, endtime, 1))
        {
          return 0;
        }

        /* Merge two segments that now fit if autohealing */
        if (autoheal && segafter && segbefore != segafter)
        {
          /* Add segafter coverage to segbefore */
          if (!mstl_addsegtoseg (segbefore, segafter))
          {
            return 0;
          }

          /* Shift last segment pointer if it's going to be removed */
          if (segafter == id->last)
            id->last = id->last->prev;

          /* Remove segafter from list */
          if (segafter->prev)
            segafter->prev->next = segafter->next;
          if (segafter->next)
            segafter->next->prev = segafter->prev;

          /* Free data samples, private data and segment structure */
          if (segafter->datasamples)
            free (segafter->datasamples);

          if (segafter->prvtptr)
            free (segafter->prvtptr);

          free (segafter);
        }

        seg = segbefore;
      }
      /* Add MSRecord coverage to beginning of segment after */
      else if (segafter)
      {
        if (!mstl_addmsrtoseg (segafter, msr, endtime, 2))
        {
          return 0;
        }

        seg = segafter;
      }
      /* Add MSRecord coverage to new segment */
      else
      {
        /* Create new segment */
        if (!(seg = mstl_msr2seg (msr, endtime)))
        {
          return 0;
        }

        /* Add new segment as first in list */
        if (!followseg)
        {
          seg->next = id->first;
          if (id->first)
            id->first->prev = seg;

          id->first = seg;
        }
        /* Add new segment after the followseg segment */
        else
        {
          seg->next = followseg->next;
          seg->prev = followseg;
          if (followseg->next)
            followseg->next->prev = seg;
          followseg->next         = seg;

          if (followseg == id->last)
            id->last = seg;
        }

        id->numsegments++;
      }

      /* Track earliest and latest times */
      if (msr->starttime < id->earliest)
        id->earliest = msr->starttime;

      if (endtime > id->latest)
        id->latest = endtime;
    } /* End of searching segment list */
  }   /* End of adding coverage to matching ID */

  /* Sort modified segment into place, logic above should limit these to few shifts if any */
  while (seg->next && (seg->starttime > seg->next->starttime ||
                       (seg->starttime == seg->next->starttime && seg->endtime < seg->next->endtime)))
  {
    /* Move segment down list, swap seg and seg->next */
    segafter = seg->next;

    if (seg->prev)
      seg->prev->next = segafter;

    if (segafter->next)
      segafter->next->prev = seg;

    segafter->prev = seg->prev;
    seg->prev      = segafter;
    seg->next      = segafter->next;
    segafter->next = seg;

    /* Reset first and last segment pointers if replaced */
    if (id->first == seg)
      id->first = segafter;

    if (id->last == segafter)
      id->last = seg;
  }
  while (seg->prev && (seg->starttime < seg->prev->starttime ||
                       (seg->starttime == seg->prev->starttime && seg->endtime > seg->prev->endtime)))
  {
    /* Move segment up list, swap seg and seg->prev */
    segbefore = seg->prev;

    if (seg->next)
      seg->next->prev = segbefore;

    if (segbefore->prev)
      segbefore->prev->next = seg;

    segbefore->next = seg->next;
    seg->next       = segbefore;
    seg->prev       = segbefore->prev;
    segbefore->prev = seg;

    /* Reset first and last segment pointers if replaced */
    if (id->first == segbefore)
      id->first = seg;

    if (id->last == seg)
      id->last = segbefore;
  }

  /* Set MSTraceID as last accessed */
  mstl->last = id;

  return seg;
} /* End of mstl_addmsr() */

/***************************************************************************
 * mstl_msr2seg:
 *
 * Create an MSTraceSeg structure from an MSRecord structure.
 *
 * Return a pointer to a MSTraceSeg otherwise 0 on error.
 ***************************************************************************/
MSTraceSeg *
mstl_msr2seg (MSRecord *msr, hptime_t endtime)
{
  MSTraceSeg *seg = 0;
  int samplesize;

  if (!(seg = (MSTraceSeg *)calloc (1, sizeof (MSTraceSeg))))
  {
    ms_log (2, "mstl_addmsr(): Error allocating memory\n");
    return 0;
  }

  /* Populate MSTraceSeg */
  seg->starttime  = msr->starttime;
  seg->endtime    = endtime;
  seg->samprate   = msr->samprate;
  seg->samplecnt  = msr->samplecnt;
  seg->sampletype = msr->sampletype;
  seg->numsamples = msr->numsamples;

  /* Allocate space for and copy datasamples */
  if (msr->datasamples && msr->numsamples)
  {
    samplesize = ms_samplesize (msr->sampletype);

    if (!(seg->datasamples = malloc ((size_t) (samplesize * msr->numsamples))))
    {
      ms_log (2, "mstl_msr2seg(): Error allocating memory\n");
      return 0;
    }

    /* Copy data samples from MSRecord to MSTraceSeg */
    memcpy (seg->datasamples, msr->datasamples, (size_t) (samplesize * msr->numsamples));
  }

  return seg;
} /* End of mstl_msr2seg() */

/***************************************************************************
 * mstl_addmsrtoseg:
 *
 * Add data coverage from a MSRecord structure to a MSTraceSeg structure.
 *
 * Data coverage is added to the beginning or end of MSTraceSeg
 * according to the whence flag:
 * 1 : add coverage to the end
 * 2 : add coverage to the beginninig
 *
 * Return a pointer to a MSTraceSeg otherwise 0 on error.
 ***************************************************************************/
MSTraceSeg *
mstl_addmsrtoseg (MSTraceSeg *seg, MSRecord *msr, hptime_t endtime, flag whence)
{
  int samplesize = 0;
  void *newdatasamples;

  if (!seg || !msr)
    return 0;

  /* Allocate more memory for data samples if included */
  if (msr->datasamples && msr->numsamples > 0)
  {
    if (msr->sampletype != seg->sampletype)
    {
      ms_log (2, "mstl_addmsrtoseg(): MSRecord sample type (%c) does not match segment sample type (%c)\n",
              msr->sampletype, seg->sampletype);
      return 0;
    }

    if (!(samplesize = ms_samplesize (msr->sampletype)))
    {
      ms_log (2, "mstl_addmsrtoseg(): Unknown sample size for sample type: %c\n", msr->sampletype);
      return 0;
    }

    if (!(newdatasamples = realloc (seg->datasamples, (size_t) ((seg->numsamples + msr->numsamples) * samplesize))))
    {
      ms_log (2, "mstl_addmsrtoseg(): Error allocating memory\n");
      return 0;
    }

    seg->datasamples = newdatasamples;
  }

  /* Add coverage to end of segment */
  if (whence == 1)
  {
    seg->endtime = endtime;
    seg->samplecnt += msr->samplecnt;

    if (msr->datasamples && msr->numsamples > 0)
    {
      memcpy ((char *)seg->datasamples + (seg->numsamples * samplesize),
              msr->datasamples,
              (size_t) (msr->numsamples * samplesize));

      seg->numsamples += msr->numsamples;
    }
  }
  /* Add coverage to beginning of segment */
  else if (whence == 2)
  {
    seg->starttime = msr->starttime;
    seg->samplecnt += msr->samplecnt;

    if (msr->datasamples && msr->numsamples > 0)
    {
      memmove ((char *)seg->datasamples + (msr->numsamples * samplesize),
               seg->datasamples,
               (size_t) (seg->numsamples * samplesize));

      memcpy (seg->datasamples,
              msr->datasamples,
              (size_t) (msr->numsamples * samplesize));

      seg->numsamples += msr->numsamples;
    }
  }
  else
  {
    ms_log (2, "mstl_addmsrtoseg(): unrecognized whence value: %d\n", whence);
    return 0;
  }

  return seg;
} /* End of mstl_addmsrtoseg() */

/***************************************************************************
 * mstl_addsegtoseg:
 *
 * Add data coverage from seg2 to seg1.
 *
 * Return a pointer to a seg1 otherwise 0 on error.
 ***************************************************************************/
MSTraceSeg *
mstl_addsegtoseg (MSTraceSeg *seg1, MSTraceSeg *seg2)
{
  int samplesize = 0;
  void *newdatasamples;

  if (!seg1 || !seg2)
    return 0;

  /* Allocate more memory for data samples if included */
  if (seg2->datasamples && seg2->numsamples > 0)
  {
    if (seg2->sampletype != seg1->sampletype)
    {
      ms_log (2, "mstl_addsegtoseg(): MSTraceSeg sample types do not match (%c and %c)\n",
              seg1->sampletype, seg2->sampletype);
      return 0;
    }

    if (!(samplesize = ms_samplesize (seg1->sampletype)))
    {
      ms_log (2, "mstl_addsegtoseg(): Unknown sample size for sample type: %c\n", seg1->sampletype);
      return 0;
    }

    if (!(newdatasamples = realloc (seg1->datasamples, (size_t) ((seg1->numsamples + seg2->numsamples) * samplesize))))
    {
      ms_log (2, "mstl_addsegtoseg(): Error allocating memory\n");
      return 0;
    }

    seg1->datasamples = newdatasamples;
  }

  /* Add seg2 coverage to end of seg1 */
  seg1->endtime = seg2->endtime;
  seg1->samplecnt += seg2->samplecnt;

  if (seg2->datasamples && seg2->numsamples > 0)
  {
    memcpy ((char *)seg1->datasamples + (seg1->numsamples * samplesize),
            seg2->datasamples,
            (size_t) (seg2->numsamples * samplesize));

    seg1->numsamples += seg2->numsamples;
  }

  return seg1;
} /* End of mstl_addsegtoseg() */

/***************************************************************************
 * mstl_convertsamples:
 *
 * Convert the data samples associated with an MSTraceSeg to another
 * data type.  ASCII data samples cannot be converted, if supplied or
 * requested an error will be returned.
 *
 * When converting float & double sample types to integer type a
 * simple rounding is applied by adding 0.5 to the sample value before
 * converting (truncating) to integer.
 *
 * If the truncate flag is true data samples will be truncated to
 * integers even if loss of sample precision is detected.  If the
 * truncate flag is false (0) and loss of precision is detected an
 * error is returned.
 *
 * Returns 0 on success, and -1 on failure.
 ***************************************************************************/
int
mstl_convertsamples (MSTraceSeg *seg, char type, flag truncate)
{
  int32_t *idata;
  float *fdata;
  double *ddata;
  int64_t idx;

  if (!seg)
    return -1;

  /* No conversion necessary, report success */
  if (seg->sampletype == type)
    return 0;

  if (seg->sampletype == 'a' || type == 'a')
  {
    ms_log (2, "mstl_convertsamples: cannot convert ASCII samples to/from numeric type\n");
    return -1;
  }

  idata = (int32_t *)seg->datasamples;
  fdata = (float *)seg->datasamples;
  ddata = (double *)seg->datasamples;

  /* Convert to 32-bit integers */
  if (type == 'i')
  {
    if (seg->sampletype == 'f') /* Convert floats to integers with simple rounding */
    {
      for (idx = 0; idx < seg->numsamples; idx++)
      {
        /* Check for loss of sub-integer */
        if (!truncate && (fdata[idx] - (int32_t)fdata[idx]) > 0.000001)
        {
          ms_log (1, "mstl_convertsamples: Warning, loss of precision when converting floats to integers, loss: %g\n",
                  (fdata[idx] - (int32_t)fdata[idx]));
          return -1;
        }

        idata[idx] = (int32_t) (fdata[idx] + 0.5);
      }
    }
    else if (seg->sampletype == 'd') /* Convert doubles to integers with simple rounding */
    {
      for (idx = 0; idx < seg->numsamples; idx++)
      {
        /* Check for loss of sub-integer */
        if (!truncate && (ddata[idx] - (int32_t)ddata[idx]) > 0.000001)
        {
          ms_log (1, "mstl_convertsamples: Warning, loss of precision when converting doubles to integers, loss: %g\n",
                  (ddata[idx] - (int32_t)ddata[idx]));
          return -1;
        }

        idata[idx] = (int32_t) (ddata[idx] + 0.5);
      }

      /* Reallocate buffer for reduced size needed */
      if (!(seg->datasamples = realloc (seg->datasamples, (size_t) (seg->numsamples * sizeof (int32_t)))))
      {
        ms_log (2, "mstl_convertsamples: cannot re-allocate buffer for sample conversion\n");
        return -1;
      }
    }

    seg->sampletype = 'i';
  } /* Done converting to 32-bit integers */

  /* Convert to 32-bit floats */
  else if (type == 'f')
  {
    if (seg->sampletype == 'i') /* Convert integers to floats */
    {
      for (idx     = 0; idx < seg->numsamples; idx++)
        fdata[idx] = (float)idata[idx];
    }
    else if (seg->sampletype == 'd') /* Convert doubles to floats */
    {
      for (idx     = 0; idx < seg->numsamples; idx++)
        fdata[idx] = (float)ddata[idx];

      /* Reallocate buffer for reduced size needed */
      if (!(seg->datasamples = realloc (seg->datasamples, (size_t) (seg->numsamples * sizeof (float)))))
      {
        ms_log (2, "mstl_convertsamples: cannot re-allocate buffer after sample conversion\n");
        return -1;
      }
    }

    seg->sampletype = 'f';
  } /* Done converting to 32-bit floats */

  /* Convert to 64-bit doubles */
  else if (type == 'd')
  {
    if (!(ddata = (double *)malloc ((size_t) (seg->numsamples * sizeof (double)))))
    {
      ms_log (2, "mstl_convertsamples: cannot allocate buffer for sample conversion to doubles\n");
      return -1;
    }

    if (seg->sampletype == 'i') /* Convert integers to doubles */
    {
      for (idx     = 0; idx < seg->numsamples; idx++)
        ddata[idx] = (double)idata[idx];

      free (idata);
    }
    else if (seg->sampletype == 'f') /* Convert floats to doubles */
    {
      for (idx     = 0; idx < seg->numsamples; idx++)
        ddata[idx] = (double)fdata[idx];

      free (fdata);
    }

    seg->datasamples = ddata;
    seg->sampletype  = 'd';
  } /* Done converting to 64-bit doubles */

  return 0;
} /* End of mstl_convertsamples() */

/***************************************************************************
 * mstl_printtracelist:
 *
 * Print trace list summary information for the specified MSTraceList.
 *
 * By default only print the srcname, starttime and endtime for each
 * trace.  If details is greater than 0 include the sample rate,
 * number of samples and a total trace count.  If gaps is greater than
 * 0 and the previous trace matches (srcname & samprate) include the
 * gap between the endtime of the last trace and the starttime of the
 * current trace.
 *
 * The timeformat flag can either be:
 * 0 : SEED time format (year, day-of-year, hour, min, sec)
 * 1 : ISO time format (year, month, day, hour, min, sec)
 * 2 : Epoch time, seconds since the epoch
 ***************************************************************************/
void
mstl_printtracelist (MSTraceList *mstl, flag timeformat,
                     flag details, flag gaps)
{
  MSTraceID *id   = 0;
  MSTraceSeg *seg = 0;
  char stime[30];
  char etime[30];
  char gapstr[20];
  flag nogap;
  double gap;
  double delta;
  int tracecnt = 0;
  int segcnt   = 0;

  if (!mstl)
  {
    return;
  }

  /* Print out the appropriate header */
  if (details > 0 && gaps > 0)
    ms_log (0, "   Source                Start sample             End sample        Gap  Hz  Samples\n");
  else if (details <= 0 && gaps > 0)
    ms_log (0, "   Source                Start sample             End sample        Gap\n");
  else if (details > 0 && gaps <= 0)
    ms_log (0, "   Source                Start sample             End sample        Hz  Samples\n");
  else
    ms_log (0, "   Source                Start sample             End sample\n");

  /* Loop through trace list */
  id = mstl->traces;
  while (id)
  {
    /* Loop through segment list */
    seg = id->first;
    while (seg)
    {
      /* Create formatted time strings */
      if (timeformat == 2)
      {
        snprintf (stime, sizeof (stime), "%.6f", (double)MS_HPTIME2EPOCH (seg->starttime));
        snprintf (etime, sizeof (etime), "%.6f", (double)MS_HPTIME2EPOCH (seg->endtime));
      }
      else if (timeformat == 1)
      {
        if (ms_hptime2isotimestr (seg->starttime, stime, 1) == NULL)
          ms_log (2, "Cannot convert trace start time for %s\n", id->srcname);

        if (ms_hptime2isotimestr (seg->endtime, etime, 1) == NULL)
          ms_log (2, "Cannot convert trace end time for %s\n", id->srcname);
      }
      else
      {
        if (ms_hptime2seedtimestr (seg->starttime, stime, 1) == NULL)
          ms_log (2, "Cannot convert trace start time for %s\n", id->srcname);

        if (ms_hptime2seedtimestr (seg->endtime, etime, 1) == NULL)
          ms_log (2, "Cannot convert trace end time for %s\n", id->srcname);
      }

      /* Print segment info at varying levels */
      if (gaps > 0)
      {
        gap   = 0.0;
        nogap = 0;

        if (seg->prev)
          gap = (double)(seg->starttime - seg->prev->endtime) / HPTMODULUS;
        else
          nogap = 1;

        /* Check that any overlap is not larger than the trace coverage */
        if (gap < 0.0)
        {
          delta = (seg->samprate) ? (1.0 / seg->samprate) : 0.0;

          if ((gap * -1.0) > (((double)(seg->endtime - seg->starttime) / HPTMODULUS) + delta))
            gap = -(((double)(seg->endtime - seg->starttime) / HPTMODULUS) + delta);
        }

        /* Fix up gap display */
        if (nogap)
          snprintf (gapstr, sizeof (gapstr), " == ");
        else if (gap >= 86400.0 || gap <= -86400.0)
          snprintf (gapstr, sizeof (gapstr), "%-3.1fd", (gap / 86400));
        else if (gap >= 3600.0 || gap <= -3600.0)
          snprintf (gapstr, sizeof (gapstr), "%-3.1fh", (gap / 3600));
        else if (gap == 0.0)
          snprintf (gapstr, sizeof (gapstr), "-0  ");
        else
          snprintf (gapstr, sizeof (gapstr), "%-4.4g", gap);

        if (details <= 0)
          ms_log (0, "%-17s %-24s %-24s %-4s\n",
                  id->srcname, stime, etime, gapstr);
        else
          ms_log (0, "%-17s %-24s %-24s %-s %-3.3g %-" PRId64 "\n",
                  id->srcname, stime, etime, gapstr, seg->samprate, seg->samplecnt);
      }
      else if (details > 0 && gaps <= 0)
        ms_log (0, "%-17s %-24s %-24s %-3.3g %-" PRId64 "\n",
                id->srcname, stime, etime, seg->samprate, seg->samplecnt);
      else
        ms_log (0, "%-17s %-24s %-24s\n", id->srcname, stime, etime);

      segcnt++;
      seg = seg->next;
    }

    tracecnt++;
    id = id->next;
  }

  if (tracecnt != mstl->numtraces)
    ms_log (2, "mstl_printtracelist(): number of traces in trace list is inconsistent\n");

  if (details > 0)
    ms_log (0, "Total: %d trace(s) with %d segment(s)\n", tracecnt, segcnt);

  return;
} /* End of mstl_printtracelist() */

/***************************************************************************
 * mstl_printsynclist:
 *
 * Print SYNC trace list summary information for the specified MSTraceList.
 *
 * The SYNC header line will be created using the supplied dccid, if
 * the pointer is NULL the string "DCC" will be used instead.
 *
 * If the subsecond flag is true the segment start and end times will
 * include subsecond precision, otherwise they will be truncated to
 * integer seconds.
 *
 ***************************************************************************/
void
mstl_printsynclist (MSTraceList *mstl, char *dccid, flag subsecond)
{
  MSTraceID *id   = 0;
  MSTraceSeg *seg = 0;
  char starttime[30];
  char endtime[30];
  char yearday[10];
  time_t now;
  struct tm *nt;

  if (!mstl)
  {
    return;
  }

  /* Generate current time stamp */
  now = time (NULL);
  nt  = localtime (&now);
  nt->tm_year += 1900;
  nt->tm_yday += 1;
  snprintf (yearday, sizeof (yearday), "%04d,%03d", nt->tm_year, nt->tm_yday);

  /* Print SYNC header line */
  ms_log (0, "%s|%s\n", (dccid) ? dccid : "DCC", yearday);

  /* Loop through trace list */
  id = mstl->traces;
  while (id)
  {
    /* Loop through segment list */
    seg = id->first;
    while (seg)
    {
      ms_hptime2seedtimestr (seg->starttime, starttime, subsecond);
      ms_hptime2seedtimestr (seg->endtime, endtime, subsecond);

      /* Print SYNC line */
      ms_log (0, "%s|%s|%s|%s|%s|%s||%.10g|%" PRId64 "|||||||%s\n",
              id->network, id->station, id->location, id->channel,
              starttime, endtime, seg->samprate, seg->samplecnt,
              yearday);

      seg = seg->next;
    }

    id = id->next;
  }

  return;
} /* End of mstl_printsynclist() */

/***************************************************************************
 * mstl_printgaplist:
 *
 * Print gap/overlap list summary information for the specified
 * MSTraceList.  Overlaps are printed as negative gaps.
 *
 * If mingap and maxgap are not NULL their values will be enforced and
 * only gaps/overlaps matching their implied criteria will be printed.
 *
 * The timeformat flag can either be:
 * 0 : SEED time format (year, day-of-year, hour, min, sec)
 * 1 : ISO time format (year, month, day, hour, min, sec)
 * 2 : Epoch time, seconds since the epoch
 ***************************************************************************/
void
mstl_printgaplist (MSTraceList *mstl, flag timeformat,
                   double *mingap, double *maxgap)
{
  MSTraceID *id   = 0;
  MSTraceSeg *seg = 0;

  char time1[30], time2[30];
  char gapstr[30];
  double gap;
  double delta;
  double nsamples;
  flag printflag;
  int gapcnt = 0;

  if (!mstl)
    return;

  if (!mstl->traces)
    return;

  ms_log (0, "   Source                Last Sample              Next Sample       Gap  Samples\n");

  id = mstl->traces;
  while (id)
  {
    seg = id->first;
    while (seg->next)
    {
      /* Skip segments with 0 sample rate, usually from SOH records */
      if (seg->samprate == 0.0)
      {
        seg = seg->next;
        continue;
      }

      gap = (double)(seg->next->starttime - seg->endtime) / HPTMODULUS;

      /* Check that any overlap is not larger than the trace coverage */
      if (gap < 0.0)
      {
        delta = (seg->next->samprate) ? (1.0 / seg->next->samprate) : 0.0;

        if ((gap * -1.0) > (((double)(seg->next->endtime - seg->next->starttime) / HPTMODULUS) + delta))
          gap = -(((double)(seg->next->endtime - seg->next->starttime) / HPTMODULUS) + delta);
      }

      printflag = 1;

      /* Check gap/overlap criteria */
      if (mingap)
        if (gap < *mingap)
          printflag = 0;

      if (maxgap)
        if (gap > *maxgap)
          printflag = 0;

      if (printflag)
      {
        nsamples = ms_dabs (gap) * seg->samprate;

        if (gap > 0.0)
          nsamples -= 1.0;
        else
          nsamples += 1.0;

        /* Fix up gap display */
        if (gap >= 86400.0 || gap <= -86400.0)
          snprintf (gapstr, sizeof (gapstr), "%-3.1fd", (gap / 86400));
        else if (gap >= 3600.0 || gap <= -3600.0)
          snprintf (gapstr, sizeof (gapstr), "%-3.1fh", (gap / 3600));
        else if (gap == 0.0)
          snprintf (gapstr, sizeof (gapstr), "-0  ");
        else
          snprintf (gapstr, sizeof (gapstr), "%-4.4g", gap);

        /* Create formatted time strings */
        if (timeformat == 2)
        {
          snprintf (time1, sizeof (time1), "%.6f", (double)MS_HPTIME2EPOCH (seg->endtime));
          snprintf (time2, sizeof (time2), "%.6f", (double)MS_HPTIME2EPOCH (seg->next->starttime));
        }
        else if (timeformat == 1)
        {
          if (ms_hptime2isotimestr (seg->endtime, time1, 1) == NULL)
            ms_log (2, "Cannot convert trace end time for %s\n", id->srcname);

          if (ms_hptime2isotimestr (seg->next->starttime, time2, 1) == NULL)
            ms_log (2, "Cannot convert next trace start time for %s\n", id->srcname);
        }
        else
        {
          if (ms_hptime2seedtimestr (seg->endtime, time1, 1) == NULL)
            ms_log (2, "Cannot convert trace end time for %s\n", id->srcname);

          if (ms_hptime2seedtimestr (seg->next->starttime, time2, 1) == NULL)
            ms_log (2, "Cannot convert next trace start time for %s\n", id->srcname);
        }

        ms_log (0, "%-17s %-24s %-24s %-4s %-.8g\n",
                id->srcname, time1, time2, gapstr, nsamples);

        gapcnt++;
      }

      seg = seg->next;
    }

    id = id->next;
  }

  ms_log (0, "Total: %d gap(s)\n", gapcnt);

  return;
} /* End of mstl_printgaplist() */
