/** 
 *  BlackHole Source File
 *
 *  GNU Copyright (C) 2008  Gaspar Sinai gaspar(at)adys.org 
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package sinai.gaspar.blackhole;

import java.awt.Panel;

/**
 * A Prime stack that will rotate.
 * This class is used in BoardPanel for the applet version.
 *
 * @author Gaspar Sinai
 * @version 2008-04-30
 */
public class PrimeStack  {

    private int cells = 2;

    private boolean primeList[];

    private long current;

    private IChangeListener changeListener = null;

    /**
     * @param cells are cells in a row/column.
     */
    public PrimeStack (int cells) {
        this.cells = cells;
        this.primeList = new boolean[cells*cells];
        long start = 1L;
        markPrimes (primeList, start);
        current = primeList.length + start;
    }

    public void setChangeListener (IChangeListener changeListener) {
        this.changeListener = changeListener;
    }

    public synchronized void setStart (long start) {
        markPrimes (primeList, start);
        current = primeList.length + start;
        if (changeListener != null) changeListener.changed ();
    }

    /**
     * @return the first number in the sequence.
     */
    public synchronized long getStart () {
        return current - (long) primeList.length;
    }

    /**
     * @return true if number in our sequence at index is prime.
     * The sequence starts with getStart()
     */
    public boolean isPrimeAt (int index) {
        if (index < 0 || index > primeList.length) return false;
        return primeList[index];
    }

    /**
     * Shift the whole map.
     */
    public synchronized void forward () {
        for (int i=1; i<primeList.length; i++) {
           primeList[i-1] = primeList[i];
        }
        primeList[primeList.length-1] = isPrime (current);
        current++;
        if (changeListener != null) changeListener.changed ();
    }

    /**
     * Shift the whole map.
     */
    public synchronized void back () {
        if (current <= primeList.length+1) return;
        for (int i=primeList.length-1; i>0; i--) {
           primeList[i] = primeList[i-1];
        }
        current--;
        primeList[0] = isPrime (current-primeList.length);
        if (changeListener != null) changeListener.changed ();
    }

    // The first primes, 2 excluded
    private static final long preTest[];

    static {
        // 5000th prime is 48619 (not including 2)
        // 48619 * 48619 = 2363807161 
        // This is the largest number that can be tested with the array.
        preTest = new long[5000]; // 40 kbytes
        int index =0;
        long number = 3;
        while (index < preTest.length) {
            // This would require sqrt (number) steps, so the array can
            // built while isPrime is using it.
            if (isPrime (number)) {
                preTest[index++] = number;
            }
            number++;
        }
    }

    /**
     * @param number is the number to test
     * @return true if number is a prime.
     */
    public static boolean isPrime (long number) {
        if (number < 2) return false;
        if (number < 4) return true;
        if (number % 2 == 0) return false;

        long divisor = 1;
        long limit = number;

        int index = 0;
        int len = preTest.length;
        // Go through a pre-set of prime numbers first.
        while (limit > divisor && index < len) {
            divisor = preTest [index++];
            // if divisor is 0, internal error.
            if ((number % divisor) == 0) return false;
            limit = number / divisor;
        }
        divisor = divisor + 2;

        while (limit > divisor) {
            if ((number % divisor) == 0) return false;
            limit = number / divisor;
            divisor = divisor + 2;
        }
        return true;
    }

    /**
     * Mark prime number in the array, where array[0] represents
     * start.
     */
    public static void markPrimes (boolean array[], long start) {
        // This method will be faster. 100 is experimental.
        if (start / array.length > 100) {
            for (long i=0; i<array.length; i++) {
                array[(int)i] = isPrime (start+i);
            }
            return;
        }
        for (long i=0; i<array.length; i++) {
            array[(int)i] = (i+start > 1) ;
        }
        long arrayLength = array.length;
        long limitCheck = (start + arrayLength) / 41; // 41 is experimental 
        for (long i=2; i<start+arrayLength; i++) {
            long index = i - start;
            // First level prime checking, in array, or outside. 
            if (index >= 0 && index < arrayLength) {
                if (array[(int)index] == false) continue;
            } else {
                if (i<limitCheck && !isPrime (i))  continue;
            }
            long mark = i+i; 
            while (mark-start < arrayLength) {
                if (mark - start >= 0) {
                    array[(int) (mark-start)] = false;
                }
                mark += i;
            }
        }
    }
}
