#ifndef _LIBRARY_UTILS_BUFFERWRAPPER_HPP_
#define _LIBRARY_UTILS_BUFFERWRAPPER_HPP_

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

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

// ------------------------------------------------------------------------------------------------
template < typename T > class BufferInterpreter;

/* ------------------------------------------------------------------------------------------------
 * Squirrel wrapper for the shared buffer class.
*/
class BufferWrapper
{
private:

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

    // --------------------------------------------------------------------------------------------
    SRef m_Buffer; // The managed memory buffer.

public:

    // --------------------------------------------------------------------------------------------
    typedef Buffer::Value   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.

public:

    /* --------------------------------------------------------------------------------------------
     * Create a memory buffer with the requested size.
    */
    static Object Create(SzType n);

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

    /* --------------------------------------------------------------------------------------------
     * Buffer constructor.
    */
    BufferWrapper(Buffer && b)
        : m_Buffer(new Buffer(std::move(b)))
    {
        /* ... */
    }

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

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

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

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

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

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

    /* --------------------------------------------------------------------------------------------
     * Validate the managed memory buffer reference.
    */
    void Validate() const
    {
        // Do we even point to a valid buffer?
        if (!m_Buffer)
        {
            STHROWF("Invalid memory buffer reference");
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Validate the managed memory buffer reference and the buffer itself.
    */
    void ValidateDeeper() const
    {
        // Do we even point to a valid buffer?
        if (!m_Buffer)
        {
            STHROWF("Invalid memory buffer reference");
        }
        // Validate the buffer itself
        else if (!(*m_Buffer))
        {
            STHROWF("Invalid memory buffer");
        }
    }

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

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

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

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

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

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

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

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

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

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

    /* --------------------------------------------------------------------------------------------
     * Reposition the edit cursor to the specified number of elements ahead.
    */
    void Advance(SzType n)
    {
        // Validate the managed buffer reference
        Validate();
        // Perform the requested operation
        m_Buffer->Advance(n);
    }

    /* --------------------------------------------------------------------------------------------
     * Reposition the edit cursor to the specified number of elements behind.
    */
    void Retreat(SzType n)
    {
        // Validate the managed buffer reference
        Validate();
        // Perform the requested operation
        m_Buffer->Retreat(n);
    }

    /* --------------------------------------------------------------------------------------------
     * Reposition the edit cursor to a fixed position within the buffer.
    */
    void Move(SzType n)
    {
        // Validate the managed buffer reference
        Validate();
        // Perform the requested operation
        m_Buffer->Move(n);
    }

    /* --------------------------------------------------------------------------------------------
     * Append a value to the current cursor location and advance the cursor.
    */
    void Push(Value v)
    {
        // Validate the managed buffer reference
        Validate();
        // Perform the requested operation
        m_Buffer->Push(v);
    }

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

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

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

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

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

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

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

    /* --------------------------------------------------------------------------------------------
     * Retrieve the current buffer capacity in element count.
    */
    SzType GetSize() const
    {
        // Validate the managed buffer reference
        Validate();
        // Return the requested information
        return m_Buffer->Size();
    }

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

    /* --------------------------------------------------------------------------------------------
     * Retrieve the current position of the cursor in the buffer.
    */
    SzType GetPosition() const
    {
        // Validate the managed buffer reference
        Validate();
        // Return the requested information
        return m_Buffer->Position();
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the amount of unused buffer after the edit cursor.
    */
    SzType GetRemaining() const
    {
        // Validate the managed buffer reference
        Validate();
        // Return the requested information
        return m_Buffer->Remaining();
    }

    /* --------------------------------------------------------------------------------------------
     * Grow the size of the internal buffer by the specified amount of bytes.
    */
    void Grow(SzType n)
    {
        // Validate the managed buffer reference
        Validate();
        // Perform the requested operation
        return m_Buffer->Grow(n * sizeof(Value));
    }

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

    /* --------------------------------------------------------------------------------------------
     * Write a 8 bit byte to the stream buffer.
    */
    void WriteByte(SQInteger val);

    /* --------------------------------------------------------------------------------------------
     * Write a 16 bit short to the stream buffer.
    */
    void WriteShort(SQInteger val);

    /* --------------------------------------------------------------------------------------------
     * Write a 32 bit integer to the stream buffer.
    */
    void WriteInt(SQInteger val);

    /* --------------------------------------------------------------------------------------------
     * Write a 32 bit float to the stream buffer.
    */
    void WriteFloat(SQFloat val);

    /* --------------------------------------------------------------------------------------------
     * Write a string to the stream buffer.
    */
    void WriteString(CSStr val);

    /* --------------------------------------------------------------------------------------------
     * Write a raw string to the stream buffer.
    */
    void WriteRawString(CSStr val);

    /* --------------------------------------------------------------------------------------------
     * Read a 8 bit byte to the stream buffer.
    */
    SQInteger ReadByte();

    /* --------------------------------------------------------------------------------------------
     * Read a 16 bit short to the stream buffer.
    */
    SQInteger ReadShort();

    /* --------------------------------------------------------------------------------------------
     * Read a 32 bit integer to the stream buffer.
    */
    SQInteger ReadInt();

    /* --------------------------------------------------------------------------------------------
     * Read a 32 bit float to the stream buffer.
    */
    SQFloat ReadFloat();

    /* --------------------------------------------------------------------------------------------
     * Read a string to the stream buffer.
    */
    Object ReadString();

    /* --------------------------------------------------------------------------------------------
     * Read a raw string to the stream buffer.
    */
    Object ReadRawString(Uint32 len);

    /* --------------------------------------------------------------------------------------------
     * Retrieve a signed 8 bit interpreter to this buffer.
    */
    BufferInterpreter< Int8 > GetInt8Interpreter() const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve an unsigned 8 bit interpreter to this buffer.
    */
    BufferInterpreter< Uint8 > GetUint8Interpreter() const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve a signed 16 bit interpreter to this buffer.
    */
    BufferInterpreter< Int16 > GetInt16Interpreter() const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve an unsigned 16 bit interpreter to this buffer.
    */
    BufferInterpreter< Uint16 > GetUint16Interpreter() const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve a signed 32 bit interpreter to this buffer.
    */
    BufferInterpreter< Int32 > GetInt32Interpreter() const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve an unsigned 32 bit interpreter to this buffer.
    */
    BufferInterpreter< Uint32 > GetUint32Interpreter() const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve a signed 64 bit interpreter to this buffer.
    */
    BufferInterpreter< Int64 > GetInt64Interpreter() const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve an unsigned 64 bit interpreter to this buffer.
    */
    BufferInterpreter< Uint64 > GetUint64Interpreter() const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve a 32 bit floating point interpreter to this buffer.
    */
    BufferInterpreter< Float32 > GetFloat32Interpreter() const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve a 64 bit floating point interpreter to this buffer.
    */
    BufferInterpreter< Float64 > GetFloat64Interpreter() const;
};

} // Namespace:: SqMod

#endif // _LIBRARY_UTILS_BUFFERWRAPPER_HPP_