/* * LDASTools frameCPP - A library implementing the LIGO/Virgo frame
 * specification
 *
 * Copyright (C) 2018 California Institute of Technology
 *
 * LDASTools frameCPP is free software; you may redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 (GPLv2) of the
 * License or at your discretion, any later version.
 *
 * LDASTools frameCPP is distributed in the hope that it will be useful, but
 * without any warranty or even the implied warranty of merchantability
 * or fitness for a particular purpose. See the GNU General Public
 * License (GPLv2) for more details.
 *
 * Neither the names of the California Institute of Technology (Caltech),
 * The Massachusetts Institute of Technology (M.I.T), The Laser
 * Interferometer Gravitational-Wave Observatory (LIGO), nor the names
 * of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * You should have received a copy of the licensing terms for this
 * software included in the file LICENSE located in the top-level
 * directory of this package. If you did not, you can view a copy at
 * http://dcc.ligo.org/M1500244/LICENSE
 */

#include <stdio.h>

#include <string.h>
#include <unistd.h>

#include "framecppc/FrameC.h"
#include "framecppc/FrameH.h"
#include "framecppc/Stream.h"

#define CI_BUF_SIZE 4096

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

enum
{
    PASS = 0,
    FAIL = 1
};

FrameCError* ErrorCode;
int          Status = PASS;

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

static const char*
pass_fail( int Check )
{
    if ( !Check )
    {
        Status = FAIL;
    }
    return ( ( Check ) ? "PASS" : "FAIL" );
}

static void
log_test_result( const char* Routine, int PassFail, const char* Message )
{
    fprintf( stderr, "%s: %s: %s\n", pass_fail( PassFail ), Routine, Message );
}

/*---------------------------------------------------------------*/
void
ErrorCheck( const char* FunctionName, framec_errno_type EC )
{
    if ( ErrorCode )
    {
        const int s = ( ErrorCode->s_errno == EC );

        fprintf( stderr,
                 "%s: %s:error code: %d =?= %d [%s]\n",
                 pass_fail( s ),
                 FunctionName,
                 EC,
                 ErrorCode->s_errno,
                 ErrorCode->s_message );
    }
    else
    {
        const int s = ( FRAMEC_ERRNO_NO_ERROR == EC );

        fprintf( stderr,
                 "%s: %s:error code: %d =?= %d\n",
                 pass_fail( s ),
                 FunctionName,
                 EC,
                 FRAMEC_ERRNO_NO_ERROR );
    }
}

static frame_h_t*
generate_frame( int Offset, const char* Routine )
{
    frame_h_gtime_t t0 = { 600000000, 0 };
    frame_h_dt_t    dt = 16.0;
    frame_h_frame_t fn = 1;
    frame_h_t*      frame_h = (frame_h_t*)NULL;

    frame_h = FrameCFrameHAlloc( &ErrorCode, "LIGO", t0, dt, fn );
    if ( Routine )
    {
        ErrorCheck( Routine, FRAMEC_ERRNO_NO_ERROR );
    }

    return frame_h;
}

static inline int
frame_h_compare( const frame_h_t* LHS, const frame_h_t* RHS )
{
    int retval = 0;

    if ( ( LHS != (frame_h_t*)NULL ) &&
         ( RHS != (frame_h_t*)NULL )
#if DEPRICATED
         /** \todo Need to implement using query method */
         && ( LHS->t0.sec == RHS->t0.sec ) && ( LHS->t0.nan == RHS->t0.nan ) &&
         ( LHS->dt == RHS->dt )
#endif /* DEPRICATED */
    )
    {
        frame_h_name_t         name[ 2 ];
        frame_h_run_t          run[ 2 ];
        frame_h_frame_t        frame[ 2 ];
        frame_h_data_quality_t dq[ 2 ];
        frame_h_gtime_t        gtime[ 2 ];
        frame_h_uleaps_t       uleaps[ 2 ];
        frame_h_dt_t           dt[ 2 ];

        FrameCFrameHQuery( &ErrorCode,
                           LHS,
                           FRAME_H_FIELD_NAME,
                           &name[ 0 ],
                           FRAME_H_FIELD_RUN,
                           &run[ 0 ],
                           FRAME_H_FIELD_FRAME,
                           &frame[ 0 ],
                           FRAME_H_FIELD_DATA_QUALITY,
                           &dq[ 0 ],
                           FRAME_H_FIELD_GTIME,
                           &gtime[ 0 ],
                           FRAME_H_FIELD_ULEAPS,
                           &uleaps[ 0 ],
                           FRAME_H_FIELD_DT,
                           &dt[ 0 ],
                           FRAME_H_FIELD_LAST );

        FrameCFrameHQuery( &ErrorCode,
                           RHS,
                           FRAME_H_FIELD_NAME,
                           &name[ 1 ],
                           FRAME_H_FIELD_RUN,
                           &run[ 1 ],
                           FRAME_H_FIELD_FRAME,
                           &frame[ 1 ],
                           FRAME_H_FIELD_DATA_QUALITY,
                           &dq[ 1 ],
                           FRAME_H_FIELD_GTIME,
                           &gtime[ 1 ],
                           FRAME_H_FIELD_ULEAPS,
                           &uleaps[ 1 ],
                           FRAME_H_FIELD_DT,
                           &dt[ 1 ],
                           FRAME_H_FIELD_LAST );

        if ( ( strcmp( name[ 0 ], name[ 1 ] ) == 0 ) &&
             ( run[ 0 ] == run[ 1 ] ) && ( frame[ 0 ] == frame[ 1 ] ) &&
             ( dq[ 0 ] == dq[ 1 ] ) && ( gtime[ 0 ].sec == gtime[ 1 ].sec ) &&
             ( gtime[ 0 ].nan == gtime[ 1 ].nan ) &&
             ( uleaps[ 0 ] == uleaps[ 1 ] ) && ( dt[ 0 ] == dt[ 1 ] ) )
        {
            retval = 1;
        }
    }
    return retval;
}

/*---------------------------------------------------------------*/
void
NoInputFile( void )
{
    static const char* routine = "NoInputFile";
    /*
     * This test makes sure that an error is generated if a
     * request to open a non-existing file is made.
     */
    fr_file_t* ifs;

    ifs = FrameCFileOpen( &ErrorCode, "junk.gwf", FRAMEC_FILE_MODE_INPUT );

    ErrorCheck( routine, FRAMEC_ERRNO_FRAME_OPEN_ERROR );

    FrameCFileFree( &ErrorCode, ifs );
}

/*---------------------------------------------------------------*/
void
CloseUnknownFile( void )
{
    static const char* routine = "CloseUnknownFile";
    /*
     * This test makes sure that an error is generated if a
     * request to open a non-existing file is made.
     */
    fr_file_t* ofs;

    ofs = (fr_file_t*)( 3690 );
    FrameCFileClose( &ErrorCode, ofs );

    ErrorCheck( routine, FRAMEC_ERRNO_FRAME_CLOSE_ERROR );

    FrameCFileFree( &ErrorCode, ofs );
}

/*---------------------------------------------------------------*/
void
DoubleClose( void )
{
    static const char* routine = "DoubleClose";
    /*
     * This test makes sure that an error is generated if a
     * request to open a non-existing file is made.
     */
    fr_file_t* ofs;

    ofs = FrameCFileOpen( &ErrorCode, "ojunk.gwf", FRAMEC_FILE_MODE_OUTPUT );
    ErrorCheck( routine, FRAMEC_ERRNO_NO_ERROR );

    FrameCFileClose( &ErrorCode, ofs );
    ErrorCheck( routine, FRAMEC_ERRNO_NO_ERROR );

    FrameCFileClose( &ErrorCode, ofs );
    ErrorCheck( routine, FRAMEC_ERRNO_FRAME_CLOSE_ERROR );

    FrameCFileFree( &ErrorCode, ofs );
}
/*---------------------------------------------------------------*/
void
FrameHCreation( void )
{
    static const char* routine = "FrameHCreation";

    frame_h_gtime_t t0 = { 600000000, 0 };
    frame_h_dt_t    dt = 16.0;
    frame_h_frame_t fn = 1;
    frame_h_t*      f = (frame_h_t*)NULL;

    f = FrameCFrameHAlloc( &ErrorCode, "LIGO", t0, dt, fn );
    ErrorCheck( routine, FRAMEC_ERRNO_NO_ERROR );

    FrameCFrameHFree( &ErrorCode, f );
    ErrorCheck( routine, FRAMEC_ERRNO_NO_ERROR );

    FrameCFrameHFree( &ErrorCode, f );
    ErrorCheck( routine, FRAMEC_ERRNO_FRAME_H_FREE_ERROR );
}
/*---------------------------------------------------------------*/
void
StreamIO( void )
{
    static const char* routine = "StreamIO";
    static const char* filename = "StreamIO.gwf";

    fr_file_t* ofs = (fr_file_t*)NULL;
    int        test_result = 1;

    /*-------------------------------------------------------------
     * Generate a file to read
     *-------------------------------------------------------------*/
    ofs = FrameCFileOpen( &ErrorCode, filename, FRAMEC_FILE_MODE_OUTPUT );
    ErrorCheck( routine, FRAMEC_ERRNO_NO_ERROR );

    if ( ofs )
    {
        frame_h_t* frame0out = (frame_h_t*)NULL;
        frame_h_t* frame0in = (frame_h_t*)NULL;
        fr_file_t* ifs = (fr_file_t*)NULL;

        /*-----------------------------------------------------------
         * Write the frame data to the file
         *-----------------------------------------------------------*/
        frame0out = generate_frame( 0, routine );
        FrameCFrameHWrite( &ErrorCode, ofs, frame0out );
        ErrorCheck( routine, FRAMEC_ERRNO_NO_ERROR );

        if ( ofs )
        {
            FrameCFileClose( &ErrorCode, ofs );
        }
        /*-----------------------------------------------------------
         * Read the file
         *-----------------------------------------------------------*/

        ifs = FrameCFileOpen( &ErrorCode, filename, FRAMEC_FILE_MODE_INPUT );
        ErrorCheck( routine, FRAMEC_ERRNO_NO_ERROR );

        {
            char buffer[ CI_BUF_SIZE + 256 ];
            char library_name[ CI_BUF_SIZE ];
            int  ans = FrameCFrameLibrary( &ErrorCode, ifs );

            FrameCFrameLibraryName(
                &ErrorCode, ifs, library_name, sizeof( library_name ) );
            ErrorCheck( routine, FRAMEC_ERRNO_NO_ERROR );
            sprintf( buffer, "FrameLibrary: %d (%s)", ans, library_name );
            log_test_result( routine, 1, buffer );
        }

        {
            char buffer[ CI_BUF_SIZE ];
            int  ans = FrameCFrameLibraryVersion( &ErrorCode, ifs );

            ErrorCheck( routine, FRAMEC_ERRNO_NO_ERROR );
            sprintf( buffer, "FrameLibraryVersion: %d", ans );
            log_test_result( routine, 1, buffer );
        }

        {
            char buffer[ CI_BUF_SIZE ];
            int  ans = FrameCFrameLibraryVersionMinor( &ErrorCode, ifs );

            ErrorCheck( routine, FRAMEC_ERRNO_NO_ERROR );
            sprintf( buffer, "FrameLibraryVersionMinor: %d", ans );
            log_test_result( routine, 1, buffer );
        }

        frame0in = FrameCFrameHRead( &ErrorCode, ifs, 0 );
        ErrorCheck( routine, FRAMEC_ERRNO_NO_ERROR );

        test_result = frame_h_compare( frame0out, frame0in );
        log_test_result( routine,
                         test_result,
                         "Frame written is equivelant to the frame read." );

        /*-----------------------------------------------------------
         * Reclaim resources
         *-----------------------------------------------------------*/
        if ( frame0in )
        {
            FrameCFrameHFree( &ErrorCode, frame0in );
        }
        if ( frame0out )
        {
            FrameCFrameHFree( &ErrorCode, frame0out );
        }
        if ( ifs )
        {
            FrameCFileClose( &ErrorCode, ifs );
        }
        FrameCFileFree( &ErrorCode, ifs );
    }

    FrameCFileFree( &ErrorCode, ofs );
    /* unlink( filename ); */
}
/*---------------------------------------------------------------*/

/*---------------------------------------------------------------
 * This is main where the tests are actually executed.
 *---------------------------------------------------------------*/
int
main( int ArgC, char** ArgV )
{
    FrameCErrorInit( &ErrorCode );

    printf( "FrameC library version %d\n", FrameCLibraryMinorVersion( ) );

    /*
     * Test that error conditions are properly detected.
     */
    /*--------------------------------------------------------------------*/
    NoInputFile( );
    CloseUnknownFile( );
    DoubleClose( );
    /*--------------------------------------------------------------------*/
    FrameHCreation( );
    /*
     * Test Reading/Writing of a frame file
     */
    StreamIO( );
    /*--------------------------------------------------------------------*/
    /* Prepare for our departure                                          */
    /*--------------------------------------------------------------------*/
    FrameCCleanup( );
    return ( Status );
}
