#include "blitz/misc/DataBuffer.h"

#include <fstream>
#include <iomanip>
#include <sstream>

#include "blitz/misc/Format.h"
#include "blitz/misc/Log.h"

namespace blitz {

DataBuffer::DataBuffer() : m_ReadOffset(0) {}

DataBuffer::DataBuffer(const DataBuffer& other) : m_Buffer(other.m_Buffer), m_ReadOffset(other.m_ReadOffset) {}

DataBuffer::DataBuffer(DataBuffer&& other) : m_Buffer(std::move(other.m_Buffer)), m_ReadOffset(std::move(other.m_ReadOffset)) {}

DataBuffer::DataBuffer(const std::string& str) : m_Buffer(str.begin(), str.end()), m_ReadOffset(0) {}

DataBuffer::DataBuffer(const DataBuffer& other, Data::difference_type offset) : m_ReadOffset(0) {
	m_Buffer.reserve(other.GetSize() - static_cast<std::size_t>(offset));
	std::copy(other.m_Buffer.begin() + offset, other.m_Buffer.end(), std::back_inserter(m_Buffer));
}

DataBuffer& DataBuffer::operator=(const DataBuffer& other) {
	m_Buffer = other.m_Buffer;
	m_ReadOffset = other.m_ReadOffset;
	return *this;
}

DataBuffer& DataBuffer::operator=(DataBuffer&& other) {
	m_Buffer = std::move(other.m_Buffer);
	m_ReadOffset = std::move(other.m_ReadOffset);
	return *this;
}

void DataBuffer::SetReadOffset(std::size_t pos) {
	assert(pos <= GetSize());
	m_ReadOffset = pos;
}

std::string DataBuffer::ToString() const {
	return std::string(m_Buffer.begin(), m_Buffer.end());
}

std::size_t DataBuffer::GetSize() const {
	return m_Buffer.size();
}

std::size_t DataBuffer::GetRemaining() const {
	return m_Buffer.size() - m_ReadOffset;
}

DataBuffer::iterator DataBuffer::begin() {
	return m_Buffer.begin();
}

DataBuffer::iterator DataBuffer::end() {
	return m_Buffer.end();
}

DataBuffer::const_iterator DataBuffer::begin() const {
	return m_Buffer.begin();
}

DataBuffer::const_iterator DataBuffer::end() const {
	return m_Buffer.end();
}

std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer) {
	for (unsigned char u : buffer)
		os << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(u) << " ";
	os << std::dec;
	return os;
}

bool DataBuffer::ReadFile(const std::string& fileName) {
	try {
		std::ifstream file(fileName, std::istream::binary);
		std::ostringstream ss;
		ss << file.rdbuf();
		const std::string& s = ss.str();
		m_Buffer = DataBuffer::Data(s.begin(), s.end());
		m_ReadOffset = 0;
	} catch (std::exception& e) {
		utils::LOGE(utils::Format("Failed to read file %s ! reason : %s", fileName.c_str(), e.what()));
		return false;
	}
	return m_Buffer.size() > 0;
}

bool DataBuffer::WriteFile(const std::string& fileName) {
	try {
		std::ofstream file(fileName, std::ostream::binary);
		file.write(reinterpret_cast<const char*>(m_Buffer.data()), static_cast<std::streamsize>(m_Buffer.size()));
		file.flush();
	} catch (std::exception& e) {
		utils::LOGE(utils::Format("Failed to read file %s ! reason : %s", fileName.c_str(), e.what()));
		return false;
	}
	return true;
}

}  // namespace blitz
