/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * Copyright (C) 2019, Raspberry Pi Ltd
 *
 * Lux control algorithm
 */

#include <libcamera/base/log.h>

#include "../device_status.h"

#include "lux.h"

using namespace RPiController;
using namespace libcamera;
using namespace std::literals::chrono_literals;

LOG_DEFINE_CATEGORY(RPiLux)

#define NAME "rpi.lux"

Lux::Lux(Controller *controller)
	: Algorithm(controller), sensitivity_(1.0)
{
	/*
	 * Put in some defaults as there will be no meaningful values until
	 * Process has run.
	 */
	status_.aperture = 1.0;
	status_.lux = 400;
}

char const *Lux::name() const
{
	return NAME;
}

int Lux::read(const libcamera::ValueNode &params)
{
	auto value = params["reference_shutter_speed"].get<double>();
	if (!value)
		return -EINVAL;
	referenceExposureTime_ = *value * 1.0us;

	value = params["reference_gain"].get<double>();
	if (!value)
		return -EINVAL;
	referenceGain_ = *value;

	referenceAperture_ = params["reference_aperture"].get<double>(1.0);

	value = params["reference_Y"].get<double>();
	if (!value)
		return -EINVAL;
	referenceY_ = *value;

	value = params["reference_lux"].get<double>();
	if (!value)
		return -EINVAL;
	referenceLux_ = *value;

	currentAperture_ = referenceAperture_;
	return 0;
}

void Lux::setCurrentAperture(double aperture)
{
	currentAperture_ = aperture;
}

void Lux::switchMode(CameraMode const &cameraMode, [[maybe_unused]] Metadata *metadata)
{
	/* We will need to compensate for the camera sensitivity. */
	ASSERT(cameraMode.sensitivity);
	sensitivity_ = cameraMode.sensitivity;
}

void Lux::prepare(Metadata *imageMetadata)
{
	std::unique_lock<std::mutex> lock(mutex_);
	imageMetadata->set("lux.status", status_);
}

void Lux::process(StatisticsPtr &stats, Metadata *imageMetadata)
{
	DeviceStatus deviceStatus;
	if (imageMetadata->get("device.status", deviceStatus) == 0) {
		/*
		 * We've set up the first floating AGC region to collect full image stats. This
		 * is a better choice than the Y-histogram, for example, because it's invariant
		 * to the metering mode (and cheaper to evaluate).
		 */
		const auto &fullImageStats = stats->agcRegions.getFloating(0);
		double currentY = static_cast<double>(fullImageStats.val.ySum) / fullImageStats.counted;

		double currentGain = deviceStatus.analogueGain;
		double currentAperture = deviceStatus.aperture.value_or(currentAperture_);
		double gainRatio = referenceGain_ / currentGain;
		double exposureTimeRatio =
			referenceExposureTime_.count() / deviceStatus.exposureTime.count();
		double apertureRatio = referenceAperture_ / currentAperture;
		double yRatio = currentY / referenceY_;
		double estimatedLux = exposureTimeRatio * gainRatio *
				      apertureRatio * apertureRatio *
				      yRatio * referenceLux_ / sensitivity_;

		LuxStatus status;
		status.lux = estimatedLux;
		status.aperture = currentAperture;

		LOG(RPiLux, Debug) << ": estimated lux " << estimatedLux;
		{
			std::unique_lock<std::mutex> lock(mutex_);
			status_ = status;
		}
		/*
		 * Overwrite the metadata here as well, so that downstream
		 * algorithms get the latest value.
		 */
		imageMetadata->set("lux.status", status);
	} else
		LOG(RPiLux, Warning) << ": no device metadata";
}

/* Register algorithm with the system. */
static Algorithm *create(Controller *controller)
{
	return (Algorithm *)new Lux(controller);
}
static RegisterAlgorithm reg(NAME, &create);
