/*
 * 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/.
 */

#include <ox/std/memops.hpp>
#include <ox/std/strops.hpp>
#include <ox/std/trace.hpp>
#include <ox/fs/filesystem/pathiterator.hpp>

OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)

namespace ox {

PathIterator::PathIterator(const char *path, std::size_t maxSize, std::size_t iterator) {
	m_path = path;
	m_maxSize = maxSize;
	m_iterator = iterator;
}

PathIterator::PathIterator(const char *path): PathIterator(path, ox::strlen(path)) {
}

PathIterator::PathIterator(StringViewCR path): PathIterator(path.data(), path.bytes()) {
}

/**
 * @return 0 if no error
 */
Error PathIterator::dirPath(char *out, std::size_t outSize) {
	const auto idx = ox::lastIndexOf(m_path, '/', m_maxSize);
	const auto size = static_cast<std::size_t>(idx) + 1;
	if (idx >= 0 && size < outSize) {
		ox::memcpy(out, m_path, size);
		out[size] = 0;
		return {};
	} else {
		return ox::Error(1);
	}
}

// Gets the get item in the path
Error PathIterator::get(StringView &fileName) {
	std::size_t size = 0;
	if (m_iterator >= m_maxSize) {
		oxTracef("ox.fs.PathIterator.get", "m_iterator ({}) >= m_maxSize ({})", m_iterator, m_maxSize);
		return ox::Error(1);
	}
	if (!ox::strlen(&m_path[m_iterator])) {
		oxTrace("ox.fs.PathIterator.get", "!ox::strlen(&m_path[m_iterator])");
		return ox::Error(1);
	}
	auto start = m_iterator;
	if (m_path[start] == '/') {
		start++;
	}
	// end is at the next /
	const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
	// correct end if it is invalid, which happens if there is no next /
	if (!substr) {
		substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
	}
	const auto end = static_cast<size_t>(substr - m_path);
	size = end - start;
	// cannot fit the output in the output parameter
	if (size >= MaxFileNameLength || size == 0) {
		return ox::Error(1);
	}
	fileName = ox::substr(m_path, start, start + size);
	// truncate trailing /
	if (size && fileName[size - 1] == '/') {
		fileName = ox::substr(m_path, start, start + size - 1);
	}
	oxAssert(fileName[fileName.size()-1] != '/', "name ends in /");
	return {};
}

/**
 * @return 0 if no error
 */
Error PathIterator::next(StringView &fileName) {
	std::size_t size = 0;
	auto retval = ox::Error(1);
	if (m_iterator < m_maxSize && ox::strlen(&m_path[m_iterator])) {
		retval = {};
		if (m_path[m_iterator] == '/') {
			m_iterator++;
		}
		const auto start = m_iterator;
		// end is at the next /
		const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
		// correct end if it is invalid, which happens if there is no next /
		if (!substr) {
			substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
		}
		const auto end = static_cast<size_t>(substr - m_path);
		size = end - start;
		// cannot fit the output in the output parameter
		if (size >= MaxFileNameLength) {
			return ox::Error(1);
		}
		fileName = ox::substr(m_path, start, start + size);
		// truncate trailing /
		while (fileName.size() && fileName[fileName.size() - 1] == '/') {
			fileName = ox::substr(m_path, start, start + size);
		}
		m_iterator += size;
		oxAssert(fileName.size() == 0 || fileName[fileName.size()-1] != '/', "name ends in /");
	}
	return retval;
}

Result<std::size_t> PathIterator::nextSize() const {
	std::size_t size = 0;
	auto retval = ox::Error(1);
	auto it = m_iterator;
	if (it < m_maxSize && ox::strlen(&m_path[it])) {
		retval = {};
		if (m_path[it] == '/') {
			it++;
		}
		const auto start = it;
		// end is at the next /
		const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
		// correct end if it is invalid, which happens if there is no next /
		if (!substr) {
			substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
		}
		const auto end = static_cast<std::size_t>(substr - m_path);
		size = end - start;
	}
	it += size;
	return {size, retval};
}

bool PathIterator::hasNext() const {
	std::size_t size = 0;
	if (m_iterator < m_maxSize && ox::strlen(&m_path[m_iterator])) {
		std::size_t start = m_iterator;
		if (m_path[start] == '/') {
			start++;
		}
		// end is at the next /
		const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
		// correct end if it is invalid, which happens if there is no next /
		if (!substr) {
			substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
		}
		const auto end = static_cast<std::size_t>(substr - m_path);
		size = end - start;
	}
	return size > 0;
}

bool PathIterator::valid() const {
	return m_iterator < m_maxSize && m_path[m_iterator] != 0;
}

PathIterator PathIterator::next() const {
	std::size_t size = 0;
	auto iterator = m_iterator;
	if (iterator < m_maxSize && ox::strlen(&m_path[iterator])) {
		if (m_path[iterator] == '/') {
			iterator++;
		}
		const auto start = iterator;
		// end is at the next /
		const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
		// correct end if it is invalid, which happens if there is no next /
		if (!substr) {
			substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
		}
		const auto end = static_cast<std::size_t>(substr - m_path);
		size = end - start;
	}
	iterator += size;
	return {m_path, m_maxSize, iterator + 1};
}

const char *PathIterator::fullPath() const {
	return m_path;
}

}

OX_CLANG_NOWARN_END
