
package skyview.geometry.projecter;

/**
 * This class implements bit interleaving for two-d images.
 * The pixel and indices methods are complementary enabling
 * users to transform between a scalar bit-interleaved index and
 * a set of normal array indices.
 * @author tmcglynn
 */
public class Interleave {
    
    /** The number of bits to be interleaved per dimension. */
    private int order;
    
    /** Number of pixels per side */
    int nSide;
    
    /** Number pf  pixels in image */
    int nPixels;
    
    /** The x bits */
    private int[] xb = new int[]{0,1,0,1};
    /** The y bits */
    private int[] yb = new int[]{0,0,1,1};
    
    /** The interleaved bits */
    private int[] ib = new int[]{0,1,2,3};  // 00, 01, 10, 11 
    
    /** The x values for a given interleaved index. */
    private int[] x;
    
    /** The y values for a given interleaved index. */
    private int[] y;
    
    /** The interleaved pixel value  given x and y.  The
     *  X value runs fastest i.e., pixel[y][x] 
     */
    private int[][] pixel;
    
    /** Support rapid conversions from ring to image index */
    private int[] imgIndex;
    
    
    /** Mask off the pixels for an iteration.  The two masks
     *  are used for the separate numbers and for the interleaved numbers respectively.
     */
    private int mask;
    private int mask2;
    
    /** Create an interleaver with a specified buffer size for the array lookups.
     * The interleaver will be able to handle large interleaves but will
     * break them up into bit strings of the specified order length.
     * @param order The number of bits in to be [de]interleaved in an iteration. 
     */
    public Interleave(int order) {
        this.order = order;
        if (order < 1 || order > 15) {
            throw new IllegalArgumentException ("Order for interleave outside range 1-15");
        }
        int  dim1 = (int) Math.pow(2,order);
        int  dim2 = dim1 * dim1;
        nSide   = dim1;
        nPixels = dim2;
        
        // Create the interleaving buffers.
        
        x        = new int[dim2];
        y        = new int[dim2];
        pixel    = new int[dim1][dim1];
        imgIndex = new int[dim2];
        
        // The order 0 element is already properly set in x,y and pixel.
        // Now add the rest in order by order
        int ordSize = 1;
        int p2      = 1;
        int p4      = 1;
        
        // Loop over each order
        for (int ord=1;  ord <= order; ord += 1) {            
            // Make sure the masks have the appropriate number of bits turned on.
            mask |= p2;
            p2   *= 2;
            
            mask2 |= p4;
            p4    *= 2;
            mask2 |= p4;
            p4    *= 2;
            
            // Within each order we loop over the 01,10 and 11 additions. (the previous
            // order is the 00 element
            for (int p=1; p <= 3; p += 1) {
                
                for (int i=0; i<ordSize; i += 1) {
                   int n = p*ordSize + i;
                   x[n] = x[i] | xb[p];
                   y[n] = y[i] | yb[p];
                   pixel[y[n]][x[n]] = pixel[y[i]][x[i]] | ib[p];
                   imgIndex[pixel[y[n]][x[n]]] = x[n] + dim1*y[n];
                }
            }
            // Now get ready for the next size.
            ordSize *= 4;
            for (int i=1; i<4; i += 1) {
                xb[i] <<= 1;
                yb[i] <<= 1;
                ib[i] <<= 2;
            }
        }
    }
    
    /** Create a default interleaver with that can handle 512x512 images in a single
        iteration.
      */
    public Interleave() {
        this(9);
    }
    
    /* Interleave the x and y index values to get an interleaved pixel value.
     * @param x
     * @param y
     * @return 
     */
    public long pixel(int x, int y) {
        long result = 0;
        long scale  = 1;
        while (x != 0 || y != 0) {
            int tx = x&mask;
            int ty = y&mask;
            result += scale*pixel[ty][tx];
            // Look at the next order bitsi in x and y,
            // but recall that we used up 2*order bits in the result pixel.
            x     >>>= order;
            y     >>>= order;
            scale <<= 2*order;
        }
        return result;
    }
    
    /** Separate interleaved pixels bits into the
     *  x and y values.
     * @param pixel
     * @return 
     */
    public int[] indices(long pixel) {
        int[] result = new int[2];
        long scale = 1;
        while (pixel > 0) {
            int tpix = (int)(pixel & mask2);
            result[0] += x[tpix]*scale;
            result[1] += y[tpix]*scale;
            pixel     >>>= 2*order;
            scale     <<= order;
        }
        return result;
    }
    
    /** Given an a pixel with an interleaved index.
     *  return the index into the image as if it simply
     *  indexed in natural image order (y*nx+x)
     * @param pix
     * @param size
     * @return 
     */
    public long ringToImg(long pix, int size) {
        int[] indices = indices(pix);
        return ((long)indices[1])*size + indices[0];
    }
    
    /** This method can only be used if the Interleave order exactly
     *  matches the dimesion of the image.  E.g., if our tiles
     *  have 512x512 pixels then the order must be 9.
     * @param pix
     * @return 
     */
    public int quickRingToImg(int pix) {
        return imgIndex[pix];
    }
    
    /** Given the index in natural image order (y*nx+x), get the
     *  nested index.
     */
    public long ImgToRing(long pix, int size) {
        return pixel((int)(pix%size), (int)(pix/size));
    }
    
    /** This method can only be used if the dimension exactly
     *  matches the order for the Interleave.
     */
    public int quickImgToRing(int pix) {
        return pixel[pix/nSide][pix%nSide];
    }
    
    public static void main(String[] args) throws Exception {
        int  dim = Integer.parseInt(args[0]);
        long pixel = Long.parseLong(args[1]);
        int  x = Integer.parseInt(args[2]);
        int  y = Integer.parseInt(args[3]);
        long start = System.nanoTime();
        Interleave il = new Interleave(dim);
        long end = System.nanoTime();
        System.err.println("Initialization:"+(end-start)+" nanoseconds");
        int sz = (int) Math.pow(2,dim);
        int[] tind = il.indices(pixel);
        System.out.println("Pixel:"+pixel+" -> "+tind[0]+" "+tind[1]);
        long pix = il.pixel(x, y);
        System.out.println("Indices:"+x+","+y+"->"+pix);
    }
}
