/*
 * palindrome - palindrome utilities
 *
 * Copyright (C) 2021  Landon Curt Noll
 *
 * 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.
 *
 * Under source code control:	2021/11/06 14:35:37
 * File existed as early as:	2021
 *
 * Share and enjoy!  :-)	http://www.isthe.com/chongo/tech/comp/calc/
 */


/*
 * digitof - return the a digit of a value
 *
 * NOTE: We assume base 10 digits and place 1 is the units digit.
 *
 * given:
 *	val	value to find a digit of
 *	place	digit place
 *
 * returns:
 * 	value (>= 0 and < 10) that is the place-th digit of val
 *	or 0 if place is not a digit of val
 */
define digitof(val, place)
{
    local d;	/* length of val in digits */

    /* determine length */
    d = digits(val);

    /* firewall - return 0 if digit place doesn't exist */
    if (place < 1 || place > d) {
	return 0;
    }

    /* return the place-th digit of val as a single digit */
    return  (val // (10^(place-1))) % 10;
}


/*
 * copalplace - determine the other place in a palindrome
 *
 * NOTE: We assume base 10 digits and place 1 is the units digit.
 *
 * given:
 *	d	digits of a value
 *	place	digit place
 *
 * returns:
 *	given palindrome val, the other digit paired with place
 *	or 0 if place is not a digit of val
 */
define copalplace(d, place)
{
    /* firewall - return 0 if digit place doesn't exist */
    if (d < 1 || place < 1 || place > d) {
	return 0;
    }

    /* return digit coplace */
    return d+1 - place;
}


/*
 * upperhalf - return the upper half of a palindrome
 *
 * NOTE: We assume base 10 digits and place 1 is the units digit.
 *
 * NOTE: When the value has an odd number of digits, the upper half
 *	 includes the middle digit.
 *
 * given:
 *	val	a value
 *
 * returns:
 * 	the upper half digits of a value
 */
define upperhalf(val)
{
    local d;		/* length of val in digits */
    local halfd;	/* length of upper hand of val */

    /* determine length */
    d = digits(val);
    halfd = d // 2;

    /* return upper half */
    return (val // 10^halfd);
}


/*
 * mkpal - make a value into a palindrome
 *
 * NOTE: We assume base 10 digits and place 1 is the units digit.
 *
 * given:
 *	val	a value
 *
 * returns:
 * 	val as a palindrome with lower half being reverse digits of val
 */
define mkpal(val)
{
    local d;	/* length of val in digits */
    local i;	/* counter */
    local ret;	/* palindrome being formed */

    /* determine length */
    d = digits(val);

    /* insert digits in reverse order at the bottom */
    ret = val;
    for (i=0; i < d; ++i) {
	ret = ret*10 + digit(val, i);
    }
    return ret;
}


/*
 * mkpalmiddigit - make a value into a palindrome with a middle digit
 *
 * NOTE: We assume base 10 digits and place 1 is the units digit.
 *
 * given:
 *	val	a value
 *	digit	the digit to put into the middle of the palindrome
 *
 * returns:
 * 	val as a palindrome with lower half being reverse digits of val
 *		and digit as a middle digit
 */
define mkpalmiddigit(val, digit)
{
    local d;	/* length of val in digits */
    local i;	/* counter */
    local ret;	/* palindrome being formed */

    /* determine length */
    d = digits(val);

    /* insert digits in reverse order at the bottom */
    ret = val*10 + digit;
    for (i=0; i < d; ++i) {
	ret = ret*10 + digit(val, i);
    }
    return ret;
}


/*
 * ispal - determine if a value is a palindrome
 *
 * NOTE: We assume base 10 digits and place 1 is the units digit.
 *
 * given:
 *	val	a value
 *
 * returns:
 * 	1 ==> val is a palindrome
 * 	0 ==> val is NOT a palindrome
 */
define ispal(val)
{
    local half;		/* upper half of digits of val */
    local digit;	/* middle digit */

    /* case: val has an even number of digits */
    if (iseven(digits(val))) {

	/* test palindrome-ness */
	return (val == mkpal(upperhalf(val)));

    /* case: val can an odd number of digits */
    } else {

	/* test palindrome-ness */
	half = upperhalf(val);
	digit = half % 10;
	half //= 10;
	return (val == mkpalmiddigit(half, digit));
    }
}


/*
 * palnextpal - return next palindrome from a known palindrome
 *
 * NOTE: We assume base 10 digits and place 1 is the units digit.
 *
 * given:
 *	pal	a palindrome
 *
 * returns:
 *	next palindrome > pal
 */
define palnextpal(pal)
{
    local paldigits;	/* digits in pal */
    local half;		/* upper half of newval */
    local newhalf;	/* half+1 */
    local newpal;	/* new palindrome */

    /* case: negative palindrome */
    if (pal < 0) {
	return -(palprevpal(-pal));
    }

    /*
     * start with upper half
     */
    half = upperhalf(pal);

    /*
     * prep to find a larger palindrome
     */
    newhalf = half+1;

    /*
     * form palindrome from new upper half
     *
     * We need to watch for the corner case where changing the
     * half changes the number of digits as this will impact
     * or even/odd number of digits processing.
     */
    paldigits = digits(pal);
    if (digits(newhalf) == digits(half)) {
	/* no change in half digits: process as normal */
	if (iseven(paldigits)) {
	    newpal = mkpal(newhalf);
	} else {
	    newpal = mkpalmiddigit(newhalf // 10, newhalf % 10);
	}
    } else {
	/* change in half digits: process as opposite */
	if (iseven(paldigits)) {
	    newpal = mkpalmiddigit(newhalf // 10, newhalf % 10);
	} else {
	    newpal = mkpal(newhalf);
	}
    }

    /*
     * return the new palindrome
     */
    return newpal;
}


/*
 * nextpal - return next palindrome from a value
 *
 * NOTE: We assume base 10 digits and place 1 is the units digit.
 *
 * given:
 *	val	a value
 *
 * returns:
 *	next palindrome > val
 */
define nextpal(val)
{
    local newval;	/* val+1 */
    local newvaldigits;	/* digits in newval */
    local half;		/* upper half of newval */
    local pal;		/* palindrome test value */
    local newpal;	/* new palindrome */

    /* case: negative value */
    if (val < 0) {
	return -(prevpal(-val));
    }

    /*
     * start looking from a larger value
     */
    newval = val+1;
    newvaldigits = digits(newval);

    /* case: single digit palindrome */
    if (newvaldigits < 2) {
	return newval;
    }

    /*
     * start with next upper half
     */
    half = upperhalf(newval);

    /*
     * form palindrome from upper half
     *
     * We need to deal with even vs. odd digit counts
     * when forming a palindrome from a half as the
     * half may not or may include the middle digit.
     */
    if (iseven(newvaldigits)) {
	pal = mkpal(half);
    } else {
	pal = mkpalmiddigit(half // 10, half % 10);
    }

    /*
     * case: we found a larger palindrome, we are done
     */
    if (pal > val) {
	return pal;
    }

    /*
     * we need to find an even larger palindrome
     */
    newpal = palnextpal(pal);

    /*
     * return the new palindrome
     */
    return newpal;
}


/*
 * palprevpal - return previous palindrome from a palindrome
 *
 * NOTE: We assume base 10 digits and place 1 is the units digit.
 *
 * given:
 *	pal	a palindrome
 *
 * returns:
 *	previous palindrome < pal
 */
define palprevpal(pal)
{
    local paldigits;	/* digits in pal */
    local half;		/* upper half of newval */
    local newhalf;	/* half+1 */
    local newpal;	/* new palindrome */

    /* case: negative value */
    if (pal < 0) {
	return -(palnextpal(-pal));
    }

    /* case: single digit palindrome */
    if (pal < 10) {
	newpal = pal-1;
	return newpal;
    }

    /* case: 10 or 11 */
    if (pal < 12) {
	newpal = 9;
	return newpal;
    }

    /*
     * start with upper half
     */
    half = upperhalf(pal);

    /*
     * prep to find a smaller palindrome
     */
    newhalf = half-1;

    /*
     * form palindrome from new upper half
     *
     * We need to watch for the corner case where changing the
     * half changes the number of digits as this will impact
     * or even/odd number of digits processing.
     */
    paldigits = digits(pal);
    if (digits(newhalf) == digits(half)) {
	/* no change in half digits: process as normal */
	if (iseven(paldigits)) {
	    newpal = mkpal(newhalf);
	} else {
	    newpal = mkpalmiddigit(newhalf // 10, newhalf % 10);
	}
    } else {
	/* change in half digits: process as opposite */
	if (iseven(paldigits)) {
	    newpal = mkpalmiddigit(newhalf // 10, newhalf % 10);
	} else {
	    newpal = mkpal(newhalf);
	}
    }

    /*
     * return the new palindrome
     */
    return newpal;
}


/*
 * prevpal - return previous palindrome from a value
 *
 * NOTE: We assume base 10 digits and place 1 is the units digit.
 *
 * given:
 *	val	a value
 *
 * returns:
 *	previous palindrome < val
 */
define prevpal(val)
{
    local newval;	/* val-1 */
    local newvaldigits;	/* digits in newval */
    local half;		/* upper half of newval */
    local pal;		/* palindrome test value */
    local newpal;	/* new palindrome */

    /* case: negative value */
    if (val < 0) {
	return -(nextpal(-val));
    }

    /*
     * start looking from a smaller value
     */
    newval = val-1;
    newvaldigits = digits(newval);

    /* case: single digit palindrome */
    if (newvaldigits < 2) {
	return newval;
    }

    /*
     * start with previous upper half
     */
    half = upperhalf(newval);

    /*
     * form palindrome from upper half
     *
     * We need to deal with even vs. odd digit counts
     * when forming a palindrome from a half as the
     * half may not or may include the middle digit.
     */
    if (iseven(newvaldigits)) {
	pal = mkpal(half);
    } else {
	pal = mkpalmiddigit(half // 10, half % 10);
    }

    /*
     * case: we found a smaller palindrome, we are done
     */
    if (pal < val) {
	return pal;
    }

    /*
     * we need to find an even smaller palindrome
     */
    newpal = palprevpal(pal);

    /*
     * return the new palindrome
     */
    return newpal;
}


/*
 * nextprimepal - return next palindrome that is a (highly probable) prime
 *
 * NOTE: We assume base 10 digits and place 1 is the units digit.
 *
 * given:
 *	val	a value
 *
 * returns:
 *	next palindrome (highly probable) prime > val
 */
define nextprimepal(val)
{
    local pal;		/* palindrome test value */
    local dpal;		/* digits in pal */

    /*
     * pre-start under the next palindrome
     */
    pal = nextpal(val-1);

    /*
     * loop until we find a (highly probable) prime or 0
     */
    do {

	/* case: negative values and tiny values */
	if (pal < 2) {
	    return 2;
	}

	/*
	 * compute the next palindrome
	 */
	pal = palnextpal(pal);
	dpal = digits(pal);

	/* case: 11 is the only prime palindrome with even digit count */
	if (pal == 11) {
	    return 11;
	}

	/* case: even number of digits and not 11 */
	if (iseven(dpal)) {

	    /*
	     * Except for 11 (which is handled above already), there are
	     * no prime palindrome with even digits.  So we need to
	     * increase the digit count and work with that larger palindrome.
	     */
	    pal = nextpal(10^dpal);
	}

	/* case: palindrome is even or ends in 5 */
	if (iseven(pal % 10) || (pal%10 == 10/2)) {

	    /*
	     * we need to increase the bottom and top digits
	     * so that we have a chance to be prime
	     */
	    pal += (1 + 10^(dpal-1));
	}
	if (config("resource_debug") & 0x8) {
	    print "DEBUG: nextprimepal:", pal;
	}
    } while (ptest(pal) == 0 && pal > 0);

    /* return palindrome that his (highly probable) prime or 0 */
    return pal;
}


/*
 * prevprimepal - return prev palindrome that is a (highly probable) prime
 *
 * NOTE: We assume base 10 digits and place 1 is the units digit.
 *
 * given:
 *	val	a value
 *
 * returns:
 *	prev palindrome (highly probable) prime < val or 0
 */
define prevprimepal(val)
{
    local pal;		/* palindrome test value */
    local dpal;		/* digits in pal */

    /*
     * pre-start over the previous palindrome
     */
    pal = prevpal(val+1);

    /*
     * loop until we find a (highly probable) prime or 0
     */
    do {

	/*
	 * case: single digit values are always palindromes
	 */
	if (val < 10) {
	    /*
	     * The prevcand() call will return 0 if there is no previous prime
	     * such as the case when val < 2.
	     */
	    return prevcand(pal);
	}

	/*
	 * compute the previous palindrome
	 */
	pal = palprevpal(pal);
	dpal = digits(pal);

	/* case: 11 is the only prime palindrome with even digit count */
	if (pal == 11) {
	    return 11;
	}

	/* case: 2 digit palindrome and not 11 */
	if (dpal == 2) {
	    return 7;
	}

	/* case: even number of digits */
	if (iseven(dpal)) {

	    /*
	     * Except for 11 (which is handled above already), there are
	     * no prime palindrome with even digits.  So we need to
	     * decrease the digit count and work with that smaller palindrome.
	     */
	    pal = prevpal(10^(dpal-1));
	}

	/* case: palindrome is even or ends in 5 */
	if (iseven(pal % 10) || (pal%10 == 10/2)) {

	    /*
	     * we need to decrease the bottom and top digits
	     * so that we have a chance to be prime
	     */
	    pal -= (1 + 10^(dpal-1));
	}
	if (config("resource_debug") & 0x8) {
	    print "DEBUG: prevprimepal:", pal;
	}
    } while (ptest(pal) == 0 && pal > 0);

    /* return palindrome that his (highly probable) prime or 0 */
    return pal;
}
