/*
 * Copyright (C) 2018 "IoT.bzh"
 * Author Jonathan Aillet <jonathan.aillet@iot.bzh>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define _GNU_SOURCE

#include <stdio.h>
#include <string.h>

#include <limits.h>
#include <math.h>

#include <wrap-json.h>

#include "4a-hal-controllers-value-handler.h"
#include "4a-hal-controllers-alsacore-link.h"

/*******************************************************************************
 *		Simple conversion value to/from percentage functions	       *
 ******************************************************************************/

int HalCtlsConvertValueToPercentage(double val, double min, double max)
{
	double range;

	range = max - min;
	if(range <= 0)
		return -INT_MAX;

	val -= min;

	return (int) round(val / range * 100);
}

int HalCtlsConvertPercentageToValue(int percentage, int min, int max)
{
	int range;

	range = max - min;
	if(range <= 0)
		return -INT_MAX;

	return (int) round((double) percentage * (double) range * 0.01 + (double) min);
}

/*******************************************************************************
 *		Convert json object from percentage to value	 	       *
 ******************************************************************************/

int HalCtlsConvertJsonValueForIntegerControl(AFB_ApiT apiHandle,
					     struct CtlHalAlsaCtlProperties *alsaCtlProperties,
					     json_object *toConvertJ,
					     json_object **ConvertedJ,
					     enum ConversionType requestedConversion)
{
	int initialValue, convertedValue;

	if(! json_object_is_type(toConvertJ, json_type_int)) {
		AFB_ApiError(apiHandle,
			     "Can't convert json value, unrecognized json format (must be an integer) : '%s'",
			     json_object_get_string(toConvertJ));
		return -1;
	}

	initialValue = json_object_get_int(toConvertJ);

	switch(requestedConversion) {
		case CONVERSION_NORMALIZED_TO_ALSACORE:
			if(initialValue < 0 || initialValue > 100) {
				AFB_ApiError(apiHandle,
					     "Cannot convert '%i' value, value should be between 0 and 100 ('%s')",
					     initialValue,
					     json_object_get_string(toConvertJ));
				return -2;
			}

			convertedValue = HalCtlsConvertPercentageToValue(initialValue,
									 alsaCtlProperties->minval,
									 alsaCtlProperties->maxval);

			if(convertedValue == -INT_MAX) {
				AFB_ApiError(apiHandle,
					     "Didn't succeed to convert %i (using min %i et max %i)",
					     initialValue,
					     alsaCtlProperties->minval,
					     alsaCtlProperties->maxval);
				return -3;
			}

			if(alsaCtlProperties->step) {
				// Round value to the nearest step
				convertedValue = (int) round((double) convertedValue / (double) alsaCtlProperties->step);
				convertedValue *= alsaCtlProperties->step;
			}
			break;

		case CONVERSION_ALSACORE_TO_NORMALIZED:
			convertedValue = HalCtlsConvertValueToPercentage(initialValue,
									 alsaCtlProperties->minval,
									 alsaCtlProperties->maxval);

			if(convertedValue == -INT_MAX) {
				AFB_ApiError(apiHandle,
					     "Didn't succeed to convert %i (using min %i et max %i)",
					     initialValue,
					     alsaCtlProperties->minval,
					     alsaCtlProperties->maxval);
				return -4;
			}

			break;

		default:
			AFB_ApiError(apiHandle,
				     "Can't convert '%i' value, unrecognized conversion type : '%i'",
				     initialValue,
				     (int) requestedConversion);
			*ConvertedJ = NULL;
			return -5;
	}

	*ConvertedJ = json_object_new_int(convertedValue);

	return 0;
}

int HalCtlsConvertJsonValueForBooleanControl(AFB_ApiT apiHandle,
					     struct CtlHalAlsaCtlProperties *alsaCtlProperties,
					     json_object *toConvertJ,
					     json_object **ConvertedJ,
					     enum ConversionType requestedConversion)
{
	int initialValue;

	switch(json_object_get_type(toConvertJ)) {
		case json_type_int:
			initialValue = json_object_get_int(toConvertJ);
			break;

		case json_type_boolean:
			initialValue = json_object_get_boolean(toConvertJ);
			break;

		default:
			AFB_ApiError(apiHandle,
				     "Can't convert json value, unrecognized format (must be an integer or a boolean) : '%s'",
				     json_object_get_string(toConvertJ));
			return -1;
	}

	if(initialValue < 0 || initialValue > 1) {
		AFB_ApiError(apiHandle,
			     "Cannot convert '%i' value, value should be 0 or 1 ('%s')",
			     initialValue,
			     json_object_get_string(toConvertJ));
		return -2;
	}

	switch(requestedConversion) {
		case CONVERSION_NORMALIZED_TO_ALSACORE:
			*ConvertedJ = json_object_new_int(initialValue);
			break;

		case CONVERSION_ALSACORE_TO_NORMALIZED:
			*ConvertedJ = json_object_new_boolean(initialValue);
			break;

		default:
			AFB_ApiError(apiHandle,
				     "Can't convert '%i' value, unrecognized conversion type : '%i'",
				     initialValue,
				     (int) requestedConversion);
			*ConvertedJ = NULL;
			return -3;
	}

	return 0;
}

int HalCtlsConvertJsonValues(AFB_ApiT apiHandle,
			     struct CtlHalAlsaCtlProperties *alsaCtlProperties,
			     json_object *toConvertJ,
			     json_object **ConvertedJ,
			     enum ConversionType requestedConversion)
{
	int conversionError = 0, idx, count;

	json_type toConvertType;
	json_object *toConvertObjectJ, *convertedValueJ, *convertedArrayJ;

	*ConvertedJ = NULL;

	toConvertType = json_object_get_type(toConvertJ);
	count = (toConvertType == json_type_array) ? (int) json_object_array_length(toConvertJ) : 1;

	convertedArrayJ = json_object_new_array();

	for(idx = 0; idx < count; idx++) {
		if(toConvertType == json_type_array)
			toConvertObjectJ = json_object_array_get_idx(toConvertJ, idx);
		else
			toConvertObjectJ = toConvertJ;

		switch(alsaCtlProperties->type) {
			case SND_CTL_ELEM_TYPE_INTEGER:
			case SND_CTL_ELEM_TYPE_INTEGER64:
				if((conversionError = HalCtlsConvertJsonValueForIntegerControl(apiHandle,
											       alsaCtlProperties,
											       toConvertObjectJ,
											       &convertedValueJ,
											       requestedConversion))) {
					AFB_ApiError(apiHandle,
						     "Error %i happened in when trying to convert index %i for integer control ('%s')",
						     conversionError,
						     idx,
						     json_object_get_string(toConvertObjectJ));
					json_object_put(convertedArrayJ);
					return -(idx + 1);
				}
				break;

			case SND_CTL_ELEM_TYPE_BOOLEAN:
				if((conversionError = HalCtlsConvertJsonValueForBooleanControl(apiHandle,
											       alsaCtlProperties,
											       toConvertObjectJ,
											       &convertedValueJ,
											       requestedConversion))) {
					AFB_ApiError(apiHandle,
						     "Error %i happened in when trying to convert index %i for boolean control ('%s')",
						     conversionError,
						     idx,
						     json_object_get_string(toConvertObjectJ));
					json_object_put(convertedArrayJ);
					return -(idx + 1);
				}
				break;

			default:
				AFB_ApiError(apiHandle,
					     "Conversion not handle for the alsa control type %i",
					     (int) alsaCtlProperties->type);
				json_object_put(convertedArrayJ);
				return -(idx + 1);
		}

		json_object_array_put_idx(convertedArrayJ, idx, convertedValueJ);
	}

	*ConvertedJ = convertedArrayJ;

	return 0;
}