#ifndef _LIBRARY_UTILS_BUFFERINTERPRETER_HPP_
#define _LIBRARY_UTILS_BUFFERINTERPRETER_HPP_

// ------------------------------------------------------------------------------------------------
#include "Base/Shared.hpp"
#include "Base/Buffer.hpp"

// ------------------------------------------------------------------------------------------------
namespace SqMod {

// ------------------------------------------------------------------------------------------------
class BufferWrapper;

/* ------------------------------------------------------------------------------------------------
 * Used internally to obtain a reference to the memory buffer without including the wrapper header.
*/
const SharedPtr< Buffer > & GetBufferBufferRef(const BufferWrapper & buffer);

/* ------------------------------------------------------------------------------------------------
 * Utility class used to interpret a memory buffer in different ways.
*/
template < typename T > class BufferInterpreter
{
private:

    // --------------------------------------------------------------------------------------------
    typedef SharedPtr< Buffer > SRef; // Strong reference type to the interpreted memory buffer.
    typedef WeakPtr< Buffer >   WRef; // Weak reference type to the interpreted memory buffer.

    // --------------------------------------------------------------------------------------------
    WRef m_Buffer; // The interpreted memory buffer.

public:

    // --------------------------------------------------------------------------------------------
    typedef T               Value; // The type of value used to represent a byte.

    // --------------------------------------------------------------------------------------------
    typedef Value &         Reference; // A reference to the stored value type.
    typedef const Value &   ConstRef; // A const reference to the stored value type.

    // --------------------------------------------------------------------------------------------
    typedef Value *         Pointer; // A pointer to the stored value type.
    typedef const Value *   ConstPtr; // A const pointer to the stored value type.

    // --------------------------------------------------------------------------------------------
    typedef Buffer::SzType  SzType; // The type used to represent size in general.

protected:

    /* --------------------------------------------------------------------------------------------
     * Attempt to obtain a strong reference to the memory buffer at all costs.
    */
    SRef Validate() const
    {
        // Did the buffer that we reference expired?
        if (m_Buffer.Expired())
        {
            STHROWF("Invalid memory buffer reference");
        }
        // Obtain a strong reference to it
        return m_Buffer.Lock();
    }

    /* --------------------------------------------------------------------------------------------
     * Attempt to obtain a strong reference to a valid memory buffer at all costs.
    */
    SRef ValidateDeeper() const
    {
        // Did the buffer that we reference expired?
        if (m_Buffer.Expired())
        {
            STHROWF("Invalid memory buffer reference");
        }
        // Obtain a strong reference to it
        SRef ref = m_Buffer.Lock();
        // Validate the buffer itself
        if (!(*ref))
        {
            STHROWF("Invalid memory buffer");
        }
        // Return the reference
        return ref;
    }

public:

    /* --------------------------------------------------------------------------------------------
     * Default constructor. (null)
    */
    BufferInterpreter()
        : m_Buffer()
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Base constructor.
    */
    BufferInterpreter(const WRef & ref)
        : m_Buffer(ref)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Base constructor.
    */
    BufferInterpreter(const SRef & ref)
        : m_Buffer(ref)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Copy constructor.
    */
    BufferInterpreter(const BufferInterpreter & o) = default;

    /* --------------------------------------------------------------------------------------------
     * Move constructor.
    */
    BufferInterpreter(BufferInterpreter && o) = default;

    /* --------------------------------------------------------------------------------------------
     * Destructor.
    */
    ~BufferInterpreter() = default;

    /* --------------------------------------------------------------------------------------------
     * Copy assignment operator.
    */
    BufferInterpreter & operator = (const BufferInterpreter & o) = default;

    /* --------------------------------------------------------------------------------------------
     * Move assignment operator.
    */
    BufferInterpreter & operator = (BufferInterpreter && o) = default;

    /* --------------------------------------------------------------------------------------------
     * Retrieve a reference to the managed memory buffer.
    */
    const WRef & GetRef() const
    {
        return m_Buffer;
    }

    /* --------------------------------------------------------------------------------------------
     * Assign a different memory buffer to interpret from.
    */
    void UseBuffer(const BufferWrapper & buffer)
    {
        m_Buffer = GetBufferBufferRef(buffer);
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve a certain element type at the specified position.
    */
    T Get(SzType n) const
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (n >= b->Size< T >())
        {
            STHROWF("Index (%u) is out of bounds (%u)", n, b->Size< T >());
        }
        // Return the requested element
        return b->At< T >(n);
    }

    /* --------------------------------------------------------------------------------------------
     * Modify a certain element type at the specified position.
    */
    void Set(SzType n, T v)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (n >= b->Size< T >())
        {
            STHROWF("Index (%u) is out of bounds (%u)", n, b->Size< T >());
        }
        // Return the requested element
        b->At< T >(n) = v;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the element at the front of the buffer.
    */
    T GetFront() const
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Capacity() < sizeof(T))
        {
            STHROWF("Value size (%u starting at 0) is out of bounds (%u)",
                        sizeof(T), b->Capacity());
        }
        // Return the requested element
        return b->Front< T >();
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the element at the front of the buffer.
    */
    void SetFront(T v)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Capacity() < sizeof(T))
        {
            STHROWF("Value size (%u starting at 0) is out of bounds (%u)",
                        sizeof(T), b->Capacity());
        }
        // Return the requested element
        b->Front< T >() = v;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the element after the first element in the buffer.
    */
    T GetNext() const
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Capacity() < (sizeof(T) * 2))
        {
            STHROWF("Value size (%u starting at %u) is out of bounds (%u)",
                        sizeof(T), sizeof(T), b->Capacity());
        }
        // Return the requested element
        return b->Next< T >();
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the element after the first element in the buffer.
    */
    void SetNext(T v)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Capacity() < (sizeof(T) * 2))
        {
            STHROWF("Value size (%u starting at %u) is out of bounds (%u)",
                        sizeof(T), sizeof(T), b->Capacity());
        }
        // Return the requested element
        b->Next< T >() = v;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the element at the back of the buffer.
    */
    T GetBack() const
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Capacity() < sizeof(T))
        {
            STHROWF("Value size (%u starting at 0) is out of bounds (%u)",
                        sizeof(T), b->Capacity());
        }
        // Return the requested element
        return b->Back< T >();
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the element at the back of the buffer.
    */
    void SetBack(T v)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Capacity() < sizeof(T))
        {
            STHROWF("Value size (%u starting at 0) is out of bounds (%u)",
                        sizeof(T), b->Capacity());
        }
        // Return the requested element
        b->Back< T >() = v;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the element before the last element in the buffer.
    */
    T GetPrev() const
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Capacity() < (sizeof(T) * 2))
        {
            STHROWF("Value size (%u starting at %u) is out of bounds (%u)",
                        sizeof(T), sizeof(T), b->Capacity());
        }
        // Return the requested element
        return b->Prev< T >();
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the element before the last element in the buffer.
    */
    void SetPrev(T v)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Capacity() < (sizeof(T) * 2))
        {
            STHROWF("Value size (%u starting at %u) is out of bounds (%u)",
                        sizeof(T), sizeof(T), b->Capacity());
        }
        // Return the requested element
        b->Prev< T >() = v;
    }

    /* --------------------------------------------------------------------------------------------
     * Reposition the edit cursor to the specified number of elements ahead.
    */
    void Advance(SzType n)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Perform the requested operation
        b->Advance< T >(n);
    }

    /* --------------------------------------------------------------------------------------------
     * Reposition the edit cursor to the specified number of elements behind.
    */
    void Retreat(SzType n)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Perform the requested operation
        b->Retreat< T >(n);
    }

    /* --------------------------------------------------------------------------------------------
     * Reposition the edit cursor to a fixed position within the buffer.
    */
    void Move(SzType n)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Perform the requested operation
        b->Move< T >(n);
    }

    /* --------------------------------------------------------------------------------------------
     * Append a value to the current cursor location and advance the cursor.
    */
    void Push(T v)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Perform the requested operation
        b->Push< T >(v);
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the element at the cursor position.
    */
    T GetCursor() const
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Position< T >() >= b->Size< T >())
        {
            STHROWF("Value size (%u starting at %u) is out of bounds (%u)",
                        sizeof(T), b->Position(), b->Capacity());
        }
        // Return the requested element
        return b->Cursor< T >();
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the element at the cursor position.
    */
    void SetCursor(T v)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Position< T >() >= b->Size< T >())
        {
            STHROWF("Value size (%u starting at %u) is out of bounds (%u)",
                        sizeof(T), b->Position(), b->Capacity());
        }
        // Return the requested element
        b->Cursor< T >() = v;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the element before the cursor position.
    */
    T GetBefore() const
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Position() < sizeof(T))
        {
            STHROWF("Value size (%u starting at 0) is out of bounds (%u)",
                        sizeof(T), b->Capacity());
        }
        // Return the requested element
        return b->Before< T >();
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the element before the cursor position.
    */
    void SetBefore(T v)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Position() < sizeof(T))
        {
            STHROWF("Value size (%u starting at 0) is out of bounds (%u)",
                        sizeof(T), b->Capacity());
        }
        // Return the requested element
        b->Before< T >() = v;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the element after the cursor position.
    */
    T GetAfter() const
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Capacity() < sizeof(T) || (b->Position() + sizeof(T)) > (b->Capacity() - sizeof(T)))
        {
            STHROWF("Value size (%u starting at %u) is out of bounds (%u)",
                        sizeof(T), b->Position(), b->Capacity());
        }
        // Return the requested element
        return b->After< T >();
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the element after the cursor position.
    */
    void SetAfter(T v)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Are we out of the memory buffer range?
        if (b->Capacity() < sizeof(T) || (b->Position() + sizeof(T)) > (b->Capacity() - sizeof(T)))
        {
            STHROWF("Value size (%u starting at %u) is out of bounds (%u)",
                        sizeof(T), b->Position(), b->Capacity());
        }
        // Return the requested element
        b->After< T >() = v;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve maximum elements it can hold for a certain type.
    */
    SzType GetMax() const
    {
        return Buffer::Max< T >();
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the current buffer capacity in element count.
    */
    SzType GetSize() const
    {
        return Validate()->template Size< T >();
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the current buffer capacity in byte count.
    */
    SzType GetCapacity() const
    {
        return Validate()->Capacity();
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the current position of the cursor in the buffer.
    */
    SzType GetPosition() const
    {
        return Validate()->template Position< T >();
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the amount of unused buffer after the edit cursor.
    */
    SzType GetRemaining() const
    {
        return Validate()->template Remaining< T >();
    }

    /* --------------------------------------------------------------------------------------------
     * Grow the size of the internal buffer by the specified amount of bytes.
    */
    void Grow(SzType n)
    {
        return Validate()->Grow(n * sizeof(T));
    }

    /* --------------------------------------------------------------------------------------------
     * Makes sure there is enough capacity to hold the specified element count.
    */
    void Adjust(SzType n)
    {
        // Acquire a reference to the memory buffer
        SRef b(Validate());
        // Attempt to perform the requested operation
        try
        {
            Buffer bkp(b->Adjust(n * sizeof(T)));
            // Copy the data into the new buffer
            b->Write(0, bkp.Data(), bkp.Capacity());
            b->Move(bkp.Position());
        }
        catch (const std::exception & e)
        {
            STHROWF("%s", e.what()); // Re-package
        }
    }
};

} // Namespace:: SqMod

#endif // _LIBRARY_UTILS_BUFFERINTERPRETER_HPP_