//
// 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 <framecpp_config.h>

#include <memory.h>

#include <cmath>

#include "framecpp/Common/FrameSpec.hh"
#include "framecpp/Common/IOStream.hh"

#include "framecpp/FrameH.hh"
#include "framecpp/GPSTime.hh"
#include "framecpp/FrAdcData.hh"
#include "framecpp/FrDetector.hh"
#include "framecpp/FrEvent.hh"
#include "framecpp/FrHistory.hh"
#include "framecpp/FrProcData.hh"
#include "framecpp/FrSimData.hh"
#include "framecpp/FrSimEvent.hh"

#include "framecppc/FrameH.h"
#include "framecppc/FrChan.h"
#include "framecppc/FrEvent.h"

#include "FrameCInternal.hh"
#include "StreamInternal.hh"

#include "FrameHInternal.hh"
#include "FrChanInternal.hh"
#include "FrDetectorInternal.hh"
#include "FrEventInternal.hh"
#include "FrHistoryInternal.hh"

using FrameC::Set;

frame_h_t*
FrameCFrameHAlloc( FrameCError**   Error,
                   const char*     Name,
                   frame_h_gtime_t t0,
                   frame_h_dt_t    dt,
                   frame_h_frame_t frame )
{
    Set( Error );

    FrameC::FrameH*                   retval = (FrameC::FrameH*)NULL;
    std::unique_ptr< FrameC::FrameH > tmp;

    try
    {
        tmp.reset( new FrameC::FrameH( Name, t0, dt, frame ) );
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_ALLOC_ERROR, Exception.what( ) );
        tmp.reset( (FrameC::FrameH*)NULL );
    }

    if ( tmp.get( ) )
    {
        FrameC::Handle::Deposit( tmp.get( ) );
        retval = tmp.release( );
    }
    return ( reinterpret_cast< frame_h_t* >( retval ) );
}

int
FrameCFrameHFrChanAdd( FrameCError** Error,
                       frame_h_t*    frame,
                       fr_chan_t*    Channel )
{
    int retval = true;

    Set( Error );

    const FrameC::FrameH* ooframe =
        FrameC::HandleCast< const FrameC::FrameH* >( frame );

    FrameC::FrChannel* oochannel( (FrameC::FrChannel*)Channel );

    try
    {
        FrameC::Handle::Validate( ooframe, FrameC::POINTER_FRAME_H );

        switch ( oochannel->Type( ) )
        {
        case FR_ADC_CHAN_TYPE:
        {
            //---------------------------------------------------------------
            // Verify the existance of the intermediate buffer
            //---------------------------------------------------------------
            FrameCPP::FrameH::rawData_type rd = ooframe->m_data->GetRawData( );

            if ( !rd )
            {
                //-------------------------------------------------------------
                // Doesn't yet exist so create it and reassign the pointer
                // to the allocated memory
                //-------------------------------------------------------------
                FrameCPP::FrameH::rawData_type rdv(
                    new FrameCPP::FrameH::rawData_type::element_type( ) );
                ooframe->m_data->SetRawData( rdv );
                rd = ooframe->m_data->GetRawData( );
            }
            //---------------------------------------------------------------
            // Deep copy the information so as not to be tied to the callers
            // usage model.
            //---------------------------------------------------------------
            rd->RefFirstAdc( ).append(
                oochannel->Channel< FrameCPP::FrAdcData >( ) );
        }
        break;
        case FR_EVENT_CHAN_TYPE:
            ooframe->m_data->RefEvent( ).append(
                oochannel->Channel< FrameCPP::FrEvent >( ) );
            break;
        case FR_PROC_CHAN_TYPE:
            ooframe->m_data->RefProcData( ).append(
                oochannel->Channel< FrameCPP::FrProcData >( ) );
            break;
        case FR_SIM_CHAN_TYPE:
            ooframe->m_data->RefSimData( ).append(
                oochannel->Channel< FrameCPP::FrSimData >( ) );
            break;
        case FR_SIM_EVENT_CHAN_TYPE:
            ooframe->m_data->RefSimEvent( ).append(
                oochannel->Channel< FrameCPP::FrSimEvent >( ) );
            break;
        default:
            break;
        }
    }
    catch ( const std::exception& Exception )
    {
        Set( Error,
             FRAMEC_ERRNO_FR_CHANNEL_COMPRESSION_ERROR,
             Exception.what( ) );
    }

    if ( *Error )
    {
        retval = false;
    }

    return retval;
}

int
FrameCFrameHFrDetectorAdd( FrameCError**  Error,
                           frame_h_t*     frame,
                           fr_detector_t* Detector )
{
    int retval = true;

    Set( Error );

    const FrameC::FrameH* ooframe =
        FrameC::HandleCast< const FrameC::FrameH* >( frame );

    FrameC::FrDetector* oodetector( (FrameC::FrDetector*)Detector );

    try
    {
        FrameC::Handle::Validate( ooframe, FrameC::POINTER_FRAME_H );

        ooframe->m_data->RefDetectProc( ).append( oodetector->m_data );
    }
    catch ( const std::exception& Exception )
    {
        // \todo Need FRAMEC_ERRNO_FRAME_H_APPEND_ERROR
        Set( Error, FRAMEC_ERRNO_FRAME_H_ACCESSOR_ERROR, Exception.what( ) );
    }

    if ( *Error )
    {
        retval = false;
    }

    return retval;
}

int
FrameCFrameHFree( FrameCError** Error, frame_h_t* frame )
{
    int retval = true;

    Set( Error );

    FrameC::Handle::Free( Error,
                          frame,
                          FrameC::POINTER_FRAME_H,
                          FRAMEC_ERRNO_FRAME_H_FREE_ERROR );

    if ( *Error )
    {
        retval = false;
    }

    return retval;
}

int
FrameCFrameHFrEventAdd( FrameCError** Error,
                        frame_h_t*    frame,
                        fr_event_t*   Event )
{
    int retval = true;

    Set( Error );

    const FrameC::FrameH* ooframe =
        FrameC::HandleCast< const FrameC::FrameH* >( frame );

    FrameC::FrEvent* ooevent( (FrameC::FrEvent*)Event );

    try
    {
        FrameC::Handle::Validate( ooframe, FrameC::POINTER_FRAME_H );

        ooframe->m_data->RefEvent( ).append( ooevent->m_data );
    }
    catch ( const std::exception& Exception )
    {
        // \todo Need FRAMEC_ERRNO_FRAME_H_APPEND_ERROR
        Set( Error, FRAMEC_ERRNO_FRAME_H_ACCESSOR_ERROR, Exception.what( ) );
    }

    if ( *Error )
    {
        retval = false;
    }

    return retval;
}

int
FrameCFrameHFrHistoryAdd( FrameCError** Error,
                          frame_h_t*    frame,
                          fr_history_t* History )
{
    int retval = true;

    Set( Error );

    const FrameC::FrameH* ooframe =
        FrameC::HandleCast< const FrameC::FrameH* >( frame );

    FrameC::FrHistory* oohistory( (FrameC::FrHistory*)History );

    try
    {
        FrameC::Handle::Validate( ooframe, FrameC::POINTER_FRAME_H );

        ooframe->m_data->RefHistory( ).append( oohistory->m_data );
    }
    catch ( const std::exception& Exception )
    {
        // \todo Need FRAMEC_ERRNO_FRAME_H_APPEND_ERROR
        Set( Error, FRAMEC_ERRNO_FRAME_H_ACCESSOR_ERROR, Exception.what( ) );
    }

    if ( *Error )
    {
        retval = false;
    }

    return retval;
}

/**
 * Retrieve information about the FrameH structure.
 * The Option parameter dictates the number and type of
 * parameters to follow.
 * Multiple pieces of information can be retrieved by having
 * multiple Option/parameter sets.
 * The last value for Option must be FRAME_H_FIELD_LAST to indicate
 * the end of the variable length argument list.
 */
void
FrameCFrameHQuery( FrameCError**    Error,
                   const frame_h_t* Frame,
                   int              Option,
                   ... )
{
    using namespace FrameC;

    Set( Error );

    const FrameH* frame = HandleCast< const FrameH* >( Frame );

    try
    {
        va_list ap;
        va_start( ap, Option );

        if ( frame )
        {
            while ( Option != FRAME_H_FIELD_LAST )
            {
                /**
                 * The following is a discription of each value of Option and
                 * the parameters it takes.
                 *
                 * <ul>
                 *   <li> <b>FRAME_H_FIELD_LAST</b>
                 *      <p> This is the last option in the list and specifies
                 *          the end of the query.
                 *      </p>
                 *   </li>
                 */
                switch ( Option )
                {
                case FRAME_H_FIELD_DATA_QUALITY:
                    /**
                     *   <li> <b>FRAME_H_FIELD_DATA_QUALITY</b>
                     *      <p> Retrieve the data quality flag of the frame.
                     * 	    A single argument should follow this option.
                     *          The argument needs to be the address
                     *          of type <b>frame_h_data_quality_t</b>
                     *          The frame number
                     *          will be stored here.
                     *      </p>
                     *   </li>
                     */
                    {
                        typedef frame_h_data_quality_t x_t;
                        x_t*                           x;

                        x = va_arg( ap, x_t* );
                        *x = frame->m_data->GetDataQuality( );
                    }
                    break;
                case FRAME_H_FIELD_DT:
                    /**
                     *   <li> <b>FRAME_H_FIELD_DT</b>
                     *      <p> Retrieve the frame length.
                     * 	    A single argument should follow this option.
                     *          The argument needs to be the address
                     *          of type <b>frame_h_dt_t</b>
                     *          The length of the frame
                     *          will be stored here.
                     *      </p>
                     *   </li>
                     */
                    {
                        typedef frame_h_dt_t x_t;
                        x_t*                 x;

                        x = va_arg( ap, x_t* );
                        *x = frame->m_data->GetDt( );
                    }
                    break;
                case FRAME_H_FIELD_GTIME:
                    /**
                     *   <li> <b>FRAME_H_FIELD_GTIME</b>
                     *      <p> Retrieve the start time of the frame.
                     * 	    A single argument should follow this option.
                     *          The argument needs to be the address
                     *          of type <b>frame_h_gtime_t</b>
                     *          The frame start time
                     *          will be stored here.
                     *      </p>
                     *   </li>
                     */
                    {
                        typedef frame_h_gtime_t x_t;
                        x_t*                    x;

                        x = va_arg( ap, x_t* );
                        x->sec = frame->m_data->GetGTime( ).GetSeconds( );
                        x->nan = frame->m_data->GetGTime( ).GetNanoseconds( );
                    }
                    break;
                case FRAME_H_FIELD_FRAME:
                    /**
                     *   <li> <b>FRAME_H_FIELD_FRAME</b>
                     *      <p> Retrieve the frame number of the frame.
                     * 	    A single argument should follow this option.
                     *          The argument needs to be the address
                     *          of type <b>frame_h_frame_t</b>
                     *          The frame number
                     *          will be stored here.
                     *      </p>
                     *   </li>
                     */
                    {
                        typedef frame_h_frame_t x_t;
                        x_t*                    x;

                        x = va_arg( ap, x_t* );
                        *x = frame->m_data->GetFrame( );
                    }
                    break;
                case FRAME_H_FIELD_NAME:
                    /**
                     *   <li> <b>FRAME_H_FIELD_NAME</b>
                     *      <p> Retrieve the name of the project or other
                     * experiment description. A single argument should follow
                     * this option. The argument needs to be the address of type
                     * <b>frame_h_name_t</b> The description will be stored
                     * here. <b>NOTE:</b> This is a shallow pointer which is
                     * only valid while the frame_h_t object exists.
                     *      </p>
                     *   </li>
                     */
                    {
                        typedef frame_h_name_t x_t;
                        x_t*                   x;

                        x = va_arg( ap, x_t* );
                        *x = frame->m_data->GetName( ).c_str( );
                    }
                    break;
                case FRAME_H_FIELD_RUN:
                    /**
                     *   <li> <b>FRAME_H_FIELD_RUN</b>
                     *      <p> Retrieve the run number of the frame.
                     * 	    A single argument should follow this option.
                     *          The argument needs to be the address
                     *          of type <b>frame_h_run_t</b>
                     *          The run number
                     *          will be stored here.
                     *      </p>
                     *   </li>
                     */
                    {
                        typedef frame_h_run_t x_t;
                        x_t*                  x;

                        x = va_arg( ap, x_t* );
                        *x = frame->m_data->GetRun( );
                    }
                    break;
                default:
                    /**************************************************************
                      An unknown option has been specified. Terminate the
                    request.
                    ***************************************************************/
                    Option = FRAME_H_FIELD_LAST;
                    continue;
                }
                /**
                 * </ul>
                 */
#if 0
	int next_opt = va_arg(ap, int /* frame_h_fields*/ );
	Option = (frame_h_fields)next_opt;
#else
                Option = va_arg( ap, int /* frame_h_fields*/ );
#endif
            }
        }
        else
        {
            Set( Error,
                 FRAMEC_ERRNO_FRAME_H_ACCESSOR_ERROR,
                 "NULL reference to the frame" );
        }
        va_end( ap );
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_ACCESSOR_ERROR, Exception.what( ) );
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_ACCESSOR_ERROR, FrameC::UnknownError );
    }
}

/**
 * Set information about the FrameH structure.
 * The Option parameter dictates the number and type of
 * parameters to follow.
 * Multiple pieces of information can be set by having
 * multiple Option/parameter sets.
 * The last value for Option must be FRAME_H_FIELD_LAST to indicate
 * the end of the variable length argument list.
 *
 */
void
FrameCFrameHSet( FrameCError** Error, const frame_h_t* Frame, int Option, ... )
{
    using namespace FrameC;

    Set( Error );

    const FrameH* frame = HandleCast< const FrameH* >( Frame );

    try
    {
        va_list ap;
        va_start( ap, Option );

        if ( frame )
        {
            while ( Option != FRAME_H_FIELD_LAST )
            {
                /**
                 * The following is a discription of each value of Option and
                 * the parameters it takes.
                 *
                 * <ul>
                 *   <li> <b>FRAME_H_FIELD_LAST</b>
                 *      <p> This is the last option in the list and specifies
                 *          the end of the query.
                 *      </p>
                 *   </li>
                 */
                switch ( Option )
                {
                case FRAME_H_FIELD_RUN:
                    /**
                     *   <li> <b>FRAME_H_FIELD_RUN</b>
                     *      <p> Set the run number of the frame.
                     * 	    A single argument should follow this option.
                     *          The argument needs to be
                     *          of type <b>frame_h_run_t</b>
                     *          and contains the new value for
                     *          the run number.
                     *      </p>
                     *   </li>
                     */
                    {
                        typedef frame_h_run_t x_t;
                        x_t                   x;

                        x = va_arg( ap, x_t );
                        frame->m_data->SetRun( x );
                    }
                    break;
                default:
                    /**************************************************************
                      An unknown option has been specified. Terminate the
                    request.
                    ***************************************************************/

                    Set( Error,
                         FRAMEC_ERRNO_FRAME_H_ACCESSOR_ERROR,
                         "Unknown option passed to set request" );
                    Option = FRAME_H_FIELD_LAST;
                    continue;
                }
                /**
                 * </ul>
                 */
#if 0
	int next_opt = va_arg(ap, int /* frame_h_fields*/ );
	Option = (frame_h_fields)next_opt;
#else
                Option = va_arg( ap, int /* frame_h_fields*/ );
#endif /* 0 */
            }
        }
        else
        {
            Set( Error,
                 FRAMEC_ERRNO_FRAME_H_ACCESSOR_ERROR,
                 "NULL reference to the frame" );
        }
        va_end( ap );
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_ACCESSOR_ERROR, Exception.what( ) );
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_ACCESSOR_ERROR, FrameC::UnknownError );
    }
}

frame_h_t*
FrameCFrameHRead( FrameCError** Error,
                  fr_file_t* restrict Stream,
                  frame_h_offset_t    Pos )
{
    Set( Error );

    std::unique_ptr< FrameC::FrameH > frame;

    try
    {
        frame.reset( new FrameC::FrameH );
        frame->Read( Stream, Pos );
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_READ_ERROR, Exception.what( ) );
        frame.reset( NULL );
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_READ_ERROR, FrameC::UnknownError );
        frame.reset( NULL );
    }

    if ( frame.get( ) )
    {
        FrameC::Handle::Deposit( frame.get( ) );
    }

    return ( reinterpret_cast< frame_h_t* >( frame.release( ) ) );
}

frame_h_t*
FrameCFrameHReadNext( FrameCError** Error, fr_file_t* restrict Stream )
{
    Set( Error );

    std::unique_ptr< FrameC::FrameH > frame;

    try
    {
        frame.reset( new FrameC::FrameH );
        frame->ReadNext( Stream );
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_READ_ERROR, Exception.what( ) );
        frame.reset( NULL );
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_READ_ERROR, FrameC::UnknownError );
        frame.reset( NULL );
    }

    if ( frame.get( ) )
    {
        FrameC::Handle::Deposit( frame.get( ) );
    }

    return ( reinterpret_cast< frame_h_t* >( frame.release( ) ) );
}

int
FrameCFrameHWrite( FrameCError** Error,
                   fr_file_t* restrict stream,
                   frame_h_t* restrict frame )
{
    Set( Error );

    int retval = false;

    try
    {
        const FrameC::FrameH* ooframe =
            FrameC::HandleCast< const FrameC::FrameH* >( frame );

        FrameC::Handle::Validate( ooframe, FrameC::POINTER_FRAME_H );
        FrameCPP::OFrameStream* fs( FrameC::StreamAsOutput( stream ) );

        //------------------------------------------------------------------
        // Write it out to disk
        //------------------------------------------------------------------
        fs->WriteFrame( ooframe->m_data );
        retval = true;
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_WRITE_ERROR, Exception.what( ) );
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_WRITE_ERROR, FrameC::UnknownError );
    }

    return retval;
}

int
FrameCFrameHWriteCompressed( FrameCError** Error,
                             fr_file_t* restrict stream,
                             frame_h_t* restrict           frame,
                             fr_vect_compression_schemes_t compression,
                             fr_vect_compression_level_t   level )
{
    Set( Error );

    int retval = false;

    try
    {
        const FrameC::FrameH* ooframe =
            FrameC::HandleCast< const FrameC::FrameH* >( frame );

        FrameC::Handle::Validate( ooframe, FrameC::POINTER_FRAME_H );
        FrameCPP::OFrameStream* fs( FrameC::StreamAsOutput( stream ) );

        //------------------------------------------------------------------
        // Write it out to disk
        //------------------------------------------------------------------
        fs->WriteFrame( ooframe->m_data, compression, level );
        retval = true;
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_WRITE_ERROR, Exception.what( ) );
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_H_WRITE_ERROR, FrameC::UnknownError );
    }

    return retval;
}

namespace FrameC
{
    FrameH::FrameH( ) : Handle( POINTER_FRAME_H )
    {
    }

    FrameH::FrameH( const char*     Name,
                    frame_h_gtime_t Start,
                    frame_h_dt_t    Dt,
                    frame_h_frame_t FrameNumber )
        : Handle( POINTER_FRAME_H )
    {
        FrameCPP::GPSTime start( Start.sec, Start.nan );
        // Issue #251: when ULeapS was removed from the Universal::FrameH ctor
        // (commit 44bb9385a), this call site wasn't updated, so Dt landed in
        // dataQuality and start.GetLeapSeconds() landed in dt.
        m_data.reset( new FrameCPP::FrameH( std::string( Name ),
                                            0, /* run */
                                            FrameNumber,
                                            start,
                                            Dt,
                                            0 /* dataQuality */ ) );
    }

    FrameH::~FrameH( )
    {
    }
} // namespace FrameC
