/*
 * Copyright 2015 - 2025 gary@drinkingtea.net
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include "concepts.hpp"
#include "error.hpp"
#include "types.hpp"

namespace ox {

namespace ios_base {
enum seekdir {
	beg,
	end,
	cur,
};
}

template<typename T>
concept Writer_c = requires(T v) {
	{v.put(static_cast<char>(0))} -> ox::same_as<ox::Error>;
	{v.write(static_cast<const char*>(""), static_cast<std::size_t>(0))} -> ox::same_as<ox::Error>;
	{v.seekp(static_cast<std::size_t>(0))} -> ox::same_as<ox::Error>;
	{v.seekp(static_cast<std::size_t>(0), ios_base::beg)} -> ox::same_as<ox::Error>;
	{v.tellp()} -> ox::same_as<std::size_t>;
};

class Writer_v {
	public:
		virtual constexpr ~Writer_v() noexcept = default;
		virtual constexpr auto put(char) noexcept -> ox::Error = 0;
		virtual constexpr auto write(const char*, std::size_t) noexcept -> ox::Error = 0;
		virtual constexpr auto seekp(std::size_t) noexcept -> ox::Error = 0;
		virtual constexpr auto seekp(int, ios_base::seekdir) -> ox::Error = 0;
		virtual constexpr auto tellp() noexcept -> std::size_t = 0;
};

template<Writer_c T>
class WriterT: public Writer_v {
	private:
		T m_writer{};
	public:
		template<typename ...Args>
		constexpr explicit WriterT(Args&&... args) noexcept: m_writer(args...) {
		}
		constexpr auto put(char v) noexcept -> ox::Error override {
			return m_writer.put(v);
		}
		constexpr auto write(const char *v, std::size_t cnt) noexcept -> ox::Error override {
			return m_writer.write(v, cnt);
		}
		constexpr auto seekp(std::size_t p) noexcept -> ox::Error override {
			return m_writer.seekp(p);
		}
		constexpr auto seekp(int p, ios_base::seekdir sd) noexcept -> ox::Error override {
			return m_writer.seekp(p, sd);
		}
		constexpr auto tellp() noexcept -> std::size_t override {
			return m_writer.tellp();
		}
};

/**
 * Allocates the specified amount of data at the end of the current write stream.
 * @param writer
 * @param sz
 * @return
 */
constexpr ox::Result<std::size_t> allocate(Writer_c auto &writer, std::size_t sz) noexcept {
	const auto p = writer.tellp();
	OX_RETURN_ERROR(writer.seekp(0, ios_base::end));
	const auto out = writer.tellp();
	OX_RETURN_ERROR(writer.write(nullptr, sz));
	OX_RETURN_ERROR(writer.seekp(p));
	return out;
}

}
