/**
 * @file src/utils/dynamic_buffer.cpp
 * @brief Implementation of class for buffered data mainpulation.
 * @copyright (c) 2017 Avast Software, licensed under the MIT license
 */

#include "retdec/utils/dynamic_buffer.h"

using namespace retdec::utils;

namespace retdec {
namespace utils {

/**
 * Creates the empty DynamicBuffer object with no capacity and
 * specified endianness.
 *
 * @param endianness Endianness of the bytes in the buffer.
 */
DynamicBuffer::DynamicBuffer(Endianness endianness)
		: _data()
		, _endianness(endianness)
		, _capacity(0)
{
}

/**
 * Creates the DynamicBuffer object with specified capacity and endianness.
 *
 * @param capacity Capacity of the buffer.
 * @param endianness Endianness of the bytes in the buffer.
 */
DynamicBuffer::DynamicBuffer(uint32_t capacity, Endianness endianness)
	: _data(), _endianness(endianness), _capacity(capacity)
{
	_data.reserve(capacity);
}

/**
 * Creates the DynamicBuffer object and fills it with specified data with
 * specified endianness.
 *
 * @param data The bytes to initialize the buffer with.
 * @param endianness Endiannes of the bytes in the buffer.
 */
DynamicBuffer::DynamicBuffer(
		const std::vector<uint8_t>& data,
		Endianness endianness)
		: _data(data)
		, _endianness(endianness)
		, _capacity(static_cast<uint32_t>(data.size()))
{
}

/**
 * Creates the copy of the DynamicBuffer object.
 *
 * @param dynamicBuffer Buffer to copy.
 */
DynamicBuffer::DynamicBuffer(const DynamicBuffer& dynamicBuffer)
		: _data(dynamicBuffer._data)
		, _endianness(dynamicBuffer._endianness)
		, _capacity(dynamicBuffer._capacity)
{
}

/**
 * Creates the copy of the DynamicBuffer object, but only the
 * specified subbuffer.
 *
 * @param dynamicBuffer Buffer to copy.
 * @param startPos Starting position in the specified buffer where to
 *        start the copying.
 * @param amount Number of bytes from startPos to copy.
 */
DynamicBuffer::DynamicBuffer(
		const DynamicBuffer& dynamicBuffer,
		uint32_t startPos,
		uint32_t amount)
{
	std::vector<uint8_t> tmpBuffer = dynamicBuffer.getBuffer();
	std::vector<uint8_t> buffer(
			tmpBuffer.begin() + startPos,
			tmpBuffer.begin() + startPos + amount
	);

	_data = buffer;
	_endianness = dynamicBuffer._endianness;
	_capacity = static_cast<uint32_t>(buffer.size());
}

/**
 * Assign operator, creates the copy of the DynamicBuffer.
 *
 * @param rhs Right hand side of the operator.
 *
 * @return The new DynamicBuffer object.
 */
DynamicBuffer& DynamicBuffer::operator =(DynamicBuffer rhs)
{
	std::swap(_data, rhs._data);
	std::swap(_endianness, rhs._endianness);
	std::swap(_capacity, rhs._capacity);
	return *this;
}

/**
 * Sets the capacity of the buffer.
 *
 * @param capacity The new capacity to set to the buffer.
 */
void DynamicBuffer::setCapacity(uint32_t capacity)
{
	_capacity = capacity;
	_data.reserve(_capacity);
}

/**
 * Gets the actual capacity of the buffer.
 *
 * @return The capacity of the buffer.
 */
uint32_t DynamicBuffer::getCapacity() const
{
	return _capacity;
}

/**
 * Sets the endianness of the bytes in the buffer. It doesn't result in any
 * changes to the actual bytes in the buffer. It reflects only when reading
 * from or writing to the buffer.
 *
 * @param endianness The endianness to set.
 */
void DynamicBuffer::setEndianness(Endianness endianness)
{
	_endianness = endianness;
}

/**
 * Gets the current endianness of the buffer.
 *
 * @return The endianness of the bytes in the buffer.
 */
Endianness DynamicBuffer::getEndianness() const
{
	return _endianness;
}

/**
 * Gets the size of the data that are actually written to the buffer.
 * This cannot be greater than the capacity of the buffer.
 *
 * @return The size of the written data to the buffer.
 */
uint32_t DynamicBuffer::getRealDataSize() const
{
	return static_cast<uint32_t>(_data.size());
}

/**
 * Erases the bytes from the buffer. Also reduces the capacity of the buffer.
 *
 * @param startPos The starting position where to start erasing.
 * @param amount Number of bytes from the startPos including to erase.
 */
void DynamicBuffer::erase(uint32_t startPos, uint32_t amount)
{
	if (startPos >= _data.size())
		return;

	amount = startPos + amount > _data.size() ? _data.size() - startPos : amount;
	_data.erase(_data.begin() + startPos, _data.begin() + startPos + amount);
}

/**
 * Gets the buffer as the vector of bytes.
 *
 * @return The vector with the bytes.
 */
std::vector<uint8_t> DynamicBuffer::getBuffer() const
{
	return _data;
}

/**
 * Gets the raw pointer to the bytes in the buffer.
 *
 * @return The pointer to the bytes in the buffer.
 */
const uint8_t* DynamicBuffer::getRawBuffer() const
{
	return _data.data();
}

/**
 * Runs the specified function for every single byte in the DynamicBuffer.
 *
 * @param func Function to run for every byte.
 */
void DynamicBuffer::forEach(const std::function<void(uint8_t&)>& func)
{
	for (uint8_t& byte : _data)
		func(byte);
}

/**
 * Runs the specified function for every single byte in the DynamicBuffer
 * in the reverse order.
 *
 * @param func Function to run for every byte.
 */
void DynamicBuffer::forEachReverse(const std::function<void(uint8_t&)>& func)
{
	for (std::vector<uint8_t>::reverse_iterator itr = _data.rbegin();
			itr != _data.rend();
			++itr)
	{
		uint8_t& byte = *itr;
		func(byte);
	}
}

/**
 * Reads the null or length terminated string from the buffer.
 *
 * @param pos The poisition in the buffer where to start reading.
 * @param maxLength The maximal length of the string that is read. If this is 0,
 *        the length limit is ignored and the string is read up to the next
 *        0 byte.
 *
 * @return String read from buffer.
 */
std::string DynamicBuffer::readString(uint32_t pos, uint32_t maxLength) const
{
	std::string str;
	char ch;

	while (((ch = read<char>(pos++)) != 0)
			&& (!maxLength || str.length() < maxLength))
		str += ch;

	return str;
}

/**
 * Writes the single byte into the buffer for repeating amount of times.
 *
 * @param byte The byte to write into the buffer.
 * @param pos The position where to start writing the byte.
 * @param repeatAmount The number of times the byte is written into the
 *        buffer starting from pos including.
 */
void DynamicBuffer::writeRepeatingByte(
		uint8_t byte,
		uint32_t pos,
		uint32_t repeatAmount)
{
	if (pos + repeatAmount > _capacity)
		repeatAmount = _capacity - pos;

	if (pos + repeatAmount > _data.size())
		_data.resize(pos + repeatAmount);

	memset(&_data[pos], byte, repeatAmount);
}

} // namespace unpacker
} // namespace retdec
