/********************************************************************************
 * Copyright (c) 2025-2026 ZF Friedrichshafen AG
 *
 * This program and the accompanying materials are made available under the 
 * terms of the Apache License Version 2.0 which is available at
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Contributors:
 *   Erik Verhoeven - initial API and implementation
 ********************************************************************************/

#ifndef TRACE_FIFO_H
#define TRACE_FIFO_H

#include <cstdint>
#include <mutex>
#include <string>
#include <sstream>
#include <map>
#include <atomic>
#include <memory>
#include <thread>

/**
 * @brief trace fifo open flags
 */
enum class ETraceFifoOpenFlags : uint32_t
{
    open_only = 1,      ///< Open only. Do not create a new shared memory area.
    force_create = 2,   ///< Create a new shared memory area, removing any previous connection.
    read_only = 4,      ///< Open the shared memory for read-only access.
};

/**
 * @brief Trace fifo class allowing the publishing and monitoring of trace messages.
 */
class CTraceFifoBase
{
public:
    /**
     * @brief Default constructor
     * @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
     * @param[in] nSize Default size of the fifo.
     */
    CTraceFifoBase(uint32_t uiInstanceID, size_t nSize);

    /**
     * @brief Default destructor
     * @remarks Automatically closes the fifo if opened before.
     */
    virtual ~CTraceFifoBase();

    /**
     * @brief Copy constructor is not available.
     * @param[in] rfifo Reference to the fifo to copy.
     */
    CTraceFifoBase(const CTraceFifoBase& rfifo) = delete;

    /**
     * @brief Move constructor.
     * @param[in] rfifo Reference to the fifo.
     */
    CTraceFifoBase(CTraceFifoBase&& rfifo) noexcept;

    /**
     * @brief Copy assignment is not available.
     * @param[in] rfifo Reference to the fifo.
     * @return Returns a reference to this fifo.
     */
    CTraceFifoBase& operator=(const CTraceFifoBase& rfifo) = delete;

    /**
     * @brief Move assignment.
     * @param[in] rfifo Reference to the fifo.
     * @return Returns a reference to this fifo.
     */
    CTraceFifoBase& operator=(CTraceFifoBase&& rfifo) noexcept;

    /**
     * @brief Open the fifo. Implemented by derived class.
     * @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
     * established.
     * @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
     * @return Returns true when connected; false otherwise.
     */
    virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) = 0;

    /**
     * @brief Cancel any running task and close an open fifo. Implemented by derived class.
     */
    virtual void Close() = 0;

    /**
     * @brief Is the fifo open for reading and writing? Implemented by derived class.
     * @return Returns true when opened; false otherwise.
     */
    virtual bool IsOpened() const = 0;

    /**
     * @brief Set a new instance ID for the communication.
     * @remarks The instance ID can only be set when the fifo is not opened yet.
     * @param[in] uiInstanceID The new instance ID.
     * @return Returns whether setting the instance ID was successful.
     */
    bool SetInstanceID(uint32_t uiInstanceID);

    /**
     * @brief Return the instance ID to use for the communication.
     * @return The instance ID.
     */
    uint32_t GetInstanceID() const;

    /**
     * @brief Set a new default size to be used during creation of the fifo.
     * @param[in] nSize The new default size of the buffer.
     */
    void SetDefaultSize(size_t nSize);

    /**
     * @brief Get the default size used during creation of the fifo.
     * @return The default size of the buffer.
     */
    size_t GetDefaultSize() const;

    /**
     * @brief Get the size of the view.
     * @return The view size.
     */
    size_t GetViewSize() const;

     /**
     * @brief Get the size of the buffer without any headers.
     * @return Returns the size of the buffer or 0 when the buffer is not initialized.
     */
    size_t GetDataBufferSize() const;

protected:
    /**
     * @brief Initialize the buffer after successful opening.
     * @param[in] pView Pointer to the shared memory view.
     * @param[in] nBufferSize The size of the buffer allocated.
     * @param[in] bReadOnly When set, the buffer is initialized as read-only. Multiple read-only and only one writable buffer access
     * are allowed.
     */
    void InitializeBuffer(void* pView, size_t nBufferSize, bool bReadOnly);

    /**
     * @brief Check whether the buffer is initialized (signature and instance ID are set).
     * @return Returns true when the buffer has been initialized; false when not.
    */
    bool IsInitialized() const;

    /**
     * @brief Clear the buffer pointers
     */
    void Terminate();

    /**
     * @brief Create a lock object for exclusive access of the buffer.
     * @return The lock object that allows access.
     */
    std::unique_lock<std::recursive_mutex> CreateAccessLockObject() const;

    /**
     * @brief Get access to the view.
     * @return Get a pointer to the buffer. Returns NULL when the buffer is not initialized.
     */
    void* GetView();

    /**
     * @{
     * @brief Get access to the data portion of the buffer.
     * @return Get a pointer to the data. Returns NULL when the buffer is not initialized.
     */
    uint8_t* GetDataPtr();
    const uint8_t* GetDataPtr() const;
    /**
     * @}
     */

    /**
     * @brief Get the write position.
     * @return The current write position within the buffer.
    */
    size_t GetWritePos() const;

    /**
     * @brief Set the write position. Can only be used by writable buffer (causing an access violation otherwise).
     * @param[in] nTxPos The new write position.
     */
    void SetWritePos(size_t nTxPos);

private:
    /**
     * @brief Shared memory header structure; at the front of the shared memory buffer.
     */
    struct SSharedMemBufHeader
    {
        char                rgszSignature[8];   ///< Signature "SDV_MON\0"
        uint32_t            uiInstanceID;       ///< Instance ID of the server instance
        volatile uint32_t   uiTxOffs;           ///< Tx position
    };

    mutable volatile bool           m_bInitConfirmed = false;           ///< When set, bypasses the header checking.
    uint32_t                        m_uiInstanceID = 1000;              ///< Instance ID to use while connecting.
    size_t                          m_nSize = 0;                        ///< Size of the fifo.
    size_t                          m_nDefaultSize = 0;                 ///< Requested size.
    uint8_t*                        m_pBuffer = nullptr;                ///< Pointer to the buffer.
    SSharedMemBufHeader*            m_psHdr = nullptr;                  ///< Shared memory header at from of the buffer.
    mutable std::recursive_mutex    m_mtxAccess;                        ///< Protect against sudden closure.
};

// Include the implementation classes classes for the trace fifo
#include "trace_fifo_windows.h"
#include "trace_fifo_posix.h"

/**
 * @brief Reader class for the trace fifo. Multiple readers can coexist.
 */
class CTraceFifoReader : public CTraceFifoImpl
{
public:
    /**
     * @brief Default constructor
     * @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
     * @param[in] nSize Default size of the fifo.
     */
    CTraceFifoReader(uint32_t uiInstanceID = 1000u, size_t nSize = 16384);

    /**
     * @brief Default destructor
     * @remarks Automatically closes the fifo if opened before.
     */
    virtual ~CTraceFifoReader() override;

    /**
     * @brief Copy constructor is not available.
     * @param[in] rfifo Reference to the fifo to copy.
     */
    CTraceFifoReader(const CTraceFifoReader& rfifo) = delete;

    /**
     * @brief Move constructor.
     * @param[in] rfifo Reference to the fifo.
     */
    CTraceFifoReader(CTraceFifoReader&& rfifo) noexcept;

    /**
     * @brief Copy assignment is not available.
     * @param[in] rfifo Reference to the fifo.
     * @return Returns a reference to this fifo.
     */
    CTraceFifoReader& operator=(const CTraceFifoReader& rfifo) = delete;

    /**
     * @brief Move assignment.
     * @param[in] rfifo Reference to the fifo.
     * @return Returns a reference to this fifo.
     */
    CTraceFifoReader& operator=(CTraceFifoReader&& rfifo) noexcept;

    /**
     * @brief Open the fifo. Override of CTraceFifoBase::Open.
     * @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
     * established.
     * @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
     * @return Returns true when connected; false otherwise.
     */
    virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;

    // Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
    // cppcheck-suppress virtualCallInConstructor
    /**
     * @brief Cancel any running task and close an open fifo. Override of CTraceFifoBase::Close.
     */
    virtual void Close() override;

    /**
     * @brief Wait for a message.
     * @remarks Automatically opens the fifo if not opened before.
     * @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a message has been
     * received.
     * @return The received message or an empty message if a timeout occurred or the publisher had sent an empty message.
     */
    std::string WaitForMessage(size_t nTimeout = 1000);

private:
    size_t      m_nRxOffs = 0;      ///< Reader position
};

/**
 * @brief Writer class for a trace fifo. Only one writer should be present.
 */
class CTraceFifoWriter : public CTraceFifoImpl
{
public:
    /**
     * @brief Default constructor
     * @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
     * @param[in] nSize Default size of the fifo.
     */
    CTraceFifoWriter(uint32_t uiInstanceID = 1000u, size_t nSize = 16384);

    /**
     * @brief Default destructor
     * @remarks Automatically closes the fifo if opened before.
     */
    virtual ~CTraceFifoWriter() override;

    /**
     * @brief Copy constructor is not available.
     * @param[in] rfifo Reference to the fifo to copy.
     */
    CTraceFifoWriter(const CTraceFifoWriter& rfifo) = delete;

    /**
     * @brief Move constructor.
     * @param[in] rfifo Reference to the fifo.
     */
    CTraceFifoWriter(CTraceFifoWriter&& rfifo) noexcept;

    /**
     * @brief Copy assignment is not available.
     * @param[in] rfifo Reference to the fifo.
     * @return Returns a reference to this fifo.
     */
    CTraceFifoWriter& operator=(const CTraceFifoWriter& rfifo) = delete;

    /**
     * @brief Move assignment.
     * @param[in] rfifo Reference to the fifo.
     * @return Returns a reference to this fifo.
     */
    CTraceFifoWriter& operator=(CTraceFifoWriter&& rfifo) noexcept;

    /**
     * @brief Open the fifo. Override of CTraceFifoBase::Open.
     * @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
     * established.
     * @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
     * @return Returns true when connected; false otherwise.
     */
    virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;

    // Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
    // cppcheck-suppress virtualCallInConstructor
    /**
     * @brief Cancel any running task and close an open fifo. Override of CTraceFifoBase::Close.
     */
    virtual void Close() override;

    /**
     * @brief Publish a message. If the buffer is full, the oldest message is removed.
     * @remarks Automatically opens the fifo if not opened before.
     * @param[in] rssMessage Reference to the message to publish.
     */
    void Publish(const std::string& rssMessage);

};

/**
 * @brief Trace fifo stream buffer object.
 * @remarks This implementation works for applications within the scope of the C++ library source compilation into one binary unit.
 * This is due to the templated nature of the C++ library allowing very little reuse of compiled code across binary units. This
 * means, that although interception will work in one unit (e.g. executable or static/shared library), it might not intercept
 * messages from another unit.
 */
class CTraceFifoStreamBuffer : public CTraceFifoWriter, public std::stringbuf
{
public:
    /**
     * @brief Constructor
     * @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
     * @param[in] nSize Default size of the fifo.
     */
    CTraceFifoStreamBuffer(uint32_t uiInstanceID = 1000u, size_t nSize = 16384);

    /**
     * @brief Destructor
     */
    virtual ~CTraceFifoStreamBuffer() override;

    /**
     * @brief Assign this buffer to a stream object. Any message will be streamed to both the original stream as well as the trace
     * fifo.
     * @attention The stream object needs to stay in scope until the assignment is removed or this class is destroyed.
     * @attention Works for text only.
     * @remarks Removal of this assignment is done automatically during destruction of this class.
     * @param[in] rstream Reference to the stream to intercept the communication for.
     */
    void InterceptStream(std::ostream& rstream);

    /**
     * @brief Remove the interception of a stream.
     * @param[in] rstream Reference to the stream to revert the interception for.
     */
    void RevertInterception(std::ostream& rstream);

    // Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
    // cppcheck-suppress virtualCallInConstructor
    /**
     * @brief Cancel any running task and close an open fifo. Override of CTraceFifoBase::Close.
     */
    virtual void Close() override;

    /**
     * @brief Synchronizes the buffers with the associated character sequence.
     * @return Returns 0 if successful; -1 if not.
     */
    int sync();

private:
    /**
     * @brief Stream interception binding.
     */
    struct SInterceptBinding
    {
        /**
         * @brief Constructor doing the binding.
         */
        SInterceptBinding(std::ostream& rstream, CTraceFifoStreamBuffer& rstreamBuffer);

        //SInterceptBinding(SInterceptBinding& rsBinding) : rstreamIntercepted(rsBinding.rstreamIntercepted), streamOrginal(rsBinding.streamOrginal.rdbuf()) {}

        /**
         * @brief Destructor undoing the binding.
         */
        ~SInterceptBinding();

        std::ostream&   rstreamIntercepted;     ///< The intercepted stream
        std::ostream    streamOrginal;          ///< Stream object redirecting the buffer.
    };

    std::map<std::ostream*, std::unique_ptr<SInterceptBinding>> m_mapBindings;    ///< Map with intercepted stream bindings.
};

/**
 * @brief Interception and dispatching of all messages from STDOUT and STDERR to the trace fifo.
 */
class CTraceFifoStdBuffer : public CTraceFifoWriter
{
public:
    /**
     * @brief Constructor
     * @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
     * @param[in] nSize Default size of the fifo.
     */
    CTraceFifoStdBuffer(uint32_t uiInstanceID = 1000u, size_t nSize = 16384);

    /**
     * @brief Destructor
     */
    virtual ~CTraceFifoStdBuffer() override;

    /**
     * @brief Open the fifo and direct all messages from StdOut and StdErr to the trace fifo. Override of CTraceFifoBase::Open.
     * @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
     * established.
     * @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
     * @return Returns true when connected; false otherwise.
     */
    virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;

    // Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
    // cppcheck-suppress virtualCallInConstructor
    /**
     * @brief Revert the redirection and close the open fifo. Override of CTraceFifoBase::Close.
     */
    virtual void Close() override;

private:
    /**
     * @brief Read from the pipe and forward the text to the trace fifo. Read until shutdown flag is switch on.
     */
    void DispatchThreadFunc();

    const size_t        nReadIndex = 0;                    ///< The read index of the pipe descriptor.
    const size_t        nWriteIndex = 1;                   ///< The write index of the pipe descriptor.
    std::atomic_bool    m_bShutdown = false;                ///< Shutdown flag for the pipe reader thread.
    int                 m_rgPipeStdOut[2] = { -1, -1 };     ///< StdOut pipe with read and write descriptors.
    int                 m_rgPipeStdErr[2] = { -1, -1 };     ///< StdErr pipe with read and write descriptors.
    int                 m_iOldStdOut = -1;                  ///< Old descriptor for the StdOut
    int                 m_iOldStdErr = -1;                  ///< Old descriptor for the StdErr
    std::thread         m_threadDispatch;                   ///< Dispatch thread
};

#endif // !defined TRACE_FIFO_H