package nz.util;

/**
 * Very simple sparse array class that maintains
 * a set of pages, so that a sparse array can be
 * reasonably fast but not too big.  This one
 * holds object pointers.  This is probably
 * not as efficient as a true sparse array, but
 * it is simpler.  The page size is adjustable,
 * but must be a power of 2; 32 and 64 are usually
 * reasonable choices.
 * <p>
 * The current implementation is wasteful of space
 * for arrays where the indices are very spread out.
 * It is efficiently only for arrays where values
 * tend to be set in tight little clusters.
 */

public class PagedArray implements java.io.Serializable {
    public static int pageCountTotal = 0;
    private Object pages[]	 = null;
    private int maxindex	 = 0;
    private int pagesize	 = 0;
    private int ndexmask	 = 0;
    private int ndexbits	 = 0;

    private final void initPagesArray(Object [] old, int cnt) {
	if (cnt == 0) return;
	pages = new Object[cnt];
	if (old != null) 
	    System.arraycopy(old, 0, pages, 0, 
			     ((cnt > old.length)?(old.length):(cnt)));
    }

    /**
     * Create a PagedArray of size upto 65536, with
     * a page size of 128, and an initialized page set
     * of 2.
     */
    public PagedArray() {
	this(65536, 7, 0);
    }

    /**
     * Create a PagedArray of maximum size maxsiz, with
     * a page size of 2^indexbits, and an initialized page
     * set of initcnt.
     *
     * @param maxsiz maximum size of this sparse array
     * @param indexbits number of bits in the index within a page (usually 4-8)
     * @param initcnt how many of the bins to initialize (usually 0)
     */
    public PagedArray(int maxsiz, int indexbits, int initcnt) {
	maxindex = maxsiz;
	ndexbits = indexbits;
	pagesize = (1 << ndexbits);
	ndexmask = (pagesize - 1);
	initPagesArray(null, initcnt);
    }

    /**
     * Get an item from the PagedArray.  Returns null
     * if that index in the array is empty.
     *
     * @param n array index
     */
    public final Object get(int n) {
	if (n < maxindex && pages != null) {
	    int page = n >> ndexbits;

	    if (page < pages.length) {
		Object pobj = pages[page];
		if (pobj != null) {
		    return ((Object [])pobj)[n & ndexmask];
		}
	    }
	}
	return null;
    }

    /**
     * Get the next valid index after a given one, or 
     * return -1 if there are no more.  A valid index is one where
     * non-null value is stored.
     *
     * @param n an array index
     * @returns next valid index, or -1 if there arent any
     */
    public int next(int n) {
	int ret = -1;
	if (pages == null) return ret;
	Object pobj;
	n++;
	int page = n >> ndexbits;
	int ndex = n & ndexmask;

	outer: for( ;page < pages.length; page++, ndex = 0) {
	    pobj = pages[page];
	    if (pobj != null) {
		inner: for( ; ndex < pagesize; ndex++) {
		    if (((Object [])pobj)[ndex] != null) {
			ret = (page << ndexbits) + ndex;
			break outer;
		    }
		}
	    }
	}
	return ret;
    }

    /**
     * Put an item into the PagedArray.  Any old value
     * is overwritten.  May cause the array to grow,
     * or even portions of the pages array to be copied.
     *
     * @param n array index
     * @param v new value in the array
     */
    public final void put(int n, Object v) {
	if (n < 0) throw new IllegalArgumentException("Negative index");
	if (n > maxindex) throw new IllegalArgumentException("Out of range");
	int page = n >> ndexbits;
	int ndex = n & ndexmask;

	if (pages == null || page >= pages.length) {
	    initPagesArray(pages, page+1);
	}
	Object pobj = pages[page];
	if (pobj == null) {
	    pobj = (pages[page] = new Object[pagesize]);
	    pageCountTotal++;
	}
	((Object [])pobj)[ndex] = v;
	return;
    }

    public static void main(String [] args) {
	PagedArray pa = new PagedArray();

	pa.put(4,"Four");
	pa.put(14,"Fourteen");
	pa.put(200,"Two hundred");
	pa.put(10000,"Ten thousand");
	pa.put(10001,"Ten thousand and one");
	pa.put(4096,"Two to the 12th");
	
	for(int i = 0; i>= 0 && i < 20000; ) {
	    Object o = pa.get(i);
	    if (o != null) {
		System.out.println("  " + i + ": " + o.toString());
		i++;
	    }
	    else {
		i = pa.next(i);
		System.out.println("Skipping to " + i);
	    }
	}
    }
}

