/* **********************************************************************
 * *	                                                                *
 * *               Real Time Display V1 Transmitter                     *
 * *                                                                    *
 * **********************************************************************
 * File: libs/rtq/rtd_v1_xmt.C
 *
 * COPYRIGHT (c) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
 *                              2005 BY
 *         SIGMET INCORPORATED, WESTFORD MASSACHUSETTS, U.S.A.
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 */


#include <errno.h>
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>  /* place after netinet/in.h on some platforms */

#include "sigtypes.h"
#include "setup.h"
#include "sig_rtdisp.h"

#include "user_lib.h"
#include "rtq_lib.h"

#include "RtdSupport_h.h"

static FILE *iLogFile_c;
static volatile struct rtdisp_control *pRtqGlobal_c;
static SINT4 iLastSequenceNum_c;

static void rtd_exit_handler(void);

static SINT4 iread_disq
( UINT1 **pBuffer_a,
  int iChannel_a );

static SINT4 iReadRtdV1
( union rtd_v1_packet **pBuffer_a );

static void HeaderFromElementV1
( struct rtd_v1_state      *pState_a,            /* Fill in internal state here */
  const struct rtq_volume_header *pRtqHeader_a );

static UINT1 lVHeaderChanged
( const struct rtq_volume_header *pLastRtqHeader_a,  /* Header last converted */
  const struct rtq_volume_header *pThisRtqHeader_a );    /* Header of this ray */

static UINT2 RayFromElementV1
( struct rtd_v1_ray_header  *pRay_a,
  struct rtd_v1_state *pState_a,
  const struct RtqQueueElement *pElement_a );

static void convert_raydata8
(UINT1 *prtray_data_a,
 const UINT1 *pray_data_a,   /* Input data */
 const struct rtd_v1_state *pState_a,
 UINT4 ibins_a,              /* Actual number of bins (possibly truncated) */
 UINT2 *pisize_a );          /* Increment for size used */

static void convert_raydata16
(UINT1 *prtray_data_a,       /* Output data for XMT to RTD */
 const UINT2 *pray_data_a,   /* Input data */
 const struct rtd_v1_state *pState_a,
 UINT4 ibins_a,              /* Actual number of bins (possibly truncated) */
 UINT2 *pisize_a,            /* Increment for size used */
 SINT2 iInData_a );          /* Data type of the task data */

static void RayHeaderFromElementV1
(struct rtd_v1_ray_header *prthead_a,
 const struct RtqQueueElement *pElement_a,   /* Input element */
 struct rtd_v1_state *pState_a,
 UINT2 *pisize_a );                  /* Increment for size used */

static void HeaderHostToNet
( struct rtd_v1_vol_header *pNetVHeader_a,
  const struct rtd_v1_vol_header *pHostVHeader_a );

int main( int iArgc_a, char *sArgv_a[] )
{
  int ierror, iChannel;
  MESSAGE istatus ;

  setlocale( LC_ALL, "" ) ;
  iLogFile_c = NULL;  /* Skip logging until we open the file */
  
  iChannel = iGetChannel( iArgc_a, sArgv_a );

  /* Open the logging file, if any */
  {
    char sLogPath[PATHNAME_SIZE]; char *sptr;
    char sDirectory[DIRECTORY_SIZE];
    sptr = getenv( "IRIS_LOG" );
    if (!sptr)
      {
	fprintf( stderr, "Cannot translate IRIS_LOG\n" );
	exit(EXIT_FAILURE);
      }
    strncpy( sDirectory, sptr, DIRECTORY_SIZE-1 );
    sDirectory[PATHNAME_SIZE-1] = 0;

    /* Origin 1 log file name */
    sprintf( sLogPath, "%srtd_v1_xmt%d.log", sDirectory, iChannel+1 );
    iLogFile_c = fopen( sLogPath, "w" );
    if (NULL == iLogFile_c)
      {
	fprintf( stderr, "Can not open log file: '%s'\n", sLogPath );
	exit(EXIT_FAILURE);
      }
  }

  istatus = RtqBufferAttach( &pRtqGlobal_c, RTQ_CLIENT_READER );
  if( istatus != SS_NORMAL )
    {
      sig_signal( istatus );
      exit(EXIT_FAILURE);
    }
  ierror = atexit( rtd_exit_handler );
  if (ierror == -1) sig_signal( errno_to_message_va
				( errno, 1, "main(atexit())" ) ) ;

  if (iArgc_a>1)
    sig_establish( RtdSignalHandler, sArgv_a[1] );
  else
    sig_establish( RtdSignalHandler, "IRIS_V1_XMT" );

  /* Set the priority. */
  istatus = sig_setpriority( pRtqGlobal_c->RSetup.iPriority );
  if ( istatus != SS_NORMAL) sig_signal( istatus );

  /* This will return when the sender exits */
  iLastSequenceNum_c = RtqGetCurrentSequence( pRtqGlobal_c );
  istatus = RtdXmtOn( pRtqGlobal_c, iread_disq, iChannel );
  exit(EXIT_SUCCESS);
}

/* ======================================================================
 * This is the exit handler used by the RTD_XMT processes.
 */
static void rtd_exit_handler(void)
{
  MESSAGE istatus;
  
  istatus = RtqBufferRelease(pRtqGlobal_c);
  if (istatus!=SS_NORMAL) sig_signal(istatus);

}


/* ============================================================
 * Read the next ray from the display buffer.  Returns both the
 * size of write in bytes, and the buffer pointer.
 */
static SINT4 iread_disq
( UINT1 **pBuffer_a,
  int iChannel_a )
{

  SINT4 iSize;

  iSize = iReadRtdV1( (union rtd_v1_packet**)pBuffer_a );

  return iSize;
}

struct rtd_v1_state
{
  rtd_v1_vol_header VHeader;     /* Saved in host byte order */
  rtq_volume_header LastInVHeader;  /* Input volume header we last converted */
  UINT2 iInData[3];              /* Input data types, used for conversion if needed */
  SINT4 irtd_index[3];
  struct data_convert Convert;
  SINT4 iSkipStart;              /* Number of bins to remove from the start, before skips */
  UINT4 iSkipRate;
  UINT1 iInDataRes;
  UINT1 lSendHeader;             /* We must send a new header now */

};
/* ============================================================
 * Read the next ray from the display buffer.  Handles SIGMET RTD_V1 format.
 */
static SINT4 iReadRtdV1
( union rtd_v1_packet **pBuffer_a )
{
  UINT1 lValid;
  
  static struct rtd_v1_state  SavedState;
  static union  rtd_v1_packet SavedRay;     /* stored in network byte order */
  static struct rtd_v1_vol_header NetVHeader;  /* stored in network byte order */
  static UINT2 iCode;
  static SINT4 iRaySize=0;
  static UINT1 lFirstCall = TRUE;
  static UINT1 irays_since_header = 0;
  static UINT1 lRayReadyToSend=FALSE;
  struct RtqQueueElement Element;

  /* Initialize all static storage */
  if (lFirstCall)
    {
      memset( &SavedState, 0, sizeof(SavedState) );
      memset( &SavedRay,   0, sizeof(SavedRay) );
      memset( &NetVHeader, 0, sizeof(NetVHeader) );
    }

  /* Periodically, send a new header to deal with possibly missed data.
   */
  if (irays_since_header > 25)
    {
      irays_since_header=0;
      *pBuffer_a = (union rtd_v1_packet*)(void*)&NetVHeader;
      return sizeof( struct rtd_v1_vol_header );
    }

  /* Send a the previous ray if it is ready to go */
  if (lRayReadyToSend)
    {
      lRayReadyToSend=FALSE;
      irays_since_header++;
      *pBuffer_a = &SavedRay;
      return iRaySize;
    }

  /* Get new info from the RTQ */
  lValid = RtqGetRay( pRtqGlobal_c, &Element, iLastSequenceNum_c );

  /* Return zero size if nothing available */
  if (!lValid)
    {
      *pBuffer_a = NULL;
      return 0;
    }

  /* We have a valid element from the RTQ, latch the new sequence number */
  iLastSequenceNum_c = Element.iSequenceNum;

  /* If volume end flag, send it through */
  if (Element.VHeader.iFlags & RTQ_FLAG_VOLUME_END)
    {
      iCode = RTD_PACKET_TYPE_END | 2;
      iCode = htons( iCode );
      *pBuffer_a = (union rtd_v1_packet*)&iCode;
      return 2;
    }
  
  /* Calculate the header if this is the first call, it is the start of a 
   * volume scan, or if it has changed significantly.  There is no need to
   * look at the sequence number to detect missing inputs because the
   * volume header will change if there was a change. */
  if (lFirstCall || (Element.VHeader.iFlags & RTQ_FLAG_VOLUME_START) ||
      lVHeaderChanged(&SavedState.LastInVHeader, &(Element.VHeader) ) )
    {
      lFirstCall = FALSE;
      /* Convert the header */
      HeaderFromElementV1( &SavedState, &(Element.VHeader) );
      SavedState.lSendHeader = TRUE;
    }

  /* Convert the ray, might force a new header */
  iRaySize = RayFromElementV1( &(SavedRay.RHeader), &SavedState, &Element );

  /* Send the header if flagged */
  if (SavedState.lSendHeader)
    {
      /* Convert the header to net order */
      HeaderHostToNet( &NetVHeader, &(SavedState.VHeader) );
      SavedState.lSendHeader = FALSE;
      
      /* Send the ray later */
      lRayReadyToSend=TRUE;
      irays_since_header=0;
      *pBuffer_a = (union rtd_v1_packet*)(void*)&NetVHeader;
      return sizeof( struct rtd_v1_vol_header );
    }

  /* Finally send a normal ray */
  irays_since_header++;
  *pBuffer_a = &SavedRay;
  return iRaySize;
  
}
/* ===============================================
 * Make a new real time display header.  Also determine if we will make a display,
 * and if so, set static variables to define it.
 */
static void HeaderFromElementV1
( struct rtd_v1_state      *pState_a,            /* Fill in internal state here */
  const struct rtq_volume_header *pRtqHeader_a )
{
  SINT4 imax_total_bins, imax_bins, ii, ibin_first;
  
  memset( pState_a, 0, sizeof(*pState_a) );

  /* Save the last input header */
  pState_a->LastInVHeader = *pRtqHeader_a;

  /* Now determine the moments we will be able to transmit. */
  if (pRtqGlobal_c->RSetup.lrtd_send_refl)
    {
      UINT1 lFound=FALSE;
      /* Search for reflectivity */
      for (ii=0; ii<pRtqHeader_a->iMomentCount; ii++)
	if( (pRtqHeader_a->iDTypes[ii]==DB_DBZ) ||
	    (pRtqHeader_a->iDTypes[ii]==DB_DBZ2) )
	  {
	    pState_a->iInData[pState_a->VHeader.iMomentCount] = pRtqHeader_a->iDTypes[ii];
	    pState_a->VHeader.idata[pState_a->VHeader.iMomentCount] = DB_DBZ;
	    pState_a->irtd_index[pState_a->VHeader.iMomentCount++] = ii;
	    lFound=TRUE;
	  }
      /* If it was not found, then also search for Total power */
      if (!lFound)
	for (ii=0; ii<pRtqHeader_a->iMomentCount; ii++)
	  if( (pRtqHeader_a->iDTypes[ii]==DB_DBT) ||
	      (pRtqHeader_a->iDTypes[ii]==DB_DBT2) )
	    {
	      pState_a->iInData[pState_a->VHeader.iMomentCount] = pRtqHeader_a->iDTypes[ii];
	      pState_a->VHeader.idata[pState_a->VHeader.iMomentCount] = DB_DBZ;
	      pState_a->irtd_index[pState_a->VHeader.iMomentCount++] = ii;
	    }
    }

  if (pRtqGlobal_c->RSetup.lrtd_send_velocity)
    {
      /* Search for velocity */
      for (ii=0; ii<pRtqHeader_a->iMomentCount; ii++)
	if( (pRtqHeader_a->iDTypes[ii]==DB_VEL) || 
	    (pRtqHeader_a->iDTypes[ii]==DB_VEL2) )
	  {
	    pState_a->iInData[pState_a->VHeader.iMomentCount] = pRtqHeader_a->iDTypes[ii];
	    pState_a->VHeader.idata[pState_a->VHeader.iMomentCount] = DB_VEL;
	    pState_a->irtd_index[pState_a->VHeader.iMomentCount++] = ii;
	  }
    }

  if (pRtqGlobal_c->RSetup.lrtd_send_width)
    {
      /* Search for width */
      for (ii=0; ii<pRtqHeader_a->iMomentCount; ii++)
	if( (pRtqHeader_a->iDTypes[ii]==DB_WIDTH) ||
	    (pRtqHeader_a->iDTypes[ii]==DB_WIDTH2) )
	  {
	    pState_a->iInData[pState_a->VHeader.iMomentCount] = pRtqHeader_a->iDTypes[ii];
	    pState_a->VHeader.idata[pState_a->VHeader.iMomentCount] = DB_WIDTH;
	    pState_a->irtd_index[pState_a->VHeader.iMomentCount++] = ii;
	  }
    }

  if (pState_a->VHeader.iMomentCount == 0) return;

  /* Calculate the total number of range bins to process.  First
   * network hardware limit (subtract the ray header).
   */
  imax_total_bins = UDP_DATA_SIZE-(2+sizeof(struct rtd_v1_ray_header));

  /* Next the setup limit
   */
  imax_total_bins = MIN(imax_total_bins,
			pState_a->VHeader.iMomentCount * pRtqGlobal_c->RSetup.irtd_max_bins );
  imax_bins       = imax_total_bins/pState_a->VHeader.iMomentCount;

  /* Require a minimum of 2 bins
   */
  if (imax_bins<3) {pState_a->VHeader.iMomentCount = 0; return; }

  /* If the start range is negative, we should skip the first few bins */
  if ((pRtqHeader_a->ibin_first<0)&&(pRtqHeader_a->ibin_step>0))
    {
      FLT4 fSkipBins = ((FLT4)-pRtqHeader_a->ibin_first)/((FLT4)pRtqHeader_a->ibin_step);
      pState_a->iSkipStart = NINT(fSkipBins);
    }
  else
    pState_a->iSkipStart = 0;

  /* Require some bins beyond zero */
  if (pRtqHeader_a->iNominalBinCount<pState_a->iSkipStart) 
    { pState_a->VHeader.iMomentCount = 0; return; }

  /* Now quantize based on skipping at a fixed level from the input data
   */
  pState_a->iSkipRate = 
    (int)ceil( ((FLT4)(pRtqHeader_a->iNominalBinCount-pState_a->iSkipStart))/
	       ((FLT4)imax_bins) );
  pState_a->VHeader.iNominalBinCount = 
    (pRtqHeader_a->iNominalBinCount-pState_a->iSkipStart)/pState_a->iSkipRate;

  /* Require a minimum of 2 bins
   */
  if (pState_a->VHeader.iNominalBinCount<2) { pState_a->VHeader.iMomentCount = 0; return; }

  /* Fill in the rest of the structure
   */
  pState_a->VHeader.iCode      = RTD_PACKET_TYPE_HEADER | sizeof(struct rtd_v1_vol_header);
  pState_a->VHeader.ipolar        = pRtqHeader_a->ipolar ;
  strncpy( pState_a->VHeader.sSiteName16, pRtqHeader_a->sSiteName16, 16 );
  strncpy( pState_a->VHeader.sSiteName3, pRtqHeader_a->sSiteName3, 4 );
  pState_a->VHeader.sSiteName3[3] = 0;
  pState_a->VHeader.ilat          = pRtqHeader_a->ilat;
  pState_a->VHeader.ilon          = pRtqHeader_a->ilon;
  pState_a->VHeader.ing_time      = pRtqHeader_a->ing_time;
  ibin_first    = pRtqHeader_a->ibin_first+ (pRtqHeader_a->ibin_step*pState_a->iSkipStart);
  /* Clip at zero if we have a small negative start */
  if (ibin_first<0) 
    pState_a->VHeader.ibin_first  = 0;
  else
    pState_a->VHeader.ibin_first  = ibin_first;

  pState_a->VHeader.ibin_step     = pRtqHeader_a->ibin_step*pState_a->iSkipRate;
  pState_a->VHeader.iprf          = pRtqHeader_a->iprf;
  pState_a->VHeader.itrig         = pRtqHeader_a->itrig;
  pState_a->VHeader.ilambda       = pRtqHeader_a->ilambda;
  strncpy( pState_a->VHeader.stname, pRtqHeader_a->stname, 16 );
  pState_a->VHeader.iScanType     = pRtqHeader_a->iScanType;

  FillDataConvert( &pState_a->Convert, pRtqHeader_a->iprf, pRtqHeader_a->itrig,
		   pRtqHeader_a->ilambda, pRtqHeader_a->ipolar,
		   CONVERT_FLAGS_DEFAULT );

  /* Fill in the data resolution, if any */
  if (pState_a->VHeader.iMomentCount>0)
    pState_a->iInDataRes = idata_resolution(pState_a->iInData[0]);

}
/* ===============================================
 * Test to see if a significant change to the volume header changed.
 * This can be used to force a new header to be sent.
 */
static UINT1 lVHeaderChanged
( const struct rtq_volume_header *pLastRtqHeader_a,  /* Header last converted */
  const struct rtq_volume_header *pThisRtqHeader_a )     /* Header of this ray */
{
  struct rtq_volume_header LastHeader, ThisHeader;
  
  /* We are going to do a full memcmp of the two structures, 
   * leaving out fields which we know are not important for a
   * change in the middle of the volume scan.
   */

  LastHeader = *pLastRtqHeader_a;
  ThisHeader = *pThisRtqHeader_a;

  LastHeader.iFlags = 0;          ThisHeader.iFlags = 0;  /* Unused */
  /* Only notice motion between volumes*/
  LastHeader.ilat   = 0;          ThisHeader.ilat   = 0;  
  LastHeader.ilon   = 0;          ThisHeader.ilon   = 0;
  LastHeader.iAntStatus = 0;      ThisHeader.iAntStatus   = 0;  /* Unused */
  LastHeader.iAntStatusMask = 0;  ThisHeader.iAntStatusMask = 0; /* Unused */
  LastHeader.ipad_69x1 = 0;       ThisHeader.ipad_69x1 = 0;   /* Unused */

  return (0 != memcmp( &LastHeader, &ThisHeader, sizeof(LastHeader) ));

}

/* ==============================================================
 * Function to convert a ray from RTQ format to RTD V1 format.  Returns
 * the number of bytes in the ray.
 */
static UINT2 RayFromElementV1
( struct rtd_v1_ray_header  *pRay_a,
  struct rtd_v1_state *pState_a,
  const struct RtqQueueElement *pElement_a )
{
  int iMoment;
  UINT2 iSize;
  const UINT1 *pInRay;
  
  if (pState_a->VHeader.iMomentCount<=0) return 0;

  iSize = 0;
  for (iMoment=0; iMoment<pState_a->VHeader.iMomentCount; iMoment++)
    {
      pInRay = pElement_a->iData[pState_a->irtd_index[iMoment]];
      if (16==pState_a->iInDataRes)
	convert_raydata16( &pRay_a->iData[iSize], (UINT2*)pInRay, pState_a,
			   pElement_a->RHeader.iBinCount, &iSize, 
			   pState_a->iInData[iMoment] );
      else
	convert_raydata8 ( &pRay_a->iData[iSize], pInRay, pState_a,
			   pElement_a->RHeader.iBinCount, &iSize );
      
    }
  RayHeaderFromElementV1( pRay_a, pElement_a, pState_a, &iSize );
  return iSize;
}
/* ====================================================
 * Convert just the ray data
 */
static void convert_raydata8
(UINT1 *prtray_data_a,
 const UINT1 *pray_data_a,   /* Input data */
 const struct rtd_v1_state *pState_a,
 UINT4 ibins_a,              /* Actual number of bins (possibly truncated) */
 UINT2 *pisize_a )           /* Increment for size used */
{
  SINT4 icount_remaining;
  SINT4 idata_count;
  UINT1 *prtray_data;

  idata_count = (ibins_a-pState_a->iSkipStart)/pState_a->iSkipRate;
  if (idata_count<=0) return;

  if (pState_a->iSkipRate == 1)
    {
      memmove( prtray_data_a+pState_a->iSkipStart, pray_data_a, idata_count );
    }
  else
    {
      prtray_data = prtray_data_a+pState_a->iSkipStart;
      icount_remaining = idata_count;
      while( icount_remaining-- > 0 )
	{
	  *prtray_data++ = *pray_data_a;
	  pray_data_a += pState_a->iSkipRate;
	}

    }
  *pisize_a += idata_count;
}
/* ====================================================
 * Convert just the ray data
 */
static void convert_raydata16
(UINT1 *prtray_data_a,       /* Output data for XMT to RTD */
 const UINT2 *pray_data_a,   /* Input data */
 const struct rtd_v1_state *pState_a,
 UINT4 ibins_a,              /* Actual number of bins (possibly truncated) */
 UINT2 *pisize_a,            /* Increment for size used */
 SINT2 iInData_a )            /* Data type of the task data */
{
  SINT4 idata_count;

  idata_count = (ibins_a-pState_a->iSkipStart)/pState_a->iSkipRate;
  if (idata_count<=0) return;
  if (pState_a->iSkipRate == 1)
    {
      ConvertDataFrom16( iInData_a, idata_count, pray_data_a+pState_a->iSkipStart, 
			 prtray_data_a, &(pState_a->Convert) );
    }
  else
    {
      SINT4 icount_remaining;
      UINT1 *prtray_data;
      prtray_data = prtray_data_a+pState_a->iSkipStart;
      icount_remaining = idata_count;
      while( icount_remaining-- > 0 )
	{
	  ConvertDataFrom16( iInData_a, 1, pray_data_a, prtray_data, 
			     &(pState_a->Convert) );
	  prtray_data++;
	  pray_data_a += pState_a->iSkipRate;
	}

    }
  *pisize_a += idata_count;
}

/* ====================================================
 * Convert just the ray header
 */
static void RayHeaderFromElementV1
(struct rtd_v1_ray_header *pRtdHead_a,
 const struct RtqQueueElement *pElement_a,   /* Input element */
 struct rtd_v1_state *pState_a,
 UINT2 *pisize_a )                   /* Increment for size used */
{
  SINT4 iTimeOffset =
    iymds_subtract( &pElement_a->RHeader.Ymds, &pState_a->VHeader.ing_time );
#if 0
  printf( "Az: %5.2f, El: %5.2f, bins: %d, time: %d\n",
	  fPDegFromBin2(pray->iaz_start), fElDegFromBin2(pray->iel_start),
	  pray->ibincount, iTimeOffset );
#endif

  /* If the time offset overflows reason, then zero it and force a new
   * header transmission.
   */
  if ((iTimeOffset<0)||(iTimeOffset>0x0000ffff))
    {
      pState_a->VHeader.ing_time = pElement_a->RHeader.Ymds;
      iTimeOffset = 0;
      pState_a->lSendHeader = TRUE;
    }

  /* Copying everything into network order */
  strncpy( pRtdHead_a->sSiteName3, pElement_a->VHeader.sSiteName3, 4 );
  pRtdHead_a->sSiteName3[3] = 0;
  pRtdHead_a->azimuth_start = htons(pElement_a->RHeader.azimuth_start);
  pRtdHead_a->azimuth       = htons(pElement_a->RHeader.azimuth);
  pRtdHead_a->elevation_start = htons(pElement_a->RHeader.elevation_start);
  pRtdHead_a->elevation     = htons(pElement_a->RHeader.elevation);
  pRtdHead_a->itime         = htons((UINT2)iTimeOffset);
  pRtdHead_a->iBinCount     = htons(pElement_a->RHeader.iBinCount/pState_a->iSkipRate);
  *pisize_a += sizeof( struct rtd_v1_ray_header )-1;
  pRtdHead_a->iCode         = htons( RTD_PACKET_TYPE_RAY|*pisize_a );

}

/* ==============================================================
 * Convert a RTD V1 Volume header from host byte order to net byte order.
 */
static void HeaderHostToNet
( struct rtd_v1_vol_header *pNetVHeader_a,
  const struct rtd_v1_vol_header *pHostVHeader_a )
{
  pNetVHeader_a->iCode          = htons( pHostVHeader_a->iCode );
  pNetVHeader_a->ipolar         = htons( pHostVHeader_a->ipolar );
  memcpy( pNetVHeader_a->sSiteName16, (char*)pHostVHeader_a->sSiteName16, 20 );
  memcpy( pNetVHeader_a->sSiteName3, (char*)pHostVHeader_a->sSiteName3, 4 );
  pNetVHeader_a->ilat           = htonl( pHostVHeader_a->ilat );
  pNetVHeader_a->ilon           = htonl( pHostVHeader_a->ilon );
  pNetVHeader_a->ing_time.isec  = htonl( pHostVHeader_a->ing_time.isec );
  pNetVHeader_a->ing_time.imills= htons( pHostVHeader_a->ing_time.imills );
  pNetVHeader_a->ing_time.iyear = htons( pHostVHeader_a->ing_time.iyear );
  pNetVHeader_a->ing_time.imon  = htons( pHostVHeader_a->ing_time.imon );
  pNetVHeader_a->ing_time.iday  = htons( pHostVHeader_a->ing_time.iday );
  pNetVHeader_a->ibin_first     = htonl( pHostVHeader_a->ibin_first );
  pNetVHeader_a->ibin_step      = htonl( pHostVHeader_a->ibin_step );
  pNetVHeader_a->iNominalBinCount = htonl( pHostVHeader_a->iNominalBinCount );
  pNetVHeader_a->iMomentCount   = htons( pHostVHeader_a->iMomentCount );
  pNetVHeader_a->idata[0]       = htons( pHostVHeader_a->idata[0] );
  pNetVHeader_a->idata[1]       = htons( pHostVHeader_a->idata[1] );
  pNetVHeader_a->idata[2]       = htons( pHostVHeader_a->idata[2] );
  pNetVHeader_a->iprf           = htonl( pHostVHeader_a->iprf );
  pNetVHeader_a->itrig          = htonl( pHostVHeader_a->itrig );
  pNetVHeader_a->ilambda        = htonl( pHostVHeader_a->ilambda );
  memcpy( pNetVHeader_a->stname, (char*)pHostVHeader_a->stname, 16 );
  pNetVHeader_a->iScanType      = pHostVHeader_a->iScanType;

}

/* ===========================================
 * Print string to log and terminal, if desired.
 */
void Lprintf
( const char *sString_a )
{
  struct ymds_time Ymds;
  char sDateBuf[TIMENAME_SIZE], sTimeBuf[TIMENAME_SIZE] ;


  if (iLogFile_c)
    {

      GetTimeYmds( &Ymds, TIME_FLG_LOCAL );
      
      sddmonyyyy_r( Ymds.iyear, Ymds.imon, Ymds.iday, sDateBuf );
      shhmmss_r( Ymds.isec, sTimeBuf );

      fprintf( iLogFile_c, "%s %s.%03d: ", sDateBuf, sTimeBuf, MS_FROM_MILLS(Ymds.imills) );
      fputs( sString_a, iLogFile_c );
      fflush( iLogFile_c );
    }

  return;
}
