/* **********************************************************************
 * *	                                                                *
 * *              Real Time Display NIDS3 Transmitter                   *
 * *                                                                    *
 * **********************************************************************
 * File: libs/rtq/rtd_nids3_xmt.C
 *
 *                 COPYRIGHT (c) 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.
 */

/* Use IRIS/dsp setup to configurate the max number of range bins to
 * send.  Suggested value is 512.
 */

#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 <sys/time.h>
#define  INCLUDE_SYSTIME_PROTOTYPES 1

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

#include "antenna_lib.h"
#include "misc_lib.h"
#include "user_lib.h"
#include "rtq_lib.h"

#include "RtdSupport_h.h"

//#define DEBUG_ON (1)

struct convert_seams
{
  FLT4  fStart;          /* Start value of color scale */
  FLT4  fStep;     
};
struct convert_channel_list
{
  SINT2 iResolution;          /* Either 4 or 8 bits */
  UINT1 lTData;
  UINT1 lZData;
  UINT1 lVData;
  UINT1 lWData;
  char  ipad_end[2];
};

struct convert_config
{
  struct convert_channel_list Channel[RTD_CHANNEL_COUNT];
  struct convert_seams TSeams;
  struct convert_seams ZSeams;
  struct convert_seams VSeams;
  struct convert_seams WSeams;
  FILE *iLogFile;              /* Open file descriptor to log file */
  UINT1 lLogToFile;
  UINT1 lLogToTerm;
  UINT1 lVerbose;              /* Log more details */
  UINT1 lAppendToLog;          /* Append to previous log file */
  char  ipad_end[1];
};

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 iReadRtdNids3
( union rtd_nids3_packet **pBuffer_a,
  int iChannel_a );

static void StateFromElement
( struct rtd_state      *pState_a,            /* Fill in internal state here */
  const struct RtqQueueElement *pRtqElement_a,
  int iChannel_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 void ComputeColormap
(UINT2 idtype_a,                    /* Data type */
 const struct rtq_volume_header *pRtqHeader_a,
 union level_control *plevel_a,     /* Filled in so we can levelize */
 const struct convert_seams *pSeams_a );

static UINT2 RayFromElementNids3
( struct rtd_nids3_ray_header  *pRay_a,
  const struct rtd_state *pState_a,
  const struct RtqQueueElement *pElement_a );

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

static void convert_raydata16
(UINT2 *prtray_data_a,       /* Output data for XMT to RTD */
 const UINT2 *pray_data_a,   /* Input data */
 const struct rtd_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 RayHeaderFromElementNids3
(struct rtd_nids3_ray_header *prthead_a,
 const struct rtq_ray_header *phead_a,   /* Input ray header */
 UINT2 *pisize_a );                  /* Increment for size used */

static void LoadSetup
( struct convert_config *pCSetup_a );

static UINT2 ProcessConfigLine
(char *sline_a,
 struct convert_config *pConfig_a );

static UINT1 ParseDataList
(struct convert_channel_list *pOneChannel_a,
 char *sString_a );

static struct convert_config CSetup_c;

/* ===================================================================
 * Main program starts here.
 */
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 );
  
  /* Process the setup file, if any */
  LoadSetup( &CSetup_c );

  /* Open the logging file, if any */
  if (CSetup_c.lLogToFile)
    {
      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;
      
      sprintf( sLogPath, "%srtd_nids3_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);
	}
    }


  if (CSetup_c.lVerbose)
    {
      char sMessage[40];
      struct full_time_spec Time;
      GetTimeFull( &Time, TIME_FLG_LOCAL );
      sprintf( sMessage, "Verbose logging on, TZ=%s\n", Time.sRecordTZName );
      Lprintf( sMessage );
    }

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

  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())" ) ) ;

  /* 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 );
  if (istatus!=SS_NORMAL) { sig_signal(istatus); exit(EXIT_FAILURE); }

  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);

  if (CSetup_c.lVerbose)
    {
      Lprintf( "RTD Transmitter exit.\n" );
    }

}


/* ============================================================
 * 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 = iReadRtdNids3( (union rtd_nids3_packet**)pBuffer_a, iChannel_a );

  if (iSize)  // Max packet rate will be limited, suggested by Baron.
    sig_microSleep( 10000 );

#ifdef DEBUG_ON
  {
    char sMessage[40];
    sprintf( sMessage, "size:%d, ptr:%d\n", iSize, (int)(*pBuffer_a) );
    Lprintf( sMessage );
  }
#endif  /* #ifdef DEBUG_ON */

  return iSize;
}

struct rtd_state
{
  struct rtd_nids3_ray_header RHeader;
  struct convert_channel_list Channel;
  union level_control Level;
  SINT4 iMomentCount;
  UINT2 iInData[4];           /* Input data types, used for conversion if needed */
  UINT2 iOutData[4];          /* Output data types, used for conversion if needed */
  SINT4 irtd_index[4];
  struct data_convert Convert;
  UINT4 iSkipRate;
  UINT1 iInDataRes;

};
/* ============================================================
 * Read the next ray from the display buffer.  Handles SIGMET RTD_NIDS3 format.
 */
static SINT4 iReadRtdNids3
( union rtd_nids3_packet **pBuffer_a,
  int iChannel_a )  /* Origin 1 channel number */
{
  UINT1 lValid;
  
  static struct rtd_state  SavedState;
  static union  rtd_nids3_packet SavedRay;     /* stored in network byte order? */
  static struct rtq_volume_header SavedRtqVHeader;
  static SINT4 iRaySize=0;
  struct RtqQueueElement Element;

  static UINT1 lFirstCall = TRUE;

  if (lFirstCall)
    {
      memset( &SavedState,      0, sizeof(SavedState) );
      memset( &SavedRay,        0, sizeof(SavedRay) );
      memset( &SavedRtqVHeader, 0, sizeof(SavedRtqVHeader) );
    }

  /* 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, ignore it */
  if (Element.VHeader.iFlags & RTQ_FLAG_VOLUME_END)
    {
      *pBuffer_a = NULL;
      return 0;
    }

  /* Process the header if this is the first call, it is the start of
   * a volume scan, or if it has changed significantly. */
  if (lFirstCall || (Element.VHeader.iFlags&(RTQ_FLAG_VOLUME_START|RTQ_FLAG_SWEEP_START)) ||
      lVHeaderChanged(&SavedRtqVHeader, &(Element.VHeader) ) )
    {
      struct rtd_nids3_ray_header OldRHeader=SavedState.RHeader;
      /* Convert the header */
      StateFromElement( &SavedState, &Element, iChannel_a );

      if (lFirstCall)
	{
	  SavedState.RHeader.iModes |= RTD_NIDS3_CHANGE_MASK;
	}
      else
	{
	  /* Check for obvious changes */
	  /* If sweep has changed then we flag elevation change */
	  if (Element.VHeader.iFlags&RTQ_FLAG_SWEEP_START)
	    SavedState.RHeader.iModes |= RTD_NIDS3_CHANGE_ELEV;
	  /* If the product types have changed, then flag the product change */
	  if ( (RTD_NIDS3_DATA_MASK&SavedState.RHeader.iModes) != 
	       (RTD_NIDS3_DATA_MASK&OldRHeader.iModes) )
	    SavedState.RHeader.iModes |= RTD_NIDS3_CHANGE_PROD;

	  /* Range change if the range spacing or count changes */
	  if ( ( SavedState.RHeader.iBinSpacing != OldRHeader.iBinSpacing )||
	       ( SavedState.RHeader.iBinCount != OldRHeader.iBinCount ))
	    {
	      SavedState.RHeader.iModes |= RTD_NIDS3_CHANGE_RANGE;
	    }
	  /* Other bit set if the Nyquist velocity changes */
	  if ( SavedState.RHeader.iNyquists != OldRHeader.iNyquists )
	    {
	      SavedState.RHeader.iModes |= RTD_NIDS3_CHANGE_OTHER;
	    }
	}

      lFirstCall = FALSE;
      SavedRtqVHeader = Element.VHeader;
    }
  else
    {
      /* Clear change bits if no change */
      SavedState.RHeader.iModes &= ~RTD_NIDS3_CHANGE_MASK;
      /* The number of range bins can always change */
      SavedState.RHeader.iBinCount = Element.RHeader.iBinCount/SavedState.iSkipRate;
    }


  /* Convert the ray, zero first */
  memset( &SavedRay, 0, sizeof(SavedRay) );
  iRaySize = RayFromElementNids3( &(SavedRay.RHeader), &SavedState, &Element );

  if (0x1234 != htons(0x1234)) Swap2inplace( (SINT2*)&SavedRay, iRaySize/2 );

  /* Finally send a normal ray */
  *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 StateFromElement
( struct rtd_state      *pState_a,            /* Fill in internal state here */
  const struct RtqQueueElement *pRtqElement_a,
  int iChannel_a )
{
  SINT4 imax_bins, ii, iRangeBinSpacingCm;
  UINT4 iNyquistV, iNyquistW;
  const struct rtq_volume_header *pRtqHeader= &(pRtqElement_a->VHeader);
  
  memset( pState_a, 0, sizeof(*pState_a) );

  pState_a->Channel = CSetup_c.Channel[iChannel_a];
  /* Now determine the moments we will be able to transmit.
   */

  if (pState_a->Channel.lTData)
    {
      /* Search for total power */
      for (ii=0; ii<pRtqHeader->iMomentCount; ii++)
	if( (pRtqHeader->iDTypes[ii]==DB_DBT) ||
	    (pRtqHeader->iDTypes[ii]==DB_DBT2) )
	  {
	    if (0==pState_a->iMomentCount)
	      {
		SINT4 iStart, iStep;
		iStart=NINT(CSetup_c.TSeams.fStart);
		iStep =NINT(10.0*CSetup_c.TSeams.fStep);
		pState_a->RHeader.iScales = (iStart<<8) | (0xff &iStep);
	      }
	    pState_a->iInData[pState_a->iMomentCount] = pRtqHeader->iDTypes[ii];
	    pState_a->iOutData[pState_a->iMomentCount] = DB_DBZ;
	    pState_a->irtd_index[pState_a->iMomentCount++] = ii;
	    pState_a->RHeader.iModes |= RTD_NIDS3_DATA_T;
	    if (pState_a->Channel.iResolution == 4)
	      ComputeColormap( DB_DBT, pRtqHeader, &pState_a->Level, &CSetup_c.TSeams );
	  }
    }

  if (pState_a->Channel.lZData)
    {
      /* Search for reflectivity */
      for (ii=0; ii<pRtqHeader->iMomentCount; ii++)
	if( (pRtqHeader->iDTypes[ii]==DB_DBZ) ||
	    (pRtqHeader->iDTypes[ii]==DB_DBZ2) )
	  {
	    if (0==pState_a->iMomentCount)
	      {
		SINT4 iStart, iStep;
		iStart=NINT(CSetup_c.ZSeams.fStart);
		iStep =NINT(10.0*CSetup_c.ZSeams.fStep);
		pState_a->RHeader.iScales = (iStart<<8) | (0xff &iStep);
	      }
	    pState_a->iInData[pState_a->iMomentCount] = pRtqHeader->iDTypes[ii];
	    pState_a->iOutData[pState_a->iMomentCount] = DB_DBZ;
	    pState_a->irtd_index[pState_a->iMomentCount++] = ii;
	    pState_a->RHeader.iModes |= RTD_NIDS3_DATA_Z;
	    if (pState_a->Channel.iResolution == 4)
	      ComputeColormap( DB_DBZ, pRtqHeader, &pState_a->Level, &CSetup_c.TSeams );
	  }
    }

  if (pState_a->Channel.lVData)
    {
      /* Search for velocity */
      for (ii=0; ii<pRtqHeader->iMomentCount; ii++)
	if( (pRtqHeader->iDTypes[ii]==DB_VEL) || 
	    (pRtqHeader->iDTypes[ii]==DB_VEL2) )
	  {
	    if (0==pState_a->iMomentCount)
	      {
		SINT4 iStart, iStep;
		iStart=NINT(5.0*CSetup_c.VSeams.fStart);
		iStep =NINT(5.0*CSetup_c.VSeams.fStep);
		pState_a->RHeader.iScales = (iStart<<8) | (0xff &iStep);
	      }
	    pState_a->iInData[pState_a->iMomentCount] = pRtqHeader->iDTypes[ii];
	    pState_a->iOutData[pState_a->iMomentCount] = DB_VEL;
	    pState_a->irtd_index[pState_a->iMomentCount++] = ii;
	    pState_a->RHeader.iModes |= RTD_NIDS3_DATA_V;
	    if (pState_a->Channel.iResolution == 4)
	      ComputeColormap( DB_VEL, pRtqHeader, &pState_a->Level, &CSetup_c.TSeams );
	  }
    }

  if (pState_a->Channel.lWData)
    {
      /* Search for width */
      for (ii=0; ii<pRtqHeader->iMomentCount; ii++)
	if( (pRtqHeader->iDTypes[ii]==DB_WIDTH) ||
	    (pRtqHeader->iDTypes[ii]==DB_WIDTH2) )
	  {
	    if (0==pState_a->iMomentCount)
	      {
		SINT4 iStart, iStep;
		iStart=NINT(10.0*CSetup_c.TSeams.fStart);
		iStep =NINT(10.0*CSetup_c.TSeams.fStep);
		pState_a->RHeader.iScales = (iStart<<8) | (0xff &iStep);
	      }
	    pState_a->iInData[pState_a->iMomentCount] = pRtqHeader->iDTypes[ii];
	    pState_a->iOutData[pState_a->iMomentCount] = DB_WIDTH;
	    pState_a->irtd_index[pState_a->iMomentCount++] = ii;
	    pState_a->RHeader.iModes |= RTD_NIDS3_DATA_W;
	    if (pState_a->Channel.iResolution == 4)
	      ComputeColormap( DB_WIDTH, pRtqHeader, &pState_a->Level, &CSetup_c.TSeams );
	  }
    }

  /* Calculate the total number of range bins to process.  First the network
   * hardware limit, divided by the number of moments.  Round down to a multiple
   * of the word size.
   */
  if (pState_a->iMomentCount>0)
    imax_bins = RTD_NIDS3_MAX_BINS/pState_a->iMomentCount;
  else
    imax_bins = RTD_NIDS3_MAX_BINS;
      
  if (pState_a->Channel.iResolution == 4)
    imax_bins &= 0xfffffffc;
  else
    imax_bins &= 0xfffffffe;

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

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

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

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

  /* Fill in the rest of the structure
   */

  pState_a->RHeader.iStartCode = 0xaf1f;
  /* Radar status */
  pState_a->RHeader.iRadarStatus  = 0xffff; 

  /* Always turn off the main power */
  pState_a->RHeader.iRadarStatus &= ~RTD_NIDS3_STATUS_POWER;
  
  if (ASF_SERVO & pRtqHeader->iAntStatusMask)
    if (ASF_SERVO & pRtqHeader->iAntStatus) 
      pState_a->RHeader.iRadarStatus &= ~RTD_NIDS3_STATUS_SERVO;

  if (ASF_TRPOWER & pRtqHeader->iAntStatusMask)
    if (ASF_TRPOWER & pRtqHeader->iAntStatus) 
      pState_a->RHeader.iRadarStatus &= ~RTD_NIDS3_STATUS_XMTPOWER;

  if (ASF_RADIATE & pRtqHeader->iAntStatusMask)
    if (ASF_RADIATE & pRtqHeader->iAntStatus) 
      pState_a->RHeader.iRadarStatus &= ~RTD_NIDS3_STATUS_RADIATE;

  /* If it is radiating, then it cannot be in either standby or
   * warming state.
   */
  if ((pState_a->RHeader.iRadarStatus & RTD_NIDS3_STATUS_RADIATE)&&
      (ASF_STANDBY & pRtqHeader->iAntStatusMask))
    {
      if (ASF_STANDBY & pRtqHeader->iAntStatus) 
	pState_a->RHeader.iRadarStatus &= ~RTD_NIDS3_STATUS_STANDBY;
      else
	pState_a->RHeader.iRadarStatus &= ~RTD_NIDS3_STATUS_WARMING;
    }

  /* Set the product modes */
  pState_a->RHeader.iModes |= RTD_NIDS3_MODE_PRECIP;
  if (pState_a->Channel.iResolution != 4)
    pState_a->RHeader.iModes |= RTD_NIDS3_MODE_8BIT;

  /* Range bin spacing */
  iRangeBinSpacingCm = pRtqHeader->ibin_step*pState_a->iSkipRate;
  pState_a->RHeader.iBinSpacing = (UINT2)((iRangeBinSpacingCm+5)/10);

  iNyquistV = NINT( 3.0*fNyquistVelocity( pRtqHeader->iprf, pRtqHeader->itrig, 
					  pRtqHeader->ilambda, pRtqHeader->ipolar));
  iNyquistW = NINT( 3.0*fNyquistWidth( pRtqHeader->iprf, pRtqHeader->ilambda,
				   pRtqHeader->ipolar ));

  pState_a->RHeader.iNyquists  = 0xff & iNyquistV;
  pState_a->RHeader.iNyquists |= (iNyquistW<<8);
  FillDataConvert( &pState_a->Convert, pRtqHeader->iprf, pRtqHeader->itrig,
		   pRtqHeader->ilambda, pRtqHeader->ipolar,
		   CONVERT_FLAGS_DEFAULT );

  /* Fill in the data resolution, if any */
  if (pState_a->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;

  /* Only test bits we are looking at */
  LastHeader.iAntStatus &= (ASF_SERVO|ASF_TRPOWER|ASF_RADIATE|ASF_STANDBY);
  ThisHeader.iAntStatus &= (ASF_SERVO|ASF_TRPOWER|ASF_RADIATE|ASF_STANDBY);

  LastHeader.iAntStatusMask = 0;  ThisHeader.iAntStatusMask = 0; /* Unused */
  LastHeader.ipad_69x1 = 0;       ThisHeader.ipad_69x1 = 0;   /* Unused */

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

}

/* ===========================================================================
 * This subroutine computes the color map and the color parameter scale
 * text from the product header.
 */
static void ComputeColormap
(UINT2 idtype_a,                    /* Data type */
 const struct rtq_volume_header *pRtqHeader_a,
 union level_control *plevel_a,     /* Filled in so we can levelize */
 const struct convert_seams *pSeams_a )
{
  struct data_convert Convert;
  struct color_scale_def ScaleDef;
  struct scale_text NewScale[16];  /* Label text is here */
  SINT4 iColor;
  UINT2 iColorScale[COLOR_MAP_ZOOM][16];
  UINT1 iColorNoData, iColorThresh;
  
  /* ----------------------------------------------------------------------- */
  
  FillDataConvert( &Convert, pRtqHeader_a->iprf, pRtqHeader_a->itrig, pRtqHeader_a->ilambda, 
		   pRtqHeader_a->ipolar, CONVERT_FLAGS_DEFAULT ) ;
  
  memset( &ScaleDef, 0, sizeof(ScaleDef) );
  ScaleDef.iflags  = COLOR_SCALE_TOP_SAT;
  ScaleDef.istart  = NINT( 10.0*pSeams_a->fStart);
  ScaleDef.istep   = NINT( 10.0*pSeams_a->fStep);
  ScaleDef.icolcnt = 15;

  /* Color scale is replicated because we are not using zoomed scale */
  for (iColor=0; iColor<16; iColor++)
    {
      iColorScale[0][iColor] = MIN( iColor+1, 15 );
      iColorScale[1][iColor] = MIN( iColor+1, 15 );
    }
  iColorNoData = 0;
  iColorThresh = 0;
  generic_map( idtype_a, &ScaleDef, iColorScale,
	       iColorThresh, iColorNoData, plevel_a, &Convert, NewScale );

}

/* ==============================================================
 * Function to convert a ray from RTQ format to RTD NIDS3 format.  Returns
 * the number of bytes in the ray.
 */
static UINT2 RayFromElementNids3
( struct rtd_nids3_ray_header  *pRay_a,
  const struct rtd_state *pState_a,
  const struct RtqQueueElement *pElement_a )
{
  int iMoment;
  UINT2 iSize;  /* Size is in bytes */
  const UINT1 *pInRay;

  iSize = 0;

  /* Copy default header in first */
  *pRay_a = pState_a->RHeader;

  for (iMoment=0; iMoment<pState_a->iMomentCount; iMoment++)
    {
      pInRay = pElement_a->iData[pState_a->irtd_index[iMoment]];
      if (16==pState_a->iInDataRes)
	convert_raydata16( &pRay_a->iData[iSize/2], (UINT2*)pInRay, pState_a,
			   pElement_a->RHeader.iBinCount, &iSize, 
			   pState_a->iInData[iMoment] );
      else
	convert_raydata8 ( &pRay_a->iData[iSize/2], pInRay, pState_a,
			   pElement_a->RHeader.iBinCount, &iSize );
      
    }
  RayHeaderFromElementNids3( pRay_a, &(pElement_a->RHeader), &iSize );
  return iSize;
}
/* ====================================================
 * Convert just the ray data
 */
static void convert_raydata8
(UINT2 *prtray_data_a,       /* Output data */
 const UINT1 *pray_data_a,   /* Input data, IRIS 8-bit format */
 const struct rtd_state *pState_a,
 UINT4 ibins_a,              /* Actual number of bins (possibly truncated) */
 UINT2 *pisize_a )           /* Increment for size used (in bytes) */
{
  UINT4 iCount;
  UINT4 iWordCount, iBinCount;
  UINT2 *pRtrayData, iDataWord;

  iBinCount  = ibins_a/pState_a->iSkipRate;
  pRtrayData = prtray_data_a;
  iCount     = 0;

  if (pState_a->Channel.iResolution == 4)
    {
      iWordCount = (iBinCount+3)/4;

      while( iCount < iBinCount )
	{
	  SINT4 iRemainder = iCount%4;
	  iDataWord = pState_a->Level.level1.imap[*pray_data_a];

	  switch (iRemainder)
	    {
	    case 0:
	      *pRtrayData   = iDataWord<<12;
	      pray_data_a  += pState_a->iSkipRate;
	      break;
	    case 1:
	      *pRtrayData  |= iDataWord<<8;
	      pray_data_a  += pState_a->iSkipRate;
	      break;
	    case 2:
	      *pRtrayData  |= iDataWord<<4;
	      pray_data_a  += pState_a->iSkipRate;
	      break;
	    case 3:
	      *pRtrayData++|= iDataWord;
	      pray_data_a  += pState_a->iSkipRate;
	      break;
	    }
	  iCount++;
	}
    }
  else
    {
      iWordCount = (iBinCount+1)/2;
      
      while( iCount < iBinCount )
	{
	  SINT4 iRemainder = iCount%2;
	  iDataWord = (UINT2)*pray_data_a;
	  switch (iRemainder)
	    {
	    case 0:
	      *pRtrayData   = iDataWord<<8;
	      pray_data_a  += pState_a->iSkipRate;
	      break;
	    case 1:
	      *pRtrayData++|= iDataWord;
	      pray_data_a  += pState_a->iSkipRate;
	      break;
	    }
	  iCount++;
	}
    }

  *pisize_a += 2*iWordCount;
}
/* ====================================================
 * Convert just the ray data
 */
static void convert_raydata16
(UINT2 *prtray_data_a,       /* Output data for XMT to RTD */
 const UINT2 *pray_data_a,   /* Input data */
 const struct rtd_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 iFullDataCount;
  UINT1 iBuffer[RTQ_MAX_BINS];
  iFullDataCount = ibins_a;
  ConvertDataFrom16( iInData_a, iFullDataCount, pray_data_a, iBuffer,
			 &(pState_a->Convert) );

  convert_raydata8( prtray_data_a, iBuffer, pState_a, ibins_a, pisize_a );

}

/* ====================================================
 * Convert just the ray header.  Most is already filled in, just
 * add the AZ and EL.
 */
static void RayHeaderFromElementNids3
(struct rtd_nids3_ray_header *pRtdHead_a,
 const struct rtq_ray_header *pRtqHead_a,   /* Input ray header */
 UINT2 *pisize_a )                   /* Increment for size used */
{
  FLT4 fAngle;
  UINT4 iMs;
  time_t iTime;
  MESSAGE iStatus;

#if 0
  printf( "Az: %5.2f, El: %5.2f, bins: %d, time: %d\n",
	  fPDegFromBin2(pray->iaz_start), fElDegFromBin2(pray->iel_start),
	  pray->ibincount, pray->itime );
#endif

  fAngle = fPDegFromBin2( iBin2NarrowMidPt( pRtqHead_a->azimuth_start,pRtqHead_a->azimuth ));
  pRtdHead_a->iAzimuth = NINT(100.0*fAngle);

  fAngle = fPDegFromBin2( iBin2NarrowMidPt( pRtqHead_a->elevation_start,pRtqHead_a->elevation ));
  pRtdHead_a->iElevation = NINT(100.0*fAngle);

  /* Convert absolute time in ms */
  iTime = time_tFromYmds( &pRtqHead_a->Ymds, &iStatus );
  iMs = (1000 * (UINT4)iTime) + MS_FROM_MILLS( pRtqHead_a->Ymds.imills );
  pRtdHead_a->iTime = (UINT2)iMs;

  *pisize_a += (sizeof( struct rtd_nids3_ray_header)-2 );

}

/* ============================================================
 * Function to load the convert setup from a configuration file.
 */
static void LoadSetup
( struct convert_config *pCSetup_a )
{
  char sPath[PATHNAME_SIZE];
  char sline[81], *sptr;
  FILE* stream;
  SINT4 iline, iChannel;
  UINT2 lerror;

  /* Initialize the data to the default values */
  memset( pCSetup_a, 0, sizeof(*pCSetup_a) );

  /* Defaults */
  pCSetup_a->lLogToFile=TRUE;  
  pCSetup_a->lLogToTerm=TRUE;
  pCSetup_a->TSeams.fStart =   0.0;
  pCSetup_a->TSeams.fStep  =   4.0;
  pCSetup_a->ZSeams.fStart =   0.0;
  pCSetup_a->ZSeams.fStep  =   4.0;
  pCSetup_a->VSeams.fStart = -35.0;
  pCSetup_a->VSeams.fStep  =   5.0;
  pCSetup_a->WSeams.fStart =   0.0;
  pCSetup_a->WSeams.fStep  =   0.5;
  for (iChannel=0; iChannel<RTD_CHANNEL_COUNT; iChannel++)
    {
      pCSetup_a->Channel[iChannel].iResolution = 8;
      pCSetup_a->Channel[iChannel].lZData      = TRUE;
    }
  
  /* Generate the file name and open the file */
  sptr = getenv( "IRIS_CONFIG" );
  if (NULL == sptr) 
    {
      sig_signal_va( USER_NO_ENV, 1, "IRIS_CONFIG" );
      exit(EXIT_FAILURE);
    }

  strncpy( sPath, sptr, 140 );
  strcat( sPath, "rtd_nids3_xmt.conf" );
  stream = fopen( sPath, "r" );

  /* Don't worry about a missing config file */
  if (NULL == stream) return;

  /* now loop through the file reading and parsing lines */
  iline = 1;
  sptr = fgets( sline, 81, stream );
  while (NULL != sptr)
    {
      lerror = ProcessConfigLine( sptr, pCSetup_a );
      if (lerror) fprintf( stderr, "Syntax error on config file line %d\n", iline );
      iline +=1;
      sptr = fgets( sline, 81, stream );
    }

  fclose( stream );

  return;
}

/* ===============================================================
 * Process one line from the configuration file.  Returns nonzero if there
 * is a syntax error.
 *
 * The config can contain the following commands.  All blank lines and
 * lines beginning with "#" are ignored.  Commands can be in any order.
 * Commands take effect in the order received.  For example, syntax errors
 * before the LOG command will not be logged.
 *
 */
static UINT2 ProcessConfigLine
(char *sline_a,
 struct convert_config *pConfig_a )
{
  char *stokens[9];
  UINT4 icount, ioffset;
  
  ioffset = 0;
  icount  = 0;
  /* Process up to 9 tokens, stop when the line is exhausted. */
  while (icount<9) 
    {
      stokens[icount] = sigtok( sline_a, &ioffset );
      if (NULL == stokens[icount])
	break;
      else
	icount++;
    }
  
  /* Blank lines are allowed and skipped */
  if (icount == 0) return FALSE;
  
  /* Skip commented lines */
  if (stokens[0][0] == '#') return FALSE;

  if ( 0 == strcmp( "APPEND_TO_LOG", stokens[0] ) )
    {
      pConfig_a->lAppendToLog=TRUE;
      return FALSE;
    }

  /* See if the line contains the LOG file command */
  if ( 0 == strcmp( "LOG", stokens[0] ) )
    {
      if ( icount < 2 ) return TRUE;
      
      if      ( 0 == strcmp( "BOTH", stokens[1] ) ) 
	{pConfig_a->lLogToFile=TRUE;  pConfig_a->lLogToTerm=TRUE;}
      else if ( 0 == strcmp( "FILE", stokens[1] ) ) 
	{pConfig_a->lLogToFile=TRUE;  pConfig_a->lLogToTerm=FALSE;}
      else if ( 0 == strcmp( "TERM", stokens[1] ) )
	{pConfig_a->lLogToFile=FALSE; pConfig_a->lLogToTerm=TRUE;}
      else if ( 0 == strcmp( "NONE", stokens[1] ) )
	{pConfig_a->lLogToFile=FALSE; pConfig_a->lLogToTerm=FALSE;}
      else
	return TRUE;
      return FALSE;
    }

  if ( 0 == strcmp( "SEAMS", stokens[0] ) )
    {
      struct convert_seams *pOneSeam;
      char *sptr;
      if ( icount < 4 ) return TRUE;

      switch( stokens[1][0] )
	{
	case 'T':
	  pOneSeam = &pConfig_a->TSeams;
	  break;
	case 'Z':
	  pOneSeam = &pConfig_a->ZSeams;
	  break;
	case 'V':
	  pOneSeam = &pConfig_a->VSeams;
	  break;
	case 'W':
	  pOneSeam = &pConfig_a->WSeams;
	  break;
	default:
	  return TRUE;
	  /* break; */
	}

      pOneSeam->fStart = strtod( stokens[2], &sptr );
      if (sptr == stokens[2]) return TRUE;

      pOneSeam->fStep  = strtod( stokens[3], &sptr );
      if (sptr == stokens[3]) return TRUE;

      return FALSE;
    }

  /* See if the line contains a CHANNEL command */
  /* Format is something like: CHANNEL  1  8  Z,V */
  if ( 0 == strcmp( "CHANNEL", stokens[0] ) )
    {
      int iChannel;
      struct convert_channel_list *pOneChannel;
      char *sptr;
      if ( icount < 4 ) return TRUE;

      iChannel = strtol( stokens[1], &sptr, 10 );
      if (sptr == stokens[1]) return TRUE;
      if ((iChannel<1)||(iChannel>6)) return TRUE;

      pOneChannel = &(pConfig_a->Channel[iChannel-1]);

      pOneChannel->iResolution = strtol( stokens[2], &sptr, 10 );
      if (sptr == stokens[2]) return TRUE;
      if ((pOneChannel->iResolution!=4)&&(pOneChannel->iResolution!=8)) return TRUE;

      if (ParseDataList( pOneChannel, stokens[3] )) return TRUE;

      return FALSE;
    }
  
  if ( 0 == strcmp( "VERBOSE", stokens[0] ) )
    {
      pConfig_a->lVerbose = TRUE;
      return FALSE;
    }
  
  /* Unrecognized lines will generate a syntax error */
  return TRUE;
}

/* ===================================================
 * Parse the list of data types in the token.  Return
 * TRUE on a syntax error.
 */
static UINT1 ParseDataList
(struct convert_channel_list *pOneChannel_a,
 char *sString_a )
{
  char *sToken, *sState;
  sToken = strtok_r( sString_a, ",", &sState );

  /* First erase the default configuration */
  pOneChannel_a->lTData=FALSE;
  pOneChannel_a->lZData=FALSE;
  pOneChannel_a->lVData=FALSE;
  pOneChannel_a->lWData=FALSE;

  if (NULL == sToken) return TRUE;  /* Must be at least one token */
  while( sToken )
    {
      switch( sToken[0] )
	{
	case '*':
	  pOneChannel_a->lTData=TRUE; 
	  pOneChannel_a->lZData=TRUE; 
	  pOneChannel_a->lVData=TRUE; 
	  pOneChannel_a->lWData=TRUE; break; 
	case 'T':
	  pOneChannel_a->lTData=TRUE; break;
	case 'Z':
	  pOneChannel_a->lZData=TRUE; break;
	case 'V':
	  pOneChannel_a->lVData=TRUE; break;
	case 'W':
	  pOneChannel_a->lWData=TRUE; break;
	default:
	  return TRUE;      /* Unknown letter is error */
	  break;
	}
      sToken = strtok_r( NULL, ",", &sState );
    }
  return FALSE;

}

/* ===========================================
 * 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 (!(CSetup_c.lLogToTerm||CSetup_c.lLogToFile)) return;

  GetTimeYmds( &Ymds, TIME_FLG_LOCAL );

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

  if (CSetup_c.lLogToTerm)
    {
      fprintf( stderr, "%s %s.%03d: ", sDateBuf, sTimeBuf, MS_FROM_MILLS(Ymds.imills) );
      fputs( sString_a, stderr );
    }

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

  return;
}
