#include <boostAsioLinkageFix.h>

#include <cameraModeRequest.h>
#include <planarYuvFormatPolicy.h>
#include <sessionModeConfigure.h>
#include <libcamera/camera.h>
#include <libcamera/stream.h>
#include <stdexcept>
#include <utility>

namespace lcamera_dev {

namespace {

LcameraDevConfiguredCameraMode buildConfiguredModeFromStreamConfig(
	const LcameraDevCameraModeRequest& request,
	const libcamera::StreamConfiguration& streamConfig)
{
	LcameraDevConfiguredCameraMode configuredMode;
	configuredMode.width = streamConfig.size.width;
	configuredMode.height = streamConfig.size.height;
	configuredMode.colourSpace = request.colourSpace;
	configuredMode.pixelFormatName = streamConfig.pixelFormat.toString();
	configuredMode.isFullyPlanar =
		isFullyPlanarYuv(streamConfig.pixelFormat);
	configuredMode.planeCount =
		yuvCapturePlaneCount(streamConfig.pixelFormat);
	return configuredMode;
}

std::unique_ptr<libcamera::CameraConfiguration> generateCaptureConfiguration(
	const std::shared_ptr<libcamera::Camera>& camera)
{
	std::unique_ptr<libcamera::CameraConfiguration> config =
		camera->generateConfiguration(
			{libcamera::StreamRole::VideoRecording});

	if (!config || config->empty())
	{
		config = camera->generateConfiguration(
			{libcamera::StreamRole::Viewfinder});
	}

	if (!config || config->empty())
	{
		throw std::runtime_error(
			"lcameraDev: camera does not support VideoRecording or "
			"Viewfinder stream roles");
	}

	return config;
}

void validateConfigurationStatus(
	libcamera::CameraConfiguration::Status status,
	const std::string& cameraId)
{
	if (status == libcamera::CameraConfiguration::Valid
		|| status == libcamera::CameraConfiguration::Adjusted)
	{
		return;
	}

	throw std::runtime_error(
		"lcameraDev: libcamera configuration invalid for camera "
		+ cameraId);
}

} // namespace

ConfigureSessionModeStatus applyModeRequestToSessionState(
	CameraSessionResources& resources,
	const LcameraDevCameraModeRequest& request,
	const LcameraDevConfiguredCameraMode& resolvedMode,
	std::shared_ptr<libcamera::CameraConfiguration> heldConfiguration)
{
	if (resources.configuredMode.has_value())
	{
		if (cameraModeRequestsEqual(resources.configuredRequest, request)) {
			return ConfigureSessionModeStatus::NoOpAlreadyConfigured;
		}

		throw std::runtime_error(
			"lcameraDev: conflicting camera mode request on configured "
			"session");
	}

	resources.configuredRequest = request;
	resources.configuredMode = resolvedMode;
	resources.heldConfiguration = heldConfiguration;
	++resources.libcameraConfigureCallCount;
	return ConfigureSessionModeStatus::Configured;
}

LcameraDevConfiguredCameraMode configureLibcameraSessionMode(
	const std::shared_ptr<libcamera::Camera>& camera,
	const LcameraDevCameraModeRequest& request,
	std::shared_ptr<libcamera::CameraConfiguration>& heldConfiguration)
{
	if (!camera)
	{
		throw std::runtime_error(
			"lcameraDev: configureSessionModeCReq camera is null");
	}

	std::unique_ptr<libcamera::CameraConfiguration> config =
		generateCaptureConfiguration(camera);

	libcamera::StreamConfiguration& streamConfig = config->at(0);
	streamConfig.size = libcamera::Size(request.width, request.height);

	const std::vector<libcamera::PixelFormat> pixelFormatCandidates =
		streamConfig.formats().pixelformats();
	const std::optional<libcamera::PixelFormat> selectedPixelFormat =
		selectYuvCaptureFormat(
			pixelFormatCandidates,
			request.fullPlanarIsOptional);

	if (!selectedPixelFormat.has_value())
	{
		throw std::runtime_error(
			"lcameraDev: failed to select YUV capture format");
	}

	streamConfig.pixelFormat = *selectedPixelFormat;

	const libcamera::CameraConfiguration::Status validateStatus =
		config->validate();
	validateConfigurationStatus(validateStatus, camera->id());

	const int configureRc = camera->configure(config.get());
	if (configureRc != 0)
	{
		throw std::runtime_error(
			"lcameraDev: libcamera configure failed for camera "
			+ camera->id());
	}

	const LcameraDevConfiguredCameraMode configuredMode =
		buildConfiguredModeFromStreamConfig(request, streamConfig);
	heldConfiguration = std::move(config);
	return configuredMode;
}

} // namespace lcamera_dev
