package org.sunflow.math;

import org.sunflow.system.UI;
import org.sunflow.system.UI.Module;

public final class QMC {
    public static final int MAX_SIGMA_ORDER = 15;
    private static final int NUM = 128;
    private static final int[][] SIGMA = new int[NUM][];
    private static final int[] PRIMES = new int[NUM];
    private static final int[] FIBONACCI = new int[47];
    private static final double[] FIBONACCI_INV = new double[FIBONACCI.length];
    private static final double[] KOROBOV = new double[NUM];

    static {
        UI.printInfo(Module.QMC, "Initializing Faure scrambling tables ...");
        // build table of first primes
        PRIMES[0] = 2;
        for (int i = 1; i < PRIMES.length; i++)
            PRIMES[i] = nextPrime(PRIMES[i - 1]);
        int[][] table = new int[PRIMES[PRIMES.length - 1] + 1][];
        table[2] = new int[2];
        table[2][0] = 0;
        table[2][1] = 1;
        for (int i = 3; i <= PRIMES[PRIMES.length - 1]; i++) {
            table[i] = new int[i];
            if ((i & 1) == 0) {
                int[] prev = table[i >> 1];
                for (int j = 0; j < prev.length; j++)
                    table[i][j] = 2 * prev[j];
                for (int j = 0; j < prev.length; j++)
                    table[i][prev.length + j] = 2 * prev[j] + 1;
            } else {
                int[] prev = table[i - 1];
                int med = (i - 1) >> 1;
                for (int j = 0; j < med; j++)
                    table[i][j] = prev[j] + ((prev[j] >= med) ? 1 : 0);
                table[i][med] = med;
                for (int j = 0; j < med; j++)
                    table[i][med + j + 1] = prev[j + med] + ((prev[j + med] >= med) ? 1 : 0);
            }
        }
        for (int i = 0; i < PRIMES.length; i++) {
            int p = PRIMES[i];
            SIGMA[i] = new int[p];
            System.arraycopy(table[p], 0, SIGMA[i], 0, p);
        }
        UI.printInfo(Module.QMC, "Initializing lattice tables ...");
        FIBONACCI[0] = 0;
        FIBONACCI[1] = 1;
        for (int i = 2; i < FIBONACCI.length; i++) {
            FIBONACCI[i] = FIBONACCI[i - 1] + FIBONACCI[i - 2];
            FIBONACCI_INV[i] = 1.0 / FIBONACCI[i];
        }
        KOROBOV[0] = 1;
        for (int i = 1; i < KOROBOV.length; i++)
            KOROBOV[i] = 203 * KOROBOV[i - 1];
    }

    private static final int nextPrime(int p) {
        p = p + (p & 1) + 1;
        while (true) {
            int div = 3;
            boolean isPrime = true;
            while (isPrime && ((div * div) <= p)) {
                isPrime = ((p % div) != 0);
                div += 2;
            }
            if (isPrime)
                return p;
            p += 2;
        }
    }

    private QMC() {
    }

    public static double riVDC(int bits, int r) {
        bits = (bits << 16) | (bits >>> 16);
        bits = ((bits & 0x00ff00ff) << 8) | ((bits & 0xff00ff00) >>> 8);
        bits = ((bits & 0x0f0f0f0f) << 4) | ((bits & 0xf0f0f0f0) >>> 4);
        bits = ((bits & 0x33333333) << 2) | ((bits & 0xcccccccc) >>> 2);
        bits = ((bits & 0x55555555) << 1) | ((bits & 0xaaaaaaaa) >>> 1);
        bits ^= r;
        return (double) (bits & 0xFFFFFFFFL) / (double) 0x100000000L;
    }

    public static double riS(int i, int r) {
        for (int v = 1 << 31; i != 0; i >>>= 1, v ^= v >>> 1)
            if ((i & 1) != 0)
                r ^= v;
        return (double) (r & 0xFFFFFFFFL) / (double) 0x100000000L;
    }

    public static double riLP(int i, int r) {
        for (int v = 1 << 31; i != 0; i >>>= 1, v |= v >>> 1)
            if ((i & 1) != 0)
                r ^= v;
        return (double) (r & 0xFFFFFFFFL) / (double) 0x100000000L;
    }

    public static final double halton(int d, int i) {
        // generalized Halton sequence
        switch (d) {
            case 0: {
                i = (i << 16) | (i >>> 16);
                i = ((i & 0x00ff00ff) << 8) | ((i & 0xff00ff00) >>> 8);
                i = ((i & 0x0f0f0f0f) << 4) | ((i & 0xf0f0f0f0) >>> 4);
                i = ((i & 0x33333333) << 2) | ((i & 0xcccccccc) >>> 2);
                i = ((i & 0x55555555) << 1) | ((i & 0xaaaaaaaa) >>> 1);
                return (double) (i & 0xFFFFFFFFL) / (double) 0x100000000L;
            }
            case 1: {
                double v = 0;
                double inv = 1.0 / 3;
                double p;
                int n;
                for (p = inv, n = i; n != 0; p *= inv, n /= 3)
                    v += (n % 3) * p;
                return v;
            }
            default:
        }
        int base = PRIMES[d];
        int[] perm = SIGMA[d];
        double v = 0;
        double inv = 1.0 / base;
        double p;
        int n;
        for (p = inv, n = i; n != 0; p *= inv, n /= base)
            v += perm[n % base] * p;
        return v;
    }

    /**
     * Compute mod(x,1), assuming that x is positive or 0.
     * 
     * @param x any number >= 0
     * @return mod(x,1)
     */
    public static final double mod1(double x) {
        // assumes x >= 0
        return x - (int) x;
    }

    /**
     * Compute sigma function used to seed QMC sequence trees. The sigma table
     * is exactly 2^order elements long, and therefore i should be in the: [0,
     * 2^order) interval. This function is equal to 2^order*halton(0,i)
     * 
     * @param i index
     * @param order
     * @return sigma function
     */
    public static final int sigma(int i, int order) {
        assert order > 0 && order < 32;
        assert i >= 0 && i < (1 << order);
        i = (i << 16) | (i >>> 16);
        i = ((i & 0x00ff00ff) << 8) | ((i & 0xff00ff00) >>> 8);
        i = ((i & 0x0f0f0f0f) << 4) | ((i & 0xf0f0f0f0) >>> 4);
        i = ((i & 0x33333333) << 2) | ((i & 0xcccccccc) >>> 2);
        i = ((i & 0x55555555) << 1) | ((i & 0xaaaaaaaa) >>> 1);
        return i >>> (32 - order);
    }

    public static final int getFibonacciRank(int n) {
        int k = 3;
        while (FIBONACCI[k] <= n)
            k++;
        return k - 1;
    }

    public static final int fibonacci(int k) {
        return FIBONACCI[k];
    }

    public static final double fibonacciLattice(int k, int i, int d) {
        return d == 0 ? i * FIBONACCI_INV[k] : mod1((i * FIBONACCI[k - 1]) * FIBONACCI_INV[k]);
    }

    public static final double reducedCPRotation(int k, int d, double x0, double x1) {
        int j1 = FIBONACCI[2 * ((k - 1) >> 2) + 1];
        int j2 = FIBONACCI[2 * ((k + 1) >> 2)];
        if (d == 1) {
            j1 = ((j1 * FIBONACCI[k - 1]) % FIBONACCI[k]);
            j2 = ((j2 * FIBONACCI[k - 1]) % FIBONACCI[k]) - FIBONACCI[k];
        }
        return (x0 * j1 + x1 * j2) * FIBONACCI_INV[k];
    }

    public static final double korobovLattice(int m, int i, int d) {
        return mod1(i * KOROBOV[d] / (1 << m));
    }
}