/***************************************************************************
 * pack.c:
 *
 * Generic routines to pack Mini-SEED records using an MSrecord as a
 * header template and data source.
 *
 * Written by Chad Trabant,
 *   IRIS Data Management Center
 *
 * modified: 2011.129
 ***************************************************************************/

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

#include "psnmseed.h"

/* Function(s) internal to this file */
static int msr_pack_header_raw (MSRecord *msr, char *rawrec, int maxheaderlen,
				flag swapflag, flag normalize,
				struct blkt_1001_s **blkt1001, flag verbose);
static int msr_update_header (MSRecord * msr, char *rawrec, flag swapflag,
			      struct blkt_1001_s *blkt1001, flag verbose);
static int msr_pack_data (void *dest, void *src, int maxsamples, int maxdatabytes,
			  int *packsamples, int32_t *lastintsample, flag comphistory,
			  char sampletype, flag encoding, flag swapflag,
			  flag verbose, char *srcName );

/* Header and data byte order flags controlled by environment variables */
/* -2 = not checked, -1 = checked but not set, or 0 = LE and 1 = BE */
flag packheaderbyteorder = 0;
flag packdatabyteorder = 0;

/***************************************************************************
 * msr_pack:
 *
 * Pack data into SEED data records.  Using the record header values
 * in the MSRecord as a template the common header fields are packed
 * into the record header, blockettes in the blockettes chain are
 * packed and data samples are packed in the encoding format indicated
 * by the MSRecord->encoding field.  A Blockette 1000 will be added if
 * one is not present.
 *
 * The MSRecord->datasamples array and MSRecord->numsamples value will
 * not be changed by this routine.  It is the responsibility of the
 * calling routine to adjust the data buffer if desired.
 *
 * As each record is filled and finished they are passed to
 * record_handler which expects 1) a char * to the record, 2) the
 * length of the record and 3) a pointer supplied by the original
 * caller containing optional private data (handlerdata).  It is the
 * responsibility of record_handler to process the record, the memory
 * will be re-used or freed when record_handler returns.
 *
 * If the flush flag != 0 all of the data will be packed into data
 * records even though the last one will probably not be filled.
 *
 * Default values are: data record & quality indicator = 'D', record
 * length = 4096, encoding = 11 (Steim2) and byteorder = 1 (MSBF).
 * The defaults are triggered when the the msr->dataquality is 0 or
 * msr->reclen, msr->encoding and msr->byteorder are -1 respectively.
 *
 * Returns the number of records created on success and -1 on error.
 ***************************************************************************/
int msr_pack ( MSRecord * msr, void (*record_handler) (char *, int, void *),
	   void *handlerdata, int64_t *packedsamples, flag flush, flag verbose )
{
	uint16_t *HPnumsamples;
	uint16_t *HPdataoffset;
	struct blkt_1001_s *HPblkt1001 = NULL;
	char *rawrec;
	char *envvariable;
	char srcname[50];
	flag headerswapflag = 0;
  	flag dataswapflag = 0;
  	flag packret;
  
  	int samplesize;
  	int headerlen;
  	int dataoffset;
  	int maxdatabytes;
  	int maxsamples;
  	int recordcnt = 0;
  	int packsamples, packoffset;
  	int64_t totalpackedsamples;
  	hptime_t segstarttime;
  
  	if( ! msr )
    	return -1;
  
	if( ! record_handler )  {
		ms_log (2, "msr_pack(): record_handler() function pointer not set!\n");
      	return -1;
    }
	
  	msr_reset( msr );

    memset( &msr->ststate, 0, sizeof(StreamState) );

	/* Generate source name for MSRecord */
	if( msr_srcname (msr, srcname, 1) == NULL )  {
      	ms_log (2, "msr_unpack_data(): Cannot generate srcname\n");
      	return MS_GENERROR;
    }
  
  	/* Set shared srcname pointer to source name */
  	msr->srcName = &srcname[0];
  
  	/* Track original segment start time for new start time calculation */
  	segstarttime = msr->starttime;

  	/* Set default indicator, record length, byte order and encoding if needed */
  	if ( msr->dataquality == 0 ) msr->dataquality = 'D';
  	if ( msr->reclen == -1 ) msr->reclen = 4096;
  	if ( msr->byteorder == -1 )  msr->byteorder = 1;
  	if ( msr->encoding == -1 ) msr->encoding = DE_STEIM2;
  
  	/* Cleanup/reset sequence number */
  	if ( msr->sequence_number <= 0 || msr->sequence_number > 999999)
    	msr->sequence_number = 1;
  
  	if( msr->reclen < MINRECLEN || msr->reclen > MAXRECLEN )  {
		ms_log (2, "msr_pack(%s): Record length is out of range: %d\n", msr->srcName, msr->reclen);
		return -1;
    }
  
  	if( msr->numsamples <= 0 )  {
      	ms_log (2, "msr_pack(%s): No samples to pack\n", msr->srcName );
      	return -1;
    }
  
  	samplesize = ms_samplesize (msr->sampletype);
  
  	if( ! samplesize )  {
		ms_log (2, "msr_pack(%s): Unknown sample type '%c'\n", msr->srcName, msr->sampletype);
    	return -1;
    }
  
  	/* Sanity check for msr/quality indicator */
  	if( ! MS_ISDATAINDICATOR(msr->dataquality) )  {
		ms_log (2, "msr_pack(%s): Record header & quality indicator unrecognized: '%c'\n",
	      msr->srcName, msr->dataquality);
      	ms_log (2, "msr_pack(%s): Packing failed.\n", msr->srcName );
      	return -1;
    }
  
  	/* get space for data record */
	rawrec = msr->gRawRec;
  
	/* Set header pointers to known offsets into FSDH */
  	HPnumsamples = (uint16_t *) (rawrec + 30);
  	HPdataoffset = (uint16_t *) (rawrec + 44);
  
  	/* Check to see if byte swapping is needed */
  	if ( msr->byteorder != ms_bigendianhost() )
    	headerswapflag = dataswapflag = 1;
  
  	/* Check if byte order is forced */
  	if ( packheaderbyteorder >= 0 )  {
    	headerswapflag = ( msr->byteorder != packheaderbyteorder ) ? 1 : 0;
    }
  
	if( packdatabyteorder >= 0 )  {
      dataswapflag = ( msr->byteorder != packdatabyteorder ) ? 1 : 0;
    }
  
	if( verbose > 2 )  {
		if ( headerswapflag && dataswapflag )
			ms_log (1, "%s: Byte swapping needed for packing of header and data samples\n", msr->srcName );
      	else if ( headerswapflag )
			ms_log (1, "%s: Byte swapping needed for packing of header\n", msr->srcName );
      	else if ( dataswapflag )
			ms_log (1, "%s: Byte swapping needed for packing of data samples\n", msr->srcName );
      	else
			ms_log (1, "%s: Byte swapping NOT needed for packing\n", msr->srcName );
    }
  
	/* Add a blank 1000 Blockette if one is not present, the blockette values
     will be populated in msr_pack_header_raw()/msr_normalize_header() */
	if( !msr->Blkt1000 )  {
    	struct blkt_1000_s blkt1000;
      	memset (&blkt1000, 0, sizeof (struct blkt_1000_s));
      	if( verbose > 2 )
			ms_log (1, "%s: Adding 1000 Blockette\n", msr->srcName);
      
		if( ! msr_addblockette (msr, (char *) &blkt1000, sizeof(struct blkt_1000_s), 1000, 0) )  {
			ms_log (2, "msr_pack(%s): Error adding 1000 Blockette\n", msr->srcName );
	  		return -1;
		}
    }
  
	/* Add a blank 1000 Blockette if one is not present, the blockette values
     will be populated in msr_pack_header_raw()/msr_normalize_header() */
	if( ! msr->Blkt1001 )  {
    	struct blkt_1001_s blkt1001;
    	memset (&blkt1001, 0, sizeof (struct blkt_1001_s));
    	if ( verbose > 2 )
			ms_log (1, "%s: Adding 1001 Blockette\n", msr->srcName );
		if ( ! msr_addblockette (msr, (char *) &blkt1001, sizeof(struct blkt_1001_s), 1001, 0) )  {
	  		ms_log (2, "msr_pack(%s): Error adding 1001 Blockette\n", msr->srcName );
	  		return -1;
		}
	}
  
	headerlen = msr_pack_header_raw (msr, rawrec, msr->reclen, headerswapflag, 1, &HPblkt1001, verbose );
  
	if( headerlen == -1 )  {
		ms_log (2, "msr_pack(%s): Error packing header\n", msr->srcName );
      	return -1;
    }
  
  	/* Determine offset to encoded data */
  	if ( msr->encoding == DE_STEIM1 || msr->encoding == DE_STEIM2 )  {
		dataoffset = 64;
      	while ( dataoffset < headerlen )
			dataoffset += 64;
      	/* Zero memory between blockettes and data if any */
      	memset (rawrec + headerlen, 0, dataoffset - headerlen);
    }
  	else
      dataoffset = headerlen;
  
	*HPdataoffset = (uint16_t) dataoffset;
	if( headerswapflag ) 
		ms_gswap2 (HPdataoffset);
  
  	/* Determine the max data bytes and sample count */
  	maxdatabytes = msr->reclen - dataoffset;
  
  	if ( msr->encoding == DE_STEIM1 )
		maxsamples = (int) (maxdatabytes/64) * STEIM1_FRAME_MAX_SAMPLES;
  	else if ( msr->encoding == DE_STEIM2 )
      maxsamples = (int) (maxdatabytes/64) * STEIM2_FRAME_MAX_SAMPLES;
	else
      maxsamples = maxdatabytes / samplesize;
  
	/* Pack samples into records */
  	*HPnumsamples = 0;
  	totalpackedsamples = 0;
  	if( packedsamples ) 
		*packedsamples = 0;
  	packoffset = 0;
  
  	while( (msr->numsamples - totalpackedsamples) > maxsamples || flush )  {
		packret = msr_pack_data (rawrec + dataoffset, (char *) msr->datasamples + packoffset,
			       (msr->numsamples - totalpackedsamples), maxdatabytes,
			       &packsamples, &msr->ststate.lastintsample, msr->ststate.comphistory,
			       msr->sampletype, msr->encoding, dataswapflag, verbose, msr->srcName );
		if ( packret )  {
	  		ms_log (2, "msr_pack(%s): Error packing record\n", msr->srcName );
	  		return -1;
		}
      
      	packoffset += packsamples * samplesize;
      
      	/* Update number of samples */
      	*HPnumsamples = (uint16_t) packsamples;
      	if( headerswapflag ) 
			ms_gswap2 (HPnumsamples);
      
      	if ( verbose > 0 )
			ms_log (1, "%s: Packed %d samples\n", msr->srcName, packsamples);
      
      	/* Send record to handler */
      	record_handler (rawrec, msr->reclen, handlerdata);
      
      	totalpackedsamples += packsamples;
      	if ( packedsamples ) 
			*packedsamples = totalpackedsamples;
      	msr->ststate.packedsamples += packsamples;
      
      	/* Update record header for next record */
      	msr->sequence_number = ( msr->sequence_number >= 999999 ) ? 1 : msr->sequence_number + 1;
      	if ( msr->samprate > 0 )
			msr->starttime = segstarttime + (hptime_t)(totalpackedsamples / msr->samprate * HPTMODULUS + 0.5);
      
      	msr_update_header (msr, rawrec, headerswapflag, HPblkt1001, verbose);
      
      	recordcnt++;
      	msr->ststate.packedrecords++;
      
      	/* Set compression history flag for subsequent records (Steim encodings) */
      	if ( ! msr->ststate.comphistory )
        	msr->ststate.comphistory = 1;
     
      	if ( totalpackedsamples >= msr->numsamples )
			break;
	}
  
	if ( verbose > 2 )
    	ms_log (1, "%s: Packed %d total samples\n", msr->srcName, totalpackedsamples);
  
  	return recordcnt;
}


/***************************************************************************
 * msr_pack_header:
 *
 * Pack data header/blockettes into the SEED record at
 * MSRecord->record.  Unlike msr_pack no default values are applied,
 * the header structures are expected to be self describing and no
 * Blockette 1000 will be added.  This routine is only useful for
 * re-packing a record header.
 *
 * Returns the header length in bytes on success and -1 on error.
 ***************************************************************************/
int
msr_pack_header ( MSRecord *msr, flag normalize, flag verbose )
{
  char srcname[50];
  char *envvariable;
  flag headerswapflag = 0;
  int headerlen;
  int maxheaderlen;
  
  if ( ! msr )
    return -1;
  
  /* Generate source name for MSRecord */
  if ( msr_srcname (msr, srcname, 1) == NULL )
    {
      ms_log (2, "msr_unpack_data(): Cannot generate srcname\n");
      return MS_GENERROR;
    }
  
  /* Set shared srcname pointer to source name */
  msr->srcName = &srcname[0];

  if ( msr->reclen < MINRECLEN || msr->reclen > MAXRECLEN )
    {
      ms_log (2, "msr_pack_header(%s): record length is out of range: %d\n", msr->srcName, msr->reclen);
      return -1;
    }
  
  if ( msr->byteorder != 0 && msr->byteorder != 1 )
    {
      ms_log (2, "msr_pack_header(%s): byte order is not defined correctly: %d\n", msr->srcName, msr->byteorder);
      return -1;
    }
    
    maxheaderlen = (msr->fsdh.data_offset > 0) ? msr->fsdh.data_offset : msr->reclen;
    
  /* Check to see if byte swapping is needed */
  if ( msr->byteorder != ms_bigendianhost() )
    headerswapflag = 1;
  
  /* Check if byte order is forced */
  if ( packheaderbyteorder >= 0 )
    {
      headerswapflag = ( msr->byteorder != packheaderbyteorder ) ? 1: 0;
    }
  
  if ( verbose > 2 )
    {
      if ( headerswapflag )
	ms_log (1, "%s: Byte swapping needed for packing of header\n", msr->srcName );
      else
	ms_log (1, "%s: Byte swapping NOT needed for packing of header\n", msr->srcName );
    }
  
  headerlen = msr_pack_header_raw (msr, msr->record, maxheaderlen,
				   headerswapflag, normalize, NULL, verbose);
  
  return headerlen;
}  /* End of msr_pack_header() */


/***************************************************************************
 * msr_pack_header_raw:
 *
 * Pack data header/blockettes into the specified SEED data record.
 *
 * Returns the header length in bytes on success or -1 on error.
 ***************************************************************************/
static int
msr_pack_header_raw ( MSRecord *msr, char *rawrec, int maxheaderlen,
		      flag swapflag, flag normalize, 
		      struct blkt_1001_s **blkt1001, flag verbose )
{
  struct blkt_link_s *cur_blkt;
  struct fsdh_s *fsdh;
  int16_t offset;
  int blktcnt = 0;
  int nextoffset;

  if ( ! msr || ! rawrec )
    return -1;
  
  /* Update the SEED structures associated with the MSRecord */
  if ( normalize )
    if ( msr_normalize_header (msr, verbose) < 0 )
      {
	ms_log (2, "msr_pack_header_raw(%s): error normalizing header values\n", msr->srcName );
	return -1;
      }
  
  if ( verbose > 2 )
    ms_log (1, "%s: Packing fixed section of data header\n", msr->srcName );
  
  if ( maxheaderlen > msr->reclen )
    {
      ms_log (2, "msr_pack_header_raw(%s): maxheaderlen of %d is beyond record length of %d\n",
	      msr->srcName, maxheaderlen, msr->reclen);
      return -1;
    }
  
  if ( maxheaderlen < sizeof(struct fsdh_s) )
    {
      ms_log (2, "msr_pack_header_raw(%s): maxheaderlen of %d is too small, must be >= %d\n",
	      msr->srcName, maxheaderlen, sizeof(struct fsdh_s));
      return -1;
    }
  
  fsdh = (struct fsdh_s *) rawrec;
  offset = 48;
  
  /* Roll-over sequence number if necessary */
  if ( msr->sequence_number > 999999 )
    msr->sequence_number = 1;
  
  /* Copy FSDH associated with the MSRecord into the record */  
  memcpy( fsdh, &msr->fsdh, sizeof(struct fsdh_s));
  
  /* Swap byte order? */
  if ( swapflag )
    {
      MS_SWAPBTIME (&fsdh->start_time);
      ms_gswap2 (&fsdh->numsamples);
      ms_gswap2 (&fsdh->samprate_fact);
      ms_gswap2 (&fsdh->samprate_mult);
      ms_gswap4 (&fsdh->time_correct);
      ms_gswap2 (&fsdh->data_offset);
      ms_gswap2 (&fsdh->blockette_offset);
    }
  
  /* Traverse blockette chain and pack blockettes at 'offset' */
  cur_blkt = msr->blkts;
  
  while ( cur_blkt && offset < maxheaderlen )
    {
      /* Check that the blockette fits */
      if ( (offset + 4 + cur_blkt->blktdatalen) > maxheaderlen )
	{
	  ms_log (2, "msr_pack_header_raw(%s): header exceeds maxheaderlen of %d\n",
		  msr->srcName, maxheaderlen);
	  break;
	}
      
      /* Pack blockette type and leave space for next offset */
      memcpy (rawrec + offset, &cur_blkt->blkt_type, 2);
      if ( swapflag ) ms_gswap2 (rawrec + offset);
      nextoffset = offset + 2;
      offset += 4;
      
      if ( cur_blkt->blkt_type == 100 )
	{
	  struct blkt_100_s *blkt_100 = (struct blkt_100_s *) (rawrec + offset);
	  memcpy (blkt_100, cur_blkt->blktdata, sizeof (struct blkt_100_s));
	  offset += sizeof (struct blkt_100_s);
	  
	  if ( swapflag )
	    {
	      ms_gswap4 (&blkt_100->samprate);
	    }
	}
      
      else if ( cur_blkt->blkt_type == 200 )
	{
	  struct blkt_200_s *blkt_200 = (struct blkt_200_s *) (rawrec + offset);
	  memcpy (blkt_200, cur_blkt->blktdata, sizeof (struct blkt_200_s));
	  offset += sizeof (struct blkt_200_s);
	  
	  if ( swapflag )
	    {
	      ms_gswap4 (&blkt_200->amplitude);
	      ms_gswap4 (&blkt_200->period);
	      ms_gswap4 (&blkt_200->background_estimate);
	      MS_SWAPBTIME (&blkt_200->time);
	    }
	}
      
      else if ( cur_blkt->blkt_type == 201 )
	{
	  struct blkt_201_s *blkt_201 = (struct blkt_201_s *) (rawrec + offset);
	  memcpy (blkt_201, cur_blkt->blktdata, sizeof (struct blkt_201_s));
	  offset += sizeof (struct blkt_201_s);
	  
	  if ( swapflag )
	    {
	      ms_gswap4 (&blkt_201->amplitude);
	      ms_gswap4 (&blkt_201->period);
	      ms_gswap4 (&blkt_201->background_estimate);
	      MS_SWAPBTIME (&blkt_201->time);
	    }
	}

      else if ( cur_blkt->blkt_type == 300 )
	{
	  struct blkt_300_s *blkt_300 = (struct blkt_300_s *) (rawrec + offset);
	  memcpy (blkt_300, cur_blkt->blktdata, sizeof (struct blkt_300_s));
	  offset += sizeof (struct blkt_300_s);
	  
	  if ( swapflag )
	    {
	      MS_SWAPBTIME (&blkt_300->time);
	      ms_gswap4 (&blkt_300->step_duration);
	      ms_gswap4 (&blkt_300->interval_duration);
	      ms_gswap4 (&blkt_300->amplitude);
	      ms_gswap4 (&blkt_300->reference_amplitude);
	    }
	}

      else if ( cur_blkt->blkt_type == 310 )
	{
	  struct blkt_310_s *blkt_310 = (struct blkt_310_s *) (rawrec + offset);
	  memcpy (blkt_310, cur_blkt->blktdata, sizeof (struct blkt_310_s));
	  offset += sizeof (struct blkt_310_s);
	  
	  if ( swapflag )
	    {
	      MS_SWAPBTIME (&blkt_310->time);
	      ms_gswap4 (&blkt_310->duration);
	      ms_gswap4 (&blkt_310->period);
	      ms_gswap4 (&blkt_310->amplitude);
	      ms_gswap4 (&blkt_310->reference_amplitude);
	    }
	}
      
      else if ( cur_blkt->blkt_type == 320 )
	{
	  struct blkt_320_s *blkt_320 = (struct blkt_320_s *) (rawrec + offset);
	  memcpy (blkt_320, cur_blkt->blktdata, sizeof (struct blkt_320_s));
	  offset += sizeof (struct blkt_320_s);
	  
	  if ( swapflag )
	    {
	      MS_SWAPBTIME (&blkt_320->time);
	      ms_gswap4 (&blkt_320->duration);
	      ms_gswap4 (&blkt_320->ptp_amplitude);
	      ms_gswap4 (&blkt_320->reference_amplitude);
	    }
	}

      else if ( cur_blkt->blkt_type == 390 )
	{
	  struct blkt_390_s *blkt_390 = (struct blkt_390_s *) (rawrec + offset);
	  memcpy (blkt_390, cur_blkt->blktdata, sizeof (struct blkt_390_s));
	  offset += sizeof (struct blkt_390_s);
	  
	  if ( swapflag )
	    {
	      MS_SWAPBTIME (&blkt_390->time);
	      ms_gswap4 (&blkt_390->duration);
	      ms_gswap4 (&blkt_390->amplitude);
	    }
	}
      
      else if ( cur_blkt->blkt_type == 395 )
	{
	  struct blkt_395_s *blkt_395 = (struct blkt_395_s *) (rawrec + offset);
	  memcpy (blkt_395, cur_blkt->blktdata, sizeof (struct blkt_395_s));
	  offset += sizeof (struct blkt_395_s);
	  
	  if ( swapflag )
	    {
	      MS_SWAPBTIME (&blkt_395->time);
	    }
	}

      else if ( cur_blkt->blkt_type == 400 )
	{
	  struct blkt_400_s *blkt_400 = (struct blkt_400_s *) (rawrec + offset);
	  memcpy (blkt_400, cur_blkt->blktdata, sizeof (struct blkt_400_s));
	  offset += sizeof (struct blkt_400_s);
	  
	  if ( swapflag )
	    {
	      ms_gswap4 (&blkt_400->azimuth);
	      ms_gswap4 (&blkt_400->slowness);
	      ms_gswap2 (&blkt_400->configuration);
	    }
	}

      else if ( cur_blkt->blkt_type == 405 )
	{
	  struct blkt_405_s *blkt_405 = (struct blkt_405_s *) (rawrec + offset);
	  memcpy (blkt_405, cur_blkt->blktdata, sizeof (struct blkt_405_s));
	  offset += sizeof (struct blkt_405_s);
	  
	  if ( swapflag )
	    {
	      ms_gswap2 (&blkt_405->delay_values);
	    }

	  if ( verbose > 0 )
	    {
	      ms_log (1, "msr_pack_header_raw(%s): WARNING Blockette 405 cannot be fully supported\n", msr->srcName );
	    }
	}

      else if ( cur_blkt->blkt_type == 500 )
	{
	  struct blkt_500_s *blkt_500 = (struct blkt_500_s *) (rawrec + offset);
	  memcpy (blkt_500, cur_blkt->blktdata, sizeof (struct blkt_500_s));
	  offset += sizeof (struct blkt_500_s);
	  
	  if ( swapflag )
	    {
	      ms_gswap4 (&blkt_500->vco_correction);
	      MS_SWAPBTIME (&blkt_500->time);
	      ms_gswap4 (&blkt_500->exception_count);
	    }
	}
      
      else if ( cur_blkt->blkt_type == 1000 )
	{
	  struct blkt_1000_s *blkt_1000 = (struct blkt_1000_s *) (rawrec + offset);
	  memcpy (blkt_1000, cur_blkt->blktdata, sizeof (struct blkt_1000_s));
	  offset += sizeof (struct blkt_1000_s);
	  
	  /* This guarantees that the byte order is in sync with msr_pack() */
	  if ( packdatabyteorder >= 0 )
	    blkt_1000->byteorder = packdatabyteorder;
	}
      
      else if ( cur_blkt->blkt_type == 1001 )
	{
	  struct blkt_1001_s *blkt_1001 = (struct blkt_1001_s *) (rawrec + offset);
	  memcpy (blkt_1001, cur_blkt->blktdata, sizeof (struct blkt_1001_s));
	  offset += sizeof (struct blkt_1001_s);
	  
	  /* Track location of Blockette 1001 if requested */
	  if ( blkt1001 )
	    *blkt1001 = blkt_1001;
	}
      
      else if ( cur_blkt->blkt_type == 2000 )
	{
	  struct blkt_2000_s *blkt_2000 = (struct blkt_2000_s *) (rawrec + offset);
	  memcpy (blkt_2000, cur_blkt->blktdata, cur_blkt->blktdatalen);
	  offset += cur_blkt->blktdatalen;
	  
	  if ( swapflag )
	    {
	      ms_gswap2 (&blkt_2000->length);
	      ms_gswap2 (&blkt_2000->data_offset);
	      ms_gswap4 (&blkt_2000->recnum);
	    }
	  
	  /* Nothing done to pack the opaque headers and data, they should already
	     be packed into the blockette payload */
	}
      
      else
	{
	  memcpy (rawrec + offset, cur_blkt->blktdata, cur_blkt->blktdatalen);
	  offset += cur_blkt->blktdatalen;
	}
      
      /* Pack the offset to the next blockette */
      if ( cur_blkt->next )
	{
	  memcpy (rawrec + nextoffset, &offset, 2);
	  if ( swapflag ) ms_gswap2 (rawrec + nextoffset);
	}
      else
	{
	  memset (rawrec + nextoffset, 0, 2);
	}
      
      blktcnt++;
      cur_blkt = cur_blkt->next;
    }
  
  fsdh->numblockettes = blktcnt;
  
  if ( verbose > 2 )
    ms_log (1, "%s: Packed %d blockettes\n", msr->srcName, blktcnt);
  
  return offset;
}  /* End of msr_pack_header_raw() */


/***************************************************************************
 * msr_update_header:
 *
 * Update the header values that change between records: start time,
 * sequence number, etc.
 *
 * Returns 0 on success or -1 on error.
 ***************************************************************************/
static int
msr_update_header ( MSRecord *msr, char *rawrec, flag swapflag,
		    struct blkt_1001_s *blkt1001, flag verbose )
{
  struct fsdh_s *fsdh;
  char seqnum[7];
  
  if ( ! msr || ! rawrec )
    return -1;
  
  if ( verbose > 2 )
    ms_log (1, "%s: Updating fixed section of data header\n", msr->srcName );
  
  fsdh = (struct fsdh_s *) rawrec;
  
  /* Pack values into the fixed section of header */
  snprintf (seqnum, 7, "%06d", msr->sequence_number);
  memcpy (fsdh->sequence_number, seqnum, 6);
  
  /* Update fixed-section start time */
  ms_hptime2btime (msr->starttime, &(fsdh->start_time));
  
  /* Swap byte order? */
  if ( swapflag )
    {
      MS_SWAPBTIME (&fsdh->start_time);
    }
  
  /* Update microseconds if Blockette 1001 is present */
  if ( msr->Blkt1001 && blkt1001 )
    {
      hptime_t sec, usec;
      
      /* Calculate microseconds offset */
      sec = msr->starttime / (HPTMODULUS / 10000);
      usec = msr->starttime - (sec * (HPTMODULUS / 10000));
      usec /= (HPTMODULUS / 1000000);
      
      /* Update microseconds offset in blockette chain entry */
      msr->Blkt1001->usec = (int8_t) usec;
      
      /* Update microseconds offset in packed header */
      blkt1001->usec = (int8_t) usec;

	  msr->Blkt1001->timing_qual = msr->timeQual;
    }
  
  return 0;
}  /* End of msr_update_header() */


/************************************************************************
 *  msr_pack_data:
 *
 *  Pack Mini-SEED data samples.  The input data samples specified as
 *  'src' will be packed with 'encoding' format and placed in 'dest'.
 *  
 *  If a pointer to a 32-bit integer sample is provided in the
 *  argument 'lastintsample' and 'comphistory' is true the sample
 *  value will be used to seed the difference buffer for Steim1/2
 *  encoding and provide a compression history.  It will also be
 *  updated with the last sample packed in order to be used with a
 *  subsequent call to this routine.
 *
 *  The number of samples packed will be placed in 'packsamples' and
 *  the number of bytes packed will be placed in 'packbytes'.
 *
 *  Return 0 on success and a negative number on error.
 ************************************************************************/
static int
msr_pack_data (void *dest, void *src, int maxsamples, int maxdatabytes,
	       int *packsamples, int32_t *lastintsample, flag comphistory,
	       char sampletype, flag encoding, flag swapflag, flag verbose, char *srcName)
{
  int retval;
  int nframes;
  int npacked;
  int32_t *intbuff;
  int32_t d0;
  
  /* Decide if this is a format that we can decode */
  switch (encoding)
    {
      
    case DE_ASCII:
      if ( sampletype != 'a' )
	{
	  ms_log (2, "%s: Sample type must be ascii (a) for ASCII encoding not '%c'\n",
		  srcName, sampletype);
	  return -1;
	}
      
      if ( verbose > 1 )
	ms_log (1, "%s: Packing ASCII data\n", srcName);
      
      retval = msr_pack_text (dest, src, maxsamples, maxdatabytes, 1,
			      &npacked, packsamples);
      
      break;
      
    case DE_INT16:
      if ( sampletype != 'i' )
	{
	  ms_log (2, "%s: Sample type must be integer (i) for integer-16 encoding not '%c'\n",
		  srcName, sampletype);
	  return -1;
	}
      
      if ( verbose > 1 )
	ms_log (1, "%s: Packing INT-16 data samples\n", srcName);
      
      retval = msr_pack_int_16 (dest, src, maxsamples, maxdatabytes, 1,
				&npacked, packsamples, swapflag);
      
      break;
      
    case DE_INT32:
      if ( sampletype != 'i' )
	{
	  ms_log (2, "%s: Sample type must be integer (i) for integer-32 encoding not '%c'\n",
		  srcName, sampletype);
	  return -1;
	}

      if ( verbose > 1 )
	ms_log (1, "%s: Packing INT-32 data samples\n", srcName);
      
      retval = msr_pack_int_32 (dest, src, maxsamples, maxdatabytes, 1,
				&npacked, packsamples, swapflag);
      
      break;
      
    case DE_FLOAT32:
      if ( sampletype != 'f' )
	{
	  ms_log (2, "%s: Sample type must be float (f) for float-32 encoding not '%c'\n",
		  srcName, sampletype);
	  return -1;
	}

      if ( verbose > 1 )
	ms_log (1, "%s: Packing FLOAT-32 data samples\n", srcName);
      
      retval = msr_pack_float_32 (dest, src, maxsamples, maxdatabytes, 1,
				  &npacked, packsamples, swapflag);

      break;
      
    case DE_FLOAT64:
      if ( sampletype != 'd' )
	{
	  ms_log (2, "%s: Sample type must be double (d) for float-64 encoding not '%c'\n",
		  srcName, sampletype);
	  return -1;
	}
      
      if ( verbose > 1 )
	ms_log (1, "%s: Packing FLOAT-64 data samples\n", srcName);
      
      retval = msr_pack_float_64 (dest, src, maxsamples, maxdatabytes, 1,
				  &npacked, packsamples, swapflag);
      
      break;
      
    case DE_STEIM1:
      if ( sampletype != 'i' )
	{
	  ms_log (2, "%s: Sample type must be integer (i) for Steim-1 compression not '%c'\n",
		  srcName, sampletype);
	  return -1;
	}
      
      intbuff = (int32_t *) src;
      
      /* If a previous sample is supplied use it for compression history otherwise cold-start */
      d0 = ( lastintsample && comphistory ) ? (intbuff[0] - *lastintsample) : 0;
      
      if ( verbose > 1 )
	ms_log (1, "%s: Packing Steim-1 data frames\n", srcName);
      
      nframes = maxdatabytes / 64;
      
      retval = msr_pack_steim1 (dest, src, d0, maxsamples, nframes, 1,
				&npacked, packsamples, swapflag);
      
      /* If a previous sample is supplied update it with the last sample value */
      if ( lastintsample && retval == 0 )
	*lastintsample = intbuff[*packsamples-1];
      
      break;
      
    case DE_STEIM2:
      if ( sampletype != 'i' )
	{
	  ms_log (2, "%s: Sample type must be integer (i) for Steim-2 compression not '%c'\n",
		  srcName, sampletype);
	  return -1;
	}
      
      intbuff = (int32_t *) src;
      
      /* If a previous sample is supplied use it for compression history otherwise cold-start */
      d0 = ( lastintsample && comphistory ) ? (intbuff[0] - *lastintsample) : 0;
      
      if ( verbose > 1 )
	ms_log (1, "%s: Packing Steim-2 data frames\n", srcName);
      
      nframes = maxdatabytes / 64;
      
      retval = msr_pack_steim2 (dest, src, d0, maxsamples, nframes, 1,
				&npacked, packsamples, swapflag);
      
      /* If a previous sample is supplied update it with the last sample value */
      if ( lastintsample && retval == 0 )
	*lastintsample = intbuff[*packsamples-1];
      
      break;
      
    default:
      ms_log (2, "%s: Unable to pack format %d\n", srcName, encoding);
      
      return -1;
    }
    
  return retval;
} /* End of msr_pack_data() */
