#pragma once

#include <psemek/util/blob.hpp>

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

namespace psemek::util
{

	struct shared_blob
	{
		shared_blob() = default;
		shared_blob(shared_blob const & other);
		shared_blob(shared_blob && other);
		shared_blob(blob && other);

		shared_blob(std::size_t size);
		shared_blob(std::size_t size, std::shared_ptr<char[]> data);
		shared_blob(std::size_t size, char * data);

		shared_blob & operator = (shared_blob const & other);
		shared_blob & operator = (shared_blob && other);
		shared_blob & operator = (blob && other);

		~ shared_blob() = default;

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

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

		void reset();

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

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

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

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

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

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

	inline shared_blob::shared_blob(shared_blob const & other)
	{
		*this = other;
	}

	inline shared_blob::shared_blob(shared_blob && other)
	{
		*this = std::move(other);
	}

	inline shared_blob::shared_blob(blob && other)
	{
		*this = std::move(other);
	}

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

	inline shared_blob::shared_blob(std::size_t size, std::shared_ptr<char[]> data)
		: data_{std::move(data)}
		, size_{size}
	{}

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

	inline shared_blob & shared_blob::operator = (shared_blob const & other)
	{
		if (this != &other)
		{
			data_ = other.data_;
			size_ = other.size();
		}
		return *this;
	}

	inline shared_blob & shared_blob::operator = (shared_blob && other)
	{
		if (this != &other)
		{
			data_ = std::move(other.data_);
			size_ = other.size();
			other.size_ = 0;
		}
		return *this;
	}

	inline shared_blob & shared_blob::operator = (blob && other)
	{
		size_ = other.size();
		data_ = other.release();
		return *this;
	}

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

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

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