//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2018 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 "framecpp/Universal/FrTOCSimData.hh"

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

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

using FrameCPP::Universal::STRING;

namespace FrameCPP
{
    namespace Universal
    {
        //===================================================================
        //===================================================================
        void
        FrTOCSimData::QuerySim( const Common::TOCInfo& Info,
                                INT_4U                 FrameOffset,
                                INT_8U                 Position )
        {
            STRING name;

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

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

    } // namespace Universal
} // namespace FrameCPP

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

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

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

            void
            FrTOCSimDataClassicIO::QuerySim( 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 );

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

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

            void
            FrTOCSimDataClassicIO::GetDescription(
                unsigned int         frame_stream_version,
                Common::Description& Desc ) const
            {
                using Common::Description;

                Desc( FrSE( "nSim",
                            Description::Ascii< nsim_type >( ),
                            "Number of unique FrSimData names in file." ) );
                Desc( FrSE( "nameSim", //
                            ( frame_stream_version >= 8 )
                                ? Description::AsciiArray< name_type >( "nSim" )
                                      .c_str( )
                                : Description::Ascii< name_type* >( ), //
                            "Array of FrSimData names" ) );
                Desc( FrSE( "positionSim",
                            ( frame_stream_version >= 8 )
                                ? Description::AsciiArray< position_type >(
                                      "nSim", "nFrame" )
                                      .c_str( )
                                : Description::Ascii< position_type* >( ),
                            "Array of lists of FrSimData offset positions, in "
                            "bytes,"
                            " from beginning of file (size of nFrame*nSim)" ) );
            }

            void
            FrTOCSimDataClassicIO::Load( istream_type& Stream,
                                         INT_4U&       nframe )
            {
                auto frame_stream_version = StreamVersion( Stream );
                
                logger_log_debug(logger(), 10, "FrTOCSimData::Load() entry, stream version ", frame_stream_version, ", nframe=", nframe);
                
                nsim_type n;

                Stream >> n;
                
                logger_log_debug(logger(), 20, "FrTOCSimData::Load() read nsim=", n);
                
                if ( n && ( n != FrTOC::NO_DATA_AVAILABLE ) )
                {
                    //---------------------------------------------------------------
                    // Read in the information
                    //---------------------------------------------------------------
                    std::vector< name_type >::size_type s( n );
                    std::vector< position_type > positions( s * nframe );

                    logger_log_debug(logger(), 30, "FrTOCSimData::Load() reading ", s, " channels with ", nframe, " frames each");

                    {
                        std::vector< STRING > temp_sim_data_keys;
                        temp_sim_data_keys.resize( s );
                        
                        logger_log_debug(logger(), 30, "FrTOCSimData::Load() reading channel names and ", positions.size(), " position values");
                        Stream >> temp_sim_data_keys >> positions;

                        sim_data_keys.assign( std::make_move_iterator(
                                                  temp_sim_data_keys.begin( ) ),
                                              std::make_move_iterator(
                                                  temp_sim_data_keys.end( ) ) );
                    }

                    //---------------------------------------------------------------
                    // Move into structure.
                    //---------------------------------------------------------------
                    typedef FrameCPP::Common::FrTOCPositionInputFunctor<
                        MapSim_type,
                        name_type,
                        position_type >
                        functor;

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

                    std::for_each(
                        sim_data_keys.begin( ), sim_data_keys.end( ), f );
                    
                    logger_log_debug(logger(), 20, "FrTOCSimData::Load() processed ", n, " Sim channels with ", nframe, " frames each");
                }
                else
                {
                    logger_log_debug(logger(), 20, "FrTOCSimData::Load() no Sim data available (nsim=", n, ")");
                }
                
                logger_log_debug(logger(), 10, "FrTOCSimData::Load() complete");
            }

            //-------------------------------------------------------------------
            /// This method allows for iterting over each element of information
            /// and allows the caller to gather information about each element.
            //-------------------------------------------------------------------
            void
            FrTOCSimDataClassicIO::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 ( MapSim_type::const_iterator
                                  cur = sim_data.begin( ),
                                  last = sim_data.end( );
                              cur != last;
                              ++cur )
                        {
                            action( cur->first );
                        }
                    }
                    catch ( ... )
                    {
                        // Does not understand Action
                    }
                }
                break;
                default:
                    //---------------------------------------------------------------
                    // ignore all other requests
                    //---------------------------------------------------------------
                    break;
                }
            }

            void
            FrTOCSimDataClassicIO::write( ostream_type& Stream ) const
            {
                auto frame_stream_version = StreamVersion( Stream );
                
                logger_log_debug(logger(), 10, "FrTOCSimData::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( sim_data.size( ) );
                
                logger_log_debug(logger(), 20, "FrTOCSimData::write() processing ", s, " Sim channels");
                
                if ( s )
                {
                    std::vector< name_type >::size_type fc(
                        sim_data.begin( )->second.size( ) );

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

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

                    for ( MapSim_type::const_iterator cur = sim_data.begin( ),
                                                      last = sim_data.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, "FrTOCSimData::write() writing nsim=", s, ", ", names.size(), " names, ", positions.size(), " positions");
                    Stream << nsim_type( s ) << names << positions;
                }
                else
                {
                    logger_log_debug(logger(), 20, "FrTOCSimData::write() no data to write, nsim=NO_DATA_AVAILABLE");
                    Stream << nsim_type( FrTOC::NO_DATA_AVAILABLE );
                }
                
                logger_log_debug(logger(), 10, "FrTOCSimData::write() complete");
#if DEBUG
                std::cerr << "DEBUG: FrTOCSimData::write: "
                          << ( Stream.tellp( ) - starting_location )
                          << std::endl;
#endif /* DEBUG */
            }

        } // namespace FrTOCImpl

    } // namespace Universal
} // namespace FrameCPP
