//
// 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 "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/FrTOC.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 FrTOCSummaryData static logger method
            SimpleLogger* FrTOCSummaryData::logger() {
                static SimpleLogger* instance = get_channel_logger("framecpp.frtoc.summary");
                return instance;
            }

            // Implementation of FrTOCSummaryData dependency injection logger method
            SimpleLogger* FrTOCSummaryData::logger(const FrameCPP::Common::FrameCPPServices& services) {
                return services.logger_mgr.getLogger("framecpp.frtoc.summary");
            }
        } // 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
            FrTOCSummaryClassicIO::Bytes( const stream_base_type& Stream ) const
            {
                logger_log_debug(logger(), 10, "FrTOCSummary::Bytes: Starting calculation for stream version ", StreamVersion(Stream));
                
                auto retval =
                    for_each( m_info.begin( ), m_info.end( ), byte_counter( ) );

                Common::FrameSpec::size_type total_bytes = sizeof( n_type ) + retval.value;
                logger_log_debug(logger(), 15, "FrTOCSummary::Bytes: ", total_bytes, " bytes (", sizeof( n_type ), " header + ", retval.value, " data)");
                
                return total_bytes;
            }

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

                Desc( FrSE( "nSummary",
                            Description::Ascii< v4::n_type >( ),
                            "Number of unique FrSummary names in file." ) );
                Desc( FrSE( "nameSum",
                            ( frame_stream_version >= 8 ) ? Description::AsciiArray< v4::name_type >( "nSummary" ).c_str()
                                                          : Description::Ascii< v4::name_type* >( ),
                            "Array of FrSummary names" ) );
                Desc( FrSE( "positionSum",
                            ( frame_stream_version >= 8 )
                                ? Description::AsciiArray< v4::position_type >( "nSummary", "nFrame" ).c_str()
                                : Description::Ascii< v4::position_type* >( ),
                            "Array of lists of FrSummary offset positions, in "
                            "bytes,"
                            " from beginning of file (size of nFrame*nSer)" ) );
            }

            void
            FrTOCSummaryClassicIO::Load( istream_type& Stream,
                                         INT_4U&       nframe )
            {
                logger_log_debug(logger(), 10, "FrTOCSummary::Load: Starting load for ", nframe, " frames");
                
                n_type n;
                Stream >> n;
                
                logger_log_debug(logger(), 15, "FrTOCSummary::Load: Read summary count: ", n);

                if ( n && ( n != ~n_type( 0 ) ) )
                {
                    //---------------------------------------------------------------
                    // Read in the information
                    //---------------------------------------------------------------
                    io_key_container_type::size_type s( n );
                    std::vector< position_type > positions( s * nframe );

                    logger_log_debug(logger(), 20, "FrTOCSummary::Load: Resizing for ", s, " summaries");
                    m_io_keys.resize( s );

                    logger_log_debug(logger(), 20, "FrTOCSummary::Load: Reading ", s, " keys and ", positions.size(), " positions");
                    Stream >> m_io_keys >> positions;

                    //---------------------------------------------------------------
                    // Move into structure.
                    //---------------------------------------------------------------
                    logger_log_debug(logger(), 30, "FrTOCSummary::Load: Inflating data structures");
                    m_keys.resize( m_io_keys.size( ) );
                    std::for_each(
                        m_io_keys.begin( ),
                        m_io_keys.end( ),
                        inflate( m_keys, m_info, positions, nframe ) );
                }
                else
                {
                    logger_log_debug(logger(), 15, "FrTOCSummary::Load: No summary data to load (empty or marker)");
                }
                
                logger_log_debug(logger(), 10, "FrTOCSummary::Load: Load complete");
            }

            void
            FrTOCSummaryClassicIO::QuerySum( const Common::TOCInfo& Info,
                                             INT_4U                 FrameOffset,
                                             INT_8U                 Position )
            {
                using Common::TOCInfo;

                name_type name;

                Info.TOCQuery( TOCInfo::IC_NAME,
                               TOCInfo::DT_STRING_2,
                               &name,
                               TOCInfo::IC_EOQ );

                auto& i( m_info[ name ] );
                i.resize( FrameOffset + 1 );
                i[ FrameOffset ] = Position;
            }

            void
            FrTOCSummaryClassicIO::write( ostream_type& Stream ) const
            {
                logger_log_debug(logger(), 10, "FrTOCSummary::write: Starting write operation");
                auto starting_location{ Stream.tellp( ) };

                n_type serial_data_count( m_info.size( ) );
                logger_log_debug(logger(), 15, "FrTOCSummary::write: Writing ", serial_data_count, " summary entries");
                
                if ( serial_data_count )
                {
                    //---------------------------------------------------------------
                    // Flatten data so it is streamable
                    //---------------------------------------------------------------
                    frame_count_type frame_count(
                        m_info.begin( )->second.size( ) );

                    logger_log_debug(logger(), 20, "FrTOCSummary::write: Frame count: ", frame_count);

                    //---------------------------------------------------------------
                    // Copy data for streaming
                    //---------------------------------------------------------------
                    logger_log_debug(logger(), 30, "FrTOCSummary::write: Flattening data structures");
                    auto flattened =
                        for_each( m_info.begin( ),
                                  m_info.end( ),
                                  deflate( serial_data_count, frame_count ) );
                    //---------------------------------------------------------------
                    // Stream out
                    //---------------------------------------------------------------
                    logger_log_debug(logger(), 30, "FrTOCSummary::write: Writing flattened data");
                    Stream << n_type( serial_data_count ) << flattened.names
                           << flattened.positions;
                }
                else
                {
                    logger_log_debug(logger(), 20, "FrTOCSummary::write: Writing empty marker");
                    Stream << ~n_type( 0 );
                }
                
                auto bytes_written = Stream.tellp( ) - starting_location;
                logger_log_debug(logger(), 15, "FrTOCSummary::write: Wrote ", bytes_written, " bytes");
                logger_log_debug(logger(), 10, "FrTOCSummary::write: Write complete");
            }

            void
            FrTOCSummaryClassicIO::byte_counter::
            operator( )( const MapSummary_type::value_type& Current )
            {
                value += ( sizeof( position_type ) * Current.second.size( ) ) +
                    io_name_type::Bytes( Current.first );
            }

            // ----------------------------------------------------------

            FrTOCSummaryClassicIO::deflate::deflate(
                n_type Count, frame_count_type FrameCount )
                : count( Count ), frame_count( FrameCount ), names( Count ),
                  positions( Count * FrameCount )
            {
                cur_name = names.begin( );
                cur_position = positions.begin( );
            }

            void
            FrTOCSummaryClassicIO::deflate::
            operator( )( const MapSummary_type::value_type& Current )
            {
                *cur_name = Current.first;
                std::copy( Current.second.begin( ),
                           Current.second.end( ),
                           cur_position );
                ++cur_name;
                cur_position += frame_count;
            }

            // ----------------------------------------------------------

            FrTOCSummaryClassicIO::inflate::inflate( key_container_type& Keys,
                                                     MapSummary_type&    Info,
                                                     info_type& Positions,
                                                     size_t     FrameCount )
                : keys( Keys ), info( Info ), positions( Positions ),
                  frame_count( FrameCount )
            {
                cur_key = keys.begin( );
                cur_position = positions.begin( );
            }

            void
            FrTOCSummaryClassicIO::inflate::
            operator( )( const io_key_container_type::value_type& Current )
            {
                *cur_key = Current;
                auto& node = info[ Current ];
                node.resize( frame_count );
                auto stop_position = cur_position + frame_count;
                node.assign( cur_position, stop_position );
                ++cur_key;
                cur_position = stop_position;
            }

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