/*
 * Copyright 2024 Collabora, Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "config.h"

#include "color.h"
#include "color-properties.h"
#include "shared/helpers.h"
#include "shared/string-helpers.h"
#include "shared/xalloc.h"
#include "shared/weston-assert.h"

/** Enum that helps keep track of what params have been set. */
enum weston_color_profile_params_set {
	WESTON_COLOR_PROFILE_PARAMS_PRIMARIES = 0x01,
	WESTON_COLOR_PROFILE_PARAMS_TF = 0x02,
	WESTON_COLOR_PROFILE_PARAMS_PRIMARY_LUMINANCE = 0x04,
	WESTON_COLOR_PROFILE_PARAMS_TARGET_PRIMARIES = 0x08,
	WESTON_COLOR_PROFILE_PARAMS_TARGET_LUMINANCE = 0x10,
	WESTON_COLOR_PROFILE_PARAMS_MAXCLL = 0x20,
	WESTON_COLOR_PROFILE_PARAMS_MAXFALL = 0x40,
};

/** Builder object to create color profiles with parameters. */
struct weston_color_profile_param_builder {
	struct weston_compositor *compositor;
	struct weston_color_profile_params params;

	/*
	 * Keeps track of what params have already been set.
	 *
	 * A bitmask of values from enum weston_color_profile_params_set.
	 */
	uint32_t group_mask;

	/*
	 * Keeps track of errors.
	 *
	 * This API may produce errors, and we store all the error messages in
	 * the string below to help users to debug. Regarding error codes, we
	 * store only the first that occurs.
	 *
	 * Such errors can be queried with
	 * weston_color_profile_param_builder_get_error(). They are also
	 * return'ed when users of the API set all the params and call
	 * weston_color_profile_param_builder_create_color_profile().
	 */
	enum weston_color_profile_param_builder_error err;
	bool has_errors;
	FILE *err_fp;
	char *err_msg;
	size_t err_msg_size;
};

/**
 * Creates struct weston_color_profile_param_builder object. It should be used
 * to create color profiles from parameters.
 *
 * We expect it to be used by our frontend (to allow creating color profiles
 * from .ini files or similar) and by the color-management protocol
 * implementation (so that clients can create color profiles from parameters).
 *
 * It is invalid to set the same parameter twice using this object.
 *
 * After creating the color profile from this object, it will be automatically
 * destroyed.
 *
 * \param compositor The weston compositor.
 * \return The struct weston_color_profile_param_builder object created.
*/
WL_EXPORT struct weston_color_profile_param_builder *
weston_color_profile_param_builder_create(struct weston_compositor *compositor)
{
	struct weston_color_profile_param_builder *builder;

	builder = xzalloc(sizeof(*builder));

	builder->compositor = compositor;

	builder->err_fp = open_memstream(&builder->err_msg,
					 &builder->err_msg_size);
	weston_assert_ptr_not_null(compositor, builder->err_fp);

	return builder;
}

/**
 * Destroys a struct weston_color_profile_param_builder object.
 *
 * \param builder The object that should be destroyed.
 */
WL_EXPORT void
weston_color_profile_param_builder_destroy(struct weston_color_profile_param_builder *builder)
{
	fclose(builder->err_fp);
	free(builder->err_msg);
	free(builder);
}

static void __attribute__ ((format (printf, 3, 4)))
store_error(struct weston_color_profile_param_builder *builder,
	    enum weston_color_profile_param_builder_error err,
	    const char *fmt, ...)
{
	va_list ap;

	/* First error that we log. We also log the err code in such case. */
	if (!builder->has_errors) {
		builder->has_errors = true;
		builder->err = err;
		goto log_msg;
	}

	/* There are errors already, so add new line first. */
	fprintf(builder->err_fp, "\n");

log_msg:
	va_start(ap, fmt);
	vfprintf(builder->err_fp, fmt, ap);
	va_end(ap);
}

/**
 * Returns the code for the first error generated and a string with all error
 * messages that we caught.
 *
 * Function weston_color_profile_param_builder_create_color_profile() will also
 * fail with the first error code (if there's any), but we still need this
 * function because some users of the API may want to know about the error
 * immediately after calling a setter.
 *
 * \param builder The builder object whose parameters will be set.
 * \param err Set if there's an error, untouched otherwise. The first error code caught.
 * \param err_msg Set if there's an error, untouched otherwise. Must be free()'d
 * by the caller. Combination of all error messages caught. Not terminated with
 * a new line character.
 * \return true if there's an error, false otherwise.
 */
WL_EXPORT bool
weston_color_profile_param_builder_get_error(struct weston_color_profile_param_builder *builder,
					     enum weston_color_profile_param_builder_error *err,
					     char **err_msg)
{
	if (!builder->has_errors)
		return false;

	*err = builder->err;

	fflush(builder->err_fp);
	*err_msg = strdup(builder->err_msg);

	return true;
}

static float
triangle_area(float x1, float y1, float x2, float y2, float x3, float y3)
{
	/* Based on the shoelace formula, also known as Gauss's area formula. */
	return fabs((x1 - x3) * (y2 - y1) - (x1 - x2) * (y3 - y1)) / 2.0f;
}

static bool
is_point_inside_triangle(float point_x, float point_y,
			 float x1, float y1, float x2, float y2, float x3, float y3)
{
	float A1, A2, A3;
	float A;
	const float PRECISION = 1e-5;

	A = triangle_area(x1, y1, x2, y2, x3, y3);

	/* Bail out if something that is not a triangle was given. */
	if (A <= PRECISION)
		return false;

	A1 = triangle_area(point_x, point_y, x1, y1, x2, y2);
	A2 = triangle_area(point_x, point_y, x1, y1, x3, y3);
	A3 = triangle_area(point_x, point_y, x2, y2, x3, y3);

	if (fabs(A - (A1 + A2 + A3)) <= PRECISION)
		return true;

	return false;
}

static void
validate_CIE_value(struct weston_color_profile_param_builder *builder,
		   const char *gamut_name,
		   const char *chan_name,
		   const char *elem_name,
		   float v)
{
	/*
	 * We choose the legal range [-1.0, 2.0] for CIE xy values. It is
	 * probably more than we'd ever need, but tight enough to not cause
	 * mathematical issues. If wasn't for the ACES AP0 color space, we'd
	 * probably choose the range [0.0, 1.0].
	 */
	if (v >= -1.0f && v <= 2.0f)
		return;

	store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_CIE_XY_OUT_OF_RANGE,
		    "invalid %s color volume, the %s CIE %s value %f is out of range [-1.0, 2.0]",
		    gamut_name, chan_name, elem_name, v);
}

static void
validate_color_gamut(struct weston_color_profile_param_builder *builder,
		     const struct weston_color_gamut *gamut,
		     const char *gamut_name)
{
	const char *chan_name[4] = {
		"red primary",
		"green primary",
		"blue primary",
		"white point"
	};
	struct weston_CIExy xy[4] = {
		gamut->primary[0],
		gamut->primary[1],
		gamut->primary[2],
		gamut->white_point,
	};
	unsigned int i;

	for (i = 0; i < ARRAY_LENGTH(xy); i++) {
		validate_CIE_value(builder, gamut_name, chan_name[i], "x", xy[i].x);
		validate_CIE_value(builder, gamut_name, chan_name[i], "y", xy[i].y);
	}

	/*
	 * That is not sufficient. There are points inside the triangle that
	 * would not be valid white points. But for now that's good enough.
	 */
	if (!is_point_inside_triangle(gamut->white_point.x,
				      gamut->white_point.y,
				      gamut->primary[0].x,
				      gamut->primary[0].y,
				      gamut->primary[1].x,
				      gamut->primary[1].y,
				      gamut->primary[2].x,
				      gamut->primary[2].y)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_CIE_XY_OUT_OF_RANGE,
			    "white point out of %s volume", gamut_name);
	}
}

/**
 * Sets primaries for struct weston_color_profile_param_builder object.
 *
 * See also weston_color_profile_param_builder_set_primaries_named(), which is
 * another way of setting the primaries.
 *
 * If the primaries are already set (with this function or the one
 * mentioned above), this should fail. Setting a parameter twice is forbidden.
 *
 * If this fails, users can call weston_color_profile_param_builder_get_error()
 * to get the error details.
 *
 * \param builder The builder object whose parameters will be set.
 * \param primaries The object containing the primaries.
 * \return true on success, false otherwise.
 */
WL_EXPORT bool
weston_color_profile_param_builder_set_primaries(struct weston_color_profile_param_builder *builder,
						 const struct weston_color_gamut *primaries)
{
	struct weston_color_manager *cm = builder->compositor->color_manager;
	bool success = true;

	if (!((cm->supported_color_features >> WESTON_COLOR_FEATURE_SET_PRIMARIES) & 1)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_UNSUPPORTED,
			    "set_primaries not supported by the color manager");
		success = false;
	}

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_PRIMARIES) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET,
			    "primaries were already set");
		success = false;
	}

	if (!success)
		return false;

	builder->params.primaries = *primaries;

	builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_PRIMARIES;

	return true;
}

/**
 * Sets primaries for struct weston_color_profile_param_builder object using a
 * enum weston_color_primaries.
 *
 * See also weston_color_profile_param_builder_set_primaries(), which is another
 * way of setting the primaries.
 *
 * If the primaries are already set (with this function or the one mentioned
 * above), this should fail. Setting a parameter twice is forbidden.
 *
 * If this fails, users can call weston_color_profile_param_builder_get_error()
 * to get the error details.
 *
 * \param builder The builder object whose parameters will be set.
 * \param primaries The enum representing the primaries.
 * \return true on success, false otherwise.
 */
WL_EXPORT bool
weston_color_profile_param_builder_set_primaries_named(struct weston_color_profile_param_builder *builder,
						       enum weston_color_primaries primaries)
{
	struct weston_compositor *compositor = builder->compositor;
	struct weston_color_manager *cm = compositor->color_manager;
	const struct weston_color_primaries_info *info;
	bool success = true;

	info = weston_color_primaries_info_from(compositor, primaries);

	if (!((cm->supported_primaries_named >> primaries) & 1)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_PRIMARIES_NAMED,
			    "primaries named %s not supported by the color manager",
			    info->desc);
		success = false;
	}

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_PRIMARIES) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET,
			    "primaries were already set");
		success = false;
	}

	if (!success)
		return false;

	builder->params.primaries_info = info;
	builder->params.primaries = builder->params.primaries_info->color_gamut;
	builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_PRIMARIES;

	return true;
}

/**
 * Sets transfer function for struct weston_color_profile_param_builder object
 * using a enum weston_transfer_function.
 *
 * See also weston_color_profile_param_builder_set_tf_power_exponent(), which is
 * another way of setting the transfer function.
 *
 * If the transfer function is already set (with this function or the one
 * mentioned above), this should fail. Setting a parameter twice is forbidden.
 *
 * If this fails, users can call weston_color_profile_param_builder_get_error()
 * to get the error details.
 *
 * \param builder The builder object whose parameters will be set.
 * \param tf The enum representing the transfer function.
 * \return true on success, false otherwise.
 */
WL_EXPORT bool
weston_color_profile_param_builder_set_tf_named(struct weston_color_profile_param_builder *builder,
						enum weston_transfer_function tf)
{
	struct weston_compositor *compositor = builder->compositor;
	struct weston_color_manager *cm = compositor->color_manager;
	const struct weston_color_tf_info *info;
	bool success = true;

	info = weston_color_tf_info_from(compositor, tf);

	if (!((cm->supported_tf_named >> tf) & 1)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_TF,
			    "%s not supported by the color manager", info->desc);
		success = false;
	}

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TF) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET,
			    "tf was already set");
		success = false;
	}

	if (!success)
		return false;

	builder->params.tf.info = info;
	weston_assert_uint_eq(builder->compositor,
			      builder->params.tf.info->count_parameters, 0);

	builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_TF;

	return true;
}

/**
 * Sets transfer function for struct weston_color_profile_param_builder object
 * using a power law function exponent g. In such case, the transfer function is
 * y = x ^ g. The valid range for the given exponent is [1.0, 10.0].
 *
 * See also weston_color_profile_param_builder_set_tf_named(), which is another
 * way of setting the transfer function.
 *
 * If the transfer function is already set (with this function or the one
 * mentioned above), this should fail. Setting a parameter twice is forbidden.
 *
 * If this fails, users can call weston_color_profile_param_builder_get_error()
 * to get the error details.
 *
 * \param builder The builder object whose parameters will be set.
 * \param power_exponent The power law function exponent.
 * \return true on success, false otherwise.
 */
WL_EXPORT bool
weston_color_profile_param_builder_set_tf_power_exponent(struct weston_color_profile_param_builder *builder,
							 float power_exponent)
{
	struct weston_compositor *compositor = builder->compositor;
	struct weston_color_manager *cm = compositor->color_manager;
	bool success = true;

	if (!((cm->supported_color_features >> WESTON_COLOR_FEATURE_SET_TF_POWER) & 1)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_UNSUPPORTED,
			    "set_tf_power not supported by the color manager");
		success = false;
	}

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TF) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET,
			    "tf was already set");
		success = false;
	}

	/* The exponent should be at least 1.0 and at most 10.0. */
	if (!(power_exponent >= 1.0 && power_exponent <= 10.0)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_TF,
			    "tf power exponent %f is not in the range [1.0, 10.0]",
			    power_exponent);
		success = false;
	}

	if (!success)
		return false;

	builder->params.tf.info = weston_color_tf_info_from(compositor, WESTON_TF_POWER);
	builder->params.tf.params[0] = power_exponent;

	builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_TF;

	return true;
}

/**
 * Sets primary luminance for struct weston_color_profile_param_builder object.
 *
 * If the primary luminance is already set, this should fail. Setting a
 * parameter twice is forbidden.
 *
 * If this fails, users can call weston_color_profile_param_builder_get_error()
 * to get the error details.
 *
 * \param builder The builder object whose parameters will be set.
 * \param ref_lum The white point reference luminance.
 * \param min_lum The minimum luminance.
 * \param max_lum The maximum luminance.
 * \return true on success, false otherwise.
 */
WL_EXPORT bool
weston_color_profile_param_builder_set_primary_luminance(struct weston_color_profile_param_builder *builder,
							 float ref_lum, float min_lum, float max_lum)
{
	struct weston_color_manager *cm = builder->compositor->color_manager;
	bool success = true;

	if (!((cm->supported_color_features >> WESTON_COLOR_FEATURE_SET_LUMINANCES) & 1)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_UNSUPPORTED,
			    "set_primary_luminance not supported by the color manager");
		success = false;
	}

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_PRIMARY_LUMINANCE) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET,
			    "primary luminance was already set");
		success = false;
	}

	if (min_lum >= ref_lum) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "reference luminance (%f) must be greater than primary minimum luminance (%f)",
			    ref_lum, min_lum);
		success = false;
	}

	if (min_lum >= max_lum) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "primary minimum luminance (%f) must be less than primary maximum luminance (%f)",
			    min_lum, max_lum);
		success = false;
	}

	if (!success)
		return false;

	builder->params.reference_white_luminance = ref_lum;
	builder->params.min_luminance = min_lum;
	builder->params.max_luminance = max_lum;

	builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_PRIMARY_LUMINANCE;

	return true;
}

/**
 * Sets target primaries for struct weston_color_profile_param_builder object
 * using raw values.
 *
 * If the target primaries are already set, this should fail. Setting a
 * parameter twice is forbidden.
 *
 * If this fails, users can call weston_color_profile_param_builder_get_error()
 * to get the error details.
 *
 * \param builder The builder object whose parameters will be set.
 * \param target_primaries The object containing the target primaries.
 * \return true on success, false otherwise.
 */
WL_EXPORT bool
weston_color_profile_param_builder_set_target_primaries(struct weston_color_profile_param_builder *builder,
							const struct weston_color_gamut *target_primaries)
{
	struct weston_color_manager *cm = builder->compositor->color_manager;
	bool success = true;

	if (!((cm->supported_color_features >> WESTON_COLOR_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES) & 1)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_UNSUPPORTED,
			    "set_mastering_display_primaries not supported by " \
			    "the color manager");
		success = false;
	}

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TARGET_PRIMARIES) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET,
			    "target primaries were already set");
		success = false;
	}

	if (!success)
		return false;

	builder->params.target_primaries = *target_primaries;

	builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_TARGET_PRIMARIES;

	return true;
}

/**
 * Sets target primaries for struct weston_color_profile_param_builder object
 * from the given enumerated primaries.
 *
 * If the target primaries are already set, this should fail. Setting a
 * parameter twice is forbidden.
 *
 * If this fails, users can call weston_color_profile_param_builder_get_error()
 * to get the error details.
 *
 * \param builder The builder object whose parameters will be set.
 * \param target_primaries The entry from the enumeration to use.
 * \return true on success, false otherwise.
 */
WL_EXPORT bool
weston_color_profile_param_builder_set_target_primaries_named(struct weston_color_profile_param_builder *builder,
							      enum weston_color_primaries target_primaries)
{
	const struct weston_color_primaries_info *info;

	info = weston_color_primaries_info_from(builder->compositor, target_primaries);

	return weston_color_profile_param_builder_set_target_primaries(builder, &info->color_gamut);
}

/**
 * Sets target luminance for struct weston_color_profile_param_builder object.
 *
 * If the target luminance is already set, this should fail. Setting a parameter
 * twice is forbidden.
 *
 * If this fails, users can call weston_color_profile_param_builder_get_error()
 * to get the error details.
 *
 * \param builder The builder object whose parameters will be set.
 * \param min_lum The target minimum luminance.
 * \param max_lum The target maximum luminance.
 * \return true on success, false otherwise.
 */
WL_EXPORT bool
weston_color_profile_param_builder_set_target_luminance(struct weston_color_profile_param_builder *builder,
							float min_lum, float max_lum)
{
	struct weston_color_manager *cm = builder->compositor->color_manager;
	bool success = true;

	if (!((cm->supported_color_features >> WESTON_COLOR_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES) & 1)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_UNSUPPORTED,
			    "set_mastering_display_primaries not supported by " \
			    "the color manager, so setting target luminance is not allowed");
		success = false;
	}

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TARGET_LUMINANCE) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET,
			    "target luminance was already set");
		success = false;
	}

	if (min_lum >= max_lum) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "target min luminance (%f) must be less than target max luminance (%f)",
			    min_lum, max_lum);
		success = false;
	}

	if (!success)
		return false;

	builder->params.target_min_luminance = min_lum;
	builder->params.target_max_luminance = max_lum;

	builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_TARGET_LUMINANCE;

	return true;
}

/**
 * Sets target maxFALL for struct weston_color_profile_param_builder object.
 *
 * If the target maxFALL is already set, this should fail. Setting a parameter
 * twice is forbidden.
 *
 * If this fails, users can call weston_color_profile_param_builder_get_error()
 * to get the error details.
 *
 * \param builder The builder object whose parameters will be set.
 * \param maxFALL The maxFALL.
 * \return true on success, false otherwise.
 */
WL_EXPORT bool
weston_color_profile_param_builder_set_maxFALL(struct weston_color_profile_param_builder *builder,
					       float maxFALL)
{
	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXFALL) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET,
			    "max fall was already set");
		return false;
	}

	builder->params.maxFALL = maxFALL;

	builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_MAXFALL;

	return true;
}

/**
 * Sets target maxCLL for struct weston_color_profile_param_builder object.
 *
 * If the target maxCLL is already set, this should fail. Setting a parameter
 * twice is forbidden.
 *
 * If this fails, users can call weston_color_profile_param_builder_get_error()
 * to get the error details.
 *
 * \param builder The builder object whose parameters will be set.
 * \param maxCLL The maxCLL.
 * \return true on success, false otherwise.
 */
WL_EXPORT bool
weston_color_profile_param_builder_set_maxCLL(struct weston_color_profile_param_builder *builder,
					      float maxCLL)
{
	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXCLL) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET,
			    "max cll was already set");
		return false;
	}

	builder->params.maxCLL = maxCLL;

	builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_MAXCLL;

	return true;
}

static void
builder_validate_params_set(struct weston_color_profile_param_builder *builder)
{
	/* Primaries are mandatory. */
	if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_PRIMARIES))
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INCOMPLETE_SET,
			    "primaries not set");

	/* TF is mandatory. */
	if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TF))
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INCOMPLETE_SET,
			    "transfer function not set");
}

static void
builder_validate_primary_luminances(struct weston_color_profile_param_builder *builder)
{
	float min_lum = builder->params.min_luminance;
	float ref_lum = builder->params.reference_white_luminance;
	float max_lum = builder->params.max_luminance;

	if (!(ref_lum > 0.0f && ref_lum <= 1e6f)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "reference luminance (%f) must be in the range (0.0, 1e+6]",
			    ref_lum);
	}

	if (!(min_lum >= 0.0f && min_lum <= 1e6f)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "primary minimum luminance (%f) must be in the range [0.0, 1e+6]",
			    min_lum);
	}

	if (!(max_lum > 0.0f && max_lum <= 1e6f)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "primary maximum luminance (%f) must be in the range (0.0, 1e+6]",
			    max_lum);
	}
}

static void
builder_validate_target_luminances(struct weston_color_profile_param_builder *builder)
{
	float min_lum = builder->params.target_min_luminance;
	float max_lum = builder->params.target_max_luminance;

	if (!(min_lum >= 0.0f && min_lum <= 1e6f)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "target minimum luminance (%f) must be in the range [0.0, 1e+6]",
			    min_lum);
	}

	if (!(max_lum > 0.0f && max_lum <= 1e6f)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "target maximum luminance (%f) must be in the range (0.0, 1e+6]",
			    max_lum);
	}
}

static void
validate_maxcll(struct weston_color_profile_param_builder *builder)
{
	float maxCLL = builder->params.maxCLL;

	if (!(maxCLL > 0.0f && maxCLL <= 1e6f)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "maxCLL (%f) must be in the range (0.0, 1e+6]",
			    maxCLL);
	}

	if (builder->params.target_min_luminance >= maxCLL)
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "maxCLL (%f) should be greater than target min luminance (%f)",
			    maxCLL, builder->params.target_min_luminance);

	if (builder->params.target_max_luminance < maxCLL)
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "maxCLL (%f) should not be greater than target max luminance (%f)",
			    maxCLL, builder->params.target_max_luminance);
}

static void
validate_maxfall(struct weston_color_profile_param_builder *builder)
{
	float maxFALL = builder->params.maxFALL;

	if (!(maxFALL > 0.0f && maxFALL <= 1e6f)) {
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "maxFALL (%f) must be in the range (0.0, 1e+6]",
			    maxFALL);
	}

	if (builder->params.target_min_luminance >= maxFALL)
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "maxFALL (%f) must be greater than min luminance (%f)",
			    maxFALL, builder->params.target_min_luminance);

	if (builder->params.target_max_luminance < maxFALL)
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "maxFALL (%f) must be less than or equal to target max luminance (%f)",
			    maxFALL, builder->params.target_max_luminance);
}

static void
builder_validate_params(struct weston_color_profile_param_builder *builder)
{
	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXCLL)
		validate_maxcll(builder);

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXFALL)
		validate_maxfall(builder);

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXCLL &&
	    builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXFALL &&
	    builder->params.maxFALL > builder->params.maxCLL)
		store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE,
			    "maxFALL (%f) must be less than or equal to maxCLL (%f)",
			    builder->params.maxFALL,  builder->params.maxCLL);

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_PRIMARIES)
		validate_color_gamut(builder, &builder->params.primaries, "primary");

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TARGET_PRIMARIES)
		validate_color_gamut(builder, &builder->params.target_primaries, "target");

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_PRIMARY_LUMINANCE)
		builder_validate_primary_luminances(builder);

	if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TARGET_LUMINANCE)
		builder_validate_target_luminances(builder);
}

static void
builder_complete_params(struct weston_color_profile_param_builder *builder)
{
	/* If no target primaries were set, it matches the primaries. */
	if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TARGET_PRIMARIES))
		builder->params.target_primaries = builder->params.primaries;

	if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_PRIMARY_LUMINANCE)) {
		/* If primary luminance is not set, set it to default values.
		 * These values comes from the CM&HDR protocol. */
		builder->params.reference_white_luminance = 80.0;
		builder->params.min_luminance = 0.2;
		builder->params.max_luminance = 80.0;

		/* Some TF's override the default. Values comes from the CM&HDR
		 * protocol as well. */
		if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TF) {
			switch(builder->params.tf.info->tf) {
			case WESTON_TF_BT1886:
				builder->params.reference_white_luminance = 100.0;
				builder->params.min_luminance = 0.01;
				builder->params.max_luminance = 100.0;
				break;
			case WESTON_TF_ST2084_PQ:
				builder->params.reference_white_luminance = 203.0;
				builder->params.min_luminance = 0.005;
				builder->params.max_luminance = 10000.0;
				break;
			case WESTON_TF_HLG:
				builder->params.reference_white_luminance = 203.0;
				builder->params.min_luminance = 0.005;
				builder->params.max_luminance = 1000.0;
				break;
			default:
				break;
			}
		}
	} else {
		/* Primary luminance is set, but the CM&HDR protocol states that
		 * PQ TF should override max_lum with min_lum + 10000 cd/m². */
		if ((builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TF) &&
		    builder->params.tf.info->tf == WESTON_TF_ST2084_PQ)
			builder->params.max_luminance =
				builder->params.min_luminance + 10000.0;
	}

	/* CM&HDR protocol states that if target luminance is not set, the
	 * target min and max luminances should have the same values as the
	 * primary min and max luminances. */
	if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TARGET_LUMINANCE)) {
		builder->params.target_min_luminance = builder->params.min_luminance;
		builder->params.target_max_luminance = builder->params.max_luminance;
	}

	/* If maxCLL and maxFALL are not set, set them to negative. */
	if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXCLL))
		builder->params.maxCLL = -1.0f;
	if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXFALL))
		builder->params.maxFALL = -1.0f;
}

/**
 * Creates a color profile from a struct weston_color_profile_param_builder
 * object.
 *
 * After creating the weston_color_profile_param_builder and setting the
 * appropriate parameters, this function should be called to finally create the
 * color profile. It checks if the parameters are consistent and, if so, call
 * the color manager to create the color profile.
 *
 * Also, this is a destructor function. It destroys the builder object.
 *
 * \param builder The object that has the parameters set.
 * \param name_part A string to be used in describing the profile.
 * \param err Set if there's an error, untouched otherwise. The first error code caught.
 * \param err_msg Set if there's an error, untouched otherwise. Must be free()'d
 * by the caller. Combination of all error messages caught. Not terminated with
 * a new line character.
 * \return The color profile created, or NULL on failure.
 */
WL_EXPORT struct weston_color_profile *
weston_color_profile_param_builder_create_color_profile(struct weston_color_profile_param_builder *builder,
							const char *name_part,
							enum weston_color_profile_param_builder_error *err,
							char **err_msg)
{
	struct weston_color_manager *cm = builder->compositor->color_manager;
	struct weston_color_profile_params *params = &builder->params;
	struct weston_color_profile *cprof = NULL;
	bool ret;

	/*
	 * See struct weston_color_profile_params description. That struct has
	 * some rules that we need to fullfil (e.g. target primaries must be
	 * set, even if client does not pass anything). In this function we
	 * complete the param set in order to fullfil such rules.
	 */
	builder_complete_params(builder);

	/* Ensure that params make sense together. */
	builder_validate_params_set(builder);

	/* Ensure that each param set is reasonable. */
	builder_validate_params(builder);

	/* Something went wrong, so error out. */
	if (builder->has_errors) {
		fflush(builder->err_fp);
		*err_msg = strdup(builder->err_msg);
		*err = builder->err;
		goto out;
	}

	ret = cm->get_color_profile_from_params(cm, params, name_part,
						&cprof, err_msg);
	if (!ret)
		*err = WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_CREATE_FAILED;

out:
	weston_color_profile_param_builder_destroy(builder);
	return cprof;
}
