/* Copyright 2016 Samsung Electronics Co., Ltd.
 *
 * 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.
 */

static int scl_gnuc_builtin_overflow = 0;

#if __GNUC__ >= 5
#	define SCL_GNUC_BUILTIN_OVERFLOW scl_gnuc_builtin_overflow
#endif

#include "scl/arithmetic.h"

static int/*bool*/ verbose;

#if !defined(START_FUNC)
#	include <stdio.h>
#	if SCL_USE_LONG_LONG
#		define PIM(v) (verbose ? printf("%lld\n", (long long)(v)) : 0)
#	else
#		define PIM(v) (verbose ? printf("%ld\n", (long)(v)) : 0)
#	endif
#	define PUTS(str) (verbose ? printf("%s", str) : 0)
#else
#	define PIM(v)
#	define PUTS(str)
#endif

#define CHECK(corr, init, lval, as, first, op, second) { \
	scl_imax_t imax; \
	int correct; \
	int volatile zero = 0; \
	(lval) = init + zero; \
	correct = SCL_DO(lval, as, first, op, second); \
	PUTS(#lval " = " #init "; " #lval " " #as " " #first " " #op " " #second "; => "); \
	if (correct) { \
		PIM(lval); \
	} else { \
		PUTS("overflow\n"); \
	} \
	SCL_ASSERT(corr == correct); \
	if (corr) { \
		imax = init; \
		imax as ((scl_imax_t)(first) - zero) op ((scl_imax_t)(second) - zero); \
		SCL_ASSERT(imax == (lval)); \
	} \
}

#define CHECKU(corr, init, lval, as, first, op, second) { \
	scl_umax_t imax; \
	int correct; \
	(lval) = init; \
	correct = SCL_DO(lval, as, first, op, second); \
	PUTS(#lval " = " #init "; " #lval " " #as " " #first " " #op " " #second "; => "); \
	if (correct) { \
		PIM(lval); \
	} else { \
		PUTS("overflow\n"); \
	} \
	SCL_ASSERT(corr == correct); \
	if (corr) { \
		imax = init; \
		imax as (scl_umax_t)(first) op (scl_umax_t)(second); \
		SCL_ASSERT(imax == (lval)); \
	} \
}

static void errors(void) {
	int ret;
	char signed c;
	(void)c;
#	if ERR0
		ret = SCL_DO(c, =,3,&,2);
#	elif ERR1
		ret = SCL_DO(c,|=,3,+,2);
#	else
		ret = 1;
#	endif
	SCL_ASSERT(ret);
}

static int calc_const(void) {
	int res;
	int ret;
	res = 0;
	ret = SCL_DO(res,=,257,*,100);
	SCL_ASSERT(ret);
	return res;
}

static int calc_var_macro_do(int a) {
	int res;
	res = 1;
	if (!SCL_DO(res,=,a,*,100)) {
		res = 0;
	}
	return res;
}

static int calc_var_macro_mult(int a) {
	int res;
	res = 1;
	if (!SCL_MULT(&res, a, 100)) {
		res = 0;
	}
	return res;
}

static int calc_var_func(int a) {
	int res;
	if (!scl_mult(&res, 100, a)) {
		res = 0;
	}
	return res;
}

static int calc_var_handle(int a) {
	int res;
	if ((INT_MAX / 100 >= a) && (INT_MIN / 100 <= a)) {
		res = a * 100;
	} else {
		res = 0;
	}
	return res;
}

static int calc_var_clean(int a) {
	return a * 100;
}

static int calc_var_1(int a) {
	return 1;
}

static int bench(int (* volatile f)(int)) {
	int volatile i;
	i = 1024 * 64000;
	while ((i > 0) && (f(1 + i % 256) > 0)) {
		--i;
	}
	return i;
}

static int calc_const_by_var(void) {
	int (* volatile f)(int) = calc_var_macro_do;
	return f(300);
}

static void std_tests(void) {
	int i;
	unsigned u;
	long l;
	long unsigned lu;
	scl_imax_t im;
	scl_umax_t um;

	CHECK(1, 20,			i, =,3,*,2);
	CHECK(1, 20,			i,+=,3,*,2);
	CHECK(1, 20,			i,-=,3,*,2);
	CHECK(1, 20,			i,*=,3,*,2);
	CHECK(1, 20,			i,/=,3,*,2);
	if (sizeof(int) < sizeof(scl_imax_t)) {
		CHECK(1, INT_MIN,		i,+=,INT_MAX,*,2);
		CHECK(0, 20,			i, =,INT_MAX,*,2);
		CHECK(0, 20,			i, =,INT_MAX,+,1);
		CHECK(0, 20,			i, =,INT_MIN,-,1);
		CHECK(1, 0,				i,-=,INT_MIN,+,1);
		CHECK(0, 1,				i,-=,INT_MIN,+,1);
	}

	CHECKU(1, 20,			u, =,3,*,2);
	CHECKU(1, 20,			u,+=,3,*,2);
	CHECKU(1, 20,			u,-=,3,*,2);
	CHECKU(1, 20,			u,*=,3,*,2);
	CHECKU(1, 20,			u,/=,3,*,2);
	if (sizeof(unsigned) < sizeof(scl_imax_t)) {
		CHECKU(0, (unsigned)INT_MAX,u,+=,INT_MIN,*,2);
		CHECKU(1, 20,			u, =,INT_MAX,*,2);
		CHECKU(1, 20,			u, =,INT_MAX,+,1);
		CHECKU(0, 20,			u, =,INT_MIN,-,1);
		CHECKU(1, 0,			u,-=,INT_MIN,+,1);
		CHECKU(1, 1,			u,-=,INT_MIN,+,1);
	}

	CHECK (1, 20,			l ,/=,3,*,2);
	CHECKU(1, 20,			lu,/=,3,*,2);
	CHECK (1, 20,			im,/=,3,*,2);
	CHECKU(1, 20,			um,/=,3,*,2);
#	if defined(SCL_USE_LONG_LONG)
	{
		long long ll;
		long long unsigned llu;
		CHECK (1, 20,				ll ,/=,3,*,2);
		CHECKU(1, 20,				llu,/=,3,*,2);
		CHECK (0, LLONG_MAX,		ll ,-=,LLONG_MIN, +,ULLONG_MAX / 2);
		CHECKU(0, ULLONG_MAX/2,		llu,+=,ULLONG_MAX,-,LLONG_MAX);
		CHECK (1, LLONG_MAX,		ll ,+=,LLONG_MIN, +,ULLONG_MAX / 2);
		CHECKU(1, ULLONG_MAX/2+1,	llu,-=,ULLONG_MAX,-,ULLONG_MAX/2);

		CHECK (0, LLONG_MAX,		ll ,-=,LLONG_MIN, *,(ULLONG_MAX / 2));
		CHECKU(0, ULLONG_MAX/2,		llu,+=,ULLONG_MAX/UINT_MAX,*,LLONG_MAX);
		CHECK (1, LLONG_MAX,		ll ,+=,(long long)-1, *,(long long unsigned)112);
		CHECKU(1, ULLONG_MAX/4,		llu,+=,(long long unsigned)111,*,ULLONG_MAX/UINT_MAX);

		CHECK (0, LLONG_MAX,		ll ,-=,LLONG_MIN, /,(ULLONG_MAX / 2));
		CHECKU(0, ULLONG_MAX/2,		llu,+=,ULLONG_MAX,/,LLONG_MAX);
		CHECK (1, LLONG_MAX,		ll ,+=,(long long)-1, /,((long long unsigned)112));
		CHECKU(1, ULLONG_MAX/4,		llu,+=,((long long unsigned)11111),/,ULLONG_MAX);

		CHECK (1, LLONG_MAX,		ll ,-=,LLONG_MAX, %,ULLONG_MAX / 2);
		CHECKU(0, ULLONG_MAX/2,		llu,+=,ULLONG_MAX/UINT_MAX,%,LLONG_MAX);
		CHECK (0, LLONG_MAX,		ll ,+=,(long long)1, %,(long long unsigned)112);
		CHECKU(1, ULLONG_MAX/4,		llu,+=,(long long unsigned)111,%,ULLONG_MAX/UINT_MAX);
	}
#	endif
}

static int do_tests(int argc, char const **argv) {
	int ret, help;
	verbose = argc > 1;

	calc_const_by_var();
	calc_const();
	calc_var_handle(INT_MAX);
	calc_var_func(INT_MAX);
	calc_var_macro_do(INT_MAX);
	calc_var_macro_mult(INT_MAX);
	errors();

	help = 0 > 1;
	if ((argc <= 1) || (argv[1][0] == 'v')) {
		std_tests();
		ret = 0;
	} else if (argv[1][0] == 'f') {
		ret = bench(calc_var_func);
	} else if (argv[1][0] == 'c') {
		ret = bench(calc_var_clean);
	} else if (argv[1][0] == 'm') {
		ret = 0;
		if (argv[1][1] == 'm') {
			ret = bench(calc_var_macro_mult);
		} else if (argv[1][1] == 'd') {
			ret = bench(calc_var_macro_do);
		}
	} else if (argv[1][0] == '1') {
		ret = bench(calc_var_1);
	} else if (argv[1][0] == 'h') {
		ret = bench(calc_var_handle);
	} else {
		help = 0 < 1;
		ret = 0;
	}

	SCL_ASSERT(ret == 0);

	return help;
}

#if defined(START_FUNC) && (START_FUNC == 1)
#	undef START_FUNC
#	define START_FUNC test_interp
#endif

#if defined(START_FUNC)
	extern void START_FUNC(void) {
		do_tests(0, NULL);
		scl_gnuc_builtin_overflow = 1;
		do_tests(0, NULL);
	}
#else
	extern int main(int argc, char const **argv) {
		int help;
		help = do_tests(argc, argv);
		if (help) {
			puts(	"benchmark for expression: var * 100:\n"
					"v - verbose output\n"
					"f - function, direct using function\n"
					"md - macro, use macro SCL_DO\n"
					"mm - macro, use macro SCL_MULT\n"
					"c - clean, without checks\n"
					"1 - always 1 without calculations\n"
					"h - handle, explicit check\n");
		} else {
			scl_gnuc_builtin_overflow = 1;
			help = do_tests(argc, argv);
			SCL_ASSERT(!help);
		}
		return 0;
	}
#endif
