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

#include "framecpp/Common/FrameCPPServices.hh"

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

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

#include "framecpp/Universal/STRING.hh"
#include "framecpp/Universal/FrSE.hh"
#include "framecpp/Universal/FrSH.hh"
#include "framecpp/Universal/FrTOCStatData.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 FrTOCStatDataData static logger method
            SimpleLogger* FrTOCStatDataData::logger() {
                static SimpleLogger* instance = get_channel_logger("framecpp.frtoc.stat");
                return instance;
            }

            // Implementation of FrTOCStatDataData dependency injection logger method
            SimpleLogger* FrTOCStatDataData::logger(const FrameCPP::Common::FrameCPPServices& services) {
                return services.logger_mgr.getLogger("framecpp.frtoc.stat");
            }
        } // 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
            FrTOCStatDataClassicIO::Bytes(
                const Common::StreamBase& Stream ) const
            {
                auto frame_stream_version = StreamVersion( Stream );
                logger_log_debug( logger( ),
                                  10,
                                  "FrTOCStatData::Bytes: Starting calculation "
                                  "for stream version ",
                                  frame_stream_version );

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

                if ( frame_stream_version < 9 )
                {
                    retval += //
                        sizeof( nstat_type ) // nStatType
                        + ( ( frame_stream_version >= 8 )
                                ? sizeof( nstat_type )
                                : 0 ) // nTotalStat
                        ;

                    static const INT_4U si = sizeof( tstart_type ) +
                        sizeof( tend_type ) + sizeof( version_type ) +
                        sizeof( positionStat_type );

                    logger_log_debug( logger( ),
                                      20,
                                      "FrTOCStatData::Bytes: Processing ",
                                      m_info.size( ),
                                      " stat data entries" );
                    for ( stat_container_type::const_iterator
                              cur = m_info.begin( ),
                              last = m_info.end( );
                          cur != last;
                          ++cur )
                    {
                        retval += STRING::Bytes( cur->first ) +
                            STRING::Bytes( cur->second.detector ) +
                            sizeof( nstat_instance_type ) +
                            ( cur->second.stat_instances.size( ) * si );
                    }
                }
                else
                {
                    logger_log_debug( logger( ),
                                      20,
                                      "FrTOCStatData::Bytes: No stat data for "
                                      "stream version >= 9" );
                }

                logger_log_debug( logger( ),
                                  15,
                                  "FrTOCStatData::Bytes: Total ",
                                  retval,
                                  " bytes" );
                return retval;
            }

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

                if ( frame_stream_version < 9 )
                {
                    Desc( FrSE(
                        "nStatType",
                        Description::Ascii< nstat_type >( ),
                        "Number of static data block types in the file." ) );
                    Desc( FrSE(
                        "nameStat",
                        ( frame_stream_version >= 8 )
                            ? Description::AsciiArray< name_type >(
                                  "nStatType" )
                                  .c_str( )
                            : Description::Ascii< name_type >( ),
                        "Array of FrStatData name (size of nStatType)" ) );
                    Desc( FrSE( "detector",
                                ( frame_stream_version >= 8 )
                                    ? Description::AsciiArray< detector_type >(
                                          "nStatType" )
                                          .c_str( )
                                    : Description::Ascii< detector_type >( ),
                                "Array of Detector name(size of nStatType)" ) );
                    Desc( FrSE(
                        "nStatInstance",
                        ( frame_stream_version >= 8 )
                            ? Description::AsciiArray< nstat_instance_type >(
                                  "nStatType" )
                                  .c_str( )
                            : Description::Ascii< nstat_instance_type >( ),
                        "Array of number of instance for each "
                        "FrStatData(size of "
                        "nStatType)" ) );
                    if ( frame_stream_version >= 8 )
                    {
                        Desc( FrSE( "nTotalStat",
                                    Description::Ascii< nstat_type >( ),
                                    "Summation of nStatInstance array" ) );
                    }
                    Desc( FrSE( "tStart",
                                ( frame_stream_version >= 8 )
                                    ? Description::AsciiArray< tstart_type >(
                                          "nTotalStat" )
                                          .c_str( )
                                    : Description::Ascii< tstart_type* >( ),
                                "Array of GPS integer start times,"
                                " in seconds (size of nTotalStat)" ) );
                    Desc( FrSE( "tEnd",
                                ( frame_stream_version >= 8 )
                                    ? Description::AsciiArray< tend_type >(
                                          "nTotalStat" )
                                          .c_str( )
                                    : Description::Ascii< tend_type* >( ),
                                "Array of GPS integer end times,"
                                " in seconds (size of nTotalStat)" ) );
                    Desc( FrSE( "version",
                                ( frame_stream_version >= 8 )
                                    ? Description::AsciiArray< version_type >(
                                          "nTotalStat" )
                                          .c_str( )
                                    : Description::Ascii< version_type* >( ),
                                "Array of version time"
                                " (size of nTotalStat)" ) );
                    Desc( FrSE(
                        "positionStat",
                        ( frame_stream_version >= 8 )
                            ? Description::AsciiArray< positionStat_type >(
                                  "nTotalStat" )
                                  .c_str( )
                            : Description::Ascii< positionStat_type* >( ),
                        "Array of FrStatData positions from beginning of file"
                        " (size of nTotalStat)" ) );
                }
            }

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

                if ( frame_stream_version < 9 )
                {
                    nstat_type nstat;

                    Stream >> nstat;
                    logger_log_debug( logger( ),
                                      15,
                                      "FrTOCStatData::Load: Read ",
                                      nstat,
                                      " stat types" );

                    if ( nstat && ( nstat != NO_DATA_AVAILABLE ) )
                    {
                        std::vector< name_type >           names( nstat );
                        std::vector< detector_type >       detectors( nstat );
                        std::vector< nstat_instance_type > nstatinstances(
                            nstat );
                        nstat_type ntotalstat;

                        logger_log_debug( logger( ),
                                          20,
                                          "FrTOCStatData::Load: Reading ",
                                          nstat,
                                          " stat names and detectors" );
                        for ( auto& name : names )
                        {
                            Common::FrTOC::string_stream_type::Read( Stream,
                                                                     name );
                        }
                        for ( auto& detector : detectors )
                        {
                            Common::FrTOC::string_stream_type::Read( Stream,
                                                                     detector );
                        }
                        Stream >> nstatinstances;
                        if ( frame_stream_version >= 8 )
                        {
                            Stream >> ntotalstat;
                            logger_log_debug(
                                logger( ),
                                20,
                                "FrTOCStatData::Load: Read ntotalstat=",
                                ntotalstat,
                                " from stream" );
                        }
                        else
                        {
                            ntotalstat =
                                std::accumulate( nstatinstances.begin( ),
                                                 nstatinstances.end( ),
                                                 0 );
                            logger_log_debug(
                                logger( ),
                                20,
                                "FrTOCStatData::Load: Calculated ntotalstat=",
                                ntotalstat );
                        }

                        std::vector< tstart_type >       tstart( ntotalstat );
                        std::vector< tend_type >         tend( ntotalstat );
                        std::vector< version_type >      version( ntotalstat );
                        std::vector< positionStat_type > position( ntotalstat );

                        logger_log_debug( logger( ),
                                          30,
                                          "FrTOCStatData::Load: Reading ",
                                          ntotalstat,
                                          " stat instance records" );
                        Stream >> tstart >> tend >> version >> position;

                        //---------------------------------------------------------------
                        // Fill in the structure
                        //---------------------------------------------------------------
                        logger_log_debug( logger( ),
                                          30,
                                          "FrTOCStatData::Load: Organizing "
                                          "stat data into containers" );
                        m_info.erase( m_info.begin( ), m_info.end( ) );
                        INT_4U offset = 0;
                        INT_4U suboffset = 0;
                        for ( std::vector< name_type >::const_iterator
                                  cur_name = names.begin( ),
                                  last_name = names.end( );
                              cur_name != last_name;
                              ++cur_name )
                        {
                            stat_type& s( m_info[ *cur_name ] );

                            //-------------------------------------------------------------
                            // Fill in the static data core
                            //-------------------------------------------------------------
                            s.detector = detectors[ offset ];
                            //-------------------------------------------------------------
                            // Fill in the version specific information
                            //-------------------------------------------------------------
                            if ( nstatinstances[ offset ] )
                            {
                                s.stat_instances.resize(
                                    nstatinstances[ offset ] );
                                for ( nstat_type
                                          x = 0,
                                          x_end = nstatinstances[ offset ];
                                      x != x_end;
                                      ++x )
                                {
                                    s.stat_instances[ x ].tStart =
                                        tstart[ suboffset ];
                                    s.stat_instances[ x ].tEnd =
                                        tend[ suboffset ];
                                    s.stat_instances[ x ].version =
                                        version[ suboffset ];
                                    s.stat_instances[ x ].positionStat =
                                        position[ suboffset ];
                                    ++suboffset;
                                }
                            }
                            ++offset;
                        }
                    }
                    else
                    {
                        logger_log_debug(logger(), 15, "FrTOCStatData::Load: No stat data, reading markers");
                        // nstat was already read (line 235), but for v8+ we also need to read ntotalstat
                        if ( frame_stream_version >= 8 )
                        {
                            nstat_type ntotalstat;
                            Stream >> ntotalstat;
                            logger_log_debug( logger( ),
                                              20,
                                              "FrTOCStatData::Load: Read ntotalstat=",
                                              ntotalstat,
                                              " (should be 0)" );
                        }
                    }
                }
                else
                {
                    logger_log_debug( logger( ),
                                      15,
                                      "FrTOCStatData::Load: No stat data for "
                                      "stream version >= 9" );
                }

                logger_log_debug(
                    logger( ), 10, "FrTOCStatData::Load: Load complete" );
            }

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

                STRING name;
                STRING detector;
                INT_4U start;
                INT_4U end;
                INT_4U version;

                Info.TOCQuery( TOCInfo::IC_NAME,
                               TOCInfo::DataType( name ),
                               &name,
                               TOCInfo::IC_DETECTOR,
                               TOCInfo::DataType( detector ),
                               &detector,
                               TOCInfo::IC_START,
                               TOCInfo::DataType( start ),
                               &start,
                               TOCInfo::IC_END,
                               TOCInfo::DataType( end ),
                               &end,
                               TOCInfo::IC_VERSION,
                               TOCInfo::DataType( version ),
                               &version,
                               TOCInfo::IC_EOQ );

                stat_instance_type si;

                si.tStart = start;
                si.tEnd = end;
                si.version = version;
                si.positionStat = Position;

                stat_type& i( m_info[ name ] );
                if ( ( i.detector.length( ) ) && ( i.detector != detector ) )
                {
                    std::ostringstream msg;

                    msg << "FrStatData with the name '" << name
                        << "' is associated with detector '" << i.detector
                        << "' and detector '" << detector << "'";
                    throw std::runtime_error( msg.str( ) );
                }
                i.detector = detector;
                i.stat_instances.push_back( si );
            }

            void
            FrTOCStatDataClassicIO::write( ostream_type& Stream ) const
            {
                logger_log_debug(
                    logger( ),
                    10,
                    "FrTOCStatData::write: Starting write operation" );
                auto starting_location{ Stream.tellp( ) };
                auto frame_stream_version = StreamVersion( Stream );

                logger_log_debug(
                    logger( ),
                    15,
                    "FrTOCStatData::write: Writing for stream version ",
                    frame_stream_version );

                if ( frame_stream_version < 9 )
                {
                    if ( m_info.size( ) > 0 )
                    {
                        //---------------------------------------------------------------
                        // Flatten the strucutres.
                        //---------------------------------------------------------------
                        logger_log_debug( logger( ),
                                          20,
                                          "FrTOCStatData::write: Flattening ",
                                          m_info.size( ),
                                          " stat data entries" );

                        nstat_type offset( 0 );

                        std::vector< name_type >     names( m_info.size( ) );
                        std::vector< detector_type > detectors(
                            m_info.size( ) );
                        std::vector< nstat_type > nstatinstances(
                            m_info.size( ) );
                        nstat_type ntotalstat = 0;

                        std::vector< tstart_type >       tstart;
                        std::vector< tend_type >         tend;
                        std::vector< version_type >      version;
                        std::vector< positionStat_type > position;

                        for ( stat_container_type::const_iterator
                                  cur = m_info.begin( ),
                                  last = m_info.end( );
                              cur != last;
                              ++cur, ++offset )
                        {
                            names[ offset ] = cur->first;
                            detectors[ offset ] = cur->second.detector;
                            nstatinstances[ offset ] =
                                cur->second.stat_instances.size( );
                            ntotalstat += cur->second.stat_instances.size( );
                            for ( stat_instance_container_type::const_iterator
                                      cur_instance =
                                          cur->second.stat_instances.begin( ),
                                      last_instance =
                                          cur->second.stat_instances.end( );
                                  cur_instance != last_instance;
                                  ++cur_instance )
                            {
                                tstart.push_back( cur_instance->tStart );
                                tend.push_back( cur_instance->tEnd );
                                version.push_back( cur_instance->version );
                                position.push_back(
                                    cur_instance->positionStat );
                            }
                        }

                        logger_log_debug( logger( ),
                                          20,
                                          "FrTOCStatData::write: Total stat "
                                          "instances to write: ",
                                          ntotalstat );
                        logger_log_debug( logger( ),
                                          30,
                                          "FrTOCStatData::write: Writing stat "
                                          "type count and names" );
                        Stream << nstat_type( m_info.size( ) );
                        for ( auto& name : names )
                        {
                            STRING::Write( Stream, name );
                        }
                        logger_log_debug(
                            logger( ),
                            30,
                            "FrTOCStatData::write: Writing detectors" );
                        for ( auto& detector : detectors )
                        {
                            STRING::Write( Stream, detector );
                        }
                        logger_log_debug(
                            logger( ),
                            30,
                            "FrTOCStatData::write: Writing instance counts" );
                        Stream << nstatinstances;
                        if ( frame_stream_version >= 8 )
                        {
                            logger_log_debug( logger( ),
                                              30,
                                              "FrTOCStatData::write: Writing "
                                              "ntotalstat for v8+" );
                            Stream << ntotalstat;
                        }
                        logger_log_debug( logger( ),
                                          30,
                                          "FrTOCStatData::write: Writing stat "
                                          "instance data" );
                        Stream << tstart << tend << version << position;
                    }
                    else
                    {
                        logger_log_debug( logger( ),
                                          20,
                                          "FrTOCStatData::write: No stat data, "
                                          "writing empty markers" );
#if WORKING
                        Stream << nstat_type( NO_DATA_AVAILABLE );
#else /* WORKING */
                        Stream << nstat_type( 0 ); // nStatType
#endif /* WORKING */
                        if ( frame_stream_version >= 8 )
                        {
                            Stream << nstat_type( 0 ); // ntotalstat
                            logger_log_debug( logger( ),
                                              30,
                                              "FrTOCStatData::write: Wrote "
                                              "ntotalstat=0 for v8+" );
                        }
                    }
                }
                else
                {
                    logger_log_debug( logger( ),
                                      20,
                                      "FrTOCStatData::write: No stat data for "
                                      "stream version >= 9" );
                }

                auto bytes_written = Stream.tellp( ) - starting_location;
                logger_log_debug( logger( ),
                                  15,
                                  "FrTOCStatData::write: Wrote ",
                                  bytes_written,
                                  " bytes" );
                logger_log_debug(
                    logger( ), 10, "FrTOCStatData::write: Write complete" );
            }

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

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