#include <planarYuvFormatPolicy.h>
#include <libcamera/formats.h>
#include <sstream>
#include <stdexcept>

namespace lcamera_dev {

namespace {

using libcamera::formats::NV12;
using libcamera::formats::NV16;
using libcamera::formats::NV21;
using libcamera::formats::NV24;
using libcamera::formats::NV42;
using libcamera::formats::NV61;
using libcamera::formats::UYVY;
using libcamera::formats::VYUY;
using libcamera::formats::YUYV;
using libcamera::formats::YUV420;
using libcamera::formats::YUV422;
using libcamera::formats::YUV444;
using libcamera::formats::YVU420;
using libcamera::formats::YVU422;
using libcamera::formats::YVU444;
using libcamera::formats::YVYU;

bool pixelFormatMatches(
	const libcamera::PixelFormat& pixelFormat,
	const libcamera::PixelFormat& expectedFormat)
{
	return pixelFormat == expectedFormat;
}

bool isFullyPlanarYuvFourcc(const libcamera::PixelFormat& pixelFormat)
{
	return pixelFormatMatches(pixelFormat, YUV420)
		|| pixelFormatMatches(pixelFormat, YVU420)
		|| pixelFormatMatches(pixelFormat, YUV422)
		|| pixelFormatMatches(pixelFormat, YVU422)
		|| pixelFormatMatches(pixelFormat, YUV444)
		|| pixelFormatMatches(pixelFormat, YVU444);
}

bool isSemiPlanarYuvFourcc(const libcamera::PixelFormat& pixelFormat)
{
	return pixelFormatMatches(pixelFormat, NV12)
		|| pixelFormatMatches(pixelFormat, NV21)
		|| pixelFormatMatches(pixelFormat, NV16)
		|| pixelFormatMatches(pixelFormat, NV61)
		|| pixelFormatMatches(pixelFormat, NV24)
		|| pixelFormatMatches(pixelFormat, NV42);
}

bool isPackedYuvFourcc(const libcamera::PixelFormat& pixelFormat)
{
	return pixelFormatMatches(pixelFormat, YUYV)
		|| pixelFormatMatches(pixelFormat, YVYU)
		|| pixelFormatMatches(pixelFormat, UYVY)
		|| pixelFormatMatches(pixelFormat, VYUY);
}

} // namespace

bool isFullyPlanarYuv(const libcamera::PixelFormat& pixelFormat)
{
	return isFullyPlanarYuvFourcc(pixelFormat);
}

bool isKnownYuvCaptureFormat(const libcamera::PixelFormat& pixelFormat)
{
	return isFullyPlanarYuvFourcc(pixelFormat)
		|| isSemiPlanarYuvFourcc(pixelFormat)
		|| isPackedYuvFourcc(pixelFormat);
}

unsigned yuvCapturePlaneCount(const libcamera::PixelFormat& pixelFormat)
{
	if (isFullyPlanarYuvFourcc(pixelFormat)) {
		return 3;
	}

	if (isSemiPlanarYuvFourcc(pixelFormat)) {
		return 2;
	}

	if (isPackedYuvFourcc(pixelFormat)) {
		return 1;
	}

	return 0;
}

std::string formatCandidateListForDiagnostics(
	const std::vector<libcamera::PixelFormat>& candidates)
{
	std::ostringstream stream;
	for (std::size_t i = 0; i < candidates.size(); ++i)
	{
		if (i > 0) {
			stream << ", ";
		}

		stream << candidates[i].toString();
	}

	return stream.str();
}

std::optional<libcamera::PixelFormat>
selectYuvCaptureFormat(
	const std::vector<libcamera::PixelFormat>& candidates,
	bool fullPlanarIsOptional)
{
	if (candidates.empty())
	{
		throw std::runtime_error(
			"lcameraDev: no YUV pixel-format candidates available");
	}

	if (!fullPlanarIsOptional)
	{
		for (const libcamera::PixelFormat& candidate : candidates)
		{
			if (isFullyPlanarYuv(candidate)) {
				return candidate;
			}
		}

		throw std::runtime_error(
			"lcameraDev: no fully planar YUV format among candidates: "
			+ formatCandidateListForDiagnostics(candidates));
	}

	for (const libcamera::PixelFormat& candidate : candidates)
	{
		if (isKnownYuvCaptureFormat(candidate)) {
			return candidate;
		}
	}

	throw std::runtime_error(
		"lcameraDev: no known YUV capture format among candidates: "
		+ formatCandidateListForDiagnostics(candidates));
}

} // namespace lcamera_dev
