//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2025 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 <algorithm>

#include "framecpp/Common/FrameCPPServices.hh"

#include "framecpp/Common/Description.hh"
#include "framecpp/Common/TOCInfo.hh"

#include "framecpp/Universal/STRING.hh"

#include "framecpp/Universal/impl/FrameSpec.hh"

#include "framecpp/Universal/FrSE.hh"
#include "framecpp/Universal/FrSH.hh"
#include "framecpp/Universal/FrTOCSimEvent.hh"

#include "../Common/FrTOCPrivate.hh"

using FrameCPP::Common::Description;
using FrameCPP::Common::FrameSpec;
using FrameCPP::Common::TOCInfo;

namespace FrameCPP
{
    namespace Universal
    {
        namespace FrTOCImpl
        {
            // Implementation of FrTOCSimEventData static logger method
            SimpleLogger* FrTOCSimEventData::logger() {
                static SimpleLogger* instance = get_channel_logger("framecpp.frtoc.simevent");
                return instance;
            }

            // Implementation of FrTOCSimEventData dependency injection logger method
            SimpleLogger* FrTOCSimEventData::logger(const FrameCPP::Common::FrameCPPServices& services) {
                return services.logger_mgr.getLogger("framecpp.frtoc.simevent");
            }
        } // namespace FrTOCImpl
    } // namespace Universal
} // namespace FrameCPP

//=======================================================================
//                                D A T A
//=======================================================================

namespace FrameCPP
{
    namespace Universal
    {
        namespace FrTOCImpl
        {
        } // namespace FrTOCImpl
    } // namespace Universal
} // namespace FrameCPP

//=======================================================================
//               C L A S S I C   I O
//=======================================================================
namespace FrameCPP
{
    namespace Universal
    {
        namespace FrTOCImpl
        {
            Common::FrameSpec::size_type
            FrTOCSimEventClassicIO::Bytes(
                const Common::StreamBase& Stream ) const
            {
                auto frame_stream_version = StreamVersion( Stream );
                logger_log_debug(logger(), 10, "FrTOCSimEvent::Bytes: Starting calculation for stream version ", frame_stream_version);

                Common::FrameSpec::size_type retval{ 0 };

                if ( frame_stream_version >= 4 )
                {
                    retval += //
                        sizeof( nevent_type ) + //
                        ( ( frame_stream_version >= 8 ) ? //
                              sizeof( nTotalSEvent_type )
                                                        : 0 ) //
                        ;
                    //-----------------------------------------------------------------
                    //
                    //-----------------------------------------------------------------
                    logger_log_debug(logger(), 20, "FrTOCSimEvent::Bytes: Processing ", m_info.size(), " sim event types");
                    for ( nameSimEvent_container_type::const_iterator
                              cur = m_info.begin( ),
                              last = m_info.end( );
                          cur != last;
                          ++cur )
                    {
                        retval += //
                            Common::FrTOC::string_stream_type::Bytes(
                                cur->first ) + //
                            ( ( frame_stream_version >= 5 )
                                  ? sizeof( nevent_type )
                                  : 0 ) + //
                            ( cur->second.size( ) * //
                              ( sizeof( v4::gtimesSim_type ) + //
                                sizeof( v4::gtimenSim_type ) + //
                                ( ( frame_stream_version >= 5 )
                                      ? sizeof( v5::amplitudeSimEvent_type )
                                      : 0 ) + //
                                sizeof( v4::positionSimEvent_type ) //
                                ) ) //
                            ;
                    }
                }
                else
                {
                    logger_log_debug(logger(), 20, "FrTOCSimEvent::Bytes: No sim event data for stream version < 4");
                }
                
                logger_log_debug(logger(), 15, "FrTOCSimEvent::Bytes: Total ", retval, " bytes");
                return retval;
            }

            void
            FrTOCSimEventClassicIO::GetDescription(
                unsigned int         frame_stream_version,
                Common::Description& Desc ) const
            {

                if ( frame_stream_version < 4 )
                {
                    return;
                }
                Desc( FrSE(
                           ( ( frame_stream_version >= 5 ) //
                             ? "nSimEventType"
                             : "nSimEvt" ),
                           Description::Ascii< v4::nSimEvt_type >( ),
                           "Number of type of FrSimEvent in the file" ) );
                Desc( FrSE( ( ( frame_stream_version >= 5 ) ? "nameSimEvent"
                              : "nameSimEvt" ),
                            ( ( frame_stream_version >= 8 )
                              ? Description::AsciiArray< v4::name_type >(
                                                                         "nSimEventType" )
                              .c_str( )
                              : Description::Ascii< v4::name_type* >( ) ),
                            "Array of FrSimEvent names" ) );
                if ( frame_stream_version >= 5 )
                {
                  Desc( FrSE( "nSimEvent",
                              ( frame_stream_version >= 8 )
                              ? Description::AsciiArray< v8::nSimEvent_type >( "nSimEventType" )
                              : Description::Ascii< v5::nSimEvent_type* >( ),
                              "Number of FrSimEvent for each type of FrSimEvent (size of nSimEventType)" ) );
                  if ( frame_stream_version >= 8 )
                  {
                    Desc( FrSE( "nTotalSEvent",
                                Description::Ascii< v8::nTotalSEvent_type >( ),
                                "Total number of FrSimEvent" ) );
                  }
                }
                Desc( FrSE(
                    "GTimeSSim",
                    ( ( frame_stream_version >= 8 )
                          ? Description::AsciiArray< v4::gtimesSim_type >(
                                "nTotalSEvent" )
                                .c_str( )
                          : Description::Ascii< v4::gtimesSim_type* >( ) ),
                    "GPS time in integer seconds" ) );
                Desc( FrSE(
                    "GTimeNSim",
                    ( ( frame_stream_version >= 8 )
                          ? Description::AsciiArray< v4::gtimenSim_type >(
                                "nTotalSEvent" )
                                .c_str( )
                          : Description::Ascii< v4::gtimenSim_type* >( ) ),
                    "Residual GPS time in integer nanoseconds" ) );
                if ( frame_stream_version >= 5 )
                {
                    Desc( FrSE( "amplitudeSimEvent",
                                ( ( frame_stream_version >= 8 )
                                      ? Description::AsciiArray<
                                            v5::amplitudeSimEvent_type >(
                                            "nTotalSEvent" )
                                            .c_str( )
                                      : Description::Ascii<
                                            v5::amplitudeSimEvent_type* >( ) ),
                                "SimEvent amplitude" ) );
                }
                Desc( FrSE(
                           ( ( frame_stream_version >= 5 )
                             ? "positionSimEvent" //
                             : "positionSimEvt" //
                             ),
                    ( ( frame_stream_version >= 8 )
                          ? Description::AsciiArray<
                                v4::positionSimEvent_type >( "nTotalSEvent" )
                                .c_str( )
                          : Description::Ascii<
                                v4::positionSimEvent_type* >( ) ),
                    "Array of FrSimEvent positions, in bytes,"
                    " from beginning of file" ) );
            }

            void
            FrTOCSimEventClassicIO::Load( istream_type& Stream, INT_4U& nframe )
            {
                auto        frame_stream_version = StreamVersion( Stream );
                logger_log_debug(logger(), 10, "FrTOCSimEvent::Load: Starting load for stream version ", frame_stream_version, ", nframe=", nframe);

                nevent_type nevent;

                if ( frame_stream_version < 4 )
                {
                    logger_log_debug(logger(), 15, "FrTOCSimEvent::Load: No sim event data for stream version < 4");
                    return;
                }
                Stream >> nevent;
                logger_log_debug(logger(), 15, "FrTOCSimEvent::Load: Read ", nevent, " sim event types");

                if ( nevent )
                {
                    //---------------------------------------------------------------
                    // Read in the information
                    //---------------------------------------------------------------
                    std::vector< name_type >   names( nevent );
                    std::vector< nevent_type > events( nevent );

                    logger_log_debug(logger(), 20, "FrTOCSimEvent::Load: Reading ", nevent, " sim event names");
                    for ( auto& name : names )
                    {
                        Common::FrTOC::string_stream_type::Read( Stream, name );
                    }
                    if ( frame_stream_version >= 5 )
                    {
                        logger_log_debug(logger(), 20, "FrTOCSimEvent::Load: Reading event counts for v5+ format");
                        Stream >> events;
                    }
                    else if ( frame_stream_version >= 4 )
                    {
                        logger_log_debug(logger(), 20, "FrTOCSimEvent::Load: Filling default event counts for v4 format");
                        std::fill( events.begin( ), events.end( ), 1 );
                    }

                    nevent_type offset = 0;

                    auto adder = []( nTotalSEvent_type sum,
                                     nevent_type       increment ) {
                        return ( ( increment == NO_DATA_AVAILABLE )
                                     ? sum
                                     : sum + increment );
                    };
                    nTotalSEvent_type nevent_sum = std::accumulate(
                        events.begin( ), events.end( ), 0, adder );

                    nTotalSEvent_type nTotalSimEvent;

                    if ( frame_stream_version >= 8 )
                    {
                        Stream >> nTotalSimEvent;
                        logger_log_debug(logger(), 20, "FrTOCSimEvent::Load: Read nTotalSimEvent=", nTotalSimEvent, " from stream");
                    }
                    else if ( frame_stream_version >= 4 )
                    {
                        nTotalSimEvent = nevent_sum;
                        logger_log_debug(logger(), 20, "FrTOCSimEvent::Load: Calculated nTotalSimEvent=", nTotalSimEvent);
                    }

                    if ( nTotalSimEvent != nTotalSEvent_type( nevent_sum ) )
                    {
                        logger_log_debug(logger(), 10, "FrTOCSimEvent::Load: ERROR - nTotalSimEvent mismatch: ", nTotalSimEvent, " vs ", nevent_sum);
                        throw std::runtime_error(
                            "nTotalSimEvent is not the sumation of "
                            "nSimEvent" );
                    }

                    std::vector< gtimesSim_type > gtimes( nTotalSimEvent );
                    std::vector< gtimenSim_type > gtimen( nTotalSimEvent );
                    std::vector< amplitudeSimEvent_type > amplitude(
                        nTotalSimEvent );
                    std::vector< positionSimEvent_type > position(
                        nTotalSimEvent );

                    logger_log_debug(logger(), 30, "FrTOCSimEvent::Load: Reading ", nTotalSimEvent, " sim event records");
                    Stream //
                        >> gtimes //
                        >> gtimen //
                        ;
                    if ( frame_stream_version >= 5 )
                    {
                        Stream //
                            >> amplitude //
                            ;
                    }
                    else
                    {
                        std::fill( amplitude.begin( ), amplitude.end( ), 0 );
                    }
                    Stream //
                        >> position //
                        ;

                    std::vector< nevent_type >::const_iterator
                        cur_event_counter = events.begin( );

                    logger_log_debug(logger(), 30, "FrTOCSimEvent::Load: Organizing sim events into containers");
                    for ( std::vector< name_type >::const_iterator
                              cur = names.begin( ),
                              last = names.end( );
                          cur != last;
                          ++cur, ++cur_event_counter )
                    {
                        events_container_type& cur_events = m_info[ *cur ];

                        if ( ( *cur_event_counter == 0 ) ||
                             ( *cur_event_counter == NO_DATA_AVAILABLE ) )
                        {
                            continue;
                        }
                        cur_events.resize( *cur_event_counter );
                        for ( nevent_type x = 0, x_last = *cur_event_counter;
                              x != x_last;
                              ++x, ++offset )
                        {
                            cur_events[ x ].GTime =
                                GPSTime( gtimes[ offset ], gtimen[ offset ] );
                            cur_events[ x ].amplitudeSimEvent =
                                amplitude[ offset ];
                            cur_events[ x ].positionSimEvent =
                                position[ offset ];
                        }
                    }
                }
                else
                {
                    logger_log_debug(logger(), 15, "FrTOCSimEvent::Load: No sim events for this frame");
                    // For v8+, need to read nTotalSimEvent even when nevent=0
                    if ( frame_stream_version >= 8 )
                    {
                        nevent_type nTotalSimEvent;
                        Stream >> nTotalSimEvent;
                        logger_log_debug(logger(), 20, "FrTOCSimEvent::Load: Read nTotalSimEvent=", nTotalSimEvent, " (should be 0)");
                    }
                    // For v4-v7, write() only wrote nevent=0, nothing more to read
                }
                
                logger_log_debug(logger(), 10, "FrTOCSimEvent::Load: Load complete");
            }

            void
            FrTOCSimEventClassicIO::QuerySimEvent( const Common::TOCInfo& Info,
                                                   INT_4U FrameOffset,
                                                   INT_8U Position )
            {
                using FrameCPP::Common::TOCInfo;

                STRING name;
                INT_4U sec;
                INT_4U nsec;
                REAL_4 ampl;

                Info.TOCQuery( TOCInfo::IC_NAME,
                               TOCInfo::DataType( name ),
                               &name,
                               TOCInfo::IC_GTIME_S,
                               TOCInfo::DataType( sec ),
                               &sec,
                               TOCInfo::IC_GTIME_N,
                               TOCInfo::DataType( nsec ),
                               &nsec,
                               TOCInfo::IC_AMPLITUDE,
                               TOCInfo::DataType( ampl ),
                               &ampl,
                               TOCInfo::IC_EOQ );

                events_container_type& i( m_info[ name ] );

                event_type e;
                e.GTime = GPSTime( sec, nsec );
                e.amplitudeSimEvent = ampl;
                e.positionSimEvent = Position;

                i.push_back( e );
            }

            FrTOCSimEventClassicIO&
            FrTOCSimEventClassicIO::operator=( FrTOCSimEventClassicIO&& TOC )
            {
                static_cast< FrTOCSimEventData& >( *this ) =
                    static_cast< FrTOCSimEventData&& >( TOC );
                return *this;
            }

            void
            FrTOCSimEventClassicIO::write( Common::OStream& Stream ) const
            {
                logger_log_debug(logger(), 10, "FrTOCSimEvent::write: Starting write operation");
                auto starting_location{ Stream.tellp( ) };
                auto frame_stream_version = StreamVersion( Stream );

                logger_log_debug(logger(), 15, "FrTOCSimEvent::write: Writing ", m_info.size(), " sim event types for stream version ", frame_stream_version);

                //-----------------------------------------------------------------
                // Flatten data so it is streamable
                //-----------------------------------------------------------------
                if ( m_info.size( ) > 0 )
                {
                    std::vector< name_type >      names( m_info.size( ) );
                    std::vector< nevent_type >    nevent( m_info.size( ) );
                    nevent_type                   offset( 0 );
                    nevent_type                   eoffset( 0 );
                    std::vector< gtimesSim_type > gtimes;
                    std::vector< gtimenSim_type > gtimen;
                    std::vector< amplitudeSimEvent_type > amplitude;
                    std::vector< positionSimEvent_type >  position;

                    logger_log_debug(logger(), 20, "FrTOCSimEvent::write: Flattening data for streaming");
                    for ( nameSimEvent_container_type::const_iterator
                              cur = m_info.begin( ),
                              last = m_info.end( );
                          cur != last;
                          ++cur, ++offset )
                    {
                        names[ offset ] = cur->first;
                        const nevent_type c = ( cur->second.size( ) == 0 )
                            ? ( NO_DATA_AVAILABLE )
                            : ( cur->second.size( ) );
                        nevent[ offset ] = c;
                        if ( c != NO_DATA_AVAILABLE )
                        {
                            const int ns( c + eoffset );

                            gtimes.resize( ns );
                            gtimen.resize( ns );
                            amplitude.resize( ns );
                            position.resize( ns );

                            for ( nevent_type x = 0; x != c; ++x, ++eoffset )
                            {
                                gtimes[ eoffset ] =
                                    cur->second[ x ].GTime.GetSeconds( );
                                gtimen[ eoffset ] =
                                    cur->second[ x ].GTime.GetNanoseconds( );
                                amplitude[ eoffset ] =
                                    cur->second[ x ].amplitudeSimEvent;
                                position[ eoffset ] =
                                    cur->second[ x ].positionSimEvent;
                            }
                        }
                    }

                    const nTotalSEvent_type nTotalSimEvent( gtimes.size( ) );
                    logger_log_debug(logger(), 20, "FrTOCSimEvent::write: Total sim events to write: ", nTotalSimEvent);

                    logger_log_debug(logger(), 30, "FrTOCSimEvent::write: Writing sim event type count and names");
                    Stream << nevent_type( m_info.size( ) );
                    for ( auto& name : names )
                    {
                        STRING::Write( Stream, name );
                    }
                    if ( frame_stream_version >= 5 )
                    {
                        logger_log_debug(logger(), 30, "FrTOCSimEvent::write: Writing event counts for v5+");
                        Stream //
                            << nevent //
                            ;
                    }
                    if ( frame_stream_version >= 8 )
                    {
                        logger_log_debug(logger(), 30, "FrTOCSimEvent::write: Writing nTotalSimEvent for v8+");
                        Stream //
                            << nTotalSimEvent //
                            ;
                    }
                    logger_log_debug(logger(), 30, "FrTOCSimEvent::write: Writing GPS times");
                    Stream //
                        << gtimes //
                        << gtimen //
                        ;
                    if ( frame_stream_version >= 5 )
                    {
                        logger_log_debug(logger(), 30, "FrTOCSimEvent::write: Writing amplitudes for v5+");
                        Stream //
                            << amplitude //
                            ;
                    }
                    logger_log_debug(logger(), 30, "FrTOCSimEvent::write: Writing positions");
                    Stream //
                        << position //
                        ;
                }
                else
                {
                    logger_log_debug(logger(), 20, "FrTOCSimEvent::write: No sim events, writing empty markers");
                    Stream << nevent_type( 0 ); // nSimEventType
                    if ( frame_stream_version >= 8 )
                    {
                        Stream << nevent_type( 0 ); // nTotalSimEvent
                    }
                }
                
                auto bytes_written = Stream.tellp( ) - starting_location;
                logger_log_debug(logger(), 15, "FrTOCSimEvent::write: Wrote ", bytes_written, " bytes");
                logger_log_debug(logger(), 10, "FrTOCSimEvent::write: Write complete");
            }

        } // namespace FrTOCImpl
    } // namespace Universal
} // namespace FrameCPP
