/**
 * @file
 * Analogy for Linux, range related features
 *
 * @note Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
 * @note Copyright (C) 2008 Alexis Berlemont <alexis.berlemont@free.fr>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */

#include <errno.h>
#include <math.h>

#include <analogy/analogy.h>

#ifndef DOXYGEN_CPP

lsampl_t data32_get(void *src)
{
	return (lsampl_t) * ((lsampl_t *) (src));
}

lsampl_t data16_get(void *src)
{
	return (lsampl_t) * ((sampl_t *) (src));
}

lsampl_t data8_get(void *src)
{
	return (lsampl_t) * ((unsigned char *)(src));
}

void data32_set(void *dst, lsampl_t val)
{
	*((lsampl_t *) (dst)) = val;
}

void data16_set(void *dst, lsampl_t val)
{
	*((sampl_t *) (dst)) = (sampl_t) (0xffff & val);
}

void data8_set(void *dst, lsampl_t val)
{
	*((unsigned char *)(dst)) = (unsigned char)(0xff & val);
}

#endif /* !DOXYGEN_CPP */

/*!
 * @ingroup level2_lib
 * @defgroup rng2_lib Range / conversion API
 * @{
 */

/**
 * @brief Get the size in memory of an acquired element
 *
 * According to the board, the channels have various acquisition
 * widths. With values like 8, 16 or 32, there is no problem finding
 * out the size in memory (1, 2, 4); however with widths like 12 or
 * 24, this function might be helpful to guess the size needed in RAM
 * for a single acquired element.
 *
 * @param[in] chan Channel descriptor
 *
 * @return the size in memory of an acquired element, otherwise a negative
 * error code:
 *
 * - -EINVAL is returned if the argument chan is NULL
 *
 */
int a4l_sizeof_chan(a4l_chinfo_t * chan)
{
	/* So far, it seems there is no 64 bit acquistion stuff */
	int i = 0, sizes[3] = {8, 16, 32};

	if (chan == NULL)
		return -EINVAL;

	while (i < 3 && sizes[i] < chan->nb_bits)
		i++;

	return (i == 3) ? -EINVAL : sizes[i] / 8;
}

/**
 * @brief Get the size in memory of a digital acquired element
 *
 * This function is only useful for DIO subdevices. Digital subdevices
 * are a specific kind of subdevice on which channels are regarded as
 * bits composing the subdevice's bitfield. During a DIO acquisition,
 * all bits are sampled. Therefore, a4l_sizeof_chan() is useless in
 * this case and we have to use a4l_sizeof_subd().
 * With bitfields which sizes are 8, 16 or 32, there is no problem
 * finding out the size in memory (1, 2, 4); however with widths like
 * 12 or 24, this function might be helpful to guess the size needed
 * in RAM for a single acquired element.
 *
 * @param[in] subd Subdevice descriptor
 *
 * @return the size in memory of an acquired element, otherwise a negative
 * error code:
 *
 * - -EINVAL is returned if the argument chan is NULL or if the
 *    subdevice is not a digital subdevice
 *
 */
int a4l_sizeof_subd(a4l_sbinfo_t *subd)
{
	/* So far, it seems there is no 64 bit acquistion stuff */
	int i = 0, sizes[3] = {8, 16, 32};

	if (subd == NULL)
		return -EINVAL;

	/* This function is only useful for DIO subdevice (all
	   channels are acquired in one shot); for other kind of
	   subdevice, the user must use a4l_sizeof_chan() so as to
	   find out the size of the channel he wants to use */
	if ((subd->flags & A4L_SUBD_TYPES) != A4L_SUBD_DIO && 
	    (subd->flags & A4L_SUBD_TYPES) != A4L_SUBD_DI &&
	    (subd->flags & A4L_SUBD_TYPES) != A4L_SUBD_DO)
		return -EINVAL;

	while (i < 3 && sizes[i] < subd->nb_chan)
		i++;

	return (i == 3) ? -EINVAL : sizes[i] / 8;
}

/**
 * @brief Find the must suitable range
 *
 * @param[in] dsc Device descriptor filled by a4l_open() and
 * a4l_fill_desc()
 *
 * @param[in] idx_subd Index of the concerned subdevice
 * @param[in] idx_chan Index of the concerned channel
 * @param[in] unit Unit type used in the range
 * @param[in] min Minimal limit value
 * @param[in] max Maximal limit value
 * @param[out] rng Found range
 *
 * @return 0 on success. Otherwise:
 *
 * - -EINVAL is returned if some argument is missing or wrong;
 *    idx_subd, idx_chan and the dsc pointer should be checked; check
 *    also the kernel log ("dmesg"); WARNING: a4l_fill_desc() should
 *    be called before using a4l_find_range()
 *
 */
int a4l_find_range(a4l_desc_t * dsc,
		   unsigned int idx_subd,
		   unsigned int idx_chan,
		   unsigned long unit,
		   double min, double max, a4l_rnginfo_t ** rng)
{
	int i, ret;
	long lmin, lmax;
	a4l_chinfo_t *chinfo;
	a4l_rnginfo_t *rnginfo;

	/* Basic checkings */
	if (dsc == NULL || rng == NULL)
		return -EINVAL;

	/* a4l_fill_desc() must have been called on this descriptor */
	if (dsc->magic != MAGIC_CPLX_DESC)
		return -EINVAL;

	/* Retrieves the ranges count */
	ret = a4l_get_chinfo(dsc, idx_subd, idx_chan, &chinfo);
	if (ret < 0)
		goto out_get_range;

	/* Initializes variables */
	lmin = (long)(min * A4L_RNG_FACTOR);
	lmax = (long)(max * A4L_RNG_FACTOR);
	*rng = NULL;

	/* Performs the research */
	for (i = 0; i < chinfo->nb_rng; i++) {

		ret = a4l_get_rnginfo(dsc, idx_subd, idx_chan, i, &rnginfo);
		if (ret < 0)
			goto out_get_range;

		if (A4L_RNG_UNIT(rnginfo->flags) == unit &&
		    rnginfo->min <= lmin && rnginfo->max >= lmax) {

			if (*rng != NULL) {
				if (rnginfo->min >= (*rng)->min &&
				    rnginfo->max <= (*rng)->max)
					*rng = rnginfo;
			} else
				*rng = rnginfo;
		}
	}

out_get_range:

	if (ret < 0)
		*rng = NULL;

	return ret;
}

/**
 * @brief Unpack raw data (from the driver) into unsigned long values
 *
 * This function takes as input driver-specific data and scatters each
 * element into an entry of an unsigned long table. It is a
 * convenience routine which performs no conversion, just copy.
 *
 * @param[in] chan Channel descriptor
 * @param[in] rng Range descriptor
 * @param[out] dst Ouput buffer 
 * @param[in] src Input buffer
 * @param[in] cnt Count of transfer to copy
 *
 * @return the count of copy performed, otherwise a negative error
 * code:
 *
 * - -EINVAL is returned if some argument is missing or wrong;
 *    chan, rng and the pointers should be checked; check also the
 *    kernel log ("dmesg"); WARNING: a4l_fill_desc() should be called
 *    before using a4l_ultoraw()
 *
 */
int a4l_rawtoul(a4l_chinfo_t * chan, unsigned long *dst, void *src, int cnt)
{
	int size, i = 0, j = 0;

	/* Temporary data accessor */
	lsampl_t(*datax_get) (void *);

	/* Basic checking */
	if (chan == NULL)
		return -EINVAL;

	/* Find out the size in memory */ 
	size = a4l_sizeof_chan(chan);

	/* Get the suitable accessor */
	switch (size) {
	case 4:
		datax_get = data32_get;
		break;
	case 2:
		datax_get = data16_get;
		break;
	case 1:
		datax_get = data8_get;
		break;
	default:
		return -EINVAL;
	};

	while (j < cnt) {

		/* Properly copy the data */
		dst[j] = (unsigned long)datax_get(src + i);

		/* Update the counters */
		i += size;
		j++;
	}

	return j;	
}

/**
 * @brief Convert raw data (from the driver) to float-typed samples
 *
 * @param[in] chan Channel descriptor
 * @param[in] rng Range descriptor
 * @param[out] dst Ouput buffer 
 * @param[in] src Input buffer
 * @param[in] cnt Count of conversion to perform
 *
 * @return the count of conversion performed, otherwise a negative
 * error code:
 *
 * - -EINVAL is returned if some argument is missing or wrong;
 *    chan, rng and the pointers should be checked; check also the
 *    kernel log ("dmesg"); WARNING: a4l_fill_desc() should be called
 *    before using a4l_rawtod()
 *
 */
int a4l_rawtof(a4l_chinfo_t * chan,
	       a4l_rnginfo_t * rng, float *dst, void *src, int cnt)
{
	int size, i = 0, j = 0;
	lsampl_t tmp;

	/* Temporary values used for conversion
	   (phys = a * src + b) */
	float a, b;
	/* Temporary data accessor */
	lsampl_t(*datax_get) (void *);

	/* Basic checking */
	if (rng == NULL || chan == NULL)
		return -EINVAL;

	/* Find out the size in memory */ 
	size = a4l_sizeof_chan(chan);

	/* Get the suitable accessor */
	switch (a4l_sizeof_chan(chan)) {
	case 4:
		datax_get = data32_get;
		break;
	case 2:
		datax_get = data16_get;
		break;
	case 1:
		datax_get = data8_get;
		break;
	default:
		return -EINVAL;
	};

	/* Compute the translation factor and the constant only once */
	a = ((float)(rng->max - rng->min)) /
		(((1ULL << chan->nb_bits) - 1) * A4L_RNG_FACTOR);
	b = ((float)rng->min) / A4L_RNG_FACTOR;

	while (j < cnt) {

		/* Properly retrieve the data */
		tmp = datax_get(src + i);

		/* Perform the conversion */
		dst[j] = a * tmp + b;

		/* Update the counters */
		i += size;
		j++;
	}

	return j;
}

/**
 * @brief Convert raw data (from the driver) to double-typed samples
 *
 * @param[in] chan Channel descriptor
 * @param[in] rng Range descriptor
 * @param[out] dst Ouput buffer 
 * @param[in] src Input buffer
 * @param[in] cnt Count of conversion to perform
 *
 * @return the count of conversion performed, otherwise a negative
 * error code:
 *
 * - -EINVAL is returned if some argument is missing or wrong;
 *    chan, rng and the pointers should be checked; check also the
 *    kernel log ("dmesg"); WARNING: a4l_fill_desc() should be called
 *    before using a4l_rawtod()
 *
 */
int a4l_rawtod(a4l_chinfo_t * chan,
	       a4l_rnginfo_t * rng, double *dst, void *src, int cnt)
{
	int size, i = 0, j = 0;
	lsampl_t tmp;

	/* Temporary values used for conversion
	   (phys = a * src + b) */
	double a, b;
	/* Temporary data accessor */
	lsampl_t(*datax_get) (void *);

	/* Basic checking */
	if (rng == NULL || chan == NULL)
		return -EINVAL;

	/* Find out the size in memory */ 
	size = a4l_sizeof_chan(chan);

	/* Get the suitable accessor */
	switch (a4l_sizeof_chan(chan)) {
	case 4:
		datax_get = data32_get;
		break;
	case 2:
		datax_get = data16_get;
		break;
	case 1:
		datax_get = data8_get;
		break;
	default:
		return -EINVAL;
	};

	/* Computes the translation factor and the constant only once */
	a = ((double)(rng->max - rng->min)) /
		(((1ULL << chan->nb_bits) - 1) * A4L_RNG_FACTOR);
	b = ((double)rng->min) / A4L_RNG_FACTOR;

	while (j < cnt) {

		/* Properly retrieve the data */
		tmp = datax_get(src + i);

		/* Perform the conversion */
		dst[j] = a * tmp + b;

		/* Update the counters */
		i += size;
		j++;
	}

	return j;
}

/**
 * @brief Pack unsigned long values into raw data (for the driver)
 *
 * This function takes as input a table of unsigned long values and
 * gather them according to the channel width. It is a convenience
 * routine which performs no conversion, just formatting.
 *
 * @param[in] chan Channel descriptor
 * @param[in] rng Range descriptor
 * @param[out] dst Ouput buffer 
 * @param[in] src Input buffer
 * @param[in] cnt Count of transfer to copy
 *
 * @return the count of copy performed, otherwise a negative error
 * code:
 *
 * - -EINVAL is returned if some argument is missing or wrong;
 *    chan, rng and the pointers should be checked; check also the
 *    kernel log ("dmesg"); WARNING: a4l_fill_desc() should be called
 *    before using a4l_ultoraw()
 *
 */
int a4l_ultoraw(a4l_chinfo_t * chan, void *dst, unsigned long *src, int cnt)
{
	int size, i = 0, j = 0;

	/* Temporary data accessor */
	void (*datax_set) (void *, lsampl_t);

	/* Basic checking */
	if (chan == NULL)
		return -EINVAL;

	/* Find out the size in memory */
	size = a4l_sizeof_chan(chan);

	/* Select the suitable accessor */
	switch (size) {
	case 4:
		datax_set = data32_set;
		break;
	case 2:
		datax_set = data16_set;
		break;
	case 1:
		datax_set = data8_set;
		break;
	default:
		return -EINVAL;
	};

	while (j < cnt) {

		/* Perform the copy */
		datax_set(dst + i, (lsampl_t)src[j]);

		/* Update the counters */
		i += size;
		j++;
	}

	return j;	
}

/**
 * @brief Convert float-typed samples to raw data (for the driver)
 *
 * @param[in] chan Channel descriptor
 * @param[in] rng Range descriptor
 * @param[out] dst Ouput buffer 
 * @param[in] src Input buffer
 * @param[in] cnt Count of conversion to perform
 *
 * @return the count of conversion performed, otherwise a negative
 * error code:
 *
 * - -EINVAL is returned if some argument is missing or wrong;
 *    chan, rng and the pointers should be checked; check also the
 *    kernel log ("dmesg"); WARNING: a4l_fill_desc() should be called
 *    before using a4l_ftoraw()
 *
 */
int a4l_ftoraw(a4l_chinfo_t * chan,
	       a4l_rnginfo_t * rng, void *dst, float *src, int cnt)
{
	int size, i = 0, j = 0;

	/* Temporary values used for conversion
	   (dst = a * phys - b) */
	float a, b;
	/* Temporary data accessor */
	void (*datax_set) (void *, lsampl_t);

	/* Basic checking */
	if (rng == NULL || chan == NULL)
		return -EINVAL;

	/* Find out the size in memory */
	size = a4l_sizeof_chan(chan);

	/* Select the suitable accessor */
	switch (size) {
	case 4:
		datax_set = data32_set;
		break;
	case 2:
		datax_set = data16_set;
		break;
	case 1:
		datax_set = data8_set;
		break;
	default:
		return -EINVAL;
	};

	/* Computes the translation factor and the constant only once */
	a = (((float)A4L_RNG_FACTOR) / (rng->max - rng->min)) *
		((1ULL << chan->nb_bits) - 1);
	b = ((float)(rng->min) / (rng->max - rng->min)) *
		((1ULL << chan->nb_bits) - 1);

	while (j < cnt) {

		/* Performs the conversion */
		datax_set(dst + i, (lsampl_t) (a * src[j] - b));

		/* Updates the counters */
		i += size;
		j++;
	}

	return j;
}

/**
 * @brief Convert double-typed samples to raw data (for the driver)
 *
 * @param[in] chan Channel descriptor
 * @param[in] rng Range descriptor
 * @param[out] dst Ouput buffer 
 * @param[in] src Input buffer
 * @param[in] cnt Count of conversion to perform
 *
 * @return the count of conversion performed, otherwise a negative
 * error code:
 *
 * - -EINVAL is returned if some argument is missing or wrong;
 *    chan, rng and the pointers should be checked; check also the
 *    kernel log ("dmesg"); WARNING: a4l_fill_desc() should be called
 *    before using a4l_dtoraw()
 *
 */
int a4l_dtoraw(a4l_chinfo_t * chan,
	       a4l_rnginfo_t * rng, void *dst, double *src, int cnt)
{
	int size, i = 0, j = 0;

	/* Temporary values used for conversion
	   (dst = a * phys - b) */
	double a, b;
	/* Temporary data accessor */
	void (*datax_set) (void *, lsampl_t);

	/* Basic checking */
	if (rng == NULL || chan == NULL)
		return -EINVAL;

	/* Find out the size in memory */
	size = a4l_sizeof_chan(chan);

	/* Select the suitable accessor */
	switch (size) {
	case 4:
		datax_set = data32_set;
		break;
	case 2:
		datax_set = data16_set;
		break;
	case 1:
		datax_set = data8_set;
		break;
	default:
		return -EINVAL;
	};

	/* Computes the translation factor and the constant only once */
	a = (((double)A4L_RNG_FACTOR) / (rng->max - rng->min)) *
		((1ULL << chan->nb_bits) - 1);
	b = ((double)(rng->min) / (rng->max - rng->min)) *
		((1ULL << chan->nb_bits) - 1);

	while (j < cnt) {

		/* Performs the conversion */
		datax_set(dst + i, (lsampl_t) (a * src[j] - b));

		/* Updates the counters */
		i += size;
		j++;
	}

	return j;
}
/** @} Range / conversion  API */
