//
// 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 <fstream>
#include <memory>

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

#include "framecppc/Stream.h"

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

using std::filebuf;
using std::ios;

using FrameC::Set;

typedef FrameCPP::Common::FrameBuffer< filebuf > FrameBuffer;

namespace FrameC
{
    template <>
    pointer_type
    PointerType( fr_file_t* Value )
    {
        return POINTER_STREAM;
    }

    template <>
    const char*
    Pointers::Name< POINTER_STREAM >( )
    {
        return "Stream";
    }

#if DEPRICATED
    template <>
    inline pointer_type
    PointerType( FrameCPP::IFrameStream* Value )
    {
        return POINTER_STREAM_FILE_INPUT;
    }

    template <>
    const char*
    Pointers::Name< POINTER_STREAM_FILE_INPUT >( )
    {
        return "FrameCIFrameStream";
    }

    template <>
    inline pointer_type
    PointerType( FrameCPP::OFrameStream* Value )
    {
        return POINTER_STREAM_FILE_OUTPUT;
    }

    template <>
    const char*
    Pointers::Name< POINTER_STREAM_FILE_OUTPUT >( )
    {
        return "FrameCOFrameStream";
    }
#endif /* DEPRICATED */

} // namespace FrameC

fr_file_t*
FrameCFileOpen( FrameCError**        Error,
                const char*          Filename,
                const fr_file_mode_t Mode )
{
    FrameCPP::Initialize( );
    Set( Error );

    std::unique_ptr< fr_file_t > retval( new fr_file_t );
    try
    {
        retval->s_mode = Mode;

        if ( Mode == ::FRAMEC_FILE_MODE_INPUT )
        {
            //-----------------------------------------------------------------
            // Create the input buffer
            //-----------------------------------------------------------------
            std::unique_ptr< FrameBuffer > buf(
                new FrameBuffer( std::ios::in ) );
            try
            {
                std::string f( Filename );
                buf->open( f, std::ios::in | std::ios::binary );
            }
            catch ( const std::exception& E )
            {
            }

            //-----------------------------------------------------------------
            // Create the frame stream
            //-----------------------------------------------------------------
            std::unique_ptr< FrameCPP::IFrameStream > fs(
                new FrameCPP::IFrameStream( buf.release( ) ) );

            //-----------------------------------------------------------------
            // Record the handle for the caller
            //-----------------------------------------------------------------
            retval->s_stream.input = fs.release( );
        }
        else
        {
            //-----------------------------------------------------------------
            // Create the input buffer
            //-----------------------------------------------------------------
            std::unique_ptr< FrameBuffer > buf(
                new FrameBuffer( std::ios::out ) );
            buf->open( Filename, ios::out | ios::trunc | ios::binary );

            //-----------------------------------------------------------------
            // Create the frame stream
            //-----------------------------------------------------------------
            std::unique_ptr< FrameCPP::OFrameStream > fs(
                new FrameCPP::OFrameStream( buf.release( ) ) );

            //-----------------------------------------------------------------
            // Record the handle for the caller
            //-----------------------------------------------------------------
            retval->s_stream.output = fs.release( );
        }
        retval->filename = Filename;
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_OPEN_ERROR, Exception.what( ) );
        retval.reset( NULL );
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_OPEN_ERROR, "Unknown error" );
        retval.reset( NULL );
    }
    FrameC::Handle::Deposit( retval.get( ) );
    return retval.release( );
}

int
FrameCFileClose( FrameCError** Error, fr_file_t* Stream )
{
    Set( Error );

    try
    {
        //-------------------------------------------------------------------
        //
        //-------------------------------------------------------------------
        FrameC::Handle::Validate( Stream, FrameC::POINTER_STREAM );
        Stream->CloseStream( );
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_CLOSE_ERROR, Exception.what( ) );
        return false;
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_CLOSE_ERROR, "Unknown error" );
        return false;
    }
    return true;
}

int
FrameCFileFree( FrameCError** Error, fr_file_t* Stream )
{
    int retval = true;

    Set( Error );

    try
    {
        FrameC::Handle::Validate( Stream, FrameC::POINTER_STREAM );
        FrameC::Handle::Free( Error,
                              Stream,
                              FrameC::POINTER_STREAM,
                              FRAMEC_ERRNO_FRAME_CLOSE_ERROR );
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_FRAME_CLOSE_ERROR, "Unknown error" );
    }
    if ( *Error )
    {
        retval = false;
    }

    return retval;
}

int
FrameCFrameLibrary( FrameCError** Error, fr_file_t* Stream )
{
    Set( Error );

    try
    {
        //-------------------------------------------------------------------
        //
        //-------------------------------------------------------------------
        FrameC::Handle::Validate( Stream, FrameC::POINTER_STREAM );
        return Stream->FrameLibrary( );
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_ERROR, Exception.what( ) );
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_ERROR, "Unknown error" );
    }
    return -1;
}

int
FrameCFrameLibraryName( FrameCError** Error,
                        fr_file_t*    Stream,
                        char*         Buffer,
                        size_t        BufferSize )
{
    Set( Error );

    try
    {
        //-------------------------------------------------------------------
        //
        //-------------------------------------------------------------------
        FrameC::Handle::Validate( Stream, FrameC::POINTER_STREAM );
        return Stream->FrameLibraryName( Buffer, BufferSize );
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_ERROR, Exception.what( ) );
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_ERROR, "Unknown error" );
    }
    return false;
}

int
FrameCFrameLibraryVersion( FrameCError** Error, fr_file_t* Stream )
{
    Set( Error );

    try
    {
        //-------------------------------------------------------------------
        //
        //-------------------------------------------------------------------
        FrameC::Handle::Validate( Stream, FrameC::POINTER_STREAM );
        return Stream->FrameLibraryVersion( );
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_ERROR, Exception.what( ) );
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_ERROR, "Unknown error" );
    }
    return -1;
}

int
FrameCFrameLibraryVersionMinor( FrameCError** Error, fr_file_t* Stream )
{
    Set( Error );

    try
    {
        //-------------------------------------------------------------------
        //
        //-------------------------------------------------------------------
        FrameC::Handle::Validate( Stream, FrameC::POINTER_STREAM );
        return Stream->FrameLibraryVersionMinor( );
    }
    catch ( const std::exception& Exception )
    {
        Set( Error, FRAMEC_ERRNO_ERROR, Exception.what( ) );
    }
    catch ( ... )
    {
        Set( Error, FRAMEC_ERRNO_ERROR, "Unknown error" );
    }
    return -1;
}

//-----------------------------------------------------------------------
/// Validate the file checksum of the stream.
/// This is a lenghty process and should be avoided.
/// Beginning with version 8 of the frame specification,
/// Individual channels are validated using the per structure
/// checksums.
//-----------------------------------------------------------------------
int
FrameCFileCksumValid( FrameCError** Error, fr_file_t* Stream )
{
    bool retval = false;

    switch ( Stream->s_mode )
    {
    case FRAMEC_FILE_MODE_INPUT:
    {
        FrameCPP::Common::Verify verifier;

        verifier.CheckFileChecksumOnly( true );
        try
        {
            verifier( Stream->filename );
            retval = true;
        }
        catch ( const FrameCPP::Common::VerifyException& E )
        {
            Set( Error, FRAMEC_ERRNO_FRAME_CHECKSUM_ERROR, E.what( ) );
        }
    }
    break;
    default:
        retval = false;
        break;
    }
    return retval;
}
