/*
 * i965_vpp_avs.c - Adaptive Video Scaler (AVS) block
 *
 * Copyright (C) 2014 Intel Corporation
 *   Author: Gwenole Beauchesne <gwenole.beauchesne@intel.com>
 *
 * 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, sub license, 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 NON-INFRINGEMENT.
 * IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS 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 "sysdeps.h"
#include <math.h>
#include <va/va.h>
#include "i965_vpp_avs.h"

typedef void (*AVSGenCoeffsFunc)(float *coeffs, int num_coeffs, int phase,
								 int num_phases, float f);

/* Initializes all coefficients to zero */
static void
avs_init_coeffs(float *coeffs, int num_coeffs)
{
#if defined(__STDC_IEC_559__) && (__STDC_IEC_559__ > 0)
	memset(coeffs, 0, num_coeffs * sizeof(*coeffs));
#else
	int i;

	for (i = 0; i < num_coeffs; i++)
		coeffs[i] = 0.0f;
#endif
}

/* Computes the sinc(x) function */
static float
avs_sinc(float x)
{
	if (x == 0.0f)
		return 1.0f;
	return sin(x * M_PI) / (x * M_PI);
}

/* Convolution kernel for linear interpolation */
static float
avs_kernel_linear(float x)
{
	const float abs_x = fabsf(x);

	return abs_x < 1.0f ? 1 - abs_x : 0.0f;
}

/* Convolution kernel for Lanczos-based interpolation */
static float
avs_kernel_lanczos(float x, float a)
{
	const float abs_x = fabsf(x);

	return abs_x < a ? avs_sinc(x) * avs_sinc(x / a) : 0.0f;
}

/* Truncates floating-point value towards an epsilon factor */
static inline float
avs_trunc_coeff(float x, float epsilon)
{
	return rintf(x / epsilon) * epsilon;
}

/* Normalize coefficients for one sample/direction */
static void
avs_normalize_coeffs_1(float *coeffs, int num_coeffs, float epsilon)
{
	float s, sum = 0.0;
	int i, c, r, r1;

	for (i = 0; i < num_coeffs; i++)
		sum += coeffs[i];

	if (sum < epsilon)
		return;

	s = 0.0;
	for (i = 0; i < num_coeffs; i++)
		s += (coeffs[i] = avs_trunc_coeff(coeffs[i] / sum, epsilon));

	/* Distribute the remaining bits, while allocating more to the center */
	c = num_coeffs / 2;
	c = c - (coeffs[c - 1] > coeffs[c]);

	r = (1.0f - s) / epsilon;
	r1 = r / 4;
	if (coeffs[c + 1] == 0.0f)
		coeffs[c] += r * epsilon;
	else {
		coeffs[c] += (r - 2 * r1) * epsilon;
		coeffs[c - 1] += r1 * epsilon;
		coeffs[c + 1] += r1 * epsilon;
	}
}

/* Normalize all coefficients so that their sum yields 1.0f */
static void
avs_normalize_coeffs(AVSCoeffs *coeffs, const AVSConfig *config)
{
	avs_normalize_coeffs_1(coeffs->y_k_h, config->num_luma_coeffs,
						   config->coeff_epsilon);
	avs_normalize_coeffs_1(coeffs->y_k_v, config->num_luma_coeffs,
						   config->coeff_epsilon);
	avs_normalize_coeffs_1(coeffs->uv_k_h, config->num_chroma_coeffs,
						   config->coeff_epsilon);
	avs_normalize_coeffs_1(coeffs->uv_k_v, config->num_chroma_coeffs,
						   config->coeff_epsilon);
}

/* Validate coefficients for one sample/direction */
static bool
avs_validate_coeffs_1(float *coeffs, int num_coeffs, const float *min_coeffs,
					  const float *max_coeffs)
{
	int i;

	for (i = 0; i < num_coeffs; i++) {
		if (coeffs[i] < min_coeffs[i] || coeffs[i] > max_coeffs[i])
			return false;
	}
	return true;
}

/* Validate coefficients wrt. the supplied range in config */
static bool
avs_validate_coeffs(AVSCoeffs *coeffs, const AVSConfig *config)
{
	const AVSCoeffs * const min_coeffs = &config->coeff_range.lower_bound;
	const AVSCoeffs * const max_coeffs = &config->coeff_range.upper_bound;

	return avs_validate_coeffs_1(coeffs->y_k_h, config->num_luma_coeffs,
								 min_coeffs->y_k_h, max_coeffs->y_k_h) &&
		   avs_validate_coeffs_1(coeffs->y_k_v, config->num_luma_coeffs,
								 min_coeffs->y_k_v, max_coeffs->y_k_v) &&
		   avs_validate_coeffs_1(coeffs->uv_k_h, config->num_chroma_coeffs,
								 min_coeffs->uv_k_h, max_coeffs->uv_k_h) &&
		   avs_validate_coeffs_1(coeffs->uv_k_v, config->num_chroma_coeffs,
								 min_coeffs->uv_k_v, max_coeffs->uv_k_v);
}

/* Generate coefficients for default quality (bilinear) */
static void
avs_gen_coeffs_linear(float *coeffs, int num_coeffs, int phase, int num_phases,
					  float f)
{
	const int c = num_coeffs / 2 - 1;
	const float p = (float)phase / (num_phases * 2);

	avs_init_coeffs(coeffs, num_coeffs);
	coeffs[c] = avs_kernel_linear(p);
	coeffs[c + 1] = avs_kernel_linear(p - 1);
}

/* Generate coefficients for high quality (lanczos) */
static void
avs_gen_coeffs_lanczos(float *coeffs, int num_coeffs, int phase, int num_phases,
					   float f)
{
	const int c = num_coeffs / 2 - 1;
	const int l = num_coeffs > 4 ? 3 : 2;
	const float p = (float)phase / (num_phases * 2);
	int i;

	if (f > 1.0f)
		f = 1.0f;
	for (i = 0; i < num_coeffs; i++)
		coeffs[i] = avs_kernel_lanczos((i - (c + p)) * f, l);
}

/* Generate coefficients with the supplied scaler */
static bool
avs_gen_coeffs(AVSState *avs, float sx, float sy, AVSGenCoeffsFunc gen_coeffs)
{
	const AVSConfig * const config = avs->config;
	int i;

	for (i = 0; i <= config->num_phases; i++) {
		AVSCoeffs * const coeffs = &avs->coeffs[i];

		gen_coeffs(coeffs->y_k_h, config->num_luma_coeffs,
				   i, config->num_phases, sx);
		gen_coeffs(coeffs->uv_k_h, config->num_chroma_coeffs,
				   i, config->num_phases, sx);
		gen_coeffs(coeffs->y_k_v, config->num_luma_coeffs,
				   i, config->num_phases, sy);
		gen_coeffs(coeffs->uv_k_v, config->num_chroma_coeffs,
				   i, config->num_phases, sy);

		avs_normalize_coeffs(coeffs, config);
		if (!avs_validate_coeffs(coeffs, config))
			return false;
	}
	return true;
}

/* Initializes AVS state with the supplied configuration */
void
avs_init_state(AVSState *avs, const AVSConfig *config)
{
	avs->config = config;
	avs->flags = 0;
	avs->scale_x = 0.0f;
	avs->scale_y = 0.0f;
}

/* Checks whether the AVS scaling parameters changed */
static inline bool
avs_params_changed(AVSState *avs, float sx, float sy, uint32_t flags)
{
	if (avs->flags != flags)
		return true;

	if (flags >= VA_FILTER_SCALING_HQ) {
		if (avs->scale_x != sx || avs->scale_y != sy)
			return true;
	} else {
		if (avs->scale_x == 0.0f || avs->scale_y == 0.0f)
			return true;
	}
	return false;
}

/* Updates AVS coefficients for the supplied factors and quality level */
bool
avs_update_coefficients(AVSState *avs, float sx, float sy, uint32_t flags)
{
	AVSGenCoeffsFunc gen_coeffs;

	flags &= VA_FILTER_SCALING_MASK;
	if (!avs_params_changed(avs, sx, sy, flags))
		return true;

	switch (flags) {
	case VA_FILTER_SCALING_HQ:
		gen_coeffs = avs_gen_coeffs_lanczos;
		break;
	default:
		gen_coeffs = avs_gen_coeffs_linear;
		break;
	}
	if (!avs_gen_coeffs(avs, sx, sy, gen_coeffs)) {
		assert(0 && "invalid set of coefficients generated");
		return false;
	}

	avs->flags = flags;
	avs->scale_x = sx;
	avs->scale_y = sy;
	return true;
}
