/*
 * 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 "error.hpp"
#include "reader.hpp"
#include "span.hpp"
#include "vector.hpp"
#include "writer.hpp"

OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)

namespace ox {

extern template class Vector<char>;

using Buffer = Vector<char>;
using BufferView = Span<const char>;

class BufferWriter {
	private:
		std::size_t m_it = 0;
		ox::Buffer &m_buff;

	public:
		explicit constexpr BufferWriter(Buffer *buff) noexcept: m_it(buff->size()), m_buff(*buff) {
		}

		explicit constexpr BufferWriter(Buffer *buff, std::size_t it) noexcept: m_it(it), m_buff(*buff) {
		}

		constexpr ox::Error seekp(std::size_t p) noexcept {
			m_it = p;
			return {};
		}

		constexpr ox::Error seekp(ox::Signed<std::size_t> off, ox::ios_base::seekdir dir) noexcept {
			ox::Signed<std::size_t> base = 0;
			switch (dir) {
				case ox::ios_base::beg:
					base = 0;
					break;
				case ox::ios_base::end:
					base = static_cast<ox::Signed<std::size_t>>(m_buff.size());
					break;
				case ox::ios_base::cur:
					base = static_cast<ox::Signed<std::size_t>>(m_it);
					break;
				default:
					return ox::Error(1, "Invalid seekdir");
			}
			m_it = static_cast<std::size_t>(base + off);
			return {};
		}

		[[nodiscard]]
		constexpr auto tellp() const noexcept {
			return m_it;
		}

		constexpr ox::Error put(char val) noexcept {
			if (m_it >= m_buff.size()) {
				m_buff.resize(m_buff.size() + 1);
			}
			m_buff[m_it] = val;
			++m_it;
			return {};
		}

		constexpr ox::Error write(const char *inBuff, std::size_t cnt) noexcept {
			const auto end = m_it + cnt;
			if (end >= m_buff.size()) {
				m_buff.resize(end);
			}
			if (inBuff) {
				const auto buff = m_buff.data() + m_it;
				for (auto i = 0u; i < cnt; ++i) {
					buff[i] = inBuff[i];
				}
			}
			m_it += cnt;
			return {};
		}

		[[nodiscard]]
		constexpr const auto &buff() const noexcept {
			return m_buff;
		}
};

class CharBuffWriter {
	private:
		std::size_t m_it = 0;
		std::size_t m_cap = 0;
		std::size_t m_size = 0;
		char *m_buff = nullptr;

	public:
		explicit constexpr CharBuffWriter(ox::Span<char> buff) noexcept:
			m_cap(buff.size()),
			m_buff(buff.data()) {
		}

		constexpr ox::Error seekp(std::size_t p) noexcept {
			m_it = p;
			return {};
		}

		constexpr ox::Error seekp(ox::Signed<std::size_t> off, ox::ios_base::seekdir dir) noexcept {
			ox::Signed<std::size_t> base = 0;
			switch (dir) {
				case ox::ios_base::beg:
					base = 0;
					break;
				case ox::ios_base::end:
					base = static_cast<ox::Signed<std::size_t>>(m_size);
					break;
				case ox::ios_base::cur:
					base = static_cast<ox::Signed<std::size_t>>(m_it);
					break;
				default:
					return ox::Error(1, "Invalid seekdir");
			}
			m_it = static_cast<std::size_t>(base + off);
			return {};
		}

		[[nodiscard]]
		constexpr auto tellp() const noexcept {
			return m_it;
		}

		constexpr ox::Error put(char val) noexcept {
			if (m_it >= m_cap) [[unlikely]] {
				return ox::Error(1, "Buffer overrun");
			}
			m_buff[m_it] = val;
			++m_it;
			m_size = ox::max(m_it, m_size);
			return {};
		}

		constexpr ox::Error write(const char *buff, std::size_t cnt) noexcept {
			const auto end = m_it + cnt;
			if (end > m_cap) [[unlikely]] {
				return ox::Error(1, "Buffer overrun");
			}
			if (buff) {
				for (auto i = 0u; i < cnt; ++i) {
					m_buff[m_it + i] = buff[i];
				}
			}
			m_it += cnt;
			m_size = ox::max(m_it, m_size);
			return {};
		}

		[[nodiscard]]
		constexpr auto data() const noexcept {
			return m_buff;
		}
};

class BufferReader {
	private:
		std::size_t m_it = 0;
		std::size_t m_size = 0;
		char const* m_buff = nullptr;

	public:
		constexpr explicit BufferReader(ox::BufferView buffer) noexcept:
			m_size(buffer.size()), m_buff(buffer.data()) {}

		constexpr ox::Result<char> peek() const noexcept {
			if (m_it >= m_size) [[unlikely]] {
				return ox::Error(1, "Peek failed: buffer overrun");
			}
			return m_buff[m_it];
		}

		constexpr ox::Result<std::size_t> read(void *v, std::size_t sz) noexcept {
			sz = ox::min(sz, m_size - m_it);
			if (m_it + sz > m_size) [[unlikely]] {
				return ox::Error(1, "Read failed: Buffer overrun");
			}
			ox::memcpy(v, &m_buff[m_it], sz);
			m_it += sz;
			return sz;
		}

		constexpr ox::Error seekg(std::size_t p) noexcept {
			if (p > m_size) [[unlikely]] {
				return ox::Error(1, "Seek failed: Buffer overrun");
			}
			m_it = p;
			return {};
		}

		constexpr ox::Error seekg(int64_t off, ios_base::seekdir dir) noexcept {
			ox::Signed<std::size_t> base = 0;
			switch (dir) {
				case ox::ios_base::beg:
					base = 0;
					break;
				case ox::ios_base::end:
					base = static_cast<ox::Signed<std::size_t>>(m_size);
					break;
				case ox::ios_base::cur:
					base = static_cast<ox::Signed<std::size_t>>(m_it);
					break;
				default:
					return ox::Error(1, "Invalid seekdir");
			}
			auto const newIt = static_cast<std::size_t>(base + off);
			if (newIt > m_size) [[unlikely]] {
				return ox::Error(1, "Seek failed: Buffer overrun");
			}
			m_it = newIt;
			return {};
		}

		constexpr ox::Result<std::size_t> tellg() const noexcept {
			return m_it;
		}
};

extern template class ReaderT<BufferReader>;
extern template class WriterT<BufferWriter>;
extern template class WriterT<CharBuffWriter>;

}

OX_CLANG_NOWARN_END
