#ifndef MICROBIT_MANAGED_BUFFER_H
#define MICROBIT_MANAGED_BUFFER_H

#include "mbed.h"
#include "RefCounted.h"

struct BufferData : RefCounted
{
    uint16_t        length;             // The length of the payload in bytes
    uint8_t         payload[0];         // ManagedBuffer data
};

/**
  * Class definition for a ManagedBuffer.
  * A ManagedBuffer holds a series of bytes, used with MicroBitRadio channels and in other places.
  * n.b. This is a mutable, managed type.
  */
class ManagedBuffer
{
    BufferData      *ptr;     // Pointer to payload data
    
    public:

    /**
      * Default Constructor. 
      * Creates an empty ManagedBuffer.  The 'ptr' field in all empty buffers is shared.
      *
      * Example:
      * @code
      * ManagedBuffer p(); 
      * @endcode
      */
    ManagedBuffer();

    /**
      * Constructor. 
      * Creates a new ManagedBuffer of the given size. 
      *
      * @param length The length of the buffer to create.
      *
      * Example:
      * @code
      * ManagedBuffer p(16);         // Creates a ManagedBuffer 16 bytes long.
      * @endcode
      */
    ManagedBuffer(int length);

    /**
      * Constructor. 
      * Creates an empty ManagedBuffer of the given size,
      * and fills it with the data provided.
      *
      * @param data The data with which to fill the buffer.
      * @param length The length of the buffer to create.
      * 
      * Example:
      * @code
      * uint8_t buf[] = {13,5,2};
      * ManagedBuffer p(buf, 3);         // Creates a ManagedBuffer 3 bytes long.
      * @endcode
      */
    ManagedBuffer(uint8_t *data, int length);

    /**
      * Copy Constructor. 
      * Add ourselves as a reference to an existing ManagedBuffer.
      * 
      * @param buffer The ManagedBuffer to reference.
      *
      * Example:
      * @code
      * ManagedBuffer p();
      * ManagedBuffer p2(i);        // Refers to the same buffer as p. 
      * @endcode
      */
    ManagedBuffer(const ManagedBuffer &buffer);

    /**
      * Constructor. 
      * Create a buffer from a raw BufferData pointer. It will ptr->incr(). This is to be used by specialized runtimes.
      *
      * @param p The pointer to use.
      */    
    ManagedBuffer(BufferData *p);

    /**
      * Internal constructor helper.
      * Configures this ManagedBuffer to refer to the static empty buffer.
      */
    void initEmpty();

    /**
     * Internal constructor-initialiser.
     *
     * @param data The data with which to fill the buffer.
     * @param length The length of the buffer to create.
     * 
     */
    void init(uint8_t *data, int length);

    /**
      * Destructor. 
      * Removes buffer resources held by the instance.
      */
    ~ManagedBuffer();

    /**
      * Provide an array containing the buffer data.
      * @return The contents of this buffer, as an array of bytes.
      */
    uint8_t *getBytes()
    {
        return ptr->payload;
    }

    /**
      * Get current ptr, do not decr() it, and set the current instance to an empty buffer.
      * This is to be used by specialized runtimes which pass BufferData around.
      */
    BufferData *leakData();

    /**
      * Copy assign operation. 
      *
      * Called when one ManagedBuffer is assigned the value of another using the '=' operator.
      * Decrements our reference count and free up the buffer as necessary.
      * Then, update our buffer to refer to that of the supplied ManagedBuffer,
      * and increase its reference count.
      *
      * @param p The ManagedBuffer to reference.
      * 
      * Example:
      * @code
      * uint8_t buf = {13,5,2};
      * ManagedBuffer p1(16); 
      * ManagedBuffer p2(buf, 3);        
      *
      * p1 = p2;  
      * @endcode
      */
    ManagedBuffer& operator = (const ManagedBuffer& p);

    /**
     * Array access operation (read). 
     *
     * Called when a ManagedBuffer is dereferenced with a [] operation.
     * Transparently map this through to the underlying payload for elegance of programming.
     *
     * Example:
     * @code
     * ManagedBuffer p1(16); 
     * uint8_t data = p1[0];
     * @endcode
     */
    uint8_t operator [] (int i) const
    {
        return ptr->payload[i];
    }

    /**
     * Array access operation (modify). 
     *
     * Called when a ManagedBuffer is dereferenced with a [] operation.
     * Transparently map this through to the underlying payload for elegance of programming.
     *
     * Example:
     * @code
     * ManagedBuffer p1(16); 
     * p1[0] = 42;
     * @endcode
     */
    uint8_t& operator [] (int i)
    {
        return ptr->payload[i];
    }

    /**
      * Equality operation.
      *
      * Called when one ManagedBuffer is tested to be equal to another using the '==' operator.
      *
      * @param p The ManagedBuffer to test ourselves against.
      * @return true if this ManagedBuffer is identical to the one supplied, false otherwise.
      * 
      * Example:
      * @code
      *
      * uint8_t buf = {13,5,2};
      * ManagedBuffer p1(16); 
      * ManagedBuffer p2(buf, 3);        
      *
      * if(p1 == p2)                    // will be true
      *     uBit.display.scroll("same!");
      * @endcode
      */
    bool operator== (const ManagedBuffer& p);
    
    /**
      * Sets the byte at the given index to value provided.
      * @param position The index of the byte to change.
      * @param value The new value of the byte (0-255).
      * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER.
      *
      * Example:
      * @code
      * ManagedBuffer p1(16); 
      * p1.setByte(0,255);              // Sets the first byte in the buffer to the value 255.
      * @endcode
      */
    int setByte(int position, uint8_t value);

    /**
      * Determines the value of the given byte in the buffer.
      *
      * @param position The index of the byte to read.
      * @return The value of the byte at the given position, or MICROBIT_INVALID_PARAMETER.
      *
      * Example:
      * @code
      * ManagedBuffer p1(16); 
      * p1.setByte(0,255);              // Sets the first byte in the buffer to the value 255.
      * p1.getByte(0);                  // Returns 255.
      * @endcode
      */
    int getByte(int position);

    /**
      * Gets number of bytes in this buffer 
      * @return The size of the buffer in bytes.
      * 
      * Example:
      * @code
      * ManagedBuffer p1(16); 
      * p1.length();                 // Returns 16.
      * @endcode
      */
    int length() const { return ptr->length; }

    int fill(uint8_t value, int offset = 0, int length = -1);

    ManagedBuffer slice(int offset = 0, int length = -1) const;

    void shift(int offset, int start = 0, int length = -1);

    void rotate(int offset, int start = 0, int length = -1);

    int readBytes(uint8_t *dst, int offset, int length, bool swapBytes = false) const;

    int writeBytes(int dstOffset, uint8_t *src, int length, bool swapBytes = false);

    int writeBuffer(int dstOffset, const ManagedBuffer &src, int srcOffset = 0, int length = -1);

    bool isReadOnly() const { return ptr->isReadOnly(); }
};

#endif