/* **********************************************************************
 * *                                                                    *
 * *        Routines to Extract ANT/BITE History From Log Files         *
 * *                                                                    *
 * **********************************************************************
 * File: libs/antenna/ant_history.c
 *
 *                        COPYRIGHT (c) 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <sys/stat.h>

#define INCLUDE_STDIO_PROTOTYPES 1
#include "sigtypes.h"
#include "user_lib.h"
#include "setup.h"
#include "antenna_lib.h"
#include "antenna_int.h"

#include "ant_global.h"

static MESSAGE antHistPrepLogfile
( const char *sPathname_a, struct antHistLogfile *pFile_a ) ;

static MESSAGE antHistExtractTime
( const char *pLine_a, struct ymds_time *pTime_a ) ;

static const char *antHistBaseLine
( const struct antHistLogfile *pFile_a, SINT4 iOff_a, SINT4 *pNewOff_a ) ;

static const char *antHistNextLine
( const struct antHistLogfile *pFile_a, SINT4 iOff_a, SINT4 *pNewOff_a ) ;

static SINT4 antHistLookupTime
( const struct ymds_time *pTime_a, const struct antHistLogfile *pFile_a ) ;

static const char *antHistLineIterator
( const struct ymds_time *pBegin_a, const struct ymds_time *pEnd_a,
  const struct antHistLogfile *pFile_a, SINT4 *iState_a,
  struct ymds_time *pLineTime_a ) ;

#define HEXVALUE( CHAR ) \
  ( 0xF & ( isdigit(CHAR) ? ((CHAR) - '0') : (10 + (CHAR) - 'A') ) )

/* *******************************************
 * *                                         *
 * *  External Callable Antenna History API  *
 * *                                         *
 * *******************************************
 */

/* ------------------------------
 * Prepare for extracting information from the antenna log files right
 * at this moment.  Subsequent analysis routines will only see those
 * entries that presently exist, even if some of the log files change
 * thereafter.  A fresh look is obtained by releasing this snapshot
 * and then acquiring another.
 */
struct antHistInfo *antHistPrepare( MESSAGE *iStatus_a )
{
  MESSAGE istatus = SS_NORMAL ;
  struct antHistInfo *pInfo = malloc( sizeof(struct antHistInfo) ) ;
  if( ! pInfo ) istatus = errno_to_message( errno ) ;
  else {
    char *sPtr, sLogDir[ PATHNAME_SIZE ], sWildFile[40] ; SINT4 iFile ;

    /* We've allocated an info structure, initialize to all zeros.
     */
    memset( pInfo, 0,  sizeof(struct antHistInfo) ) ;

    /* Setup the directory from which we'll be reading the log files,
     * and prepare for scanning each one.
     */
    if( NULL != ( sPtr = getenv( "IRIS_LOG" ) ) ) {
      strncpy( sLogDir, sPtr, PATHNAME_SIZE-20 ) ;
      sLogDir[ PATHNAME_SIZE-20 ] = 0 ;
    } else {
      strcpy( sLogDir, "/tmp/" ) ;
    }
    sprintf( sWildFile,	     /* Avoid idiotic TriGraph translations */
	     "antlib-%s-%s-%s.log", "??", "???", "????" ) ;
    istatus = sig_opendir( sLogDir, sWildFile ) ;
    if( istatus == SS_NORMAL ) {
      /* Run through each of the matching filenames and prepare the
       * contents of each one.
       */
      for( iFile=0 ; iFile < ANTLOGMAXFILES ; iFile++ ) {
	char sPathName[ PATHNAME_SIZE ] ; UINT4 iName ; UINT2 lValid ;
	istatus = sig_readdir( sPathName, PATHNAME_SIZE, &iName, &lValid ) ;
	if( (istatus != SS_NORMAL) || (! lValid) ) break ;
	istatus = antHistPrepLogfile( sPathName, &pInfo->logfiles[ iFile ] ) ;
	if( istatus != SS_NORMAL ) break ;
      }
      (void)sig_closedir() ;
    }
    if( istatus != SS_NORMAL ) (void)antHistRelease( pInfo ) ;
  }
  *iStatus_a = istatus ;
  return( pInfo ) ;
}

/* ------------------------------
 * Release the history resources from the last PREPARE operation.
 */
MESSAGE antHistRelease( const struct antHistInfo *pInfo_a )
{
  MESSAGE istatus = SS_NORMAL ; SINT4 iFile ;

  for( iFile=0 ; iFile < ANTLOGMAXFILES ; iFile++ ) {
    const struct antHistLogfile *pFile = &pInfo_a->logfiles[ iFile ] ;
    if( pFile->pMapAddr ) {
      MESSAGE ists2 = imapclose( pFile->pMapAddr, pFile->iMapSize, pFile->iMapChan ) ;
      if( istatus == SS_NORMAL ) istatus = ists2 ;
    }
  }
  free( (void *)pInfo_a ) ;
  return( istatus ) ;
}

/* ------------------------------
 * Given a BITE unit ID and bit offset within the BITE records, fill
 * in a multi-state array representing the activity of that bit over a
 * specified time interval.  The ANTHISTBITE_xxx bits can be examined
 * directly, or the ANTHISTPLOT_xxx plotting macros can be applied to
 * each output slot to determine whether to draw a Hi/Lo data point,
 * or a Hi/Lo fine connecting line, or a fine vertical line.
 */
MESSAGE antHistGetBITE
( const struct antHistInfo *pInfo_a, UINT1 iID_a, SINT4 iByte_a, SINT4 iBit_a,
  const struct ymds_time *pBegin_a, const struct ymds_time *pEnd_a,
  UINT1 *pOut_a, SINT4 nOutPoints_a, SINT4 iExtrapMS_a )
{
  char sTextID[12] ; FLT4 fTimeSpanMS ; SINT4 iFile ;
  SINT4 iNybble = (2 * (iByte_a - 1)) + ((7-iBit_a) / 4) ;
  SINT4 iBitInNybble = ( iBit_a % 4 ), iTimeSpan, iExtrapLen ;
  iYmdsCmpSub_f *iYmdsSub ;

  /* Initialize the entire output array to NONE, so that those values
   * will remain wherever we do not actually fill something in.
   */
  memset( pOut_a, 0, nOutPoints_a * sizeof( *pOut_a ) ) ;

  /* Decide whether we'll be working with seconds or miliseconds of
   * basic time resolution.  Bail out entirely for bad intervals.
   */
  iYmdsSub = iymds_subtract ; iTimeSpan = (*iYmdsSub)( pEnd_a, pBegin_a ) ;
  fTimeSpanMS = (1E3 * (FLT4)iTimeSpan ) ;

  if( iTimeSpan <= 1 ) return( ANT_BAD_TIMESPAN ) ;

  if( iTimeSpan < (MAX_SUBTRACT_MILLISECONDS / 1000) ) {
    iYmdsSub = iYmdsSubtractMs ; iTimeSpan = (*iYmdsSub)( pEnd_a, pBegin_a ) ;
    fTimeSpanMS = (FLT4)iTimeSpan ;
  }
  /* Generate the prefix text for the BITE packets that we'll be
   * looking for on each line.
   */
  sprintf( sTextID, ":%2.2X%2.2X", ANT_SYNC_BITE, iID_a ) ;

  /* Run through all of the matching lines in each of the the log
   * files that we have open.
   */
  for( iFile=0 ; iFile < ANTLOGMAXFILES ; iFile++ ) {
    const struct antHistLogfile *pFile = &pInfo_a->logfiles[ iFile ] ;
    if( pFile->pMapAddr ) {
      SINT4 iState = -1 ; const char *pLine ; struct ymds_time lineTime ;
      while( ( pLine = antHistLineIterator
	       ( pBegin_a, pEnd_a, pFile, &iState, &lineTime ) ) != NULL ) {
	const char *pText = &pLine[ ANTLOGCOL_TEXT ] ;
	if( memcmp( sTextID, pText, 5 ) == 0 ) {
	  SINT4 iTimeInc  = (*iYmdsSub)( &lineTime, pBegin_a ) ;
	  FLT4  fTimeFrac = ((FLT4)iTimeInc) / (FLT4)iTimeSpan ;
	  SINT4 iIndex = NINT( fTimeFrac * (FLT4)nOutPoints_a ) ;
	  if( (iIndex >= 0) && (iIndex < nOutPoints_a) ) {
	    char iChar = *(pText + 1 + iNybble) ;
	    SINT4 iVal = HEXVALUE( iChar ) ;
	    UINT1 lBit = BTEST( iVal, iBitInNybble ) ;
	    UINT1 *pOut = iIndex + pOut_a ;
	    if( lBit ) { *pOut |= ANTHISTBITE_HIGH ; *pOut |=  ANTHISTBITE_LAST ; }
	    else       { *pOut |= ANTHISTBITE_LOW  ; *pOut &= ~ANTHISTBITE_LAST ; }
	  }
	}
      }
    }
  }
  /* Extrapolate the fundamental reported points forward across the
   * expected reporting time interval.
   */
  iExtrapLen = NINT( ((FLT4)nOutPoints_a) * ((FLT4)iExtrapMS_a) / fTimeSpanMS ) ;
  if( iExtrapLen > 0 ) {
    SINT4 iPoint, iExtrapCnt = 0 ; UINT1 iExtrapBits = 0 ;
    for( iPoint=0 ; iPoint < nOutPoints_a ; iPoint++ ) {
      UINT1 iVal = pOut_a[ iPoint ] ; pOut_a[ iPoint ] |= iExtrapBits ;
      if( iVal & (ANTHISTBITE_LOW | ANTHISTBITE_HIGH) ) {
	iExtrapBits = ANTHISTBITE_XVALID ; iExtrapCnt = 0 ;
	if( iVal & ANTHISTBITE_LAST ) iExtrapBits |= ANTHISTBITE_XLEVEL ;
      }
      if( iExtrapCnt++ >= iExtrapLen ) iExtrapBits = 0 ;
    }
  }
  return( SS_NORMAL ) ;
}

/* ------------------------------
 * Given a QBITE unit ID, byte offset and field length within the
 * QBITE records, and slope/offset conversion parameters, create an
 * output array representing the activity of that field over a given
 * time interval.
 */
MESSAGE antHistGetQBITE
( const struct antHistInfo *pInfo_a, UINT1 iID_a, SINT4 iByte_a, SINT4 nBytes_a,
  const struct ymds_time *pBegin_a, const struct ymds_time *pEnd_a,
  FLT4 fCalSlope_a, FLT4 fCalOffset_a, UINT1 lSigned_a,
  struct antHistQBITE *pOut_a, SINT4 nOutPoints_a )
{
  char sTextID[12] ; iYmdsCmpSub_f *iYmdsSub ;
  SINT4 ii, iFile, nBytes = MAX( 1, MIN( 5, nBytes_a ) ) ;
  SINT4 iTimeSpan, iEndHex = 2 * (iByte_a + nBytes - 2) ;

  /* Initialize the entire output array to NONE, so that those values
   * will remain wherever we do not actually fill something in.
   */
  memset( pOut_a, 0, nOutPoints_a * sizeof( *pOut_a ) ) ;

  /* Decide whether we'll be working with seconds or miliseconds of
   * basic time resolution.  Bail out entirely for bad intervals.
   */
  iYmdsSub = iymds_subtract ; iTimeSpan = (*iYmdsSub)( pEnd_a, pBegin_a ) ;

  if( iTimeSpan <= 1 ) return( ANT_BAD_TIMESPAN ) ;

  if( iTimeSpan < (MAX_SUBTRACT_MILLISECONDS / 1000) ) {
    iYmdsSub = iYmdsSubtractMs ; iTimeSpan = (*iYmdsSub)( pEnd_a, pBegin_a ) ;
  }
  /* Generate the prefix text for the BITE packets that we'll be
   * looking for on each line.
   */
  sprintf( sTextID, ":%2.2X%2.2X", ANT_SYNC_QBITE, iID_a ) ;

  /* Run through all of the matching lines in each of the the log
   * files that we have open.
   */
  for( iFile=0 ; iFile < ANTLOGMAXFILES ; iFile++ ) {
    const struct antHistLogfile *pFile = &pInfo_a->logfiles[ iFile ] ;
    if( pFile->pMapAddr ) {
      SINT4 iState = -1 ; const char *pLine ; struct ymds_time lineTime ;
      while( ( pLine = antHistLineIterator
	       ( pBegin_a, pEnd_a, pFile, &iState, &lineTime ) ) != NULL ) {
	const char *pText = &pLine[ ANTLOGCOL_TEXT ] ;
	if( memcmp( sTextID, pText, 5 ) == 0 ) {
	  SINT4 iTimeInc  = (*iYmdsSub)( &lineTime, pBegin_a ) ;
	  FLT4  fTimeFrac = ((FLT4)iTimeInc) / (FLT4)iTimeSpan ;
	  SINT4 iIndex = NINT( fTimeFrac * (FLT4)nOutPoints_a ) ;
	  if( (iIndex >= 0) && (iIndex < nOutPoints_a) ) {
	    const char *pChar = ( pText + 1 + iEndHex ) ;
	    struct antHistQBITE *pOut = iIndex + pOut_a ;
	    SINT4 iByte, iVal = 0 ; FLT4 fVal ;
	    for( iByte = 0 ; iByte < nBytes ; iByte++ ) {
	      SINT4 iHex = ( HEXVALUE( pChar[0] ) << 4 ) + HEXVALUE( pChar[1] ) ;
	      if( lSigned_a && (iByte == 0) && (iHex & 0x40) ) iHex |= 0xFFFFFF80 ;
	      iVal <<= 7 ; iVal |= iHex ; pChar -= 2 ;
	    }
	    fVal = fCalOffset_a + ( fCalSlope_a * (FLT4)iVal ) ;
	    if( pOut->nValues++ ) {
	      pOut->fLoVal = MIN( pOut->fLoVal, fVal ) ;
	      pOut->fHiVal = MAX( pOut->fHiVal, fVal ) ;
	      pOut->fMeanVal += fVal ;
	    } else {
	      pOut->fLoVal = pOut->fHiVal = pOut->fMeanVal = fVal ;
	    }
	  }
	}
      }
    }
  }
  /* Convert sums in each slot to mean values.
   */
  for( ii=0 ; ii < nOutPoints_a ; ii++ ) {
    struct antHistQBITE *pOut = &pOut_a[ ii ] ;
    if( pOut->nValues ) pOut->fMeanVal /= (FLT4)pOut->nValues ;
  }
  return( SS_NORMAL ) ;
}

/* *******************************
 * *                             *
 * *  Internal Support Routines  *
 * *                             *
 * *******************************
 */

/* ------------------------------
 * Open an individual antenna log file and perform a quick checkout of
 * its contents.
 */
static MESSAGE antHistPrepLogfile
( const char *sPathname_a, struct antHistLogfile *pFile_a )
{
  /* Begin by trying to open the file mapped/readonly.
   */
  MESSAGE istatus = imapopen
    ( sPathname_a, FALSE, &pFile_a->pMapAddr, &pFile_a->iMapSize, &pFile_a->iMapChan ) ;

  if( istatus == SS_NORMAL ) {
    if( pFile_a->iMapSize < (5*ANTLOGCOL_TEXT) ) istatus = ANT_BAD_LOGFILE ;
    else {
      const char *pData = pFile_a->pMapAddr ; SINT4 iSize = pFile_a->iMapSize ;
      for( ; iSize > 0 ; iSize-- ) { if( pData[ iSize-1 ] == '\n' ) break ; }
      if( iSize < (5*ANTLOGCOL_TEXT) ) istatus = ANT_BAD_LOGFILE ;
      else {
	pFile_a->iSize = iSize ;
	/* Extract the starting and ending times from the file, while
	 * also checking that it seems to be in the correct logging
	 * format.
	 */
	istatus = antHistExtractTime( pData, &pFile_a->begin ) ;
	if( istatus == SS_NORMAL ) {
	  const char *pLastLine = antHistBaseLine( pFile_a, iSize-1, NULL ) ;
	  istatus = antHistExtractTime( pLastLine, &pFile_a->end ) ;      
	}
      }
    }
    if( istatus != SS_NORMAL ) {
      (void)imapclose( pFile_a->pMapAddr, pFile_a->iMapSize, pFile_a->iMapChan ) ;
      pFile_a->pMapAddr = NULL ;
    }
  }
  return( istatus ) ;
}

/* ------------------------------
 * Given a pointer to a line of a log file, extract the YMDS time from
 * the prefix text.
 */
static MESSAGE antHistExtractTime
( const char *pLine_a, struct ymds_time *pTime_a )
{
  MESSAGE istatus = ANT_BAD_LOGFILE ;
  const char *pYMDS = &pLine_a[ ANTLOGCOL_YMDS ] ;

  if( ( pYMDS[ 2] == '-' ) && ( pYMDS[ 6] == '-' ) && ( pYMDS[ 9] == ' ' ) &&
      ( pYMDS[12] == ':' ) && ( pYMDS[15] == ':' ) && ( pYMDS[18] == '.' ) ) {

    SINT4 iHour = atoi( &pYMDS[10] ) ;
    SINT4 iMin  = atoi( &pYMDS[13] ) ;
    SINT4 iSec  = atoi( &pYMDS[16] ) ;

    pTime_a->isec   = iPackTime( iHour, iMin, iSec ) ;
    pTime_a->iday   = atoi( &pYMDS[0] ) ;
    pTime_a->imon   = imonth_from_txt( &pYMDS[3] ) ;
    pTime_a->iyear  = iYearFromFile( atoi( &pYMDS[7] ) ) ;
    pTime_a->imills = atoi( &pYMDS[19] ) ;    

    if( (pTime_a->isec  < 86400) && (pTime_a->iday < 32) &&
	(pTime_a->imon  <    13) && (pTime_a->imon >  0) &&
	(pTime_a->imills < 1000) )
      istatus = SS_NORMAL ;
  }
  return( istatus ) ;
}

/* ------------------------------
 * Given an offset into a log file, find the beginning of the line
 * that we're in, or the line immediately following the one we're in.
 * Both routines are tolerant of unexpected multiple newlines in the
 * logfile.
 */
static const char *antHistBaseLine
( const struct antHistLogfile *pFile_a, SINT4 iOff_a, SINT4 *pNewOff_a )
{
  SINT4 iOff = MAX( 0, MIN( (pFile_a->iSize - 1), iOff_a ) ) ;
  const char *pData = pFile_a->pMapAddr, *pLine ;

  for( pLine = (iOff + pData) ; pLine > pData ; pLine-- ) {
    if( ( *(pLine-1) == '\n' ) && isprint( *pLine ) ) break ;
  }
  if( pNewOff_a ) *pNewOff_a = (pLine - pData) ;
  return( pLine ) ;
}

static const char *antHistNextLine
( const struct antHistLogfile *pFile_a, SINT4 iOff_a, SINT4 *pNewOff_a )
{
  SINT4 iOff = MAX( 0, MIN( (pFile_a->iSize - 1), iOff_a ) ) ;
  const char *pData = pFile_a->pMapAddr, *pLine ;
  const char  *pEnd = pFile_a->iSize + pData ;

  for( pLine = (1 + iOff + pData) ; pLine < pEnd ; pLine++ ) {
    if( ( *(pLine-1) == '\n' ) && isprint( *pLine ) ) break ;
  }
  if( pLine >= pEnd ) pLine = (iOff + pData ) ;
  if( pNewOff_a ) *pNewOff_a = (pLine - pData) ;
  return( pLine ) ;
}

/* ------------------------------
 * Given a YMDS time and pointer to log file info, determine the
 * offset of the first line in the file that is at or after the
 * indicated time.  Return (-1) if no such line exists.  A rapid
 * binary search is used.
 */
static SINT4 antHistLookupTime
( const struct ymds_time *pTime_a, const struct antHistLogfile *pFile_a )
{
  SINT4 iOffset ; MESSAGE istatus ;
  if( iYmdsCompareMs( &pFile_a->begin, pTime_a ) >= 0 ) iOffset = 0 ;
  else {
    if( iYmdsCompareMs( &pFile_a->end, pTime_a ) < 0 ) iOffset = -1 ;
    else {
      SINT4 iTryBit = ipower_of_two( pFile_a->iSize - 1 ) ;
      for( iOffset=0 ; iTryBit > ANTLOGCOL_TEXT ; iTryBit >>= 1 ) {
	SINT4 iTryOff = ( iOffset + iTryBit ) ;
	if( iTryOff < pFile_a->iSize ) {
	  struct ymds_time lineTime ;
	  const char *pLine = antHistBaseLine( pFile_a, iTryOff, NULL ) ;
	  istatus = antHistExtractTime( pLine, &lineTime ) ;
	  if( istatus != SS_NORMAL ) { iOffset = -1 ; break ; }
	  if( iYmdsCompareMs( &lineTime, pTime_a ) < 0 ) iOffset = iTryOff ;
	}
      }
      (void)antHistNextLine( pFile_a, iOffset, &iOffset ) ;
    }
  }
  return( iOffset ) ;
}

/* ------------------------------
 * Given a Begin/End pair of YMDS times and a log file info pointer,
 * return successive lines from the file that were logged within the
 * specified time interval.  State variable should be (-1) the first
 * time the routine is called on a given file.  NULL is returned when
 * no more lines can be found.  Valid lines return the line pointer,
 * YMDS time of the line, and updated state variable.
 */
static const char *antHistLineIterator
( const struct ymds_time *pBegin_a, const struct ymds_time *pEnd_a,
  const struct antHistLogfile *pFile_a, SINT4 *iState_a,
  struct ymds_time *pLineTime_a )
{
  const char *pLine = NULL ;
  if( *iState_a < 0 ) {
    /* The first time through we check whether there is any overlap at
     * all within this file, and lookup the starting line if there is.
     */
    if( ( iYmdsCompareMs( &pFile_a->begin, pEnd_a   ) <= 0 ) &&
	( iYmdsCompareMs( &pFile_a->end  , pBegin_a ) >= 0 ) ) {
      SINT4 iOffset = antHistLookupTime( pBegin_a, pFile_a ) ;
      if( iOffset >= 0 ) {
	const char *pNewLine = iOffset + (const char *)pFile_a->pMapAddr ;
	if( SS_NORMAL == antHistExtractTime( pNewLine, pLineTime_a ) ) {
	  *iState_a = iOffset ;	pLine = pNewLine ;
	}
      }
    }
  } else {
    /* Subsequently we advance to the next line and keep going as long
     * as we do not exceed the ending time of the desired interval.
     */
    SINT4 iOffset ;
    const char *pNewLine = antHistNextLine( pFile_a, *iState_a, &iOffset ) ;
    if( *iState_a != iOffset ) {
      if( SS_NORMAL == antHistExtractTime( pNewLine, pLineTime_a ) ) {
	if( iYmdsCompareMs( pLineTime_a, pEnd_a ) <= 0 ) {
	  *iState_a = iOffset ; pLine = pNewLine ;
	}
      }
    }
  }
  return( pLine ) ;
}
