/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.math;

import java.util.Random;

/**
 * PRNG generates with very long series without cycle.
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class LFSR extends Random {

    private static final long serialVersionUID = 2L;
    
    private static final int M = 32;
    // hard-coded for 32-bits
    private static final int[] TAPS = {1, 2, 22, 32};
    
    private boolean[] vector;

    public LFSR() {
        this( (int)System.nanoTime() );
    }
    
    public LFSR(int seed) {
        super(seed);
    }
    
    @Override
    public void setSeed(long seed) {
        synchronized(this) {
            vector = new boolean[M + 1];
            for (int i = 0; i < M; i++) {
                vector[i] = (seed & (1 << i)) != 0;
            }
            super.setSeed(0);
        }
    }

    @Override
    public int next(int bits) {
        synchronized(this) {
            // nawet jeśli dla liczby używamy tylko [bits] rejestrów
            // to w następnych krokach wykonujemy standardowe przesunięcie całej sekwencji
            int result = 0;
            for (int i = 0; i < bits; i++) {
                result |= (vector[i] ? 1 : 0) << i;
            }

            // zero jest dopuszczalne. tracimy co prawda wartość -2^31
            if (result < 0) {
                result++;
            }

            // modyfikacja ostatniego rejestru na podstawie wyróżnionych rejestrów
            vector[M] = false;
            for (int i = 0; i < TAPS.length; i++) {
                vector[M] ^= vector[M - TAPS[i]];
            }

            // przesunięcie całego wektora
            for (int i = 0; i < M; i++) {  // NOPMD: licho wie, czy arraycopy bardziej efektywne
                vector[i] = vector[i + 1];
            }

            return result;
        }
    }

}

