#pragma once

#include <psemek/util/span.hpp>

#include <memory>
#include <string>
#include <string_view>

namespace psemek::util
{

	struct blob
	{
		blob() noexcept = default;
		blob(blob const & other) = delete;
		blob(blob && other) noexcept;

		blob(std::size_t size);
		blob(std::size_t size, std::unique_ptr<char[]> data) noexcept;
		blob(std::size_t size, char * data) noexcept;

		blob & operator = (blob const & other) = delete;
		blob & operator = (blob && other) noexcept;

		~blob() = default;

		char * data() noexcept { return data_.get(); }
		char const * data() const noexcept { return data_.get(); }

		std::size_t size() const noexcept { return size_; }

		explicit operator bool() const noexcept { return static_cast<bool>(data_); }

		void reset();
		std::unique_ptr<char[]> release() noexcept;

		void swap(blob & other) noexcept;

		using iterator = char *;
		using const_iterator = char const *;

		char * begin() noexcept { return data(); }
		char const * begin() const noexcept { return data(); }

		char * end() noexcept { return data() + size(); }
		char const * end() const noexcept { return data() + size(); }

		char & operator[](std::size_t i) noexcept { return data()[i]; }
		char const & operator[](std::size_t i) const noexcept { return data()[i]; }

		std::string string() const;
		std::string_view string_view() const noexcept;

		util::span<char> span() noexcept;
		util::span<char const> span() const noexcept;

		blob copy() const;

	private:
		std::unique_ptr<char[]> data_;
		std::size_t size_ = 0;
	};

	inline blob::blob(blob && other) noexcept
		: data_{std::move(other.data_)}
		, size_{other.size_}
	{
		other.size_ = 0;
	}

	inline blob::blob(std::size_t size)
		: data_{new char [size]}
		, size_{size}
	{}

	inline blob::blob(std::size_t size, std::unique_ptr<char[]> data) noexcept
		: data_{std::move(data)}
		, size_{size}
	{}

	inline blob::blob(std::size_t size, char * data) noexcept
		: data_{data}
		, size_{size}
	{}

	inline blob & blob::operator = (blob && other) noexcept
	{
		blob(std::move(other)).swap(*this);
		return *this;
	}

	inline void blob::reset()
	{
		data_.reset();
		size_ = 0;
	}

	inline std::unique_ptr<char[]> blob::release() noexcept
	{
		size_ = 0;
		return std::move(data_);
	}

	inline void blob::swap(blob & other) noexcept
	{
		std::swap(data_, other.data_);
		std::swap(size_, other.size_);
	}

	inline std::string blob::string() const
	{
		return std::string(data_.get(), data_.get() + size_);
	}

	inline std::string_view blob::string_view() const noexcept
	{
		return std::string_view(data_.get(), size_);
	}

	inline util::span<char> blob::span() noexcept
	{
		return {data_.get(), size_};
	}

	inline util::span<char const> blob::span() const noexcept
	{
		return {data_.get(), size_};
	}

	inline blob blob::copy() const
	{
		std::unique_ptr<char[]> data(new char [size_]);
		std::copy(begin(), end(), data.get());
		return blob(size_, std::move(data));
	}

}
