import java.util.ArrayList;
import java.util.List;

/** Reverse, palindrome, Armstrong, perfect and strong numbers, with dry-run tracing. */
public class NumberPrograms {

    // BEGIN reverse
    static int reverse(int n) {
        int rev = 0;
        while (n != 0) {
            int digit = n % 10;          // last digit
            rev = rev * 10 + digit;      // append it to the reversed number
            n /= 10;                     // drop it from n
        }
        return rev;
    }
    // END reverse

    // BEGIN reverseSafe
    static long reverseLong(long n) {
        long rev = 0;
        while (n != 0) {
            rev = rev * 10 + n % 10;
            n /= 10;
        }
        return rev;
    }
    // END reverseSafe

    // BEGIN palindrome
    static boolean isPalindrome(int n) {
        if (n < 0) return false;
        return n == reverseLong(n);
    }
    // END palindrome

    // BEGIN armstrong
    static boolean isArmstrong(int n) {
        int digits = String.valueOf(n).length();
        int sum = 0;
        for (int t = n; t > 0; t /= 10) {
            sum += (int) Math.pow(t % 10, digits);
        }
        return sum == n;
    }
    // END armstrong

    // BEGIN perfect
    static boolean isPerfect(int n) {
        if (n < 2) return false;
        int sum = 1;                                   // 1 divides everything
        for (int i = 2; (long) i * i <= n; i++) {
            if (n % i == 0) {
                sum += i;
                if (i != n / i) sum += n / i;          // the paired divisor
            }
        }
        return sum == n;
    }
    // END perfect

    // BEGIN strong
    static boolean isStrong(int n) {
        int[] fact = new int[10];
        fact[0] = 1;
        for (int i = 1; i < 10; i++) fact[i] = fact[i - 1] * i;   // 0! .. 9!
        int sum = 0;
        for (int t = n; t > 0; t /= 10) sum += fact[t % 10];
        return sum == n;
    }
    // END strong

    static List<Integer> collect(java.util.function.IntPredicate p, int from, int to) {
        List<Integer> out = new ArrayList<>();
        for (int i = from; i <= to; i++) if (p.test(i)) out.add(i);
        return out;
    }

    public static void main(String[] args) {
        System.out.println("== Reverse 1234: dry run ==");
        System.out.printf("%-6s %-6s %-6s %-6s%n", "step", "n", "digit", "rev");
        int n = 1234, rev = 0, step = 0;
        while (n != 0) {
            int digit = n % 10;
            rev = rev * 10 + digit;
            n /= 10;
            System.out.printf("%-6d %-6d %-6d %-6d%n", ++step, n, digit, rev);
        }
        System.out.println("reverse(1234) = " + reverse(1234) + ", reverse(120) = " + reverse(120) + ", reverse(-45) = " + reverse(-45));
        System.out.println("reverse(1_999_999_999)     = " + reverse(1_999_999_999) + "   <- int overflow");
        System.out.println("reverseLong(1_999_999_999) = " + reverseLong(1_999_999_999));

        System.out.println();
        System.out.println("== Palindrome ==");
        for (int x : new int[]{121, 123, 1221, 10, 7, -121}) System.out.println("isPalindrome(" + x + ") = " + isPalindrome(x));

        System.out.println();
        System.out.println("== Armstrong 153: dry run (3 digits, so cube each) ==");
        System.out.printf("%-8s %-6s %-8s %-6s%n", "digit", "power", "value", "sum");
        int sum = 0;
        for (int t = 153; t > 0; t /= 10) {
            int d = t % 10, v = d * d * d;
            sum += v;
            System.out.printf("%-8d %-6d %-8d %-6d%n", d, 3, v, sum);
        }
        System.out.println("Armstrong numbers 1..10000: " + collect(NumberPrograms::isArmstrong, 1, 10000));
        System.out.println("isArmstrong(154) = " + isArmstrong(154));

        System.out.println();
        System.out.println("== Perfect 28: divisor pairs found up to sqrt(28) ==");
        System.out.printf("%-4s %-10s %-10s%n", "i", "28 % i", "running sum");
        int ps = 1;
        System.out.printf("%-4s %-10s %-10d%n", "1", "0", ps);
        for (int i = 2; i * i <= 28; i++) {
            if (28 % i == 0) {
                ps += i;
                if (i != 28 / i) ps += 28 / i;
            }
            System.out.printf("%-4d %-10d %-10d%n", i, 28 % i, ps);
        }
        System.out.println("Perfect numbers 1..10000: " + collect(NumberPrograms::isPerfect, 1, 10000));

        System.out.println();
        System.out.println("== Strong 145: digit factorials ==");
        System.out.printf("%-8s %-10s %-6s%n", "digit", "digit!", "sum");
        int fs = 0;
        int[] f = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
        for (int t = 145; t > 0; t /= 10) {
            fs += f[t % 10];
            System.out.printf("%-8d %-10d %-6d%n", t % 10, f[t % 10], fs);
        }
        System.out.println("Strong numbers 1..100000: " + collect(NumberPrograms::isStrong, 1, 100000));
    }
}
