/*
								+----------------------------------+
								|                                  |
								|  ***   Data buffer struct   ***  |
								|                                  |
								|   Copyright © -tHE SWINe- 2009   |
								|                                  |
								|             Buffer.h             |
								|                                  |
								+----------------------------------+
*/

#pragma once
#ifndef __BUFFER_INCLUDED
#define __BUFFER_INCLUDED

/**
 *	@file Buffer.h
 *	@author -tHE SWINe-
 *	@date 2009
 *	@brief Simple data buffer class.
 *
 *	@date 2012-06-19
 *
 *	Moved multiple inclusion guard before file documentation comment.
 *
 */

#include "Integer.h"

/**
 *	@brief simple data buffer structure
 *
 *	Can hold up to 4GB of data on 32-bit machine.
 *
 *	@note While this may seem rather similar to std::vector<uint8_t>, it's wholy different
 *		approach. While std::vector<uint8_t> is array of bytes, seen as separate entities,
 *		TBuffer is one chunk of data, a single entity. Interface gets a bit simpler, and
 *		some operations (such as changing buffer size) can be optimized more.
 *	@note This throws no std::bad_alloc (or different) exceptions.
 */
struct TBuffer {
public:
	/**
	 *	@brief size type
	 *
	 *	This normally equals to global size_t type, however, in case __LARGE_BUFFER__
	 *	is defined, uint64_t is used instead (may come in handy for x64 apps where
	 *	size_t is 32-bit integer; are there any such environments?).
	 */
#ifdef __LARGE_BUFFER__
	typedef uint64_t size_t;
#endif // __LARGE_BUFFER__

private:
	uint8_t *p_data;
	size_t n_size;
	size_t n_capacity;

public:
	/**
	 *	@brief default constructor
	 *
	 *	Initializes an empty buffer.
	 */
	TBuffer();

	/**
	 *	@brief constructs buffer with required size
	 *
	 *	Creates a new buffer with size _n_size bytes.
	 *
	 *	@param[in] _n_size is size in bytes of a new buffer
	 *
	 *	@note Buffer is going to be empty in case there was not enough memory (call n_Size()).
	 */
	TBuffer(size_t _n_size);

	/**
	 *	@brief copy-constructor
	 *
	 *	Creates a new buffer with the same size and contents as r_t_buffer,
	 *	possibly with smaller capacity (capacity will equal to size).
	 *
	 *	@param[in] r_t_buffer is buffer containing contents to be copied to this buffer
	 *
	 *	@note Buffer is going to be empty in case there was not enough memory (call n_Size()).
	 */
	TBuffer(const TBuffer &r_t_buffer);

	/**
	 *	@brief destructor
	 *
	 *	Deletes buffer data.
	 */
	~TBuffer();

	/**
	 *	@brief copy-operator
	 *
	 *	Copies contents of r_t_buffer to this buffer, original data are lost. No reallocation
	 *	really occurs, unless capacity of this buffer is insufficient.
	 *
	 *	@param[in] r_t_buffer is buffer containing contents to be copied to this buffer
	 *
	 *	@return Returns reference to this buffer.
	 *
	 *	@note Buffer is going to be empty in case there was not enough memory (call n_Size()).
	 */
	TBuffer &operator =(const TBuffer &r_t_buffer);

	/**
	 *	@brief swaps contents of two buffers
	 *
	 *	Swaps contents of this and r_t_buffer, while just pointers are swapped, no 
	 *
	 *	@param[in,out] r_t_buffer is the buffer to be swapped with
	 */
	void Swap(TBuffer &r_t_buffer);

	/**
	 *	@brief changes size of the buffer
	 *
	 *	Changes size of this buffer. If b_data_important is set, buffer data are copied when
	 *		reallocating, otherwise they're not (saving time).
	 *
	 *	@param[in] n_new_size is requested size in bytes
	 *	@param[in] b_data_important is flag. setting it preserves data, otherwise original
	 *		contents are discarded when reallocating.
	 *
	 *	@return Returns true on success, false on failure (data aren't lost on failure,
	 *		but buffer size remains unchaged).
	 *
	 *	@note Note that resizing below buffer capacity never causes reallocations.
	 */
	bool Resize(size_t n_new_size, bool b_data_important = true);

	/**
	 *	@brief increases size of the buffer
	 *
	 *	Resizes the buffer, so it contains n_add more free bytes. In case capacity is less
	 *		than size + n_add, buffer is reallocated. Original data are copied to the
	 *		beginning of the new memory, in case original data aren't important, Resize()
	 *		should be used instead.
	 *
	 *	@param[in] n_add is requested size increment (new size will be n_Size() + n_add).
	 *
	 *	@return Returns true on success, false on failure (data aren't lost on failure,
	 *		but buffer size remains unchaged).
	 *
	 *	@note This changes buffer capacity with exponential steps, so adding pieces of
	 *		data in sequence results in complexity O(n log n) (as opposed to O(n^2) if
	 *		capacity increased in linear steps).
	 */
	bool Grow(size_t n_add);

	/**
	 *	@brief erases the buffer
	 *
	 *	Clears contents of the buffer, both size and capacity will be 0.
	 */
	void Clear();

	/**
	 *	@brief empty buffer predicate
	 *
	 *	Determines wheter buffer is empty (size is 0, capacity doesn't matter), or not.
	 *
	 *	@return Returns true in case buffer is empty, otherwise false
	 */
	inline bool b_Empty() const
	{
		return !n_size;
	}

	/**
	 *	@brief gets buffer size
	 *
	 *	@return Returns size of the buffer in bytes.
	 */
	inline size_t n_Size() const
	{
		return n_size;
	}

	/**
	 *	@brief gets buffer capacity
	 *
	 *	@return Returns capacity of the buffer in bytes.
	 */
	inline size_t n_Capacity() const
	{
		return n_capacity;
	}

	/**
	 *	@brief gets buffer data
	 *
	 *	@return Returns pointer to the beginning of the buffer.
	 */
	inline uint8_t *p_Data()
	{
		return p_data;
	}

	/**
	 *	@brief gets buffer data for reading
	 *
	 *	@return Returns const pointer to the beginning of the buffer.
	 */
	inline const uint8_t *p_Data() const
	{
		return p_data;
	}

	/**
	 *	@brief gets maximal buffer size
	 *
	 *	@return Returns the greatest size possible buffer can be allocated to.
	 *
	 *	@note This doesn't really depend on amount of available memory at the
	 *		target machine, but rather on implementation.
	 */
	static inline size_t n_Max_Size()
	{
		return size_t(CMaxIntValue<size_t>::result());
	}
};

/**
 *	@brief overrides global ::swap to use TBuffer::Swap()
 *
 *	@param[in,out] a is reference to the first buffer to be swapped
 *	@param[in,out] b is reference to the other buffer to be swapped
 *
 *	@note This is not TBuffer member function, but a global function instead.
 */
inline void swap(TBuffer &a, TBuffer &b)
{
	a.Swap(b);
}

namespace std {
/**
 *	@brief overrides std::swap to use TBuffer::Swap()
 *
 *	@param[in,out] a is reference to the first buffer to be swapped
 *	@param[in,out] b is reference to the other buffer to be swapped
 *
 *	@note This is not TBuffer member function, but a global function instead.
 */
inline void swap(TBuffer &a, TBuffer &b)
{
	a.Swap(b);
}
};

#endif // !__BUFFER_INCLUDED
