/* ***********************************************************************
 * *                                                                     *
 * *   Function to correct an IRIS VIL product to average reflectivity   *
 * *                                                                     *
 * ***********************************************************************
 * File: utils/pipes_out/VilToVir.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.
 */

#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "sigtypes.h"
#include "headers.h"
#include "product.h"

#include "misc_lib.h"
#include "user_lib.h"


struct convert_pname_list
{
  char  sInputPName[16];     /* Input pname, null terminated, wildcards legal */
  char  sOutputPName[16];    /* output pname, null terminated */
};

/* This holds information to customize the conversion.  This
 * information comes both from the .conf file, and from looking
 * at the input product.
 */
#define MAX_CONVERT_PNAMES (40)
struct convert_config
{
  struct convert_pname_list PNames[MAX_CONVERT_PNAMES];
  struct convert_pname_list *pThisPName;
  struct data_convert Convert;
  FLT8  fThickness;
  FLT8  fZLConstant;
  FLT8  fZLExponent;
  SINT4 iPNameCount;
  SINT4 iPixelCount;
  UINT1 lLogToFile;
  UINT1 lLogToTerm;
  UINT1 lVerbose;              /* Log more details */
  FILE *iLogFile;              /* Open file descriptor to log file */
};

/* This holds information to customize the convertor */
struct command_line
{
  char  sInFile[FILENAME_SIZE];
  char  sInPath[PATHNAME_SIZE];
  char  sOutPath[PATHNAME_SIZE];
  int   iDevice;    /* Device number */
};

struct convert_config CSetup_c;

/* Local function prototypes
 */
static UINT1 CheckInputProduct
(const struct cartesian_product *pInputVil_a,
 SINT4 iInputVilSize_a );

static void Convert
(struct cartesian_product *pOutputVir,
 const struct cartesian_product *pInputVil );

static void ConvertHeader
( struct product_hdr *pOutputVirHeader_a,
  const struct product_hdr *pInputVilHeader_a );

static void ConvertData
( UINT1 *pOutputData_a,
  const UINT2 *pInputData_a );

static void ProcessCommandLine
(int iArgc_a, 
 char *sArgv_a[],
 struct command_line *pCommand_a );

static void Usage( void );

static void LoadSetup
( struct convert_config *pCSetup_a );

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

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

static void Lprintf
( const char *sString_a );

#define MAX_LINELENGTH (120)
/* ===============================================================
 * Main program starts here.
 */
int main(int iArgc, char *sArgv[])
{
  MESSAGE istatus;
  struct command_line Command;
  const struct cartesian_product *pInputVil;
  SINT4 iInputVilSize, iInputVilChannel;
  int iExitStatus;

  setlocale( LC_ALL, "" ) ;

  /* Process the command line arguments, if any */
  ProcessCommandLine( iArgc, sArgv, &Command );

  /* Process the setup file, if any */
  LoadSetup( &CSetup_c );

  /* Open the logging file, if any */
  if (CSetup_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, "VilToVir.log" );
      CSetup_c.iLogFile = fopen( sLogPath, "w" );
      if (NULL == CSetup_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, "VilToVir" );

  /* Open Input Rain1 */
  iExitStatus = EXIT_FAILURE;
  istatus = imapopen( Command.sInPath, FALSE, (void **)&pInputVil, &iInputVilSize,
		      &iInputVilChannel );
  if (istatus == SS_NORMAL)
    {
      UINT1 lOk = CheckInputProduct( pInputVil, iInputVilSize );
      if (lOk)
	{

	  struct cartesian_product *pOutputVir;
	  SINT4 iSize, iOutputVirSize, iOutputVirChannel;
	  
	  iSize = PRODUCT_HDR_SIZE + CSetup_c.iPixelCount;
	    
	  /* Open Output VIR */
	  istatus = imapcreate( Command.sOutPath, iSize, (void **)&pOutputVir,
				&iOutputVirSize, &iOutputVirChannel );
	  if (istatus == SS_NORMAL)
	    {
	      iExitStatus = EXIT_SUCCESS;
	      
	      /* Convert it */
	      Convert( pOutputVir, pInputVil );
	      /* Close the OutputVir */
	      istatus = imapclose( pOutputVir, iOutputVirSize, iOutputVirChannel );
	      if (istatus!=SS_NORMAL) sig_signal(istatus);
	    }
	  else
	    {
	      sig_signal( istatus );
	    }
	}

      if (CSetup_c.lVerbose)
	{
	  char sString[80], sTimeBuf[TIMENAME_SIZE];
	  sprintf( sString, "Converted VIL product: %s, date: %s\n", 
		   nullify( pInputVil->Header.pcf.spname, 12), 
		   shhmmddmonyyyy_r(&(pInputVil->Header.pcf.ing_time),sTimeBuf) );
	  Lprintf( sString );
	}
      
      /* Close the InputVil */
      istatus = imapclose( pInputVil, iInputVilSize, iInputVilChannel );
      if (istatus!=SS_NORMAL) sig_signal(istatus);
    }
  else
    {
      sig_signal( istatus );
    }

  /* Done */
  exit( iExitStatus );
}

/* =================================================
 * Check the supplied product to make sure it is OK.  Returns
 * TRUE only if it is OK.
 */
static UINT1 CheckInputProduct
(const struct cartesian_product *pInputVil_a,
 SINT4 iInputVilSize_a )
{
  char sMessage[160], sPName[16];
  SINT4 iRes, iPName, icalc_size, isrc_size[3];
  UINT1 lOk = TRUE;

  /* Check for bad header-IDs */
  if (pInputVil_a->Header.hdr.id != ST_PRODUCT_HDR ) 
    { 
      Lprintf( "ERROR: Bad product header ID.\n" );
      lOk=FALSE; 
    }

  if (pInputVil_a->Header.pcf.hdr.id != ST_PRODUCT_CONF )
    { 
      Lprintf( "ERROR: Bad product configuration header ID.\n" );
      lOk=FALSE; 
    }

  /* Check product type */
  if (pInputVil_a->Header.pcf.iptype != PROD_VIL)
    {
      sprintf( sMessage, "ERROR: Invalid product type %s (%d), must be VIL.\n",
	       sproduct_name5(pInputVil_a->Header.pcf.iptype),
	       pInputVil_a->Header.pcf.iptype );
      Lprintf( sMessage );
      lOk=FALSE;
    }

  /* Check data type */
  if (pInputVil_a->Header.pcf.idata_out != DB_VIL2)
    {
      sprintf( sMessage, "ERROR: Invalid data type %s (%d), must be VIL2.\n",
	       sdata_name6(pInputVil_a->Header.pcf.idata_out),
	       pInputVil_a->Header.pcf.idata_out );
      Lprintf( sMessage );
      lOk=FALSE;
    }


  /* Now check the product dimensions */
  isrc_size[0] = pInputVil_a->Header.pcf.ixsize;
  isrc_size[1] = pInputVil_a->Header.pcf.iysize;
  isrc_size[2] = pInputVil_a->Header.pcf.izsize;

  if( ( isrc_size[0] < MIN_PRODUCT_SIZE ) ||
      ( isrc_size[0] > MAX_PRODUCT_SIZE ) ||
      ( isrc_size[1] < MIN_PRODUCT_SIZE ) ||
      ( isrc_size[1] > MAX_PRODUCT_SIZE ) )
    {
      sprintf( sMessage, "ERROR: Invalid product dimensions (%dx%d).\n",
	       isrc_size[0], isrc_size[1] );
      Lprintf( sMessage );
      lOk=FALSE;
    }

  if (isrc_size[2]==0) isrc_size[2]=1;
  if (isrc_size[2] != 1 )
    {
      Lprintf( "ERROR: Cannot run on 3D inputs.\n" );
      lOk = FALSE;
    }

  if( ( isrc_size[2] < 0 ) ||
      ( isrc_size[2] > MAX_PRODUCT_SURFACES ) )
    {
      sprintf( sMessage, "ERROR: Invalid Z size of %d.\n", isrc_size[2] );
      Lprintf( sMessage );
      lOk=FALSE;
    }
  
  /* Check that the product is big enough to hold the contents */
  CSetup_c.iPixelCount = isrc_size[0]*isrc_size[1]*isrc_size[2];
  iRes = idata_resolution( pInputVil_a->Header.pcf.idata_out );
  icalc_size = PRODUCT_HDR_SIZE + (CSetup_c.iPixelCount*(iRes/8));
  if ( icalc_size > iInputVilSize_a )
    {
      sprintf( sMessage, "ERROR: File size of %d cannot hold expected size of %d.\n",
	       iInputVilSize_a, icalc_size );
      Lprintf( sMessage );
      lOk=FALSE;
    }
  
  /* Find product name mapping */
  nullify_ncpy( sPName, pInputVil_a->Header.pcf.spname, 12 );
  for (iPName=0; iPName<CSetup_c.iPNameCount; iPName++)
    {
      struct convert_pname_list *pOneName=&CSetup_c.PNames[iPName];
      if (sig_fnmatch( pOneName->sInputPName, sPName ))
	{
	  CSetup_c.pThisPName = pOneName;
	  goto NAME_MATCH;
	}
    }
  sprintf( sMessage, "ERROR: Product name '%s' not found in pname table.\n",
	   sPName );
  Lprintf( sMessage );
  lOk=FALSE;
 NAME_MATCH:

  {
    FLT8 fBottom, fTop;
    fBottom = ((FLT8)pInputVil_a->Header.pcf.psi.vil.ivil_bottom)/100000.0;
    fTop    = ((FLT8)pInputVil_a->Header.pcf.psi.vil.ivil_top)/100000.0;
    CSetup_c.fThickness = fTop - fBottom;
    if ( CSetup_c.fThickness < 0.01)
      {
	sprintf( sMessage, "ERROR: VIL height span of %4.2f to %4.2f km is thinner than 10 meters.\n",
		 fBottom, fTop );
	Lprintf( sMessage );
	lOk=FALSE;
      }
  }
  PendFillDataConvert( &CSetup_c.Convert, &(pInputVil_a->Header.end), CONVERT_FLAGS_DEFAULT );

  CSetup_c.fZLConstant = ((FLT8)pInputVil_a->Header.pcf.izr_const)/1000.0;
  CSetup_c.fZLExponent = ((FLT8)pInputVil_a->Header.pcf.izr_exp)/1000.0;

  if (CSetup_c.fZLConstant < 0.01)
    {
      sprintf( sMessage, "ERROR: Z/L constant of %4.3f in invalid.\n",
	       CSetup_c.fZLConstant );
      Lprintf( sMessage );
      lOk=FALSE;
    }

  if (CSetup_c.fZLExponent < 0.01)
    {
      sprintf( sMessage, "ERROR: Z/L exponent of %4.3f in invalid.\n",
	       CSetup_c.fZLExponent );
      Lprintf( sMessage );
      lOk=FALSE;
    }

  return lOk;
}

/* ======================================================================
 * Function to do the conversion.
 */
static void Convert
(struct cartesian_product *pOutputVir_a,
 const struct cartesian_product *pInputVil_a )
{
  ConvertHeader( &(pOutputVir_a->Header), &(pInputVil_a->Header) );

  ConvertData( pOutputVir_a->Data, (UINT2*)pInputVil_a->Data );

}

/* =====================================================================
 * Convert the header.
 */
static void ConvertHeader
( struct product_hdr *pOutputVirHeader_a,
  const struct product_hdr *pInputVilHeader_a )
{
  *pOutputVirHeader_a = *pInputVilHeader_a;
  pOutputVirHeader_a->pcf.idata_out = DB_DBZ;
  spacify_ncpy( pOutputVirHeader_a->pcf.spname, CSetup_c.pThisPName->sOutputPName, 12 );
  pOutputVirHeader_a->hdr.ibytes = PRODUCT_HDR_SIZE +
    (pOutputVirHeader_a->pcf.ixsize * pOutputVirHeader_a->pcf.iysize);
  return;
}
/* =====================================================================
 * Convert the data.
 */
static void ConvertData
( UINT1 *pOutputData_a,
  const UINT2 *pInputData_a )
{
  SINT4 iIndex;
  UINT2 iInput; UINT1 iOutput;
  FLT8  fInput, fZ, fdBZ;
    
  memset( pOutputData_a, DV_THRESH1, CSetup_c.iPixelCount );

  /* Loop through all the data...*/
  for (iIndex=0; iIndex<CSetup_c.iPixelCount; iIndex++)
    {
      iInput=pInputData_a[iIndex];
      switch( iInput)
	{
	case DV_THRESH2:  /* Output already set to threshold! */
	case 1:           /* 1 means no rain, cannot map back in formula */
	  break;
	case DV_NOSCAN2:
	  pOutputData_a[iIndex] = DV_NOSCAN1;
	  break;
	default:
	  fInput = fdata_to_user( iInput, DB_VIL2, &(CSetup_c.Convert) );
	  fInput /= CSetup_c.fThickness;
	  fZ = CSetup_c.fZLConstant * pow( fInput, CSetup_c.fZLExponent );
	  fdBZ = 10.0 * log10(fZ);
	  iOutput = iuser_to_data( fdBZ, DB_DBZ, &(CSetup_c.Convert) );
	  pOutputData_a[iIndex]=iOutput;
	  break;
	}
    }

  return;
}
/* ======================================================================
 * Function to process the command line arguments.
 */
static void ProcessCommandLine
(int iArgc_a, 
 char *sArgv_a[],
 struct command_line *pCommand_a )
{
  
  int iArg;
  char *pEnd;
  memset( pCommand_a, 0, sizeof(*pCommand_a) );
  
  /* 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, "-h", 2 ))
	{
	  Usage();
	}
      else if (0 == strncmp( sArg, "-if:", 4 ))
	{
	  strncpy( pCommand_a->sInFile, &sArg[4], FILENAME_SIZE-1 );
	}
      else if (0 == strncmp( sArg, "-ip:", 4 ))
	{
	  strncpy( pCommand_a->sInPath, &sArg[4], PATHNAME_SIZE-1 );
	}
      else if (0 == strncmp( sArg, "-op:", 4 ))
	{
	  strncpy( pCommand_a->sOutPath, &sArg[4], PATHNAME_SIZE-1 );
	}
      else if (0 == strncmp( sArg, "-device:", 8 ))
	{
	  pCommand_a->iDevice = strtol( &sArg[8], &pEnd, 10 );
	}
      
    }
  return;
}
/* ==================================================
 */
static void Usage( void )
{
  fprintf( stderr, "UfToIris options:\n" );
  fprintf( stderr, "  -help      -- Print this list\n" );
  fprintf( stderr, "  -device:#  -- Device number\n" );
  fprintf( stderr, "  -if:<file> -- Specify input filename\n" );
  fprintf( stderr, "  -ip:<path> -- Specify input pathname\n" );
  fprintf( stderr, "  -op:<path> -- Specify output pathname\n" );
  fprintf( stderr, "   (all other arguments ignored)\n" );
  exit(EXIT_FAILURE) ;
}

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

  /* Initialize the data to the default values */
  memset( pConfig_a, 0, sizeof(*pConfig_a) );
  pConfig_a->lLogToTerm=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, "VilToVir.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, pConfig_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.
 *
 * LOG           <FILE TERM BOTH NONE>
 * VERBOSE
 *
 */
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;

  /* 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( "PNAME_TABLE", stokens[0] ) )
    {
      struct convert_pname_list *pOneName;
      if ( icount < 3 ) return TRUE;
      if ( pConfig_a->iPNameCount >= MAX_CONVERT_PNAMES) return TRUE;
      pOneName = &pConfig_a->PNames[pConfig_a->iPNameCount];
      strncpy( pOneName->sInputPName, stokens[1], 15 );
      spacify_ncpy( pOneName->sOutputPName, stokens[2], 15 );
      pConfig_a->iPNameCount++;
      return FALSE;
    }

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

  /* Unrecognized lines will generate a syntax error */
  return TRUE;
}

/* ======================================================================
 * Function to process the command line arguments.  First three strings not
 * starting with "-" are assumed to be file names.
 */
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 );
  strcat( sMessage, "\n" );

  Lprintf( sMessage );
  return(SS_NORMAL);
}
/* ===========================================
 * Print string to log and terminal, if desired.
 */
static void Lprintf
( const char *sString_a )
{
  if (CSetup_c.lLogToTerm) fputs( sString_a, stderr );
  if (CSetup_c.lLogToFile)
    {
      fputs( sString_a, CSetup_c.iLogFile );
      fflush( CSetup_c.iLogFile );
    }
}
