/*
 * 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 <afb/afb-binding.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_api_t 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_API_ERROR(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_API_ERROR(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_API_ERROR(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_API_ERROR(apiHandle,
					      "Didn't succeed to convert %i (using min %i et max %i)",
					      initialValue,
					      alsaCtlProperties->minval,
					      alsaCtlProperties->maxval);
				return -4;
			}

			break;

		default:
			AFB_API_ERROR(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_api_t 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_API_ERROR(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_API_ERROR(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_API_ERROR(apiHandle,
				      "Can't convert '%i' value, unrecognized conversion type : '%i'",
				      initialValue,
				      (int) requestedConversion);
			*ConvertedJ = NULL;
			return -3;
	}

	return 0;
}

int HalCtlsConvertJsonValues(afb_api_t 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_API_ERROR(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_API_ERROR(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_API_ERROR(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;
}

int HalCtlsChangePreviousValuesUsingJson(afb_api_t apiHandle,
					 struct CtlHalAlsaCtlProperties *alsaCtlProperties,
					 json_object *requestedPercentageVariationJ,
					 json_object *previousControlValuesJ,
					 json_object **ChangedJ)
{
	int requestedPercentageVariation, requestedeVariation, toChangeValue, changedValue, idx, count;

	char *requestedPercentageVariationString, *conversionEnd;

	json_object *toChangeObjectJ, *changedArrayJ;

	*ChangedJ = NULL;

	requestedPercentageVariationString = (char *) json_object_get_string(requestedPercentageVariationJ);

	requestedPercentageVariation = (int) strtol(requestedPercentageVariationString, &conversionEnd, 10);
	if(conversionEnd == requestedPercentageVariationString) {
		AFB_API_ERROR(apiHandle,
			      "Tried to increase/decrease an integer control \
			      but string sent in json is not a increase/decrease string : '%s'",
			      json_object_get_string(requestedPercentageVariationJ));
		return -1;
	}

	if(alsaCtlProperties->type != SND_CTL_ELEM_TYPE_INTEGER &&
	   alsaCtlProperties->type != SND_CTL_ELEM_TYPE_INTEGER64) {
		AFB_API_ERROR(apiHandle,
			      "Tried to increase/decrease values on a incompatible \
			      control type (%i), control type must be an integer",
			      alsaCtlProperties->type);
		return -2;
	}

	if(requestedPercentageVariation < -100 || requestedPercentageVariation > 100) {
		AFB_API_ERROR(apiHandle,
			      "Tried to increase/decrease values but specified change is \
			      not a valid percentage, it should be between -100 and 100");
		return -3;
	}

	requestedeVariation = HalCtlsConvertPercentageToValue((int) abs(requestedPercentageVariation),
							      alsaCtlProperties->minval,
							      alsaCtlProperties->maxval);

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

	if(requestedPercentageVariation < 0)
		requestedeVariation = -requestedeVariation;

	count = (int) json_object_array_length(previousControlValuesJ);

	changedArrayJ = json_object_new_array();

	for(idx = 0; idx < count; idx++) {
		toChangeObjectJ = json_object_array_get_idx(previousControlValuesJ, idx);

		if(! json_object_is_type(toChangeObjectJ, json_type_int)) {
			AFB_API_ERROR(apiHandle,
				      "Current json object %s is not an integer",
				      json_object_get_string(toChangeObjectJ));
			return -(10 + idx);
		}

		toChangeValue = json_object_get_int(toChangeObjectJ);

		if((toChangeValue + requestedeVariation) < alsaCtlProperties->minval)
			changedValue = alsaCtlProperties->minval;
		else if((toChangeValue + requestedeVariation) > alsaCtlProperties->maxval)
			changedValue = alsaCtlProperties->maxval;
		else
			changedValue = toChangeValue + requestedeVariation;

		json_object_array_put_idx(changedArrayJ, idx, json_object_new_int(changedValue));
	}

	*ChangedJ = changedArrayJ;

	return 0;
}