#include <pixelAndColorFormatDecisions.h>
#include <algorithm>
#include <cctype>
#include <stdexcept>
#include <string>
#include <unordered_set>

namespace smo {
namespace stim_buff {
namespace lcamera_buff {

namespace {

std::string normalizeFourccToken(std::string token)
{
	for (char& character : token)
	{
		character = static_cast<char>(
			std::toupper(static_cast<unsigned char>(character)));
	}

	return token;
}

bool fourccInSet(
	const std::string& fourcc,
	const std::unordered_set<std::string>& candidates)
{
	return candidates.find(fourcc) != candidates.end();
}

const std::unordered_set<std::string>& yuv420FourccSet()
{
	static const std::unordered_set<std::string> fourccs = {
		"YU12", "YV12", "YUV420", "YVU420", "NV12", "NV21",
	};
	return fourccs;
}

const std::unordered_set<std::string>& yuv422FourccSet()
{
	static const std::unordered_set<std::string> fourccs = {
		"YUYV", "YVYU", "UYVY", "VYUY", "YUV422", "YVU422",
		"NV16", "NV61", "YU16", "YV16",
	};
	return fourccs;
}

const std::unordered_set<std::string>& yuv444FourccSet()
{
	static const std::unordered_set<std::string> fourccs = {
		"YUV444", "YVU444",
	};
	return fourccs;
}

unsigned chromaPlaneWidth(unsigned frameWidth, YuvChromaSubsampling subsampling)
{
	switch (subsampling)
	{
		case YuvChromaSubsampling::Yuv420:
		case YuvChromaSubsampling::Yuv422:
			return (frameWidth + 1u) / 2u;

		case YuvChromaSubsampling::Yuv444:
			return frameWidth;
	}

	throw std::logic_error(
		"lcameraBuff: unhandled YuvChromaSubsampling in chromaPlaneWidth");
}

unsigned chromaPlaneHeight(unsigned frameHeight, YuvChromaSubsampling subsampling)
{
	switch (subsampling)
	{
		case YuvChromaSubsampling::Yuv420:
			return (frameHeight + 1u) / 2u;

		case YuvChromaSubsampling::Yuv422:
		case YuvChromaSubsampling::Yuv444:
			return frameHeight;
	}

	throw std::logic_error(
		"lcameraBuff: unhandled YuvChromaSubsampling in chromaPlaneHeight");
}

} // namespace

YuvChromaSubsampling classifyYuvChromaSubsampling(
	const lcamera_dev::LcameraDevConfiguredCameraMode& configuredMode)
{
	const std::string fourcc =
		normalizeFourccToken(configuredMode.pixelFormatName);

	if (fourccInSet(fourcc, yuv420FourccSet())) {
		return YuvChromaSubsampling::Yuv420;
	}

	if (fourccInSet(fourcc, yuv422FourccSet())) {
		return YuvChromaSubsampling::Yuv422;
	}

	if (fourccInSet(fourcc, yuv444FourccSet())) {
		return YuvChromaSubsampling::Yuv444;
	}

	throw std::runtime_error(
		"lcameraBuff: unsupported YUV pixel format for chroma geometry: "
		+ configuredMode.pixelFormatName);
}

size_t computeDeinterleavedChannelByteSize(
	YuvChannelKind channel,
	unsigned width, unsigned height,
	YuvChromaSubsampling subsampling)
{
	switch (channel)
	{
		case YuvChannelKind::Y:
			return static_cast<size_t>(width) * static_cast<size_t>(height);

		case YuvChannelKind::U:
		case YuvChannelKind::V:
		{
			const unsigned chromaWidth = chromaPlaneWidth(width, subsampling);
			const unsigned chromaHeight =
				chromaPlaneHeight(height, subsampling);
			return static_cast<size_t>(chromaWidth)
				* static_cast<size_t>(chromaHeight);
		}
	}

	throw std::logic_error(
		"lcameraBuff: unhandled YuvChannelKind in computeDeinterleavedChannelByteSize");
}

} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
