//
// 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
//

#ifndef FRAME_C_CC
#define FRAME_C_CC

#include <fstream>
#include <sstream>

#include "framecpp/FrameCPP.hh"
#include "framecpp/GPSTime.hh"

#include "framecppc/FrameC.h"

#include "FrameCInternal.hh"

using LDASTools::AL::MutexLock;

using namespace FrameCPP;

/**
 * \addtogroup CInterface C Interface
 *
 * This API provides an interface for developers using the C language
 * to the FrameCPP library.
 * The library is written to be linked with a C compiler as all
 * exceptions from the FrameCPP library are caught and returned
 * to the caller.
 *
 * <ul>
 * <li>
 * <b>Error Handling</b> -
 * The first argument to each function is a pointer to the address
 * of a FrameCError structure.
 * If an error occurs, a #FrameCError structure is allocated and its
 * address is returned.
 * If no error occurs,then a null pointer is returned.
 * This allows for easy error handling by testing for a non-NULL
 * address upon return.
 * </li>
 * </ul>
 *
 * @{
 * @}
 */

namespace
{
    const char*
    type_name( FrameC::pointer_type Type )
    {
        using namespace FrameC;

        switch ( Type )
        {
        case POINTER_FR_CHANNEL:
            return Pointers::Name< POINTER_FR_CHANNEL >( );
            break;
        case POINTER_FR_DETECTOR:
            return Pointers::Name< POINTER_FR_DETECTOR >( );
            break;
        case POINTER_FR_EVENT:
            return Pointers::Name< POINTER_FR_EVENT >( );
            break;
        case POINTER_FR_HISTORY:
            return Pointers::Name< POINTER_FR_HISTORY >( );
            break;
        case POINTER_FRAME_H:
            return Pointers::Name< POINTER_FRAME_H >( );
            break;
        case POINTER_FR_TOC:
            return Pointers::Name< POINTER_FR_TOC >( );
            break;
        case POINTER_STREAM:
            return Pointers::Name< POINTER_STREAM >( );
            break;
#if DEPRICATED
        case POINTER_STREAM_FILE_INPUT:
            return Pointers::Name< POINTER_STREAM_FILE_INPUT >( );
            break;
        case POINTER_STREAM_FILE_OUTPUT:
            return Pointers::Name< POINTER_STREAM_FILE_OUTPUT >( );
            break;
#endif /* DEPRICATED */
        }
        return "unknown pointer type";
    }
} // namespace

/*=======================================================================
 * Implementation of functions for general consumption
 *=======================================================================*/

INT_2U
FrameCLibraryMinorVersion( )
{
    return GetFrameLibraryMinorVersion( );
}

void
FrameCErrorInit( FrameCError** Handle )
{
    *Handle = (FrameCError*)NULL;
}

gpstime_seconds_t
FrameCGPSTimeNow( )
{
    FrameCPP::GPSTime n;
    n.Now( );
    return n.GetSeconds( );
}

void
FrameCInitialize( )
{
}

void
FrameCCleanup( )
{
}

/*=======================================================================
 * Implementation of internal functions
 *=======================================================================*/

namespace FrameC
{
    const char* UnknownError = "Unknown error";

    MutexLock::baton_type         Handle::m_baton;
    Handle::handle_container_type Handle::m_handles;

    void
    Handle::Deposit( const Handle* Ptr )
    {
        if ( Ptr )
        {
            MutexLock l( m_baton, __FILE__, __LINE__ );

            m_handles.insert( Ptr );
        }
    }

    void
    Handle::Validate( const Handle* Ptr, pointer_type Type )
    {
        MutexLock l( m_baton, __FILE__, __LINE__ );

        handle_container_type::const_iterator cur = m_handles.find( Ptr );

        if ( cur == m_handles.end( ) )
        {
            std::ostringstream msg;

            msg << "Invalid pointer: nothing is known about the pointer";

            throw std::invalid_argument( msg.str( ) );
        }
        else if ( ( *cur )->Type( ) != Type )
        {
            std::ostringstream msg;

            msg << "Invalid pointer: Expected type: " << type_name( Type )
                << " but received type: " << type_name( ( *cur )->Type( ) );

            throw std::invalid_argument( msg.str( ) );
        }
    }

    void
    Handle::Withdraw( const Handle* Ptr )
    {
        MutexLock l( m_baton, __FILE__, __LINE__ );

        try
        {
            m_handles.erase( Ptr );
        }
        catch ( ... )
        {
        }
    }

    Pointers::Pointers( )
#if DEPRICATED
        : m_baton( MutexLock::Initialize( ) )
#endif /* DEPRICATED */
    {
    }

#if DEPRICATED
    void
    Pointers::Deposit( void* Ptr, pointer_type Type )
    {
        MutexLock l( m_baton, __FILE__, __LINE__ );

        pointer_container_type::mapped_type& v( m_pointers[ Ptr ] );
        v.s_type = Type;
    }

    void
    Pointers::Validate( void* Ptr, pointer_type Type )
    {
        MutexLock l( m_baton, __FILE__, __LINE__ );

        pointer_container_type::const_iterator cur = m_pointers.find( Ptr );

        if ( cur == m_pointers.end( ) )
        {
            std::ostringstream msg;

            msg << "Invalid pointer: nothing is known about the pointer";

            throw std::invalid_argument( msg.str( ) );
        }
        else if ( cur->second.s_type != Type )
        {
            std::ostringstream msg;

            msg << "Invalid pointer: Expected type: " << type_name( Type )
                << " but received type: " << type_name( cur->second.s_type );

            throw std::invalid_argument( msg.str( ) );
        }
    }

    void
    Pointers::Withdraw( void* Ptr )
    {
        MutexLock l( m_baton, __FILE__, __LINE__ );

        try
        {
            m_pointers.erase( Ptr );
        }
        catch ( ... )
        {
        }
    }
#endif /* DEPRICATED */

#if DEPRICATED
    Pointers PointerHash;
#endif /* DEPRICATED */

    Handle::Handle( pointer_type Type ) : m_type( Type )
    {
    }

    Handle::~Handle( )
    {
    }

    void
    Handle::Free( FrameCError**     Error,
                  void*             Pointer,
                  pointer_type      ExpectedType,
                  framec_errno_type ErrorCode )
    {
        Set( Error );

        const FrameC::Handle* data(
            reinterpret_cast< const FrameC::Handle* >( Pointer ) );

        try
        {
            if ( data )
            {
                FrameC::Handle::Validate( data, ExpectedType );
                FrameC::Handle::Withdraw( data );
                delete data;
            }
        }
        catch ( const std::exception& Exception )
        {
            Set( Error, ErrorCode, Exception.what( ) );
        }
    }

} // namespace FrameC

#endif /* FRAME_C_CC */
