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

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

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

namespace FrameCPP
{
    namespace Universal
    {
        namespace FrTOCImpl
        {
            FrTOCAdcDataData::MapADC_type::const_iterator
            FrTOCAdcDataData::GetADC( int ChannelIndex ) const
            {
                return ( GetADC( m_keys[ ChannelIndex ] ) );
            }

            FrTOCAdcDataData::MapADC_type::const_iterator
            FrTOCAdcDataData::GetADC(
                const FrTOCAdcDataData::name_type& ChannelName ) const
            {
                auto retval = m_info.find( ChannelName );
                return retval;
            }
        } // 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
            FrTOCAdcDataClassicIO::Bytes(
                const Common::StreamBase& Stream ) const
            {
                auto frame_stream_version = StreamVersion( Stream );
                
                logger_log_debug(logger(), 10, "FrTOCAdcData::Bytes() entry, stream version ", frame_stream_version);
                
                Common::FrameSpec::size_type retval = sizeof( nadc_type );
                
                logger_log_debug(logger(), 15, "FrTOCAdcData::Bytes() nadc field: ", sizeof( nadc_type ), " bytes");

                if ( m_info.size( ) )
                {
                    retval += ( m_info.size( ) *
                                ( ( sizeof( position_type ) *
                                    m_info.begin( )
                                        ->second.m_positionADC.size( ) ) ) );

                    logger_log_debug(logger(), 15, "FrTOCAdcData::Bytes() position data: ", m_info.size(), " channels, ",
                                   m_info.begin()->second.m_positionADC.size(), " frames each");

                    // Add channelID and groupID arrays for v4-v8
                    if ( frame_stream_version >= 4 && frame_stream_version < 9 )
                    {
                        retval += m_info.size( ) * sizeof( channel_id_type );
                        retval += m_info.size( ) * sizeof( group_id_type );
                        logger_log_debug(logger(), 15, "FrTOCAdcData::Bytes() channelID/groupID arrays: ",
                                       m_info.size(), " * (", sizeof(channel_id_type), " + ",
                                       sizeof(group_id_type), ") = ",
                                       m_info.size() * (sizeof(channel_id_type) + sizeof(group_id_type)), " bytes");
                    }
                }
                for ( MapADC_type::const_iterator cur = m_info.begin( ),
                                                  last = m_info.end( );
                      cur != last;
                      ++cur )
                {
                    retval += STRING::Bytes( cur->first );
                }
                
                logger_log_debug(logger(), 10, "FrTOCAdcData::Bytes() final result: ", retval, " bytes");
                
#if DEBUG
                std::cerr << "DEBUG: FrTOCAdcData::Bytes: " << retval
                          << std::endl;
#endif /* DEBUG */
                return retval;
            }

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

                STRING name;
                INT_4U channel_id;
                INT_4U group_id;

                Info.TOCQuery( TOCInfo::IC_NAME,
                               TOCInfo::DT_STRING_2,
                               &name,
                               TOCInfo::IC_CHANNEL_ID,
                               TOCInfo::DT_INT_4U,
                               &channel_id,
                               TOCInfo::IC_GROUP_ID,
                               TOCInfo::DT_INT_4U,
                               &group_id,
                               TOCInfo::IC_EOQ );

                adc_info_type& i( m_info[ name ] );
                i.m_positionADC.resize( FrameOffset + 1 );
                i.m_positionADC[ FrameOffset ] = Position;
                i.m_channelID.resize( FrameOffset + 1 );
                i.m_channelID[ FrameOffset ] = channel_id;
                i.m_groupID.resize( FrameOffset + 1 );
                i.m_groupID[ FrameOffset ] = group_id;
            }

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

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

                Desc( FrSE( "nADC",
                            Description::Ascii< nadc_type >( ),
                            "Number of unique FrAdcData names in file." ) );
                Desc( FrSE( (frame_stream_version >= 9 ) //
                            ? "nameAdc" //
                            : "name",
                            ( frame_stream_version >= 8 )
                                ? Description::AsciiArray< name_type >( "nADC" )
                                      .c_str( )
                                : Description::Ascii< name_type* >( ),
                            "Array of FrAdcData names" ) );
                if ( frame_stream_version < 9 )
                {
                  Desc( FrSE(
                             "channelID",
                             ( frame_stream_version >= 8 )
                             ? Description::AsciiArray< channel_id_type >( "nADC" )
                             .c_str( )
                             : Description::Ascii< channel_id_type* >( ),
                             "Array of ADC channel IDs" ) );
                  Desc( FrSE(
                             "groupID",
                             ( frame_stream_version >= 8 )
                             ? Description::AsciiArray< group_id_type >( "nADC" )
                             .c_str( )
                             : Description::Ascii< group_id_type* >( ),
                             "Array of ADC group IDs" ) );
                }
                Desc( FrSE( "positionADC",
                            ( frame_stream_version >= 8 )
                                ? Description::AsciiArray< position_type >(
                                      "nADC", "nFrame" )
                                      .c_str( )
                                : Description::Ascii< position_type* >( ),
                            "Array of lists of FrAdcData offset positions, in "
                            "bytes,"
                            " from beginning of file (size of nFrame*nADC)" ) );
            }

            void
            FrTOCAdcDataClassicIO::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 ( MapADC_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
            FrTOCAdcDataClassicIO::cachePositions( istream_type& Stream )
            {
                std::streampos here( Stream.tellg( ) );

                // Calculate the number of elements to read from the offset
                // positions_cache_offset is in bytes, convert to element count
                size_t cache_element_count = positions_cache_offset / sizeof(unsigned long);
                size_t frame_count = (m_keys.size() > 0) ? (cache_element_count / m_keys.size()) : 0;
                size_t position_offset = 0;

                // Resize the vector before reading into it
                positions_cache.resize(cache_element_count);

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

#if DEBUG
                std::cerr << "DEBUG: Read " << positions_cache.size( )
                          << std::endl;
#endif /* DEBUG */
                for ( key_container_type::const_iterator cur = m_keys.begin( ),
                                                         last = m_keys.end( );
                      cur != last;
                      ++cur )
                {
                    std::copy(
                        &( positions_cache[ position_offset ] ),
                        &( positions_cache[ position_offset + frame_count ] ),
                        &( getADC( *cur )->second.m_positionADC[ 0 ] ) );
                    position_offset += frame_count;
                }
            }

            void
            FrTOCAdcDataClassicIO::seekPositions( istream_type&   Stream,
                                                  channel_id_type Channel )
            {
                const INT_4U frame_count( positions_cache.size( ) /
                                          m_keys.size( ) );
                const INT_4U offset( Channel * frame_count );

                if ( positions_cache[ offset ] != 0 )
                {
                    std::copy(
                        &( positions_cache[ offset ] ),
                        &( positions_cache[ offset + frame_count ] ),
                        &( getADC( Channel )->second.m_positionADC[ 0 ] ) );
                    positions_cache[ offset ] = 0;
                }
            }

            void
            FrTOCAdcDataClassicIO::seekPositions( istream_type&      Stream,
                                                  const std::string& Channel )
            {
                const INT_4U frame_count( positions_cache.size( ) /
                                          m_keys.size( ) );
                const INT_4U offset( reverse_lookup( Channel ) * frame_count );

                if ( positions_cache[ offset ] != 0 )
                {
                    std::copy(
                        &( positions_cache[ offset ] ),
                        &( positions_cache[ offset + frame_count ] ),
                        &( getADC( Channel )->second.m_positionADC[ 0 ] ) );
                    positions_cache[ offset ] = 0;
                }
            }

            void
            FrTOCAdcDataClassicIO::write( Common::OStream& Stream ) const
            {
                auto frame_stream_version = StreamVersion( Stream );
                
                logger_log_debug(logger(), 10, "FrTOCAdcData::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, "FrTOCAdcData::write() processing ", s, " ADC channels");
                
                if ( s )
                {
                    std::vector< name_type >::size_type fc(
                        m_info.begin( )->second.m_positionADC.size( ) );

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

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

                    for ( MapADC_type::const_iterator cur = m_info.begin( ),
                                                      last = m_info.end( );
                          cur != last;
                          ++cur, ++cur_name, ++cur_channelID, ++cur_groupID, cur_position += fc )
                    {
                        *cur_name = cur->first;
                        // For channelID and groupID, use first frame's value (should be constant)
                        *cur_channelID = cur->second.m_channelID.empty() ? 0 : cur->second.m_channelID[0];
                        *cur_groupID = cur->second.m_groupID.empty() ? 0 : cur->second.m_groupID[0];
                        std::copy( cur->second.m_positionADC.begin( ),
                                   cur->second.m_positionADC.end( ),
                                   cur_position );
                    }
                    //---------------------------------------------------------------
                    // Stream out
                    //---------------------------------------------------------------
                    logger_log_debug(logger(), 20, "FrTOCAdcData::write() writing nadc=", s);
                    Stream << nadc_type( s );

                    logger_log_debug(logger(), 30, "FrTOCAdcData::write() writing ", names.size(), " channel names");
                    for ( auto& name : names )
                    {
                        STRING::Write( Stream, name );
                    }

                    // Write channelID and groupID arrays for v4-v8
                    if ( frame_stream_version >= 4 && frame_stream_version < 9 )
                    {
                        logger_log_debug(logger(), 30, "FrTOCAdcData::write() writing ", channelIDs.size(), " channelID values");
                        Stream << channelIDs;
                        logger_log_debug(logger(), 30, "FrTOCAdcData::write() writing ", groupIDs.size(), " groupID values");
                        Stream << groupIDs;
                    }

                    logger_log_debug(logger(), 30, "FrTOCAdcData::write() writing ", positions.size(), " position values");
                    Stream << positions;
                }
                else
                {
                    logger_log_debug(logger(), 20, "FrTOCAdcData::write() no data to write, nadc=0");
                    Stream << nadc_type( s );
                    // Stream << nadc_type( FrTOC::NO_DATA_AVAILABLE );
                }
                
                logger_log_debug(logger(), 10, "FrTOCAdcData::write() complete");
                
#if DEBUG
                std::cerr << "DEBUG: FrTOCAdcData::write: "
                          << ( Stream.tellp( ) - starting_location )
                          << std::endl;
#endif /* DEBUG */
            }

            inline FrTOCAdcDataClassicIO::reverse_lookup_type::mapped_type
            FrTOCAdcDataClassicIO::reverse_lookup(
                const reverse_lookup_type::key_type& Key ) const
            {
                if ( 0 == reverse_lookup_.size( ) )
                {
                    reverse_lookup_type::mapped_type index = 0;
                    for ( key_container_type::const_iterator
                              cur = m_keys.begin( ),
                              last = m_keys.end( );
                          cur != last;
                          ++cur, ++index )
                    {
                        reverse_lookup_[ *cur ] = index;
                    }
                }
                return reverse_lookup_[ Key ];
            }

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