/* *********************************************************
 * *                                                       *
 * *  Function to convert an IRIS product to ADIDS format  *
 * *                                                       *
 * *********************************************************
 * File: utils/adids/IrisToAdids.C
 *
 *                   COPYRIGHT (c) 2003, 2004  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.
 */

/* This program is a pipe which takes an IRIS product in the standard input
 * and writes a modified product out the standard output.
 *
 * Note if you are modifying this program, and wish to insert a diagnostic
 * printf, be sure to write it to the error output.
 */

#include <errno.h>
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#define INCLUDE_TERMIOS_PROTOTYPES 1

#include "sigtypes.h"
#include "projection.h"
#include "setup.h"
#include "headers.h"
#include "product.h"
#include "user_lib.h"
#include "dsp_lib.h"
#include "misc_lib.h"

#include "SigAdids.h"

/* Information from the setup file useful in the conversion */
struct convert_config CConfig_c;

/* Information from the product useful in the conversion */
struct convert_info
{
  union level_control Level;
  const struct convert_product_table *pProduct;
};

/* Information from the command line */
struct private_scale_def
{
  UINT4 iflags;
  SINT2 icolcnt;
  UINT2 ilevel_seams[16];
};

/* Local function prototypes
 */
static void ProcessCommandLine
(int iArgc, 
 char *sArgv[],
 struct private_scale_def *pScaleDef_a );

static void Usage( void );

static MESSAGE CheckProductHeader
(const struct product_hdr *pheader_a,
 struct convert_info *pConvert_a );

static const struct convert_product_table * FindProduct
( const struct product_hdr *pPHead_a,    /* IRIS product header */
  const struct convert_config *pCConfig_a ); /* Configuration */

static void ConvertProduct
(const struct cartesian_product *pProd_a,
 struct convert_info *pConvert_a );

static void ConvertProductData
( const struct cartesian_product *pProd_a,
  const struct convert_info *pConvert_a );

static void RunLengthLine
( SINT4 iYPos_a,           /* Y-component position */
  const UINT1 *pBuffer_a,  /* Buffer of data */  
  SINT4 iXSize_a,          /* Number of pixels */
  const struct convert_info *pConvert_a );

static void ComputeColormap
(UINT2 idtype_a,                    /* Data type */
 const struct product_end *pend_a,
 union level_control *plevel_a,     /* Filled in so we can levelize */
 struct scale_text newscale_a[16],  /* Label text is here */
 const struct private_scale_def *pPrivateScale_a );

static void WriteSOP
( const struct convert_info *pConvert_a );

static void WriteEOP
( const struct convert_info *pConvert_a );

static void WriteOneRecord
( const struct adids_single_line *pBuffer_a,
  SINT4 iSize_a );

static MESSAGE SignalHandler
(MESSAGE iStatus_a,
 UINT4 iCount_a,
 const UINT4 iSigArgs_a[] );

/* ===============================================================
 * Main program starts here.
 */
int main(int iArgc, char *sArgv[])
{
  struct product_hdr ProdHeader;
  struct scale_text Scale[17];  /* Label text is here (unused) */
  struct private_scale_def ScaleDef;
  struct cartesian_product *pInProd;
  MESSAGE iStatus;
  SINT4 iCount, iWantCount ;
  char sLog[200], sTimeBuf[TIMENAME_SIZE];
  struct convert_info Convert;
  struct ttydev Tty;

  setlocale( LC_ALL, "" ) ;

  /* Process the command line arguments */
  ProcessCommandLine( iArgc, sArgv, &ScaleDef );

  /* Process the setup file, if any */
  LoadSetup( &CConfig_c, "IrisToAdids.conf", 0 );

  /* Open the logging file, if any */
  if (CConfig_c.lLogToFile)
    {
      char *sptr;
      char sLogPath[PATHNAME_SIZE];
      sptr = getenv( "IRIS_LOG" );
      if (sptr)
	{
	  strncpy( sLogPath, sptr, PATHNAME_SIZE-20 );
	  sLogPath[PATHNAME_SIZE-20] = 0;
	}
      else
	strcpy( sLogPath, "/tmp/" );
      strcat( sLogPath, "IrisToAdids.log" );
      if (CConfig_c.lAppendToLog)
	CConfig_c.iLogFile = fopen( sLogPath, "a" );
      else
	CConfig_c.iLogFile = fopen( sLogPath, "w" );
      if (NULL == CConfig_c.iLogFile)
	{
	  fprintf( stderr, "Can not open log file: '%s'\n", sLogPath );
	  exit(EXIT_FAILURE);
	}
    }

  /* Starting here we can use the Lprintf function */
  sig_establish( SignalHandler, "IrisToAdids" );
    
  /* Check for no sites in the config file */
  if (CConfig_c.iProductCount<1)
    {
      Lprintf( &CConfig_c, "ERROR: No PRODUCT line in the IrisToAdids.conf file\n" );
      exit(EXIT_FAILURE);
    }

  /* Read in the header */
  iCount =  fread( &ProdHeader, 1, sizeof(ProdHeader), stdin );
  if (iCount != sizeof(ProdHeader)) 
    {
      sprintf( sLog, "ERROR: Bad read, got%d, wanted%d\n", iCount, sizeof(ProdHeader));
      Lprintf( &CConfig_c, sLog );
      exit(EXIT_FAILURE);
    }

  iStatus = CheckProductHeader( &ProdHeader, &Convert );
  if (iStatus != SS_NORMAL) exit(EXIT_FAILURE);

  /* Malloc input buffer */
  pInProd  = (struct cartesian_product*)malloc( ProdHeader.hdr.ibytes );
  if (!pInProd)
    {
      sig_signal(errno_to_message(errno));
      exit(EXIT_FAILURE);
    }
  pInProd->Header = ProdHeader;

  iWantCount = ProdHeader.hdr.ibytes-sizeof(ProdHeader);
  iCount =  fread( (&pInProd->Header)+1, 1, iWantCount, stdin );
  if (iCount != iWantCount)
    {
      sprintf( sLog, "ERROR: Bad read2, got%d, wanted%d\n", iCount, iWantCount);
      Lprintf( &CConfig_c, sLog );
      exit(EXIT_FAILURE);
    }
  
  /* Force saturation at the top, threshold if below the bottom.
   */
  ScaleDef.iflags |= COLOR_SCALE_TOP_SAT;

  /* Compute color mapping tables */
  ComputeColormap( pInProd->Header.pcf.idata_out, &pInProd->Header.end, 
		   &(Convert.Level), Scale, &ScaleDef );

  /* Open output device, if applicable */
  if (CConfig_c.iOutputFormat==OUTPUT_SERIAL)
    {
      iStatus = openTTYDev( &Tty, &CConfig_c.SerialSpec );
      if (iStatus!=SS_NORMAL)
	{
	  sig_signal( iStatus );
	  exit(EXIT_FAILURE);
	}
      CConfig_c.pOutFile = Tty.Tx ;
    }
  else
    {
      CConfig_c.pOutFile = stdout;
    }

  ConvertProduct( pInProd, &Convert );

  shhmmssddmonyyyy_r(&ProdHeader.pcf.ing_time, sTimeBuf);
  sprintf( sLog, "Converted %s product: %s, Date: %s, CommandCode:%d\n", 
	   sproduct_name5(ProdHeader.pcf.iptype), nullify(ProdHeader.pcf.spname,12), 
	   sTimeBuf, Convert.pProduct->iCommandCode );
  Lprintf( &CConfig_c, sLog );

  /* Close the output device, if applicable */
  if (CConfig_c.iOutputFormat==OUTPUT_SERIAL)
    {
      iStatus = closeTTYDev( &Tty );
      if (iStatus!=SS_NORMAL) sig_signal( iStatus );
    }
  
  exit( EXIT_SUCCESS );
}

/* ======================================================================
 * Function to process the command line arguments.
 */
static void ProcessCommandLine
(int iArgc_a, 
 char *sArgv_a[],
 struct private_scale_def *pScaleDef_a )
{

  int iArg, iIndex;

  /* Zero the scale, and fill in some default numbers */
  memset( pScaleDef_a, 0, sizeof(pScaleDef_a) );
  pScaleDef_a->icolcnt = 14;
  /* Default scale is a reasonable scale for dBZ */
  for (iIndex=0; iIndex<16; iIndex++)
    {
      pScaleDef_a->ilevel_seams[iIndex] = 32767 + (500*iIndex) - 1000 ;
    }
  pScaleDef_a->iflags = COLOR_SCALE_VARIABLE;
  /* Note that the top and bottom saturation flags are picked up from the file
   * pScaleDef_a->iflags = COLOR_SCALE_VARIABLE|COLOR_SCALE_TOP_SAT|COLOR_SCALE_BOT_SAT;
   */

  /* Parse command line options
   */
  for( iArg=1 ; iArg<iArgc_a ; iArg++ )
    {
      const char *sArg = sArgv_a[iArg];

      /* fprintf( stderr, "Processing %d: '%s'\n", iArg, sArg ); */
      if (0 == strncmp( sArg, "-seams", 6 ) )
	{
	  char sString[110], *sSubArg;
	  int iValue;

	  strncpy( sString, &sArg[6], 109); sString[109]=0;

	  for (iIndex=0; iIndex<16; iIndex++)
	    {
	      if (iIndex==0)
		sSubArg = strtok( sString, ":" );
	      else
		sSubArg = strtok( NULL, ":" );
	      if (sSubArg==NULL) Usage();
	      if( sscanf( sSubArg, "%d", &iValue ) != 1 )
		{
		  fprintf( stderr, "Could not parse '%s' after '%s'\n", sSubArg, sArg ) ;
		  Usage() ;
		}
	      pScaleDef_a->ilevel_seams[iIndex] = iValue;	      
	    }
	}

      else if (0 == strncmp( sArg, "-colors", 7) )
	{
	  char sString[10], *sSubArg;
	  int iValue;
	  strncpy( sString, &sArg[7], 9); sString[9]=0;
	  sSubArg = strtok( sString, ":" );
	  if (sSubArg==NULL) Usage();
	  if( sscanf( sSubArg, "%d", &iValue ) != 1 )
	    {
	      fprintf( stderr, "Could not parse '%s' after '%s'\n", sSubArg, sArg ) ;
	      Usage() ;
	    }
	  pScaleDef_a->icolcnt = MAX( 2, MIN( 14, iValue));	      
	}

      else if (0 == strncmp( sArg, "-h", 2))
	{
	  Usage();
	}
      
    }
  return;
}

/* ==================================================
 */
static void Usage( void )
{
  fprintf( stderr, "Options: -levels:N1:N2:N3:...:N16\n" );
  fprintf( stderr, "         -colors:N\n" );
  fprintf( stderr, "         -help\n" );
  fprintf( stderr, "         (all other options ignored)\n" );
  exit(EXIT_FAILURE) ;
}
/* =======================================================================
 * Check for some consistency. 
 */
static MESSAGE CheckProductHeader
(const struct product_hdr *pheader_a,
 struct convert_info *pConvert_a )
{
  char sLog[200];

  memset( pConvert_a, 0, sizeof(*pConvert_a) );

  /* Make sure that this file really looks like a product.  Check
   * the ID in the product.
   */
  if (pheader_a->hdr.id != ST_PRODUCT_HDR)
    {
      Lprintf( &CConfig_c, "ERROR: File header contains invalid ID.\n" ) ;
      return USER_ERROR_SIG;
    }

  /* Check that the product type is valid */
  if (!lProductCanBeDisplayed(pheader_a->pcf.iptype))
    {
      sprintf( sLog, "ERROR: Product type %s (%d) not supported.\n", 
	       sproduct_name5(pheader_a->pcf.iptype), pheader_a->pcf.iptype );
      Lprintf( &CConfig_c, sLog );
      return USER_ERROR_SIG;
    }

  /* Check that the size is less than 240x324 pixels */
  if (( pheader_a->pcf.ixsize>ADIDS_MAX_WIDTH)||
      (pheader_a->pcf.iysize>ADIDS_MAX_HEIGHT))
    {
      sprintf( sLog, "ERROR: Product size of %dx%d pixels exceeds max of %dx%d.\n", 
	       pheader_a->pcf.ixsize, pheader_a->pcf.iysize, ADIDS_MAX_WIDTH, 
	       ADIDS_MAX_HEIGHT );
      Lprintf( &CConfig_c, sLog );
      return USER_ERROR_SIG;
    }

  /* Check to see if we can find the product in the product mapping table.  Note
   * that wildcards are allowed.
   */
  pConvert_a->pProduct = FindProduct( pheader_a, &CConfig_c );
  if (!pConvert_a->pProduct)
    {
      sprintf( sLog, "ERROR, IRIS %s product '%s' not found in config product list\n", 
	       sproduct_name5(pheader_a->pcf.iptype), nullify(pheader_a->pcf.spname,12) );
      Lprintf( &CConfig_c, sLog );
      return USER_ERROR_SIG;
    }

  /* Compute the scaling factor.  This is basically the log base 2
   * of the scale in nautical miles per pixel.
   */
  if (pheader_a->pcf.ixscale<1)
    {
      sprintf( sLog, "ERROR, product has invalid scale\n" );
      Lprintf( &CConfig_c, sLog );
      return USER_ERROR_SIG;
    }

  return SS_NORMAL;
}
/* =====================================================
 * Search to find the product in the mapping table.
 */
static const struct convert_product_table * FindProduct
( const struct product_hdr *pPHead_a,    /* IRIS product header */
  const struct convert_config *pCConfig_a ) /* Configuration */
{
  SINT4 iProduct;
  char sPNameNull[16], sSiteNameNull[20];
  nullify_ncpy( sPNameNull, pPHead_a->pcf.spname, 12 );
  nullify_ncpy( sSiteNameNull, pPHead_a->end.sing_sitename, 16 );
  for (iProduct=0; iProduct<pCConfig_a->iProductCount; iProduct++)
    {
      const struct convert_product_table *pProduct;
      pProduct = &(pCConfig_a->Products[iProduct]);
      if (pProduct->iPType==pPHead_a->pcf.iptype)
	{
	  if (sig_fnmatch( pProduct->sSiteName16, sSiteNameNull ))
	    {
	      if (sig_fnmatch( pProduct->sPName, sPNameNull ))
		{
		  return pProduct;
		}
	    }
	}
    }
  return NULL;
}

/* ==============================================================
 * Convert product to ADIDS.  Entered with a pointer to the beginning 
 * of the full product.  Should exit if it gets an error.
 */
static void ConvertProduct
(const struct cartesian_product *pProd_a,
 struct convert_info *pConvert_a )
{
  /* Write the Start of Picture */
  WriteSOP( pConvert_a );

  /* Write the data */
  ConvertProductData( pProd_a, pConvert_a );

  /* Write the End of Picture */
  WriteEOP( pConvert_a );

  return;
}

/* ================================================================
 * Convert the product data section.
 */
static void ConvertProductData
( const struct cartesian_product *pProd_a,
  const struct convert_info *pConvert_a )
{
  int iLine;
  UINT1 *pLevelBuffer;
  SINT4 iRes, iSize;
  const UINT1 *pInBuffer;
  const struct product_hdr *pPHeader;

  pInBuffer = pProd_a->Data;
  pPHeader = &(pProd_a->Header);
  iRes = idata_resolution( pPHeader->pcf.idata_out );

  /* Malloc the buffer to hold the levelized data */
  iSize=pPHeader->pcf.ixsize*pPHeader->pcf.iysize;
  pLevelBuffer = (UINT1*)malloc(iSize);
  if (!pLevelBuffer)
    {
      sig_signal(errno_to_message(errno));
      exit(EXIT_FAILURE);
    }
  levelize_8bit( pInBuffer, iSize, &(pConvert_a->Level), iRes, pLevelBuffer );

  /* Search through the whole array, converting to vectors and
   * accumulating the vectors in groups of each level.
   */

  /* Go from the top down */
  for (iLine=pPHeader->pcf.iysize-1; iLine>=0; iLine--)
    {
      const UINT1 *pLevelLine=pLevelBuffer+(iLine*pPHeader->pcf.ixsize);
      RunLengthLine( iLine, pLevelLine, pPHeader->pcf.ixsize, pConvert_a );
    }

  free(pLevelBuffer);
}

/* =========================================================================
 * Convert a line of 4-bit data into run length coding.
 */
static void RunLengthLine
( SINT4 iYPos_a,           /* Y-component position */
  const UINT1 *pBuffer_a,  /* Buffer of data */  
  SINT4 iXSize_a,          /* Number of pixels */
  const struct convert_info *pConvert_a )
{
  SINT4 iStartXPos, iXPos, iRunLength, iOutPos;
  UINT1 iLevel;
  struct adids_single_line Buffer;
  UINT2 iActionWord;

  /* Run length code the data */
  iStartXPos=0; iOutPos=0;
  iLevel = 0x0f&pBuffer_a[iStartXPos];
  for (iXPos=1; iXPos<iXSize_a; iXPos++)
    {
      iRunLength=iXPos-iStartXPos;
      /* Look for value changes, or maximum run length */
      if ((iLevel!= (0x0f&pBuffer_a[iXPos])) ||(iRunLength>=252))
      {
	/* Store the previous run */
	Buffer.Codes[iOutPos].iPixelValue=iLevel;
	Buffer.Codes[iOutPos++].iRepeats=iRunLength;
	
	/* Start the new run */
	iStartXPos = iXPos;
	iLevel = 0x0f&pBuffer_a[iStartXPos];
      }
    }
  /* Store the last run */
  iRunLength=iXSize_a-iStartXPos;
  Buffer.Codes[iOutPos].iPixelValue=iLevel;
  Buffer.Codes[iOutPos++].iRepeats=iRunLength;
  
  /* Add the header */
  /* sync word */
  Buffer.iSyncLow  = 0xfd;
  Buffer.iSyncHigh = 0xfe;

  /* Action word */
  iActionWord = (iOutPos+1)<<6;
  iActionWord |= pConvert_a->pProduct->iCommandCode;
  Buffer.iActionLow  = 0xff&iActionWord;
  Buffer.iActionHigh = iActionWord>>8;

  /* Line number */
  Buffer.iLineLow  = 0xff&iYPos_a;
  Buffer.iLineHigh = iYPos_a>>8;

  /* Write data out */
  WriteOneRecord( &Buffer, 2*(3+iOutPos) );

  return;
}

/* ===========================================================================
 * 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 product_end *pend_a,
 union level_control *plevel_a,     /* Filled in so we can levelize */
 struct scale_text newscale_a[16],  /* Label text is here */
 const struct private_scale_def *pPrivateScale_a )
{
  struct data_convert Convert;
  struct color_scale_def ScaleDef;
  SINT4 iColor;
  UINT2 iColorScale[COLOR_MAP_ZOOM][16];
  UINT1 iColorNoData, iColorThresh;

  /* ----------------------------------------------------------------------- */

  FillDataConvert( &Convert, pend_a->iprf, pend_a->itrig, pend_a->ilambda, 
		   pend_a->ipolar, CONVERT_FLAGS_DEFAULT ) ;
  
  memset( &ScaleDef, 0, sizeof(ScaleDef) );
  ScaleDef.iflags  = COLOR_SCALE_VARIABLE | pPrivateScale_a->iflags;
  ScaleDef.icolcnt = pPrivateScale_a->icolcnt;

  /* Color scale is replicated because we are not using zoomed scale */
  for (iColor=0; iColor<16; iColor++)
    {
      ScaleDef.ilevel_seams[iColor] =  pPrivateScale_a->ilevel_seams[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_a, NULL );

}

/* ============================================
 * Write the SOP Record
 */
static void WriteSOP
( const struct convert_info *pConvert_a )
{
  struct adids_single_line Buffer;
  UINT2 iActionWord;

  /* Sync word */
  Buffer.iSyncLow  = 0xfd;
  Buffer.iSyncHigh = 0xfe;

  /* Action word */
  iActionWord  = 1<<6;
  iActionWord |= pConvert_a->pProduct->iCommandCode;
  Buffer.iActionLow  = 0xff&iActionWord;
  Buffer.iActionHigh = iActionWord>>8;

  /* Message Type */
  Buffer.iLineLow  = 0;
  Buffer.iLineHigh = 0x81;

  /* Write data out */
  WriteOneRecord( &Buffer, 6 );

  return;
}

/* ============================================
 * Write the EOP Record
 */
static void WriteEOP
( const struct convert_info *pConvert_a )
{
  struct adids_single_line Buffer;
  UINT2 iActionWord;

  /* Sync word */
  Buffer.iSyncLow  = 0xfd;
  Buffer.iSyncHigh = 0xfe;

  /* Action word */
  iActionWord  = 1<<6;
  iActionWord |= pConvert_a->pProduct->iCommandCode;
  Buffer.iActionLow  = 0xff&iActionWord;
  Buffer.iActionHigh = iActionWord>>8;

  /* Message Type */
  Buffer.iLineLow  = 0;
  Buffer.iLineHigh = 0x8f;

  /* Write data out */
  WriteOneRecord( &Buffer, 6 );

  return;
}

/* ==================================================
 * Write one record out.
 */
static void WriteOneRecord
( const struct adids_single_line *Buffer_a,
  SINT4 iSize_a )
{
  int iError = fwrite( Buffer_a, 1, iSize_a, CConfig_c.pOutFile );
  if (iError!=iSize_a)
    {
      perror( "fwrite" );
      exit(EXIT_FAILURE);
    }
}

/* ======================================================================
 * Function to print signaled errors to the loging file or terminal.
 */
static MESSAGE SignalHandler
(MESSAGE iStatus_a,
 UINT4 iCount_a,
 const UINT4 iSigArgs_a[] )
{
  char sMessage[170];

  strncpy( sMessage, message_to_text( iStatus_a, iCount_a, iSigArgs_a ), 165 );
  sMessage[165]=0;
  strcat( sMessage, "\n" );

  Lprintf( &CConfig_c, sMessage );
  return(SS_NORMAL);
}
