#include "Bytes.h"

using namespace RNS;

// Creates new shared data for instance
// - If capacity is specified (>0) then create empty shared data with initial reserved capacity
// - If capacity is not specified (<=0) then create empty shared data with no initial capacity
void Bytes::newData(size_t capacity /*= 0*/) {
//MEMF("Bytes is creating own data with capacity %u", capacity);
//MEM("newData: Creating new data...");
	Data* data = new Data();
	if (data == nullptr) {
		ERROR("Bytes failed to allocate empty data buffer");
		throw std::runtime_error("Failed to allocate empty data buffer");
	}
//MEM("newData: Created new data");
	if (capacity > 0) {
//MEMF("newData: Reserving data capacity of %u...", capacity);
		data->reserve(capacity);
//MEM("newData: Reserved data capacity");
	}
//MEM("newData: Assigning data to shared data pointer...");
	_data = SharedData(data);
//MEM("newData: Assigned data to shared data pointer");
	_exclusive = true;
}

// Ensures that instance has exclusive shared data
// - If instance has no shared data then create new shared data
// - If instance does not have exclusive on shared data that is not empty then make a copy of shared data (if requests) and reserve capacity (if requested)
// - If instance does not have exclusive on shared data that is empty then create new shared data
// - If instance already has exclusive on shared data then do nothing except reserve capacity (if requested)
void Bytes::exclusiveData(bool copy /*= true*/, size_t capacity /*= 0*/) {
	if (!_data) {
		newData(capacity);
	}
	else if (!_exclusive) {
		if (copy && !_data->empty()) {
			//TRACE("Bytes is creating a writable copy of its shared data");
			//Data* data = new Data(*_data.get());
//MEM("exclusiveData: Creating new data...");
			Data* data = new Data();
			if (data == nullptr) {
				ERROR("Bytes failed to duplicate data buffer");
				throw std::runtime_error("Failed to duplicate data buffer");
			}
//MEM("exclusiveData: Created new data");
			if (capacity > 0) {
//MEMF("exclusiveData: Reserving data capacity of %u...", capacity);
				// if requested capacity < existing size then reserve capacity for existing size instead
				data->reserve((capacity > _data->size()) ? capacity : _data->size());
//MEM("exclusiveData: Reserved data capacity");
			}
			else {
				data->reserve(_data->size());
			}
//MEM("exclusiveData: Copying existing data...");
			data->insert(data->begin(), _data->begin(), _data->end());
//MEM("exclusiveData: Copied existing data");
//MEM("exclusiveData: Assigning data to shared data pointer...");
			_data = SharedData(data);
//MEM("exclusiveData: Assigned data to shared data pointer");
			_exclusive = true;
		}
		else {
//MEM("Bytes is creating its own data because shared is empty");
			//data = new Data();
			//if (data == nullptr) {
			//	ERROR("Bytes failed to allocate empty data buffer");
			//	throw std::runtime_error("Failed to allocate empty data buffer");
			//}
			//_data = SharedData(data);
			//_exclusive = true;
//MEM("exclusiveData: Creating new empty data...");
			newData(capacity);
//MEM("exclusiveData: Created new empty data");
		}
	}
	else if (capacity > 0 && capacity > size()) {
		reserve(capacity);
	}
}

int Bytes::compare(const Bytes& bytes) const {
	if (_data == bytes._data) {
		return 0;
	}
	else if (!_data) {
		return -1;
	}
	else if (!bytes._data) {
		return 1;
	}
	else if (*_data < *(bytes._data)) {
		return -1;
	}
	else if (*_data > *(bytes._data)) {
		return 1;
	}
	else {
		return 0;
	}
}

int Bytes::compare(const uint8_t* buf, size_t size) const {
	if (!_data && size == 0) {
		return 0;
	}
	else if (!_data) {
		return -1;
	}
	int cmp = memcmp(_data->data(), buf, (_data->size() < size) ? _data->size() : size);
	if (cmp == 0 && _data->size() < size) {
		return -1;
	}
	else if (cmp == 0 && _data->size() > size) {
		return 1;
	}
	return cmp;
}

void Bytes::assignHex(const uint8_t* hex, size_t hex_size) {
	// if assignment is empty then clear data and don't bother creating new
	if (hex == nullptr || hex_size <= 0) {
		_data = nullptr;
		_exclusive = true;
		return;
	}
	exclusiveData(false, hex_size / 2);
	// need to clear data since we're appending below
	_data->clear();
	for (size_t i = 0; i < hex_size; i += 2) {
		uint8_t byte = (hex[i] % 32 + 9) % 25 * 16 + (hex[i+1] % 32 + 9) % 25;
		_data->push_back(byte);
	}
}

void Bytes::appendHex(const uint8_t* hex, size_t hex_size) {
	// if append is empty then do nothing
	if (hex == nullptr || hex_size <= 0) {
		return;
	}
	exclusiveData(true, size() + (hex_size / 2));
	for (size_t i = 0; i < hex_size; i += 2) {
		uint8_t byte = (hex[i] % 32 + 9) % 25 * 16 + (hex[i+1] % 32 + 9) % 25;
		_data->push_back(byte);
	}
}

const char hex_upper_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
const char hex_lower_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

std::string RNS::hexFromByte(uint8_t byte, bool upper /*= true*/) {
	std::string hex;
	if (upper) {
		hex += hex_upper_chars[ (byte&  0xF0) >> 4];
		hex += hex_upper_chars[ (byte&  0x0F) >> 0];
	}
	else {
		hex += hex_lower_chars[ (byte&  0xF0) >> 4];
		hex += hex_lower_chars[ (byte&  0x0F) >> 0];
	}
	return hex;
}

std::string Bytes::toHex(bool upper /*= false*/) const {
	if (!_data) {
		return "";
	}
	std::string hex;
	for (uint8_t byte : *_data) {
		if (upper) {
			hex += hex_upper_chars[ (byte&  0xF0) >> 4];
			hex += hex_upper_chars[ (byte&  0x0F) >> 0];
		}
		else {
			hex += hex_lower_chars[ (byte&  0xF0) >> 4];
			hex += hex_lower_chars[ (byte&  0x0F) >> 0];
		}
	}
	return hex;
}

// mid
Bytes Bytes::mid(size_t beginpos, size_t len) const {
	if (!_data || beginpos >= size()) {
		return NONE;
	}
	if ((beginpos + len) >= size()) {
		len = (size() - beginpos);
	 }
	 return {data() + beginpos, len};
}

// to end
Bytes Bytes::mid(size_t beginpos) const {
	if (!_data || beginpos >= size()) {
		return NONE;
	}
	 return {data() + beginpos, size() - beginpos};
}
