/*
 * intnum - implementation of tanhsinh- and Gauss-Legendre quadrature
 *
 * Copyright (C) 2013,2021 Christoph Zurnieden
 *
 * Calc is open software; you can redistribute it and/or modify it under
 * the terms of the version 2.1 of the GNU Lesser General Public License
 * as published by the Free Software Foundation.
 *
 * Calc 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.
 *
 * A copy of version 2.1 of the GNU Lesser General Public License is
 * distributed with calc under the filename COPYING-LGPL.  You should have
 * received a copy with calc; if not, write to Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */


static resource_debug_level;
resource_debug_level = config("resource_debug", 0);


read -once infinities;

static __CZ__tanhsinh_x;
static __CZ__tanhsinh_w;
static __CZ__tanhsinh_order;
static __CZ__tanhsinh_prec;

define quadtsdeletenodes()
{
    free(__CZ__tanhsinh_x);
    free(__CZ__tanhsinh_w);
    free(__CZ__tanhsinh_order);
    free(__CZ__tanhsinh_prec);
}

define quadtscomputenodes(order, expo, eps)
{
    local t cht sht chp sum k PI places;
    local h t0 x w;
    if (__CZ__tanhsinh_order == order && __CZ__tanhsinh_prec == eps)
	return 1;
    __CZ__tanhsinh_order = order;
    __CZ__tanhsinh_prec = eps;
    __CZ__tanhsinh_x = list();
    __CZ__tanhsinh_w = list();
    /* The tanhsinh algorithm needs a slightly higher precision than G-L */
    eps = epsilon(eps * 1e-2);
    places = highbit(1 + int (1 / epsilon())) +1;
    PI = pi();
    sum = 0;
    t0 = 2 ^ (-expo);
    h = 2 * t0;
    /*
     * The author wanted to use the mpmath trick here which was
     * advertised---and reasonably so!---to be faster. Didn't work out
     * so well with calc.
     * PI4 = PI/4;
     * expt0 = bround(exp(t0),places);
     * a = bround( PI4 * expt0,places);
     * b = bround(PI4 / expt0,places);
     * udelta = bround(exp(h),places);
     * urdelta = bround(1/udelta,places);
     */
    /* make use of x(-t) = -x(t), w(-t) = w(t)  */
    for (k = 0; k < 20 * order + 1; k++) {
	/*
	 * x = tanh(pi/2 * sinh(t))
	 * w = pi/2 * cosh(t) / cosh(pi/2 * sinh(t))^2
	 */
	t = bround(t0 + k * h, places);

	cht = bround(cosh(t), places);
	sht = bround(sinh(t), places);
	chp = bround(cosh(0.5 * PI * sht), places);
	x = bround(tanh(0.5 * PI * sht), places);
	w = bround((PI * h * cht) / (2 * chp ^ 2), places);
	/*
	 * c = bround(exp(a-b),places);
	 * d = bround(1/c,places);
	 * co =bround( (c+d)/2,places);
	 * si =bround( (c-d)/2,places);
	 * x = bround(si / co,places);
	 * w = bround((a+b) / co^2,places);
	 */
	if (abs(x - 1) <= eps)
	    break;

	append(__CZ__tanhsinh_x, x);
	append(__CZ__tanhsinh_w, w);
	/*
	 * a *= udelta;
	 * b *= urdelta;
	 */
    }

    /* Normalize the weights to make them add up to 2 (two) */
    /*
     * for(k=0;k < size(__CZ__tanhsinh_w);k++)
     * sum = bround(sum + __CZ__tanhsinh_w[k],places);
     * sum *= 2;
     * for(k=0;k < size(__CZ__tanhsinh_w);k++)
     * __CZ__tanhsinh_w[k] = bround(2.0 * __CZ__tanhsinh_w[k] / sum,places);
     */

    epsilon(eps);
    return 1;
}

define quadtscore(a, b, n)
{
    local k c d order eps places sum ret x x1 x2 xm w w1 w2 m sizel;

    eps = epsilon(epsilon() * 1e-2);
    places = highbit(1 + int (1 / epsilon())) +1;
    m = int (4 + max(0, ln(places / 30.0) / ln(2))) + 2;
    if (!isnull(n)) {
	order = n;
	m = ilog(order / 3, 2) + 1;
    } else
	order = 3 * 2 ^ (m - 1);

    quadtscomputenodes(order, m, epsilon());
    sizel = size(__CZ__tanhsinh_w);

    if (isinfinite(a) || isinfinite(b)) {
	/*
	 *           x
	 * t =  ------------
	 *                2
	 *      sqrt(1 - y )
	 */
	if (isninf(a) && ispinf(b)) {
	    for (k = 0; k < sizel; k++) {
		x1 = __CZ__tanhsinh_x[k];
		x2 = -__CZ__tanhsinh_x[k];
		w1 = __CZ__tanhsinh_w[k];

		x = bround(x1 * (1 - x1 ^ 2) ^ (-1 / 2), places);
		xm = bround(x2 * (1 - x2 ^ 2) ^ (-1 / 2), places);
		w = bround(w1 * (((1 - x1 ^ 2) ^ (-1 / 2)) / (1 - x1 ^ 2)),
			   places);
		w2 = bround(w1 * (((1 - x2 ^ 2) ^ (-1 / 2)) / (1 - x2 ^ 2)),
			    places);
		sum += bround(w * f(x), places);
		sum += bround(w2 * f(xm), places);
	    }
	}
	/*
	 *        1
	 * t =  - - + b + 1
	 *        x
	 */
	else if (isninf(a) && !iscinf(b)) {
	    for (k = 0; k < sizel; k++) {
		x1 = __CZ__tanhsinh_x[k];
		x2 = -__CZ__tanhsinh_x[k];
		w1 = __CZ__tanhsinh_w[k];

		x = bround((b + 1) - (2 / (x1 + 1)), places);
		xm = bround((b + 1) - (2 / (x2 + 1)), places);
		w = bround(w1 * (1 / 2 * (2 / (x1 + 1)) ^ 2), places);
		w2 = bround(w1 * (1 / 2 * (2 / (x2 + 1)) ^ 2), places);
		sum += bround(w * f(x), places);
		sum += bround(w2 * f(xm), places);
	    }
	}
	/*
	 *      1
	 * t =  - + a - 1
	 *      x
	 */
	else if (!iscinf(a) && ispinf(b)) {
	    for (k = 0; k < sizel; k++) {
		x1 = __CZ__tanhsinh_x[k];
		x2 = -__CZ__tanhsinh_x[k];
		w1 = __CZ__tanhsinh_w[k];
		x = bround((a - 1) + (2 / (x1 + 1)), places);
		xm = bround((a - 1) + (2 / (x2 + 1)), places);
		w = bround(w1 * (((1 / 2) * (2 / (x1 + 1)) ^ 2)), places);
		w2 = bround(w1 * (((1 / 2) * (2 / (x2 + 1)) ^ 2)), places);
		sum += bround(w * f(x), places);
		sum += bround(w2 * f(xm), places);
	    }
	} else if (isninf(a) || isninf(b)) {
	    /*TODO: swap(a,b) and negate(w)? Lookup! */
	    return newerror("quadtscore: reverse limits?");
	} else {
	    return
		newerror("quadtscore: complex infinity not yet implemented");
        }
	ret = sum;
    } else {
	/* Avoid rounding errors */
	if (a == -1 && b == 1) {
	    c = 1;
	    d = 0;
	} else {
	    c = (b - a) / 2;
	    d = (b + a) / 2;
	}
	sum = 0;
	for (k = 0; k < sizel; k++) {
	    sum +=
		bround(__CZ__tanhsinh_w[k] * f(c * __CZ__tanhsinh_x[k] + d),
		       places);
	    sum +=
		bround(__CZ__tanhsinh_w[k] * f(c * -__CZ__tanhsinh_x[k] + d),
		       places);
	}
	ret = c * sum;
    }
    epsilon(eps);
    return ret;
}

static __CZ__quadts_error;

define quadts(a, b, points)
{
    local k sp results epsbits nsect interval length segment slope C ;
    local x1 x2 y1 y2  sum D1 D2 D3 D4;
    if (param(0) < 2)
	return newerror("quadts: not enough arguments");
    epsbits = highbit(1 + int (1 / epsilon())) +1;
    if (param(0) < 3 || isnull(points)) {
	/* return as given */
	return quadtscore(a, b);
    } else {
	if ((isinfinite(a) || isinfinite(b))
	    && (!ismat(points) && !islist(points)))
	    return
		newerror(strcat
			 ("quadts: segments of infinite length ",
			  "are not yet supported"));
	if (ismat(points) || islist(points)) {
	    sp = size(points);
	    if (sp == 0)
		return
		    newerror(strcat
			     ("quadts: variable 'points` must be a list or ",
			      "1d-matrix of a length > 0"));
	    /* check if all points are numbers */
	    for (k = 0; k < sp; k++) {
		if (!isnum(points[k]))
		    return
			newerror(strcat
				 ("quadts: elements of 'points` must be",
				  " numbers only"));
	    }
	    /* We have n-1 intervals and a and b, hence n-1 + 2 results */
	    results = mat[sp + 1];
            if (a != points[0]) {
	        results[0] = quadtscore(a, points[0]);
            } else {
                results[0] = 0;
            }
	    if (sp == 1) {
                if (b != points[0]) {
		    results[1] = quadtscore(points[0], b);
                } else {
                    results[1] = 0;
                }
	    } else {
		for (k = 1; k < sp; k++) {
		    results[k] = quadtscore(points[k - 1], points[k]);
		}
                if (b != points[k - 1]) {
		    results[k] = quadtscore(points[k - 1], b);
                } else {
                    results[k] = 0;
                }
	    }
	} else {
	    if (!isint(points) || points <= 0)
		return newerror(strcat("quadts: variable 'points` must be a ",
				       "list or a positive integer"));
	    /* Taking "points" as the number of equally spaced intervals */
	    results = mat[points + 1];
	    /* It is easy if a,b lie on the real line */
	    if (isreal(a) && isreal(b)) {
		length = abs(a - b);
		segment = length / points;

		for (k = 1; k <= points; k++) {
		    results[k - 1] =
			quadtscore(a + (k - 1) * segment, a + k * segment);
		}
	    } else {
		/* We have at least one complex limit but treat "points" still
                 * as the number of equally spaced intervals on a straight line
                 * connecting a and b. Computing the segments here is a bit
                 * more complicated but not much, it should have been taught in
                 * high school.
		 * Other contours by way of a list of points */
		slope = (im(b) - im(a)) / (re(b) - re(a));
		C = (im(a) + slope) * re(a);
		length = abs(re(a) - re(b));
		segment = length / points;

		/* y = mx+C where m is the slope, x is the real part and y the
		 * imaginary part  */
                if(re(a)>re(b))swap(a,b);
		for (k = re(a); k <= (re(b)); k+=segment) {
		    x1 = slope*(k) +  C;
		    results[k] = quadtscore(k + x1 * 1i);
		}
	    }			/* else of isreal */
	}			/* else of ismat|islist */
    }				/* else of isnull(points) */
    /* With a bit of undeserved luck we have a result by now. */
    sp = size(results);
    for (k = 0; k < sp; k++) {
	sum += results[k];
    }
    return sum;
}

static __CZ__gl_x;
static __CZ__gl_w;
static __CZ__gl_order;
static __CZ__gl_prec;

define quadglcomputenodes(N)
{
    local places k l x w t1 t2 t3 t4 t5 r tmp;

    if (__CZ__gl_order == N && __CZ__gl_prec == epsilon())
	return;

    __CZ__gl_x = mat[N];
    __CZ__gl_w = mat[N];
    __CZ__gl_order = N;
    __CZ__gl_prec = epsilon();

    places = highbit(1 + int (1 / epsilon())) +1;

    /*
     * Compute roots and weights (doing it inline seems to be fastest)
     * Trick shamelessly stolen from D. Bailey et .al (program "arprec")
     */
    for (k = 1; k <= N//2; k++) {
	r = bround(cos(pi() * (k - .25) / (N + .5)), places);
	while (1) {
	    t1 = 1, t2 = 0;
	    for (l = 1; l <= N; l++) {
		t3 = t2;
		t2 = t1;
		t1 = bround(((2 * l - 1) * r * t2 - (l - 1) * t3) / l, places);
	    }
	    t4 = bround(N * (r * t1 - t2) / ((r ^ 2) - 1), places);
	    t5 = r;
	    tmp = t1 / t4;
	    r = r - tmp;
	    if (abs(tmp) <= epsilon())
		break;
	}
	x = r;
	w = bround(2 / ((1 - r ^ 2) * t4 ^ 2), places);

	__CZ__gl_x[k - 1] = x;
	__CZ__gl_w[k - 1] = w;
	__CZ__gl_x[N - k] = -__CZ__gl_x[k - 1];
	__CZ__gl_w[N - k] = __CZ__gl_w[k - 1];
    }
    return;
}

define quadgldeletenodes()
{
    free(__CZ__gl_x);
    free(__CZ__gl_w);
    free(__CZ__gl_order);
    free(__CZ__gl_prec);
}

define quadglcore(a, b, n)
{
    local k c d digs order eps places sum ret err x x1 w w1 m;
    local phalf x2 px1 spx1 u b1 a1 half;

    eps = epsilon(epsilon() * 1e-2);
    places = highbit(1 + int (1 / epsilon())) +1;
    if (!isnull(n))
	order = n;
    else {
	m = int (4 + max(0, ln(places / 30.0) / ln(2))) + 2;
	order = 3 * 2 ^ (m - 1);
    }


    quadglcomputenodes(order, 1);

    if (isinfinite(a) || isinfinite(b)) {
	if (isninf(a) && ispinf(b)) {
	    for (k = 0; k < order; k++) {
		x1 = __CZ__gl_x[k];
		w1 = __CZ__gl_w[k];

		x = bround(x1 * (1 - x1 ^ 2) ^ (-1 / 2), places);
		w = bround(w1 * (((1 - x1 ^ 2) ^ (-1 / 2)) / (1 - x1 ^ 2)),
			   places);
		sum += bround(w * f(x), places);
	    }
	} else if (isninf(a) && !iscinf(b)) {
	    for (k = 0; k < order; k++) {
		x1 = __CZ__gl_x[k];
		w1 = __CZ__gl_w[k];

		x = bround((b + 1) - (2 / (x1 + 1)), places);
		w = bround(w1 * (1 / 2 * (2 / (x1 + 1)) ^ 2), places);
		sum += bround(w * f(x), places);
	    }
	} else if (!iscinf(a) && ispinf(b)) {
	    for (k = 0; k < order; k++) {
		x1 = __CZ__gl_x[k];
		w1 = __CZ__gl_w[k];
		x = bround((a - 1) + (2 / (x1 + 1)), places);
		w = bround(w1 * (((1 / 2) * (2 / (x1 + 1)) ^ 2)), places);
		sum += bround(w * f(x), places);
	    }
	} else if (isninf(a) || isninf(b)) {
	    /*TODO: swap(a,b) and negate(w)? Lookup! */
	    return newerror("quadglcore: reverse limits?");
	} else
	    return
		newerror("quadglcore: complex infinity not yet implemented");
	ret = sum;
    } else {
	/* Avoid rounding errors */
	if (a == -1 && b == 1) {
	    c = 1;
	    d = 0;
	} else {
	    c = (b - a) / 2;
	    d = (b + a) / 2;
	}
	sum = 0;
	for (k = 0; k < order; k++) {
	    sum += bround(__CZ__gl_w[k] * f(c * __CZ__gl_x[k] + d), places);
	}
	ret = c * sum;
    }
    epsilon(eps);
    return ret;
}

define quadgl(a, b, points)
{
    local k sp results epsbits nsect interval length segment slope C x1 y1 x2
	y2;
    local sum D1 D2 D3 D4;
    if (param(0) < 2)
	return newerror("quadgl: not enough arguments");
    epsbits = highbit(1 + int (1 / epsilon())) +1;
    if (isnull(points)) {
	/* return as given */
	return quadglcore(a, b);
    } else {
	/* But if we could half the time needed to execute a single operation
	 * we could do all of it in just twice that time. */
	if (isinfinite(a) || isinfinite(b)
	    && (!ismat(points) && !islist(points)))
	    return
		newerror(strcat
			 ("quadgl: multiple segments of infinite length ",
			  "are not yet supported"));
	if (ismat(points) || islist(points)) {
	    sp = size(points);
	    if (sp == 0)
		return
		    newerror(strcat
			     ("quadgl: variable 'points` must be a list or ",
			      "1d-matrix of a length > 0"));
	    /* check if all points are numbers */
	    for (k = 0; k < sp; k++) {
		if (!isnum(points[k]))
		    return
			newerror(strcat
				 ("quadgl: elements of 'points` must be ",
				  "numbers only"));
	    }
	    /* We have n-1 intervals and a and b, hence n-1 + 2 results */
	    results = mat[sp + 1];
            if (a != points[0]) {
	        results[0] = quadglcore(a, points[0]);
            } else {
                results[0] = 0;
            }
	    if (sp == 1) {
		if (b != points[0]) {
		    results[1] = quadglcore(points[0], b);
                } else {
                    results[1] = 0;
                }
	    } else {
		for (k = 1; k < sp; k++) {
		    results[k] = quadglcore(points[k - 1], points[k]);
		}
                if (b != points[k - 1]) {
		    results[k] = quadglcore(points[k - 1], b);
                } else {
                    results[k] = 0;
                }
	    }
	} else {
	    if (!isint(points) || points <= 0)
		return newerror(strcat("quadgl: variable 'points` must be a ",
				       "list or a positive integer"));
	    /* Taking "points" as the number of equally spaced intervals */
	    results = mat[points + 1];
	    /* It is easy if a,b lie on the real line */
	    if (isreal(a) && isreal(b)) {
		length = abs(a - b);
		segment = length / points;

		for (k = 1; k <= points; k++) {
		    results[k - 1] =
			quadglcore(a + (k - 1) * segment, a + k * segment);
		}
	    } else {
		/* Other contours by way of a list of points */
		slope = (im(b) - im(a)) / (re(b) - re(a));
		C = (im(a) + slope) * re(a);
		length = abs(re(a) - re(b));
		segment = length / points;

		/* y = mx+C where m is the slope, x is the real part and y the
		 * imaginary part  */
                if(re(a)>re(b))swap(a,b);
		for (k = re(a); k <= (re(b)); k+=segment) {
		    x1 = slope*(k) +  C;
		    results[k] = quadglcore(k + x1 * 1i);
		}
	    }			/* else of isreal */
	}			/* else of ismat|islist */
    }				/* else of isnull(points) */
    /* With a bit of undeserved luck we have a result by now. */
    sp = size(results);
    for (k = 0; k < sp; k++) {
	sum += results[k];
    }
    return sum;
}

define quad(a, b, points = -1, method = "tanhsinh")
{
    if (isnull(a) || isnull(b) || param(0) < 2)
	return newerror("quad: both limits must be given");
    if (isstr(a)) {
	if (strncmp(a, "cinf", 1) == 0)
	    return
		newerror(strcat
			 ("quad: complex infinity not yet supported, use",
			  " 'pinf' or 'ninf' respectively"));
    }
    if (isstr(b)) {
	if (strncmp(b, "cinf", 1) == 0)
	    return
		newerror(strcat
			 ("quad: complex infinity not yet supported, use",
			  " 'pinf' or 'ninf' respectively"));
    }

    if (param(0) == 3) {
	if (isstr(points))
	    method = points;
    }

    if (strncmp(method, "tanhsinh", 1) == 0) {
	if (!isstr(points)) {
	    if (points == -1) {
		return quadts(a, b);
	    } else {
		return quadts(a, b, points);
	    }
	} else {
	    return quadts(a, b);
	}
    }

    if (strncmp(method, "gausslegendre", 1) == 0) {
	if (!isstr(points)) {
	    if (points == -1) {
		return quadgl(a, b);
	    } else {
		return quadgl(a, b, points);
	    }
	} else {
	    return quadgl(a, b);
	}
    }
}

define makerange(start, end, steps)
{
    local ret k l step C length slope x1 x2 y1 y2;
    local segment;
    steps = int (steps);
    if (steps < 1) {
	return newerror("makerange: number of steps must be > 0");
    }
    if (!isnum(start) || !isnum(end)) {
	return newerror("makerange: only numbers are supported yet");
    }
    if (isreal(start) && isreal(end)) {
	step = (end - start) / (steps);
	print step;
	ret = mat[steps + 1];
	for (k = 0; k <= steps; k++) {
	    ret[k] = k * step + start;
	}
    } else {
	ret = mat[steps + 1];
	if (re(start) > re(end)) {
	    swap(start, end);
	}

	slope = (im(end) - im(start)) / (re(end) - re(start));
	C = im(start) - slope * re(start);
	length = abs(re(start) - re(end));
	segment = length / (steps);

	for (k = re(start), l = 0; k <= (re(end)); k += segment, l++) {
	    x1 = slope * (k) + C;
	    ret[l] = k + x1 * 1i;
	}

    }
    return ret;
}

define makecircle(radius, center, points)
{
    local ret k a b twopi centerx centery;
    if (!isint(points) || points < 2) {
	return
	    newerror("makecircle: number of points is not a positive integer");
    }
    if (!isnum(center)) {
	return newerror("makecircle: center does not lie on the complex plane");
    }
    if (!isreal(radius) || radius <= 0) {
	return newerror("makecircle: radius is not a real > 0");
    }
    ret = mat[points];
    twopi = 2 * pi();
    centerx = re(center);
    centery = im(center);
    for (k = 0; k < points; k++) {
	a = centerx + radius * cos(twopi * k / points);
	b = centery + radius * sin(twopi * k / points);
	ret[k] = a + b * 1i;
    }
    return ret;
}

define makeellipse(angle, a, b, center, points)
{
    local ret k x y twopi centerx centery;
    if (!isint(points) || points < 2) {
	return
	    newerror("makeellipse: number of points is not a positive integer");
    }
    if (!isnum(center)) {
	return
	    newerror("makeellipse: center does not lie on the complex plane");
    }
    if (!isreal(a) || a <= 0) {
	return newerror("makecircle: a is not a real > 0");
    }
    if (!isreal(b) || b <= 0) {
	return newerror("makecircle: b is not a real > 0");
    }
    if (!isreal(angle)) {
	return newerror("makecircle: angle is not a real");
    }
    ret = mat[points];
    twopi = 2 * pi();
    centerx = re(center);
    centery = im(center);
    for (k = 0; k < points; k++) {
	x = centerx + a * cos(twopi * k / points) * cos(angle)
	    - b * sin(twopi * k / points) * sin(angle);
	y = centerx + a * cos(twopi * k / points) * sin(angle)
	    + b * sin(twopi * k / points) * cos(angle);
	ret[k] = x + y * 1i;
    }
    return ret;
}

define makepoints()
{
    local ret k;
    ret = mat[param(0)];
    for (k = 0; k < param(0); k++) {
	if (!isnum(param(k + 1))) {
	    return
		newerror(strcat
			 ("makepoints: parameter number \"", str(k + 1),
			  "\" is not a number"));
	}
	ret[k] = param(k + 1);
    }
    return ret;
}


config("resource_debug", resource_debug_level),;
if (config("resource_debug") & 3) {
    print "quadtsdeletenodes()";
    print "quadtscomputenodes(order, expo, eps)";
    print "quadtscore(a,b,n)";
    print "quadts(a,b,points)";
    print "quadglcomputenodes(N)";
    print "quadgldeletenodes()";
    print "quadglcore(a,b,n)";
    print "quadgl(a,b,points)";
    print "quad(a,b,points=-1,method=\"tanhsinh\")";
    print "makerange(start, end, steps)";
    print "makecircle(radius, center, points)";
    print "makeellipse(angle, a, b, center, points)";
    print "makepoints(a1,[...])";
}
