//
// 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/Description.hh"
#include "framecpp/Common/TOCInfo.hh"
#include "framecpp/Common/FrameCPPServices.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 "framecpp/Universal/FrTOCProcData.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 FrTOCProcDataData static logger method
            SimpleLogger* FrTOCProcDataData::logger() {
                static SimpleLogger* instance = get_channel_logger("framecpp.frtoc.proc");
                return instance;
            }

            // Implementation of FrTOCProcDataData dependency injection logger method
            SimpleLogger* FrTOCProcDataData::logger(const FrameCPP::Common::FrameCPPServices& services) {
                return services.logger_mgr.getLogger("framecpp.frtoc.proc");
            }
        } // 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
            FrTOCProcDataClassicIO::Bytes(
                const Common::StreamBase& Stream ) const
            {
                auto frame_stream_version = StreamVersion( Stream );
                
                logger_log_debug(logger(), 10, "FrTOCProcData::Bytes() entry, stream version ", frame_stream_version);
                
                Common::FrameSpec::size_type retval = sizeof( nproc_type );
                
                logger_log_debug(logger(), 15, "FrTOCProcData::Bytes() nproc field: ", sizeof( nproc_type ), " bytes");
                
                if ( m_info.size( ) )
                {
                    retval += ( m_info.size( ) *
                                ( sizeof( position_type ) *
                                  m_info.begin( )->second.size( ) ) );
                    
                    logger_log_debug(logger(), 15, "FrTOCProcData::Bytes() position data: ", m_info.size(), " channels, ", 
                                   m_info.begin()->second.size(), " frames each");
                }
                for ( MapProc_type::const_iterator cur = m_info.begin( ),
                                                   last = m_info.end( );
                      cur != last;
                      ++cur )
                {
                    retval += STRING::Bytes( cur->first );
                }
                
                logger_log_debug(logger(), 10, "FrTOCProcData::Bytes() final result: ", retval, " bytes");
                
#if DEBUG
                std::cerr << "DEBUG: FrTOCProcData::Bytes: " << retval
                          << std::endl;
#endif /* DEBUG */
                return retval;
            }

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

                STRING name;

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

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

            FrTOCProcDataClassicIO&
            FrTOCProcDataClassicIO::operator=( FrTOCProcDataClassicIO&& TOC )
            {
                if ( this != &TOC )
                {
                    FrTOCProcDataData::operator=( std::move( TOC ) );
                }
                return *this;
            }

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

                Desc( FrSE( "nProc",
                            Description::Ascii< nproc_type >( ),
                            "Number of unique FrProcData names in file." ) );
                Desc(
                    FrSE( "nameProc",
                          ( frame_stream_version >= 8 )
                              ? Description::AsciiArray< name_type >( "nProc" )
                                    .c_str( )
                              : Description::Ascii< name_type* >( ),
                          "Array of FrProcData names" ) );
                Desc(
                    FrSE( "positionProc",
                          ( frame_stream_version >= 8 )
                              ? Description::AsciiArray< position_type >(
                                    "nProc", "nFrame" )
                                    .c_str( )
                              : Description::Ascii< position_type* >( ),
                          "Array of lists of FrProcData offset positions, in "
                          "bytes,"
                          " from beginning of file (size of nFrame*nProc)" ) );
            }

            void
            FrTOCProcDataClassicIO::Load( istream_type& Stream,
                                          INT_4U&       nframe )
            {
                auto frame_stream_version = StreamVersion( Stream );
                
                logger_log_debug(logger(), 10, "FrTOCProcData::Load() entry, stream version ", frame_stream_version, ", nframe=", nframe);
                
                nproc_type nproc;
                Stream >> nproc;
                
                logger_log_debug(logger(), 20, "FrTOCProcData::Load() read nproc=", nproc);
                
                if ( nproc && ( nproc != FrTOC::NO_DATA_AVAILABLE ) )
                {
                    //---------------------------------------------------------------
                    // Read in the information
                    //---------------------------------------------------------------
                    key_container_type::size_type s( nproc );
                    std::vector< position_type >  positions( s * nframe );

                    logger_log_debug(logger(), 30, "FrTOCProcData::Load() resizing keys array to ", s, " channels");
                    m_keys.resize( s );

                    logger_log_debug(logger(), 30, "FrTOCProcData::Load() reading ", s, " channel names");
                    for ( auto& key : m_keys )
                    {
                        Common::FrTOC::string_stream_type::Read( Stream, key );
                    }
                    
                    logger_log_debug(logger(), 30, "FrTOCProcData::Load() reading ", positions.size(), " position values");
                    Stream >> positions;
                    //---------------------------------------------------------------
                    // Move into structure.
                    //---------------------------------------------------------------
                    typedef FrameCPP::Common::FrTOCPositionInputFunctor<
                        FrTOCProcData::MapProc_type,
                        FrTOCProcData::name_type,
                        FrTOCProcData::position_type >
                        functor;

                    functor f( m_info, positions.begin( ), nframe );

                    std::for_each( m_keys.begin( ), m_keys.end( ), f );
                    
                    logger_log_debug(logger(), 20, "FrTOCProcData::Load() processed ", nproc, " Proc channels with ", nframe, " frames each");
                }
                else
                {
                    logger_log_debug(logger(), 20, "FrTOCProcData::Load() no Proc data available (nproc=", nproc, ")");
                }
                
                logger_log_debug(logger(), 10, "FrTOCProcData::Load() complete");
            }

            void
            FrTOCProcDataClassicIO::cachePositions( istream_type& Stream )
            {
                std::streampos       here( Stream.tellg( ) );
                positions_cache_type positions( frame_count * m_keys.size( ) );

                Stream.seekg( -positions_cache_offset, Stream.end );
                Stream >> positions_cache;
                Stream.seekg( here, Stream.beg );

                //-------------------------------------------------------------
                // Move into structure.
                //-------------------------------------------------------------
                input_iterator f( m_info, positions.begin( ), frame_count );

                std::for_each( m_keys.begin( ), m_keys.end( ), f );
            }

            void
            FrTOCProcDataClassicIO::forEach(
                Common::FrTOC::query_info_type Info,
                Common::FrTOC::FunctionBase&   Action ) const
            {
                switch ( Info )
                {
                case Common::FrTOC::TOC_CHANNEL_NAMES:
                {
                    try
                    {
                        Common::FrTOC::FunctionString& action(
                            dynamic_cast< Common::FrTOC::FunctionString& >(
                                Action ) );

                        for ( MapProc_type::const_iterator
                                  cur = m_info.begin( ),
                                  last = m_info.end( );
                              cur != last;
                              ++cur )
                        {
                            action( cur->first );
                        }
                    }
                    catch ( ... )
                    {
                        // Does not understand Action
                    }
                }
                break;
                default:
                    //---------------------------------------------------------------
                    // ignore all other requests
                    //---------------------------------------------------------------
                    break;
                }
            }

            void
            FrTOCProcDataClassicIO::setPositionsOffset( istream_type& Stream )
            {
                positions_cache_offset = Stream.Size( ) - Stream.tellg( );
            }

            void
            FrTOCProcDataClassicIO::write( Common::OStream& Stream ) const
            {
                auto frame_stream_version = StreamVersion( Stream );
                
                logger_log_debug(logger(), 10, "FrTOCProcData::write() entry, stream version ", frame_stream_version);
                
#if DEBUG
                auto starting_location{ Stream.tellp( ) };
#endif /* DEBUG */

                //-----------------------------------------------------------------
                // Flatten data so it is streamable
                //-----------------------------------------------------------------
                std::vector< name_type >::size_type s( m_info.size( ) );
                
                logger_log_debug(logger(), 20, "FrTOCProcData::write() processing ", s, " Proc channels");
                
                if ( s )
                {
                    std::vector< name_type >::size_type fc(
                        m_info.begin( )->second.size( ) );

                    logger_log_debug(logger(), 30, "FrTOCProcData::write() ", fc, " frames per channel");

                    std::vector< name_type >     names( s );
                    std::vector< position_type > positions( s * fc );
                    //---------------------------------------------------------------
                    // Copy data for streaming
                    //---------------------------------------------------------------
                    std::vector< name_type >::iterator cur_name =
                        names.begin( );
                    std::vector< position_type >::iterator cur_position =
                        positions.begin( );

                    for ( MapProc_type::const_iterator cur = m_info.begin( ),
                                                       last = m_info.end( );
                          cur != last;
                          ++cur, ++cur_name, cur_position += fc )
                    {
                        *cur_name = cur->first;
                        std::copy( cur->second.begin( ),
                                   cur->second.end( ),
                                   cur_position );
                    }
                    //---------------------------------------------------------------
                    // Stream out
                    //---------------------------------------------------------------
                    logger_log_debug(logger(), 20, "FrTOCProcData::write() writing nproc=", s);
                    Stream << nproc_type( s );
                    
                    logger_log_debug(logger(), 30, "FrTOCProcData::write() writing ", names.size(), " channel names");
                    for ( auto& name : names )
                    {
                        STRING::Write( Stream, name );
                    }
                    
                    logger_log_debug(logger(), 30, "FrTOCProcData::write() writing ", positions.size(), " position values");
                    Stream << positions;
                }
                else
                {
                    logger_log_debug(logger(), 20, "FrTOCProcData::write() no data to write, nproc=NO_DATA_AVAILABLE");
                    Stream << nproc_type( NO_DATA_AVAILABLE );
                }
                
                logger_log_debug(logger(), 10, "FrTOCProcData::write() complete");
                
#if DEBUG
                std::cerr << "DEBUG: FrTOCProcData::write: "
                          << ( Stream.tellp( ) - starting_location )
                          << std::endl;
#endif /* DEBUG */
            }

            FrTOCProcDataClassicIO::input_iterator::input_iterator(
                Map_type&                          Destination,
                typename info_type::const_iterator Position,
                frame_count_type                   FrameCount )
                : destination( Destination ), cur_position( Position ),
                  frame_count( FrameCount ), it( Destination.begin( ) )
            {
            }

            void
            FrTOCProcDataClassicIO::input_iterator::
            operator( )( const typename Map_type::key_type& Name )
            {
                typedef typename Map_type::value_type  value_type;
                typedef typename Map_type::mapped_type mapped_type;

                it = destination.insert(
                    it,
                    value_type( Name,
                                mapped_type( cur_position,
                                             cur_position + frame_count ) ) );
                cur_position += frame_count;
            }
        } // namespace FrTOCImpl
    } // namespace Universal
} // namespace FrameCPP
