/********************************************************************************
 * 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 MEM_BUFFER_ACCESSOR_H
#define MEM_BUFFER_ACCESSOR_H

#include <deque>
#include <mutex>
#include <cassert>
#include <cstring>
#include <iostream>
#include <sstream>
#include <thread>
#include <optional>
#include <interfaces/core_types.h>

/**
 * @brief Accessor direction
*/
enum class EAccessType
{
    rx,     ///< Reading accessor
    tx,     ///< Writing accessor
};

/**
 * @brief Memory buffer accessor.
 * @details Lock-free memory buffer accessor. This buffer allows the writing and reading of memory packets in a circular buffer.
 * @attention This class does not synchronize calls! It works if only one thread at the time writes data and only one thread at
 * the time reads data. Reading and writing can occur simulateously.
 * @attention No protection is implemented to monitor the lifetime of the buffer. It is assumed that during any call this buffer
 * is valid.
 * @remarks This class doesn't provide any handshaking. This is the task of the objects using this class.
 */
class CMemBufferAccessorBase
{
protected:
    /**
     * @brief Memory buffer header
     */
    struct SBufferHdr
    {
        uint32_t uiVersion = SDVFrameworkInterfaceVersion;  ///< Check for compatible communication channel
        uint32_t uiSize	   = 0u;                            ///< The size of the buffer
        uint32_t uiTxPos   = 0u;                            ///< Current write position
        uint32_t uiRxPos   = 0u;                            ///< Current read position
    };

public:
    /**
     * @brief Packet header
     * @details The packet header is placed in front of every packet and describes the packet type as well as the size of the
     * packet.
     * @remarks Packets are always starting at a 64-bit boundary.
     */
    struct SPacketHdr
    {
        /**
         * @brief Packet types
         */
        enum class EType : uint16_t
        {
            data	 = 0, ///< The packet contains data
            stuffing = 1, ///< The packet doesn't contain data and is used to fill up space
        } eType;		  ///< Packet type

        /**
         * @brief Packet state
         */
        enum class EState : uint16_t
        {
            free	 = 0, ///< Packet is not reserved; overwriting is allowed
            reserved = 1, ///< Space is reserved but not committed.
            commit	 = 2, ///< Packet is committed and available for reading.
            read     = 3, ///< Packet is currently being read.
        } eState;	///< The packet state

        uint32_t uiSize; ///< Packet size
    };

    /**
     * @brief Constructor
     */
    CMemBufferAccessorBase() = default;

    /**
     * @brief Attach the buffer to the accessor.
     * @param[in] pBuffer Pointer to the buffer that should be accessed.
     * @param[in] uiSize Size of the buffer when still uninitialized (during buffer creation); otherwise 0.
     */
    virtual void Attach(uint8_t* pBuffer, uint32_t uiSize = 0);

    /**
     * @brief Detach the buffer from the accessor.
     */
    virtual void Detach();

    /**
     * @brief Is valid.
     * @return Returns 'true' when valid; otherwise 'false'.
     */
    bool IsValid() const;

    /**
     * @brief Get the buffer pointer.
     * @return Returns a pointer to the buffer or NULL when there is no buffer.
     */
    const uint8_t* GetBufferPointer() const;

    /**
     * @brief Reset the Rx position to skip any sent data. Typically needed when a reconnect should take place.
     */
    void ResetRx() { if (m_pHdr) m_pHdr->uiRxPos = m_pHdr->uiTxPos; m_bCancel = false; }

    /**
     * @brief Cancel send operation.
     */
    void CancelSend() { m_bCancel = true; TriggerDataReceive(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); }

    /**
     * @brief Canceled?
     * @return Returns 'true' when the currend send job has been canceled; 'false' when not.
     */
    bool Canceled() const { return m_bCancel; }

protected:
    /**
     * @brief Providing the position, return the packet header preceding the data.
     * @param[in] uiPos The position in the buffer
     * @return Pointer to the packet header or NULL when the position isn't within the buffer.
     */
    SPacketHdr* GetPacketHdr(uint32_t uiPos) const;

    /**
     * @brief Providing the position, return the packet data following the header.
     * @param[in] uiPos The position in the buffer
     * @return Pointer to the packet data or NULL when the position isn't within the buffer.
     */
    uint8_t* GetPacketData(uint32_t uiPos) const;

    /**
     * @brief Has unread data.
     * @return Returns 'true' when unread data is still available; otherwise returns 'false'.
     */
    virtual bool HasUnreadData() const { return m_pHdr && m_pHdr->uiRxPos != m_pHdr->uiTxPos; }

    /**
     * @brief Trigger listener that a write operation was completed.
     */
    virtual void TriggerDataSend() = 0;

    /**
     * @brief Wait for a write operation to be completed.
     * @param[in] uiTimeoutMs The amount of time (in ms) to wait for a trigger.
     * @return Returns 'true' when data was stored, 'false' when a timeout occurred.
     */
    virtual bool WaitForData(uint32_t uiTimeoutMs) const = 0;

    /**
     * @brief Trigger listener that a read operation was completed.
     */
    virtual void TriggerDataReceive() = 0;

    /**
     * @brief Wait for a read operation to be completed.
     * @param[in] uiTimeoutMs The amount of time (in ms) to wait for a trigger.
     * @return Returns 'true' when data was stored, 'false' when a timeout occurred.
     */
    virtual bool WaitForFreeSpace(uint32_t uiTimeoutMs) const = 0;

    /**
     * @brief Align to 64 bits
     * @tparam T The type of the size argument.
     * @param[in] tSize The current size.
     * @return The 64-bit aligned size.
     */
    template <typename T>
    inline uint32_t Align(T tSize)
    {
        uint32_t uiSize = static_cast<uint32_t>(tSize);
        return (uiSize % 8) ? uiSize + 8 - uiSize % 8 : uiSize;
    }

    uint8_t*    m_pBuffer = nullptr;    ///< Buffer pointer
    SBufferHdr* m_pHdr  = nullptr;      ///< Buffer header
    bool        m_bCancel = false;      ///< Cancel the send operation
};

// Forward declaration
class CMemBufferAccessorTx;

/**
 * @brief Tx access can be done randomly. To prevent shutting down while packets are reserved but not committed, the accessor
 * packet take over the management of this.
 */
class CAccessorTxPacket
{
public:
    /**
     * @brief Default constructor
     */
    CAccessorTxPacket() = default;

    /**
     * @brief Constructor
     * @param[in] rAccessor Reference to the accessor managing the buffer.
     * @param[in] pPacketHdr Pointer to the packet header.
     */
    CAccessorTxPacket(CMemBufferAccessorTx& rAccessor, CMemBufferAccessorBase::SPacketHdr* pPacketHdr);

    /**
     * @brief Copy constructor is deleted.
     * @param[in] rpacket Reference to the packet to copy from.
     */
    CAccessorTxPacket(const CAccessorTxPacket& rpacket) = delete;

    /**
     * @brief Move constructor
     * @param[in] rpacket Reference to the packet to move from.
     */
    CAccessorTxPacket(CAccessorTxPacket&& rpacket) noexcept;

    /**
     * @brief Destructor - will automatically commit the packet if not done so already.
     */
    ~CAccessorTxPacket();

    /**
     * @brief Copy operator is deleted.
     * @param[in] rpacket Reference to the packet to copy from.
     * @return Reference to this packet class.
     */
    CAccessorTxPacket& operator=(const CAccessorTxPacket& rpacket) = delete;

    /**
     * @brief Copy operator is deleted.
     * @param[in] rpacket Reference to the packet to copy from.
     * @return Reference to this packet class.
     */
    CAccessorTxPacket& operator=(CAccessorTxPacket&& rpacket) noexcept;

    /**
     * @brief Cast operator used to check validity.
     */
    operator bool() const;

    /**
     * @brief Does the packet contain valid data (data is not committed yet).
     * @return The validity of the packet.
     */
    bool IsValid() const;

    /**
     * @brief Commit the data. This will invalidate the content.
    */
    void Commit();

    /**
     * @brief Get the size of the packet data.
     * @return The packet size; could be 0 when no data was allocated.
     */
    uint32_t GetSize() const;

    /**
     * @brief Get the packet pointer.
     * @return Pointer to the buffer holding the packet data or nullptr when there is no data allocated or the packet has been
     * committed.
     */
    uint8_t* GetDataPtr();

    /**
     * @brief Templated version of GetData.
     * @tparam TData The type to cast the data pointer to.
     * @param[in] uiOffset The offset to use in bytes (default no offset).
     * @return Pointer to the data or NULL when the data doesn't fit in the buffer.
     */
    template <typename TData>
    TData* GetDataPtr(uint32_t uiOffset = 0)
    {
        return GetSize() >= (uiOffset + sizeof(TData)) ? reinterpret_cast<TData*>(GetDataPtr()) : nullptr;
    }

private:
    CMemBufferAccessorTx*               m_pAccessor = nullptr;      ///< Pointer to the accessor.
    CMemBufferAccessorBase::SPacketHdr* m_pPacketHdr = nullptr;     ///< Packet header.
};

/**
 * @brief Accessor TX implementation
 */
class CMemBufferAccessorTx : public CMemBufferAccessorBase
{
    friend CAccessorTxPacket;

public:
    /**
     * @brief Destructor
     */
    ~CMemBufferAccessorTx();

    /**
     * @brief Reserve memory for writing to the buffer without an extra copy.
     * @param[in] uiSize Size of the memory block to reserve. Must be smaller than the buffer size.
     * @param[in] uiTimeoutMs The amount of time (in ms) to wait for a free buffer. Default 1000 ms (needed when allocating large
     * chunks of data that need to be allocated on the receiving side).
     * @return Returns a packet object if successful or an empty option when a timeout occurred.
     * @attention The reader will only be able to continue reading after a commit. If, for some reason, there is no commit, the
     * reader won't be able to retrieve any more data.
     * @attention Performance is poor if the size is close to the buffer size. As a rule, uiSize should be at the most 1/4th of
     * the buffer size.
     */
    std::optional<CAccessorTxPacket> Reserve(uint32_t uiSize, uint32_t uiTimeoutMs = 1000);

    /**
     * @brief Write data to the buffer.
     * @param[in] pData Pointer to the data to write.
     * @param[in] uiSize Length of the data in bytes.
     * @return Returns 'true' if writing was successful or 'false' if the packet was invalid or (currently) doesn't fit into the
     * buffer.
     */
    bool TryWrite(const void* pData, uint32_t uiSize);

    /**
     * @brief Get the accessor access type.
     * @return The access type of this accessor.
     */
    static constexpr EAccessType GetAccessType() { return EAccessType::tx; };

private:
    /**
     * @brief Commit the writing to the buffer.
     * @param[in] pPacketHdr Pointer to the previously reserved packet header.
     */
    void Commit(SPacketHdr* pPacketHdr);

    bool                    m_bBlockReserve = false;    ///< When set, do not reserve any more packets.
    std::mutex              m_mtxReservedPackes;        ///< Access protection for the reserved access queue.
    std::deque<SPacketHdr*> m_queueReservedPackets;     ///< Queue containing the currently reserved packets.
};

// Forward declaration
class CMemBufferAccessorRx;

/**
 * @brief Rx access can be done randomly. To ensure the the data is released properly, the accessor packet is returned to manage
 * this.
 */
class CAccessorRxPacket
{
public:
    /**
     * @brief Default constructor
     */
    CAccessorRxPacket() = default;

    /**
     * @brief Constructor
     * @param[in] rAccessor Reference to the accessor managing the buffer.
     * @param[in] pPacketHdr Pointer to the packet header.
     */
    CAccessorRxPacket(CMemBufferAccessorRx& rAccessor, CMemBufferAccessorBase::SPacketHdr* pPacketHdr);

    /**
     * @brief Copy constructor is deleted.
     * @param[in] rpacket Reference to the packet to copy from.
     */
    CAccessorRxPacket(const CAccessorRxPacket& rpacket) = delete;

    /**
     * @brief Move constructor
     * @param[in] rpacket Reference to the packet to move from.
     */
    CAccessorRxPacket(CAccessorRxPacket&& rpacket) noexcept;

    /**
     * @brief Copy operator is deleted.
     * @param[in] rpacket Reference to the packet to copy from.
     * @return Reference to this packet class.
     */
    CAccessorRxPacket& operator=(const CAccessorRxPacket& rpacket) = delete;

    /**
     * @brief Copy operator is deleted.
     * @param[in] rpacket Reference to the packet to copy from.
     * @return Reference to this packet class.
     */
    CAccessorRxPacket& operator=(CAccessorRxPacket&& rpacket) noexcept;

    /**
     * @brief Cast operator used to check validity.
     */
    operator bool() const;

    /**
     * @brief Does the packet contain valid data (data pointer is available and the size is not zero).
     * @return The validity of the packet.
     */
    bool IsValid() const;

    /**
     * @brief Reset the packet (not accepting the content). Packet will be available again for next request.
     */
    void Reset();

    /**
     * @brief Get the size of the packet data.
     * @return The packet size.
     */
    uint32_t GetSize() const;

    /**
     * @brief Get the packet pointer.
     * @return Pointer to the buffer holding the packet data or nullptr when the packet is cleared.
     */
    const uint8_t* GetData() const;

    /**
     * @brief Templated version of GetData.
     * @tparam TData The type to cast the data pointer to.
     * @param[in] uiOffset The offset to use in bytes (default no offset).
     * @return Pointer to the data or NULL when the data doesn't fit in the buffer.
     */
    template <typename TData>
    const TData* GetData(uint32_t uiOffset = 0) const
    {
        return GetSize() >= (uiOffset + sizeof(TData)) ? reinterpret_cast<const TData*>(GetData()) : nullptr;
    }

    /**
     * @brief Accept the packet; packet will be released for new packet data.
     */
    void Accept();

private:
    CMemBufferAccessorRx*               m_pAccessor = nullptr;      ///< Pointer to the accessor.
    CMemBufferAccessorBase::SPacketHdr* m_pPacketHdr = nullptr;     ///< Packet header.
};

/**
 * @brief Accessor RX implementation
 */
class CMemBufferAccessorRx : public CMemBufferAccessorBase
{
    friend CAccessorRxPacket;
public:
    // Suppress cppcheck warning about a useless override. The function is here for clearer code documentation.
    // cppcheck-suppress uselessOverride
    /**
     * @brief Attach the buffer to the accessor. Overload of CMemBufferAccessorBase::Attach.
     * @param[in] pBuffer Pointer to the buffer that should be accessed.
     * @param[in] uiSize Size of the buffer when still uninitialized (during buffer creation); otherwise 0.
     */
    virtual void Attach(uint8_t* pBuffer, uint32_t uiSize = 0) override;

    /**
     * @brief Get a packet. If the packet is accepted, call release before the packet
     * @return Returns 'true' when the access request was successful or 'false' when currently there is no data.
     * @attention The writer will only be able to overwrite previously read data after it is released. If, for some reason there is
     * no release, the writer won't be able to write past the accessed packet.
     */
    std::optional<CAccessorRxPacket> TryRead();

    /**
     * @brief Get the accessor access type.
     * @return The access type of this accessor.
     */
    static constexpr EAccessType GetAccessType() { return EAccessType::rx; };

protected:
    /**
     * @brief Release any read packets and update the read pointer.
     */
    void ReleasePackets();

private:
    std::mutex          m_mtxReadAccess;            ///< Synchronize read access.
};

#endif // !defined MEM_BUFFER_ACCESSOR_H