/*
 * Copyright (C) Bull S.A. 2001
 * Copyright (c) International Business Machines  Corp., 2001
 *
 *   This program is free software;  you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program;  if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/******************************************************************************/
/*                                                                            */
/* Dec-03-2001  Created: Jacky Malcles & Jean Noel Cordenner                  */
/*              These tests are adapted from AIX float PVT tests.             */
/*                                                                            */
/******************************************************************************/
#include "tfloat.h"

#define SAFE_FREE(p) { if (p) { free(p); (p)=NULL; } }
/*
 * allocates a buffer and read a file to it
 * input parameters:
 *	fname: name of the file to read
 *	data:  pointer where buffer addr. will be returned
 *
 * uses also external variable datadir to build file pathname
 *
 * returns:
 *	0 in case of failure
 *	# of bytes read elsewhere
 */
static size_t read_file(char *fname, void **data)
{
	struct stat bufstat;
	char path[PATH_MAX];
	size_t fsize;
	void *buffer;
	int fd;
	int maxretries = 1;

	(void)sprintf(path, "%s/%s", datadir, fname);

	errno = 0;

	while (stat(path, &bufstat)) {
		if (errno == ETIMEDOUT || errno == EINTR || errno == 0) {
			printf("Error stat'ing %s: %s\n",
			       path, strerror(errno));
			pthread_testcancel();
			/* retrying... */
			if (maxretries--)
				continue;
		}
		return (size_t) 0;
	}

	fsize = bufstat.st_size;
	if (!fsize) {
		errno = ENOENT;
		return (size_t) 0;
	}

	while ((buffer = malloc(fsize)) == NULL) {
		if (errno == EINTR || errno == 0) {
			printf("Error malloc'ing: %s\n", strerror(errno));
			pthread_testcancel();
			/* retrying... */
			if (maxretries--)
				continue;
		}
		return (size_t) 0;
	}

	while ((fd = open(path, O_RDONLY)) < 0) {
		if (errno == ETIMEDOUT || errno == EINTR || errno == 0) {
			printf("Error opening %s: %s\n", path, strerror(errno));
			pthread_testcancel();
			/* retrying... */
			if (maxretries--)
				continue;
		}
		SAFE_FREE(buffer);
		return (size_t) 0;
	}

	while (read(fd, buffer, fsize) != fsize) {
		if (errno == ETIMEDOUT || errno == EINTR || errno == 0) {
			printf("Error reading %s: %s\n", path, strerror(errno));
			pthread_testcancel();
			/* retrying... */
			if (lseek(fd, (off_t) 0, SEEK_SET) == (off_t) 0) {
				if (maxretries--)
					continue;
			}
		}
		(void)close(fd);
		SAFE_FREE(buffer);
		return (size_t) 0;
	}

	(void)close(fd);
	*data = buffer;
	return fsize;
}

/* this subroutine is used in compute_xxx functions to check results
   and record errors if appropriate */
static void check_error(TH_DATA * th_data, double e, double r, int index)
{
	double x;
	int pe, pr, px;
	static const char errtmplt[] =
	    "%s failed at index %d: OLD: %2.18e NEW: %2.18e DIFF: %2.18e\n";

	x = fabs(r - e);	/* diff expected/computed */

	if (x > EPS) {		/* error ? */
		/* compute exponent parts */
		(void)frexp(r, &pr);	/* for computed */
		(void)frexp(x, &px);	/* for difference */
		(void)frexp(e, &pe);	/* for dexected */

		if (abs(pe - px) < th_data->th_func.precision ||
		    abs(pr - px) < th_data->th_func.precision) {
			/* not a rounding error */
			++th_data->th_nerror;
			/* record first error only ! */
			if (th_data->th_result == 0) {
				sprintf(th_data->detail_data,
					errtmplt,
					th_data->th_func.fident,
					index, e, r, x);
				th_data->th_result = 1;
			}
		}
	}
}

/*
 * these functions handle the various cases of computation
 * they are called by pthread_code
 */

/* normal case: compares f(input data) to expected data */
static void compute_normal(TH_DATA * th_data, double *din, double *dex,
			   int index)
{
	double d, r, e;

	d = din[index];
	e = dex[index];
	r = (*(th_data->th_func.funct)) (d);

	check_error(th_data, e, r, index);
}

/* atan2 and hypot case: compares f(sin(input data),cos(input data))
   to expected data */
static void compute_atan2_hypot(TH_DATA * th_data, double *din, double *dex,
				int index)
{
	double d, r, e;

	d = din[index];
	e = dex[index];
	r = (*(th_data->th_func.funct)) (sin(d), cos(d));

	check_error(th_data, e, r, index);
}

/* modf case: compares integral and fractional parts to expected datas */
static void compute_modf(TH_DATA * th_data, double *din, double *dex,
			 double *dex2, int index)
{
	static const char errtmplt1[] =
	    "%s failed at index %d: OLD integral part: %f NEW: %f\n";
	double d, r, e;
	double tmp;

	d = din[index];
	e = dex[index];
	r = (*(th_data->th_func.funct)) (d, &tmp);

	if (tmp != dex2[index]) {	/* bad integral part! */
		++th_data->th_nerror;
		/* record first error only ! */
		if (th_data->th_result == 0) {
			sprintf(th_data->detail_data,
				errtmplt1,
				th_data->th_func.fident,
				index, dex2[index], tmp);
			th_data->th_result = 1;
		}
		return;
	}

	check_error(th_data, e, r, index);
}

/* fmod and pow case: compares f(input data, input data2) to expected data */
static void compute_fmod_pow(TH_DATA * th_data, double *din, double *dex,
			     double *dex2, int index)
{
	double d, r, e;

	d = din[index];
	e = dex[index];
	r = (*(th_data->th_func.funct)) (d, dex2[index]);

	check_error(th_data, e, r, index);
}

/* frexp case: compares mantissa and exponent to expected datas */
/* lgamma case: compares result and signgam to expected datas */
static void compute_frexp_lgamma(TH_DATA * th_data, double *din, double *dex,
				 int *dex2, int index)
{
	static const char errtmplt2[] =
	    "%s failed at index %d: OLD (exp. or sign): %d NEW: %d\n";
	double d, r, e;
	int tmp;
	static const char xinf[8] = "lgamma";

	d = din[index];
	e = dex[index];
	r = (*(th_data->th_func.funct)) (d, &tmp);

	if (strcmp(th_data->th_func.fident, xinf) != 0) {
		if (tmp != dex2[index]) {	/* bad exponent! */
			++th_data->th_nerror;
			/* record first error only ! */
			if (th_data->th_result == 0) {
				sprintf(th_data->detail_data,
					errtmplt2,
					th_data->th_func.fident,
					index, dex2[index], tmp);
				th_data->th_result = 1;
			}
			return;
		}
	}

	check_error(th_data, e, r, index);
}

/* ldexp case: compares f(input data, input data2) to expected data */
static void compute_ldexp(TH_DATA * th_data, double *din, double *dex,
			  int *din2, int index)
{
	double d, r, e;

	d = din[index];
	e = dex[index];
	r = (*(th_data->th_func.funct)) (d, din2[index]);

	check_error(th_data, e, r, index);
}

/*
 * Function which does the job, to be called as the
 * "start routine" parameter of pthread_create subroutine.
 * Uses the compute_xxx subroutines above.
 *
 * input parameters ("arg" parameter of pthread_create subroutine):
 *	pointer to a TH_DATA structure.
 *
 */
void *thread_code(void *arg)
{
	TH_DATA *th_data = (TH_DATA *) arg;
	size_t fsize, fsize2, fsize3;
	double *din, *dex, *dex2 = NULL;
	int imax, index;

	fsize = read_file(th_data->th_func.din_fname, (void **)&din);
	if (fsize == (size_t) 0) {
		sprintf(th_data->detail_data,
			"FAIL: %s: reading %s, %s\n",
			th_data->th_func.fident,
			th_data->th_func.din_fname, strerror(errno));
		th_data->th_result = 1;
		SAFE_FREE(din);
		pthread_exit((void *)1);
	}
	fsize2 = read_file(th_data->th_func.dex_fname, (void **)&dex);
	if (fsize2 == (size_t) 0) {
		sprintf(th_data->detail_data,
			"FAIL: %s: reading %s, %s\n",
			th_data->th_func.fident,
			th_data->th_func.dex_fname, strerror(errno));
		th_data->th_result = 1;
		SAFE_FREE(din);
		SAFE_FREE(dex);
		pthread_exit((void *)1);
	}

	fsize3 = (size_t) 0;
	switch (th_data->th_func.code_funct) {
	case FUNC_MODF:
	case FUNC_FMOD:
	case FUNC_POW:
	case FUNC_FREXP:
	case FUNC_LDEXP:
	case FUNC_GAM:
		fsize3 = read_file(th_data->th_func.dex2_fname, (void **)&dex2);
		if (fsize3 == (size_t) 0) {
			sprintf(th_data->detail_data,
				"FAIL: %s: reading %s, %s\n",
				th_data->th_func.fident,
				th_data->th_func.dex2_fname, strerror(errno));
			th_data->th_result = 1;
			SAFE_FREE(din);
			SAFE_FREE(dex);
			pthread_exit((void *)1);
		}
	}

	switch (th_data->th_func.code_funct) {
	case FUNC_NORMAL:
	case FUNC_ATAN2:
	case FUNC_HYPOT:
		if (fsize2 != fsize)
			goto file_size_error;
		break;
	case FUNC_MODF:
	case FUNC_FMOD:
	case FUNC_POW:
		if (fsize2 != fsize || fsize3 != fsize)
			goto file_size_error;
		break;
	case FUNC_FREXP:
	case FUNC_LDEXP:
	case FUNC_GAM:
		if (fsize2 != fsize ||
		    (sizeof(double) / sizeof(int)) * fsize3 != fsize)
			goto file_size_error;
		break;
	default:
file_size_error:
		sprintf(th_data->detail_data,
			"FAIL: %s: file sizes don't match\n",
			th_data->th_func.fident);
		th_data->th_result = 1;
		SAFE_FREE(din);
		SAFE_FREE(dex);
		if (fsize3)
			SAFE_FREE(dex2);
		pthread_exit((void *)1);
	}

	imax = fsize / sizeof(double);

	while (th_data->th_nloop <= num_loops) {
		/* loop stopped by pthread_cancel */

		for (index = th_data->th_num; index < imax; index += num_threads) {	/* computation loop */
			switch (th_data->th_func.code_funct) {
			case FUNC_NORMAL:
				compute_normal(th_data, din, dex, index);
				break;
			case FUNC_ATAN2:
			case FUNC_HYPOT:
				compute_atan2_hypot(th_data, din, dex, index);
				break;
			case FUNC_MODF:
				compute_modf(th_data, din, dex, dex2, index);
				break;
			case FUNC_FMOD:
			case FUNC_POW:
				compute_fmod_pow(th_data,
						 din, dex, dex2, index);
				break;
			case FUNC_FREXP:
			case FUNC_GAM:
				compute_frexp_lgamma(th_data,
						     din, dex, (int *)dex2,
						     index);
				break;
			case FUNC_LDEXP:
				compute_ldexp(th_data,
					      din, dex, (int *)dex2, index);
				break;
			default:
				sprintf(th_data->detail_data,
					"FAIL: %s: unexpected function type\n",
					th_data->th_func.fident);
				th_data->th_result = 1;
				SAFE_FREE(din);
				SAFE_FREE(dex);
				if (fsize3)
					SAFE_FREE(dex2);
				pthread_exit((void *)1);
			}
			pthread_testcancel();
		}		/* end of computation loop */
		++th_data->th_nloop;
	}			/* end of loop */
	SAFE_FREE(din);
	SAFE_FREE(dex);
	if (fsize3)
		SAFE_FREE(dex2);
	pthread_exit(NULL);
}
