/*
 * 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 <ox/std/assert.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/units.hpp>

namespace ox::detail {

class CirculerBuffer {
	private:
		std::size_t m_readPt = 0;
		std::size_t m_writePt = 0;
		ox::Buffer m_buff = ox::Buffer(ox::units::MB);

	private:
		[[nodiscard]]
		constexpr auto avail() const noexcept {
			if (m_writePt >= m_readPt) {
				return m_buff.size() - (m_writePt - m_readPt);
			} else {
				return (m_buff.size() - m_writePt) - (m_buff.size() - m_readPt);
			}
		}

	public:
		constexpr ox::Error put(char v) noexcept {
			return write(&v, 1);
			if (1 > avail()) {
				return ox::Error(1, "Insufficient space in buffer");
			}
			m_buff[m_writePt] = v;
			++m_writePt;
			return {};
		}

		constexpr ox::Error write(const char *buff, std::size_t sz) noexcept {
			if (sz > avail()) {
				return ox::Error(1, "Insufficient space in buffer");
			}
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
			// write seg 1
			const auto seg1Sz = ox::min(sz, m_buff.size() - m_writePt);
			ox::listcpy(&m_buff[m_writePt], &buff[0], seg1Sz);
			m_writePt += sz;
			if (seg1Sz != sz) {
				m_writePt -= m_buff.size();
				// write seg 2
				const auto seg2Sz = sz - seg1Sz;
				ox::listcpy(&m_buff[0], &buff[seg1Sz], seg2Sz);
				oxAssert(m_buff[0] == buff[seg1Sz], "break");
			}
OX_ALLOW_UNSAFE_BUFFERS_END
			return {};
		}

		constexpr ox::Error seekp(std::size_t bytesFwd) noexcept {
			if (bytesFwd > avail()) {
				return ox::Error(1, "Insufficient space in buffer to seek that far ahead");
			}
			m_writePt += bytesFwd;
			if (m_writePt > m_buff.size()) {
				m_writePt -= m_buff.size();
			}
			return {};
		}

		constexpr ox::Error seekp(int, ios_base::seekdir) noexcept {
			return ox::Error(1, "Unimplemented");
		}

		[[nodiscard]]
		constexpr std::size_t tellp() const noexcept {
			return m_buff.size() - avail();
		}

		[[nodiscard]]
		constexpr std::size_t read(char *out, std::size_t outSize) noexcept {
			const auto bytesRead = ox::min(outSize, m_buff.size() - avail());
			// read seg 1
			const auto seg1Sz = ox::min(bytesRead, m_buff.size() - m_readPt);
			ox::listcpy(&out[0], &m_buff[m_readPt], seg1Sz);
			m_readPt += bytesRead;
			if (seg1Sz != bytesRead) {
				m_readPt -= m_buff.size();
				// read seg 2
				const auto seg2Sz = bytesRead - seg1Sz;
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
				ox::listcpy(&out[seg1Sz], &m_buff[0], seg2Sz);
OX_ALLOW_UNSAFE_BUFFERS_END
			}
			return bytesRead;
		}
};

}
