//
// LDASTools frameCPP - Sequential Frame Reader for frame file validation
//
// 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.
//

#ifndef FRAMECPP__COMMON__SEQUENTIALFRAMEREADER_HH
#define FRAMECPP__COMMON__SEQUENTIALFRAMEREADER_HH

#include <memory>
#include <map>
#include <set>
#include <functional>

#include "framecpp/Common/FrameCPPServices.hh"
#include "framecpp/Common/FrameStream.hh"
#include "framecpp/Common/FrameBuffer.hh"
#include "framecpp/Common/MemoryBuffer.hh"
#include "framecpp/Common/StreamRefInterface.hh"
#include "framecpp/Common/FrameSpec.hh"
#include "framecpp/Common/FrHeader.hh"
#include "framecpp/Common/FrSH.hh"
#include "ldastoolsal/Logger.hh"

namespace FrameCPP {
namespace Common {

/// \brief Sequential frame reader supporting both file and stream inputs
///
/// This class provides sequential reading of frame objects without using TOC.
/// It can work with file-based streams (via filename constructor) or
/// memory-based streams (via stream constructor) for testing.
class SequentialFrameReader : public IFrameStream {
public:
    /// \brief Object processor callback
    using ObjectProcessor = std::function<bool(boost::shared_ptr<FrameSpec::Object>)>;
    
    /// \brief StreamRef filter - return true to process, false to skip
    using StreamRefFilter = std::function<bool(int class_id, int instance, streamsize_type length)>;
    
    /// \brief Processing modes
    enum class Mode {
        FULL_PROCESSING,        // Read and deserialize all objects
        FILTERED_PROCESSING,    // Use StreamRef filter to skip objects
        DESCRIPTION_ONLY,       // Optimized for FrSH/FrSE only (class_id 1 & 2)
        SKIP_UNKNOWN,          // Skip unknown objects and continue
        UNKNOWN_ONLY           // Report unknown objects, skip known ones
    };
    
    /// \brief Statistics tracking
    struct Statistics {
        size_t objects_read = 0;
        size_t objects_skipped = 0;
        size_t bytes_skipped = 0;
        size_t frsh_count = 0;
        size_t frse_count = 0;
    };

private:
    const FrameCPPServices& m_services;
    bool m_initialized = false;
    boost::shared_ptr<Common::FrHeader> m_frame_header;
    Mode m_mode = Mode::FULL_PROCESSING;
    
    // Filtering
    StreamRefFilter m_streamref_filter;
    std::set<int> m_allowed_class_ids;
    
    // Class ID mappings
    std::map<int, std::string> m_class_id_to_name;
    std::map<std::string, int> m_name_to_fsi;
    
    // Statistics
    Statistics m_stats;
    
    // State management callback for failed FrSH objects
    std::function<void(bool)> m_frsh_state_callback;
    
    // Unknown object handling callback
    std::function<void(int, int, const std::string&, const std::string&)> m_unknown_object_callback;

protected:
    /// \brief Get logger for sequential reader
    SimpleLogger* logger() const {
        return IFrameStream::logger(m_services);
    }
    
    /// \brief Get static logger for SequentialFrameReader (for static methods)
    /// \return Static logger instance (thread-safe, lazy initialization)
    /// Moved to SequentialFrameReader.cc to avoid ODR violations from function-local statics
    static SimpleLogger* static_logger();

public:
    /// \brief Constructor with filename
    /// \param filename Path to frame file
    /// \param services FrameCPP services
    SequentialFrameReader(const std::string& filename,
                         const FrameCPPServices& services);
    
    /// \brief Constructor with FrameBuffer (for testing and integration)
    /// \param buffer Frame buffer to take ownership of
    /// \param services FrameCPP services
    /// \param frame_version Frame specification version (0 = auto-detect)
    SequentialFrameReader(FrameBuffer<std::filebuf>* buffer,
                         const FrameCPPServices& services,
                         INT_2U frame_version = 0);

    /// \brief Constructor with MemoryBuffer (for in-memory testing)
    /// \param buffer Memory buffer to take ownership of
    /// \param services FrameCPP services
    /// \param frame_version Frame specification version (0 = auto-detect)
    SequentialFrameReader(MemoryBuffer* buffer,
                         const FrameCPPServices& services,
                         INT_2U frame_version = 0);

    /// \brief Set processing mode
    void setMode(Mode mode);
    
    /// \brief Set custom StreamRef filter for FILTERED_PROCESSING mode
    void setStreamRefFilter(StreamRefFilter filter) { 
        m_streamref_filter = filter; 
        m_mode = Mode::FILTERED_PROCESSING;
    }
    
    /// \brief Enable optimized description-only mode (FrSH/FrSE only)
    void enableDescriptionOnlyMode() {
        m_mode = Mode::DESCRIPTION_ONLY;
        m_allowed_class_ids = {1, 2};  // FrSH and FrSE only
    }
    
    /// \brief Set callback for FrSH state management (handles failed FrSH objects)
    /// \param callback Function called when FrSH is encountered (bool param = true if failed)
    void setFrSHStateCallback(std::function<void(bool)> callback) {
        m_frsh_state_callback = callback;
    }
    
    /// \brief Enable skip-unknown mode (continue on unknown objects)
    void enableSkipUnknownMode() {
        m_mode = Mode::SKIP_UNKNOWN;
    }

    /// \brief Enable unknown-only mode (report only unknown objects)
    void enableUnknownOnlyMode() {
        m_mode = Mode::UNKNOWN_ONLY;
    }

    /// \brief Set callback for unknown object encounters
    /// \param callback Function called with (class_id, instance, object_name, error_msg)
    void setUnknownObjectCallback(std::function<void(int, int, const std::string&, const std::string&)> callback) {
        m_unknown_object_callback = callback;
    }
    
    
    /// \brief Read next object (with filtering applied)
    boost::shared_ptr<FrameSpec::Object> readNextObject();
    
    /// \brief Process all objects with callback
    void processAllObjects(ObjectProcessor processor = nullptr);
    
    /// \brief Get statistics
    const Statistics& getStatistics() const { return m_stats; }
    
    /// \brief Get frame header
    boost::shared_ptr<Common::FrHeader> getFrameHeader() const {
        return m_frame_header;
    }
    
    /// \brief Get stream version (convenience method)
    /// \return Frame specification version of the stream
    unsigned int getStreamVersion() const {
        return streamVersion();
    }
    
    /// \brief Get FSI type for structure name
    /// \param name Structure name (e.g., "FrAdcData", "FrVect")
    /// \return FSI type number, or FSI_FR_NULL if unknown
    int GetFSI(const std::string& name) const;
    
private:
    /// \brief Check if StreamRef should be processed based on mode
    bool shouldProcessStreamRef(int class_id, int instance, streamsize_type length);
    
    /// \brief Skip object data in stream (accounting for already-read StreamRef)
    void skipObjectData(boost::shared_ptr<StreamRefInterface> streamref);
    
    /// \brief Initialize class ID mappings
    void initializeMappings();
    
    /// \brief Process FrSH to update class ID mapping
    void processFrSH(const Common::FrSH* frsh);
    
    /// \brief Lookup FSI from FrameSpec system for given structure name (DEPRECATED - use getDirectFSI)
    int lookupFSIFromFrameSpec(const std::string& structureName, unsigned int version);
    
    
    /// \brief Helper to create frame buffer for constructor
    static FrameBuffer<std::filebuf>* createFrameBuffer(const std::string& filename);
};

} // namespace Common
} // namespace FrameCPP

#endif // FRAMECPP__COMMON__SEQUENTIALFRAMEREADER_HH