/************************************************************************/
/* Copyright 1993 - 1997 MBARI                                          */
/************************************************************************/
/* $Header: recorder.c,v 1.0 93/09/30 09:51:28 hebo Exp $               */
/* Summary  : Flight Recorder for VxWorks Data Manager                  */
/* Filename : recorder.c                                                */
/* Author   : Bob Herlien (rah)                                         */
/* Project  : Tiburon                                                   */
/* $Revision: 1.0 $                                                     */
/* Created  : 05/07/93                                                  */
/************************************************************************/
/* Modification History:                                                */
/* $Log:        recorder.c,v $
 * Revision 1.0  93/09/30  09:51:28  09:51:28  hebo (Bob Herlien)
 * Initial revision
 *
*/
/* 07may93 rah, created                                                 */
/************************************************************************/

#define __PROTOTYPE_5_0                 /* Enables vararge proto for taskSpawn*/
#include <vxWorks.h>
#include <semLib.h>
#include <lstLib.h>
#include <sys/times.h>
#include <time.h>
#include <tickLib.h>
#include <stdioLib.h>
#include <ioLib.h>
#include <taskLib.h>
#include <string.h>
#include <memLib.h>
#include <stat.h>

#include <mbariTypes.h>
#include <datamgr.h>
#include <dm_errno.h>
#include <dm_internal.h>

#include "recorder.h"


/********************************/
/*      External Functions      */
/********************************/

Extern Int32    recordParse( RecStruct *rp );


/********************************/
/*      External Data           */
/********************************/

Extern Nat32    type_sizes[], type_align[];     /* From Data Manager    */


/********************************/
/*      Static Data             */
/********************************/

MLocal LIST             recordList;
MLocal SEM_ID           recordSem;
MLocal char             *recordDfltFileName = RECORD_DFLT_FILE;


/************************************************************************/
/* Function    : recordCleanup                                          */
/* Purpose     : Free everything in RecStruct                           */
/* Inputs      : RecStruct Ptr                                          */
/* Outputs     : None                                                   */
/************************************************************************/
     Void
recordCleanup( RecStruct *recPtr )
{
    Reg NODE    *np;
    Reg RecItem *rp;

    while( (np = lstGet(&recPtr->recItemList)) != NODE_NULL )
    {
        rp = NODEtoRI(np);
        dm_group_delete_item( rp->rec_groupPtr->grp_num, rp->rec_item );
        dm_stop_consumer( rp->rec_item );
        dm_item_status_free( &rp->rec_sts );
        free( rp->rec_data );
        free( (char *)rp );
    }

    while( (np = lstGet(&recPtr->recGroupList)) != NODE_NULL )
    {
        dm_delete_group( (NODEtoRG(np))->grp_num );
        free( (char *)np );
    }

    semDelete( recPtr->recSem );
    fclose( recPtr->fp );
    if ( recPtr->fileName != recordDfltFileName )
        free( recPtr->fileName );

    semTake( recordSem, WAIT_FOREVER );

    if ( lstFind(&recordList, RStoNODE(recPtr)) != ERROR )
        lstDelete(&recordList, RStoNODE(recPtr));

    semGive( recordSem );

    free( (char *)recPtr );

} /* recordCleanup() */


/************************************************************************/
/* Function    : filePairTime                                           */
/* Purpose     : See if file pair is used, return file time             */
/* Inputs      : RecStruct ptr, prospective file number                 */
/* Outputs     : (time_t)0 if available, else modification time         */
/************************************************************************/
        time_t
filePairTime( RecStruct *recPtr, Nat32 num )
{
    struct stat buf;
    Int32       rtn;

    sprintf( recPtr->recbuff, "%s.%da", recPtr->fileName, num );

    if ( stat(recPtr->recbuff, &buf) == OK )
        return( buf.st_mtime );

    sprintf( recPtr->recbuff, "%s.%db", recPtr->fileName, num );

    if ( stat(recPtr->recbuff, &buf) == OK )
        return( buf.st_mtime );

    return( (time_t)0 );

} /* filePairTime() */


/************************************************************************/
/* Function    : filePairInUse                                          */
/* Purpose     : See if file pair is in current use                     */
/* Inputs      : RecStruct ptr, prospective file number                 */
/* Outputs     : (time_t)0 if available, else modification time         */
/************************************************************************/
        MBool
filePairInUse( RecStruct *recPtr, Nat32 num )
{
    Reg NODE            *p;
    Reg RecStruct       *rp;

    semTake( recordSem, WAIT_FOREVER );

    for ( p = lstFirst(&recordList); p != NODE_NULL; p = lstNext(p) )
    {
        rp = NODEtoRS(p);
        if ( (strcmp(rp->fileName, recPtr->fileName) == 0) &&
             (rp->fileNum == num) && (rp != recPtr) )
        {
            semGive( recordSem );
            return( TRUE );
        }
    }

    semGive( recordSem );
    return( FALSE );

} /* filePairInUse() */


/************************************************************************/
/* Function    : openNewRecordFile                                      */
/* Purpose     : Open (create) a new Recorder file                      */
/* Inputs      : RecStruct ptr                                          */
/* Outputs     : OK or ERROR                                            */
/************************************************************************/
     Int32
openNewRecordFile( RecStruct *recPtr )
{
    Int32       i, oldest;
    Int32       rtn;
    time_t      oldtime, filetime;
    struct stat buf;

    /* Look for available name/number.  If avail, open file and return  */
    oldtime = (time_t)INT_MAX;
    oldest = INT_MAX;
    for ( i = 0; i < recPtr->maxFilePairs; i++ )
    {
        filetime = filePairTime(recPtr, i);
        if ( filetime == (time_t)0 )
        {
            recPtr->fileNum = i;
            sprintf( recPtr->recbuff, "%s.%da", recPtr->fileName, i );
            if ( (recPtr->fp = fopen(recPtr->recbuff, "w")) != (FILE * )NULL )
                return( OK );
        }
        else if ( (filetime < oldtime) && !filePairInUse(recPtr, i) )
        {
            oldest = i;
            oldtime = filetime;
        }
    }

    /* No file pairs available.  Delete and use the oldest              */

    if ( oldest == INT_MAX )            /* If all currently in use, error*/
        return( ERROR );

    sprintf( recPtr->recbuff, "%s.%db", recPtr->fileName, oldest );
    unlink( recPtr->recbuff );
    sprintf( recPtr->recbuff, "%s.%da", recPtr->fileName, oldest );
    unlink( recPtr->recbuff );
    recPtr->fileNum = oldest;

    if ( (recPtr->fp = fopen(recPtr->recbuff, "w")) == (FILE * )NULL )
        return( ERROR );

    return( OK );

} /* openNewRecordFile() */


/************************************************************************/
/* Function    : nextRecordFile                                         */
/* Purpose     : Close existing data file, open next one                */
/* Inputs      : Rec Ptr                                                */
/* Outputs     : OK or ERROR                                            */
/************************************************************************/
     Int32
nextRecordFile( RecStruct *recPtr )
{
    Reg FILE    *newfp;

    sprintf( recPtr->recbuff, "%s.%d%c", recPtr->fileName, recPtr->fileNum,
             (recPtr->filePingPong ? 'a' : 'b') );

    if ( (newfp = fopen(recPtr->recbuff, "w")) != (FILE *)NULL )
    {
        fclose( recPtr->fp );
        recPtr->fp = newfp;
        recPtr->filePingPong = !recPtr->filePingPong;
        return( OK );
    }

    return( ERROR );

} /* nextRecordFile() */


/************************************************************************/
/* Function    : recordItem                                             */
/* Purpose     : Record one Data Manager Item to recorder file          */
/* Inputs      : RecStruct ptr, RecItem ptr                             */
/* Outputs     : None                                                   */
/************************************************************************/
     Void
recordItem( RecStruct *recPtr, Reg RecItem *itemPtr )
{
    Reg Nat32           typeCnt, eltCnt, align, dataOffset;
    Reg DM_Element      *elementPtr;
    Reg char            *p, *bufp;
    MBool               comma;
    double              x;
    struct tm           timeBuf;

    dm_read( itemPtr->rec_item, itemPtr->rec_data, itemPtr->rec_sts.is_size,
             &itemPtr->rec_time );

    bufp = recPtr->recbuff;

    if ( itemPtr->rec_flags & REC_NAME )
    {
        if ( itemPtr->rec_isArray )
            bufp += sprintf( bufp, "%s[%d] ", itemPtr->rec_sts.is_name,
                             itemPtr->rec_arrayIndex );
        else
            bufp += sprintf(bufp, "%s ", itemPtr->rec_sts.is_name);
    }

    if ( itemPtr->rec_flags & REC_DATA )
    {
        comma = FALSE;
        elementPtr = itemPtr->rec_sts.is_compound;

        for ( typeCnt = dataOffset = 0;
              typeCnt < itemPtr->rec_sts.is_num_compounds;
              typeCnt++, elementPtr++ )
        {
            for ( eltCnt = 0; eltCnt < elementPtr->elt_num; eltCnt++ )
            {
                if ( comma )
                    bufp += sprintf(bufp, ", ");

                align = type_align[elementPtr->elt_type] - 1;
                dataOffset = (dataOffset + align) & ~align;
                p = itemPtr->rec_data + dataOffset;

                switch( elementPtr->elt_type )
                {
                  case DM_EMPTY:
                    break;

                  case DM_CHAR:
                  case DM_UCHAR:
                    bufp += sprintf(bufp, "%c", *p);
                    comma = FALSE;
                    break;

                  case DM_INT16:
                    bufp += sprintf(bufp, "%hd", *(Int16 *)p);
                    break;

                  case DM_NAT16:
                    bufp += sprintf(bufp, "%hu", *(Nat16 *)p);
                    break;

                  case DM_MBOOL:
                    if ( *(MBool *)p )
                        bufp += sprintf(bufp, "TRUE");
                    else
                        bufp += sprintf(bufp, "FALSE");
                    break;

                  case DM_INT32:
                    bufp += sprintf(bufp, "%d", *(Int32 *)p);
                    break;

                  case DM_NAT32:
                  case DM_ENUM:
                    bufp += sprintf(bufp, "%u", *(Nat32 *)p);
                    break;

                  case DM_FLT32:
                    x = (double)(*(Flt32 *)p);
                    bufp += sprintf(bufp, "%f", x);
                    break;

                  case DM_FLT64:
                    x = *(Flt64 *)p;
                    bufp += sprintf(bufp, "%f", x);
                    break;

                  case DM_PTR:
                    bufp += sprintf(bufp, "%#x", *(Nat32 *)p);
                    break;

                  default:
                    bufp += sprintf(bufp, "Unknown type %d",
                                  elementPtr->elt_type );
                }

                dataOffset += type_sizes[elementPtr->elt_type];
                comma = TRUE;
            }
        }
    }

    if ( itemPtr->rec_flags & (REC_DATE | REC_TIME) )
    {
        if ( itemPtr->rec_flags & REC_TIME_GMT )
            gmtime_r( &itemPtr->rec_time.tv_sec, &timeBuf );
        else
            localtime_r( &itemPtr->rec_time.tv_sec, &timeBuf );

        if ( itemPtr->rec_flags & REC_DATE )
            bufp += sprintf(bufp, "  %4d/%02d/%02d",
                      timeBuf.tm_year+1900, timeBuf.tm_mon+1, timeBuf.tm_mday);

        if ( itemPtr->rec_flags & REC_TIME )
            bufp += sprintf(bufp, "  %02d:%02d:%02d.%06d",
                        timeBuf.tm_hour, timeBuf.tm_min, timeBuf.tm_sec,
                        itemPtr->rec_time.tv_usec);
    }

    bufp += sprintf(bufp, "\n");
    fwrite( recPtr->recbuff, bufp - recPtr->recbuff, 1, recPtr->fp );

} /* recordItem() */


/************************************************************************/
/* Function    : checkRecordItems                                       */
/* Purpose     : Check for items to record, do it if appropriate        */
/* Inputs      : RecStruct Ptr, current time in ticks                   */
/* Outputs     : Number of ticks until next data required               */
/************************************************************************/
     Nat32
checkRecordItems( RecStruct *recPtr, Nat32 curtick )
{
    Reg NODE            *nodePtr;
    Reg RecGroup        *grpPtr;
    Reg RecItem         *itemPtr;
    Reg Nat32           nextDataTick;

    for ( nodePtr = lstFirst(&recPtr->recGroupList); nodePtr != NODE_NULL;
          nodePtr = lstNext(nodePtr) )
    {
        grpPtr = NODEtoRG(nodePtr);
        grpPtr->grp_changes |= dm_get_group_changes( grpPtr->grp_num );
    }

    nextDataTick = UINT_MAX;

    for ( nodePtr = lstFirst(&recPtr->recItemList); nodePtr != NODE_NULL;
          nodePtr = lstNext(nodePtr) )
    {
        itemPtr = NODEtoRI(nodePtr);
        grpPtr = itemPtr->rec_groupPtr;

/* Note on conditional below -- first clause checks if it's time to record.*/
/* Second clause checks if data has changed OR the REC_ALWAYS flag is set  */
/* (record even if no change).  The recordParse module ensures that        */
/* REC_ALWAYS is not set if the item is not periodic (async or static).    */
/* Note also that (async | static) implies that rec_period == 0, so the    */
/* comparison between curtick and rec_tick always passes (since rec_tick   */
/* is always set to curtick + rec_period).                                 */

        if ( ((Int32)(curtick - itemPtr->rec_tick) >= 0) &&
             ((itemPtr->rec_groupBit & grpPtr->grp_changes) ||
              (itemPtr->rec_flags & REC_ALWAYS)) )
        {
            recordItem( recPtr, itemPtr );
            grpPtr->grp_changes &= ~(itemPtr->rec_groupBit);
            itemPtr->rec_tick = curtick + itemPtr->rec_period;
        }

        if ( (itemPtr->rec_period > 0) &&
             ((Nat32)(itemPtr->rec_tick - curtick) < nextDataTick) )
            nextDataTick = (Nat32)(itemPtr->rec_tick - curtick);
    }

    if ( nextDataTick == UINT_MAX )
        return( WAIT_FOREVER );

    return( nextDataTick );

} /* checkRecordItems() */


/************************************************************************/
/* Function    : recordTask                                             */
/* Purpose     : Flight Recorder Task                                   */
/* Inputs      : Configuration file name, RecStruct ptr, seconds to delay*/
/* Outputs     : None (never returns)                                   */
/************************************************************************/
     Void
recordTask( Reg RecStruct *recPtr, Nat32 delay )
{
    Reg Nat32           curtick, waitTicks, nextwrap, nextflush;
    Reg FILE            *recFilePtr;

    if ( delay > 0 )
        taskDelay( delay * sysClkRateGet() );

    if ( recordParse(recPtr) != OK )
    {
        logMsg("Flight Recorder cfg file error, exiting\n");
        recordCleanup( recPtr );
        exit( -1 );
    }

    if ( openNewRecordFile(recPtr) != OK )
    {
        logMsg("Flight Recorder: can't open data file %s.%da, exiting\n",
               recPtr->fileName, recPtr->fileNum);
        recordCleanup( recPtr );
        exit( -1 );
    }

    curtick = tickGet();
    waitTicks = NO_WAIT;
    nextwrap = curtick + recPtr->wraptime;
    nextflush = curtick + recPtr->flushtime;

    /*  Loop until user tells us to stop        */

    while( !recPtr->recStop )
    {
        semTake( recPtr->recSem, waitTicks );   /* Wait for data time   */

        curtick = tickGet();                    /* Get current time     */

        waitTicks = checkRecordItems( recPtr, curtick );

        if ( (Int32)(nextwrap - curtick) <= 0 )
        {
            if ( nextRecordFile(recPtr) != OK )
                logMsg("Flight Recorder: can't open next data file\n");

            nextwrap = curtick + recPtr->wraptime;
        }
        else if ( (Int32)(nextflush - curtick) <= 0 )
        {
            fflush( recPtr->fp );
            ioctl( fileno(recPtr->fp), FIOSYNC, 0 );
            nextflush = curtick + recPtr->flushtime;
        }

        if ( (nextwrap - curtick) < waitTicks )
            waitTicks = nextwrap - curtick;

        if ( (nextflush - curtick) < waitTicks )
            waitTicks = nextflush - curtick;

    } /* Main Loop */

    recordCleanup( recPtr );
    exit( 0 );

} /* recordTask() */


/************************************************************************/
/* Function    : recordStart                                            */
/* Purpose     : Start a Flight Recorder task                           */
/* Inputs      : Configuration file name, Number of seconds to delay    */
/* Outputs     : Pid of record task started                             */
/************************************************************************/
        Pid
recordStart( char *cfgfile, Nat32 delay )
{
    Reg RecStruct       *recPtr;
    Reg Pid             pid;
    Reg Nat32           cfgsize;

    if ( cfgfile == NULL )
        cfgfile = RECORD_CFGFILE;

    cfgsize = (strlen(cfgfile) + sizeof(int)) & ~sizeof(int);
                                                /* Add EOS, round up    */

    if ( (recPtr = (RecStruct *)malloc(sizeof(RecStruct)+cfgsize)) == RS_NULL )
    {
        logMsg("recordStart: no malloc space, exiting\n");
        return( ERROR );
    }

    bzero( (char *)recPtr, sizeof(RecStruct) );
    lstInit( &recPtr->recItemList );
    lstInit( &recPtr->recGroupList );
    recPtr->fp = (FILE *)NULL;
    recPtr->fileName = recordDfltFileName;
    recPtr->cfgFileName = (char *)(recPtr + 1);
    strcpy( recPtr->cfgFileName, cfgfile );
    recPtr->flushtime = DFLT_FLUSHTIME;
    recPtr->wraptime = DFLT_WRAPTIME;
    recPtr->maxFilePairs = DFLT_FNUM_MAX;

    if ( (recPtr->recSem = semBCreate(0, SEM_EMPTY)) == SEM_NULL )
    {
        logMsg("recordInit: no semaphore space\n");
        return( ERROR );
    }

    pid =taskSpawn("tRecord", RECORD_PRIO,
                   VX_STDIO | VX_FP_TASK, RECORD_STKSIZE,
                   (FUNCPTR)recordTask, recPtr, delay);

    if ( pid == (Pid) ERROR )
    {
        logMsg("recordStart: could not spawn record task, exiting\n");
        free( recPtr );
    }
    else
    {
        recPtr->recPid = pid;
        semTake( recordSem, WAIT_FOREVER );
        lstAdd( &recordList, RStoNODE(recPtr) );
        semGive( recordSem );
    }

    return( pid );

} /* recordStart() */


/************************************************************************/
/* Function    : recordShow                                             */
/* Purpose     : Show Flight Recorder Status                            */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
        Void
recordShow( Int32 all )
{
    NODE                *p, *q;
    RecStruct           *recPtr;
    RecItem             *rp;

    if ( lstCount(&recordList) == 0 )
    {
        printf( "No Flight Recorder tasks active\n" );
        return;
    }

    printf("Pid       Cfg File                       Data Files\n");
    printf("---       --------                       ----------\n");

    semTake( recordSem, WAIT_FOREVER );

    for ( p = lstFirst(&recordList); p != NODE_NULL; p = lstNext(p) )
    {
        recPtr = NODEtoRS(p);
        printf("%-8x  %-30.30s %s.%d[ab]\n", recPtr->recPid,
               recPtr->cfgFileName, recPtr->fileName, recPtr->fileNum);
        if ( all )
            for ( q = lstFirst(&recPtr->recItemList);
                  q != NODE_NULL; q = lstNext(q) )
             {
                 rp = NODEtoRI(q);
                 if ( rp->rec_isArray )
                     printf("    %s[%d] ", rp->rec_sts.is_name,
                            rp->rec_arrayIndex);
                 else
                     printf("    %-50s", rp->rec_sts.is_name);

                 printf("  %10.6f\n",
                        (Flt32)(rp->rec_period) / (Flt32)sysClkRateGet() );
             }
    }

    semGive( recordSem );

} /* recordShow() */


/************************************************************************/
/* Function    : recordStop                                             */
/* Purpose     : Stop Flight Recorder                                   */
/* Inputs      : Pid of recorder task to stop                           */
/* Outputs     : None                                                   */
/************************************************************************/
        Void
recordStop( Int32 pid )
{
    Reg NODE    *p;
    Reg Nat32   numTasks;

    semTake( recordSem, WAIT_FOREVER );
    numTasks = lstCount( &recordList );

    if ( numTasks == 0 )
        printf("There are no recorder tasks to stop!\n");

    else if ( pid )
    {
        for ( p = lstFirst(&recordList); p != NODE_NULL; p = lstNext(p) )
            if ( pid == (Int32) NODEtoRS(p)->recPid )
            {
                NODEtoRS(p)->recStop = TRUE;
                break;
            }

        if ( p == NODE_NULL )
            printf( "Couldn't find recorder task %d\n", pid );
        semGive( recordSem );
    }
    else if ( numTasks == 1 )
    {
        p = lstFirst( &recordList );
        NODEtoRS(p)->recStop = TRUE;
        semGive( recordSem );
    }
    else
    {
        semGive( recordSem );
        printf("Usage: recordStop <pid>\n");
        printf("There are %d recorder tasks.  Use one of the following pids\n",
               numTasks);
        recordShow(0);
    }

} /* recordStop() */


/************************************************************************/
/* Function    : recordStart                                            */
/* Purpose     : Start a Flight Recorder task                           */
/* Inputs      : Configuration file name, Number of seconds to delay    */
/* Outputs     : Pid of record task started                             */
/************************************************************************/
        Int32
recordInit( Void )
{
    lstInit( &recordList );
    recordSem =
        semMCreate( SEM_Q_PRIORITY | SEM_DELETE_SAFE | SEM_INVERSION_SAFE );

} /* recordInit() */

