/* * 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 <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "framecppc/FrameC.h"
#include "framecppc/FrChan.h"
#include "framecppc/FrDetector.h"
#include "framecppc/FrameH.h"
#include "framecppc/FrTOC.h"
#include "framecppc/Stream.h"

/*----------------------------------------------------------------------
 * Global variables
 */
FrameCError* ErrorCode; /* Records the error code	*/
const char*  ProcessName; /* Name of the running process	*/

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

int
Failed( const char* FunctionName )
{
    if ( ErrorCode )
    {
        fprintf( stderr,
                 "%s: FAIL: %s: %s (%d)\n",
                 ProcessName,
                 FunctionName,
                 ErrorCode->s_message,
                 ErrorCode->s_errno );
        return 1;
    }
    return 0;
}

const char*
compression( fr_vect_compress_t Value )
{
    switch ( ( Value & ~FR_VECT_COMPRESS_LITTLEENDIAN ) )
    {
    case FR_VECT_COMPRESS_UNKNOWN:
        return "UNKNOWN";
    case FR_VECT_COMPRESS_RAW:
        return "RAW";
    case FR_VECT_COMPRESS_GZIP:
        return "GZIP";
    case FR_VECT_COMPRESS_DIFF_GZIP:
        return "DIFF_GZIP";
    case FR_VECT_COMPRESS_ZERO_SUPPRESS_WORD_2:
        return "ZERO_SUPPRESS_WORD_2";
    case FR_VECT_COMPRESS_ZERO_SUPPRESS_WORD_4:
        return "ZERO_SUPPRESS_WORD_4";
    case FR_VECT_COMPRESS_ZERO_SUPPRESS_WORD_8:
        return "ZERO_SUPPRESS_WORD_8";
    default:
        break;
    }
    return "unknown";
}

const char*
data_type( fr_vect_type_t Value )
{
    switch ( Value )
    {
    case FR_VECT_C:
        return "FR_VECT_C";
    case FR_VECT_2S:
        return "FR_VECT_2S";
    case FR_VECT_8R:
        return "FR_VECT_8R";
    case FR_VECT_4R:
        return "FR_VECT_4R";
    case FR_VECT_4S:
        return "FR_VECT_4S";
    case FR_VECT_8S:
        return "FR_VECT_8S";
    case FR_VECT_8C:
        return "FR_VECT_8C";
    case FR_VECT_16C:
        return "FR_VECT_16C";
    case FR_VECT_STRING:
        return "FR_VECT_STRING";
    case FR_VECT_2U:
        return "FR_VECT_2U";
    case FR_VECT_4U:
        return "FR_VECT_4U";
    case FR_VECT_8U:
        return "FR_VECT_8U";
    case FR_VECT_1U:
        return "FR_VECT_1U";
    default:
        break;
    }
    return "unknown";
}

void
DumpChannel( fr_chan_t* Channel )
{
    static const char* FUNCTION_NAME = "DumpChannel";

    fr_chan_channel_group_t  ch_group;
    fr_chan_channel_number_t ch_number;
    fr_chan_comment_t        ch_comment;
    fr_chan_time_offset_t    ch_time_offset;

    fr_vect_name_t     vname;
    fr_vect_compress_t compress;
    fr_vect_type_t     type;
    fr_vect_ndata_t    ndata;
    fr_vect_nbytes_t   nbytes;
    fr_vect_ndim_t     ndim;
    fr_vect_nx_t*      nx = (fr_vect_nx_t*)NULL;
    fr_vect_dx_t*      dx = (fr_vect_dx_t*)NULL;
    fr_vect_startx_t*  startx = (fr_vect_startx_t*)NULL;
    fr_vect_unit_x_t*  unitx = (fr_vect_unit_x_t*)NULL;
    fr_vect_unit_y_t   unity;

    int    littleendian;
    size_t ul_ndata;
    size_t ul_nbytes;
    size_t ul_ndim;
    size_t dim_cur;

    FrameCFrChanQuery( &ErrorCode,
                       Channel,
                       FR_CHAN_FIELD_CHANNEL_GROUP,
                       &ch_group,
                       FR_CHAN_FIELD_CHANNEL_NUMBER,
                       &ch_number,
                       FR_CHAN_FIELD_COMMENT,
                       &ch_comment,
                       FR_CHAN_FIELD_TIME_OFFSET,
                       &ch_time_offset,
                       FR_CHAN_FIELD_LAST );

    if ( Failed( FUNCTION_NAME ) )
        goto dump_channel_free_cleanup_error;

    FrameCFrChanVectorQuery( &ErrorCode,
                             Channel,
                             FR_VECT_FIELD_NAME,
                             &vname,
                             FR_VECT_FIELD_COMPRESS,
                             &compress,
                             FR_VECT_FIELD_TYPE,
                             &type,
                             FR_VECT_FIELD_NDATA,
                             &ndata,
                             FR_VECT_FIELD_NBYTES,
                             &nbytes,
                             FR_VECT_FIELD_NDIM,
                             &ndim,
                             FR_VECT_FIELD_UNIT_Y,
                             &unity,
                             FR_VECT_FIELD_LAST );

    if ( Failed( FUNCTION_NAME ) )
        goto dump_channel_error;

    littleendian = ( compress & FR_VECT_COMPRESS_LITTLEENDIAN );
    nx = (fr_vect_nx_t*)calloc( ndim, sizeof( fr_vect_nx_t ) );
    dx = (fr_vect_dx_t*)calloc( ndim, sizeof( fr_vect_dx_t ) );
    startx = (fr_vect_startx_t*)calloc( ndim, sizeof( fr_vect_startx_t ) );
    unitx = (fr_vect_unit_x_t*)calloc( ndim, sizeof( fr_vect_unit_x_t ) );

    FrameCFrChanVectorQuery( &ErrorCode,
                             Channel,
                             FR_VECT_FIELD_NX,
                             nx,
                             ndim,
                             FR_VECT_FIELD_DX,
                             dx,
                             ndim,
                             FR_VECT_FIELD_START_X,
                             startx,
                             ndim,
                             FR_VECT_FIELD_UNIT_X,
                             unitx,
                             ndim,
                             FR_VECT_FIELD_LAST );

    if ( Failed( FUNCTION_NAME ) )
        goto dump_channel_free_cleanup_error;

    ul_ndata = ndata;
    ul_nbytes = nbytes;
    ul_ndim = ndim;

    printf( "Channel\n"
            "\tgroup: %d\n"
            "\tnumber: %d\n"
            "\tcomment: %s\n"
            "\ttime_offset: %g\n"
            "\tVector Info\n"
            "\t\tname: %s\n"
            "\t\tcompression: %s (%s)\n"
            "\t\ttype: %s\n"
            "\t\tndata: %lu\n"
            "\t\tnbytes: %lu\n"
            "\t\tndim: %lu\n",
            ch_group,
            ch_number,
            ch_comment,
            ch_time_offset,
            vname,
            compression( compress ),
            ( ( littleendian ) ? "littleendian" : "bigendian" ),
            data_type( type ),
            (unsigned long)ul_ndata,
            (unsigned long)ul_nbytes,
            (unsigned long)ul_ndim );

    for ( dim_cur = 0; dim_cur < ndim; ++dim_cur )
    {
        printf( "\t\tdim[%lu]:"
                " nx: %lu"
                " dx: %g"
                " startX: %g"
                " unitX: %s"
                "\n",
                (unsigned long)dim_cur,
                (unsigned long)( nx[ dim_cur ] ),
                dx[ dim_cur ],
                startx[ dim_cur ],
                unitx[ dim_cur ] );
    }

    printf( "\t\tunitY: %s\n", unity );

dump_channel_free_cleanup_error:

    free( nx );
    free( dx );
    free( startx );
    free( unitx );

dump_channel_error:
    return;
}

void
DumpChannels( fr_file_t* Stream, fr_toc_nframe_t FrameIndex )
{
    fr_toc_t*       toc = FrameCFrTOCRead( &ErrorCode, Stream );
    fr_toc_adc_n_t  adc_count;
    fr_toc_proc_n_t proc_count;
    fr_toc_sim_n_t  sim_count;

    FrameCFrTOCQuery( &ErrorCode,
                      toc,
                      FR_TOC_FIELD_ADC_N,
                      &adc_count,
                      FR_TOC_FIELD_PROC_N,
                      &proc_count,
                      FR_TOC_FIELD_SIM_N,
                      &sim_count,
                      FR_TOC_FIELD_LAST );

    printf( "There %s %d adc channel%s in the stream\n",
            ( ( adc_count == 1 ) ? "is" : "are" ),
            adc_count,
            ( ( adc_count == 1 ) ? "" : "s" ) );
    if ( adc_count > 0 )
    {
        fr_toc_adc_n_t cur;

        fr_toc_adc_name_t* names = (fr_toc_adc_name_t*)calloc(
            adc_count, sizeof( fr_toc_adc_name_t ) );

        FrameCFrTOCQuery( &ErrorCode,
                          toc,
                          FR_TOC_FIELD_ADC_NAMES,
                          names,
                          adc_count,
                          FR_TOC_FIELD_LAST );

        for ( cur = 0; cur < adc_count; ++cur )
        {
            fr_chan_t* channel;
            channel = FrameCFrChanRead(
                &ErrorCode, Stream, names[ cur ], FrameIndex );

            if ( ErrorCode )
            {
                goto adc_cleanup;
            }

            if ( channel )
            {
                DumpChannel( channel );
            }

        adc_cleanup:
            FrameCFrChanFree( &ErrorCode, channel );
        }
        if ( names )
        {
            free( names );
        }
    }

    printf( "There %s %d proc channel%s in the stream\n",
            ( ( proc_count == 1 ) ? "is" : "are" ),
            proc_count,
            ( ( proc_count == 1 ) ? "" : "s" ) );
    if ( proc_count > 0 )
    {
        fr_toc_proc_n_t cur;

        fr_toc_proc_name_t* names = (fr_toc_proc_name_t*)calloc(
            proc_count, sizeof( fr_toc_proc_name_t ) );

        FrameCFrTOCQuery( &ErrorCode,
                          toc,
                          FR_TOC_FIELD_PROC_NAMES,
                          names,
                          proc_count,
                          FR_TOC_FIELD_LAST );

        for ( cur = 0; cur < proc_count; ++cur )
        {
            fr_chan_t* channel;
            channel = FrameCFrChanRead(
                &ErrorCode, Stream, names[ cur ], FrameIndex );

            if ( ErrorCode )
            {
                goto proc_cleanup;
            }

            if ( channel )
            {
                DumpChannel( channel );
            }

        proc_cleanup:
            FrameCFrChanFree( &ErrorCode, channel );
        }
        if ( names )
        {
            free( names );
        }
    }

    printf( "There %s %d sim channel%s in the stream\n",
            ( ( sim_count == 1 ) ? "is" : "are" ),
            sim_count,
            ( ( sim_count == 1 ) ? "" : "s" ) );
    if ( sim_count > 0 )
    {
        fr_toc_sim_n_t cur;

        fr_toc_sim_name_t* names = (fr_toc_sim_name_t*)calloc(
            sim_count, sizeof( fr_toc_sim_name_t ) );

        FrameCFrTOCQuery( &ErrorCode,
                          toc,
                          FR_TOC_FIELD_SIM_NAMES,
                          names,
                          sim_count,
                          FR_TOC_FIELD_LAST );

        for ( cur = 0; cur < sim_count; ++cur )
        {
            fr_chan_t* channel;
            channel = FrameCFrChanRead(
                &ErrorCode, Stream, names[ cur ], FrameIndex );

            if ( ErrorCode )
            {
                goto sim_cleanup;
            }

            if ( channel )
            {
                DumpChannel( channel );
            }

        sim_cleanup:
            FrameCFrChanFree( &ErrorCode, channel );
        }
        if ( names )
        {
            free( names );
        }
    }

    FrameCFrTOCFree( &ErrorCode, toc );
}

void
ProcessFile( const char* Filename )
{
    static const char* FUNCTION_NAME = "ProcessFile";

    fr_file_t* ifs = (fr_file_t*)NULL;
    fr_toc_t*  toc = (fr_toc_t*)NULL;

    /*--------------------------------------------------------------------
     * Open the file for reading
     */
    ifs = FrameCFileOpen( &ErrorCode, Filename, FRAMEC_FILE_MODE_INPUT );

    if ( Failed( FUNCTION_NAME ) )
        goto process_file_error;

    /*--------------------------------------------------------------------
     * List all of the channels in the specified frame
     */
    toc = FrameCFrTOCRead( &ErrorCode, ifs );
    if ( Failed( FUNCTION_NAME ) )
        goto process_file_error;

    {
        /*------------------------------------------------------------------
         * Scoping introduced to allow variable declaration to happen
         * close to their use.
         */

        fr_toc_nframe_t         nframes;
        fr_toc_detector_n_t     detector_count;
        fr_toc_detector_name_t* detector_names = (fr_toc_detector_name_t*)NULL;

        FrameCFrTOCQuery( &ErrorCode,
                          toc,
                          FR_TOC_FIELD_NFRAME,
                          &nframes,
                          FR_TOC_FIELD_DETECTOR_N,
                          &detector_count,
                          FR_TOC_FIELD_LAST );
        if ( Failed( FUNCTION_NAME ) )
            goto process_file_error;

        printf( "There %s %d detector%s in the stream\n",
                ( ( detector_count == 1 ) ? "is" : "are" ),
                detector_count,
                ( ( detector_count == 1 ) ? "" : "s" ) );

        if ( detector_count > 0 )
        {
            fr_toc_detector_n_t x;

            detector_names = (fr_toc_detector_name_t*)calloc(
                detector_count, sizeof( fr_toc_detector_name_t ) );
            /*
             * Collect the names of the detectors
             */
            FrameCFrTOCQuery( &ErrorCode,
                              toc,
                              FR_TOC_FIELD_DETECTOR_NAMES,
                              detector_names,
                              detector_count,
                              FR_TOC_FIELD_LAST );
            if ( Failed( FUNCTION_NAME ) )
            {
                free( detector_names );
                goto process_file_error;
            }

            /*
             * Read the structure for each detector and display information
             * for the user.
             */
            for ( x = 0; x < detector_count; x++ )
            {
                fr_detector_t* d;

                fr_detector_name_t           name;
                fr_detector_prefix_t         prefix;
                fr_detector_latitude_t       latitude = 0.0;
                fr_detector_longitude_t      longitude = 0.0;
                fr_detector_elevation_t      elevation = 0.0;
                fr_detector_arm_x_azimuth_t  azimuth_x = 0.0;
                fr_detector_arm_y_azimuth_t  azimuth_y = 0.0;
                fr_detector_arm_x_altitude_t altitude_x = 0.0;
                fr_detector_arm_y_altitude_t altitude_y = 0.0;
                fr_detector_arm_x_midpoint_t midpoint_x = 0.0;
                fr_detector_arm_y_midpoint_t midpoint_y = 0.0;
                fr_detector_localtime_t      local_time = 0;

                d = FrameCFrDetectorRead(
                    &ErrorCode, ifs, detector_names[ x ] );
                if ( Failed( FUNCTION_NAME ) )
                {
                    free( detector_names );
                    goto process_file_error;
                }

                FrameCFrDetectorQuery( &ErrorCode,
                                       d,
                                       FR_DETECTOR_FIELD_NAME,
                                       &name,
                                       FR_DETECTOR_FIELD_PREFIX,
                                       &prefix,
                                       FR_DETECTOR_FIELD_LATITUDE,
                                       &latitude,
                                       FR_DETECTOR_FIELD_LONGITUDE,
                                       &longitude,
                                       FR_DETECTOR_FIELD_ELEVATION,
                                       &elevation,
                                       FR_DETECTOR_FIELD_ARM_X_AZIMUTH,
                                       &azimuth_x,
                                       FR_DETECTOR_FIELD_ARM_Y_AZIMUTH,
                                       &azimuth_y,
                                       FR_DETECTOR_FIELD_ARM_X_ALTITUDE,
                                       &altitude_x,
                                       FR_DETECTOR_FIELD_ARM_Y_ALTITUDE,
                                       &altitude_y,
                                       FR_DETECTOR_FIELD_ARM_X_MIDPOINT,
                                       &midpoint_x,
                                       FR_DETECTOR_FIELD_ARM_Y_MIDPOINT,
                                       &midpoint_y,
                                       FR_DETECTOR_FIELD_LOCAL_TIME,
                                       &local_time,
                                       FR_DETECTOR_FIELD_LAST );

                if ( Failed( FUNCTION_NAME ) )
                {
                    FrameCFrDetectorFree( &ErrorCode, d );
                    free( detector_names );
                    goto process_file_error;
                }

                printf( "Detector Info: %s\n"
                        "\tprefix: %c%c\n"
                        "\tlatitude: %g longitude: %g elevation: %g\n"
                        "\tazimuth: x-arm: %g y-arm: %g\n"
                        "\taltitude: x-arm: %g y-arm: %g\n"
                        "\tmidpoint: x-arm: %g y-arm: %g\n"
                        "\tlocal time: %d\n",
                        name,
                        ( prefix.prefix[ 0 ] ) ? prefix.prefix[ 0 ] : ' ',
                        ( prefix.prefix[ 0 ] && prefix.prefix[ 1 ] )
                            ? prefix.prefix[ 1 ]
                            : ' ',
                        latitude,
                        longitude,
                        elevation,
                        azimuth_x,
                        azimuth_y,
                        altitude_x,
                        altitude_y,
                        midpoint_x,
                        midpoint_y,
                        local_time );

                FrameCFrDetectorFree( &ErrorCode, d );
            }

            /*
             * Return resources back to the system.
             */
            free( detector_names );
        }

        printf( "There %s %d frame%s in the stream\n",
                ( ( nframes == 1 ) ? "is" : "are" ),
                nframes,
                ( ( nframes == 1 ) ? "" : "s" ) );

        if ( nframes > 0 )
        {
            fr_toc_nframe_t frame_cur;

            for ( frame_cur = 0; frame_cur < nframes; ++frame_cur )
            {
                frame_h_t*             frame;
                frame_h_name_t         name;
                frame_h_run_t          run;
                frame_h_frame_t        frame_num;
                frame_h_data_quality_t dq;
                frame_h_gtime_t        gtime;
                frame_h_dt_t           dt;
                frame_h_uleaps_t       uleaps;

                frame = FrameCFrameHRead( &ErrorCode, ifs, frame_cur );

                if ( Failed( FUNCTION_NAME ) )
                {
                    goto process_file_error;
                }

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

                if ( Failed( FUNCTION_NAME ) )
                {
                    FrameCFrameHFree( &ErrorCode, frame );
                    goto process_file_error;
                }

                printf( "Frame Info[%d]: %s\n"
                        "\trun: %d\n"
                        "\tframe: %d\n"
                        "\tdata quality: %d\n"
                        "\tgtime: %d.%06d\n"
                        "\tdt: %g\n"
                        "\tuleaps: %d\n",
                        frame_cur,
                        name,
                        run,
                        frame_num,
                        dq,
                        gtime.sec,
                        gtime.nan,
                        dt,
                        uleaps );

                FrameCFrameHFree( &ErrorCode, frame );

                DumpChannels( ifs, frame_cur );
            }
        }
    }

    /*--------------------------------------------------------------------
     * Reclaim system resources
     */
    FrameCFrTOCFree( &ErrorCode, toc );
    if ( Failed( FUNCTION_NAME ) )
        goto process_file_error;
    FrameCFileClose( &ErrorCode, ifs );
    if ( Failed( FUNCTION_NAME ) )
        return;
    return;

process_file_error:
    if ( toc )
        FrameCFrTOCFree( &ErrorCode, toc );
    if ( ifs )
        FrameCFileClose( &ErrorCode, ifs );
    return;
}

int
main( int ArgC, char** ArgV )
{
    char** current_arg;

    ProcessName = ArgV[ 0 ];
    current_arg = &( ArgV[ 1 ] );

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

    while ( *current_arg )
    {
        FrameCErrorInit( &ErrorCode );

        ProcessFile( *current_arg );
        ++current_arg;
    }
}
