/** 
 *  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.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;

/**
 * Convert screen coordinates to an index in the sequence.
 * @author Gaspar Sinai
 * @version 2008-04-28
 */
public class CoordConverter {

    /**
     * IOptions TYPE_XXX
     */
    private final int type;
    private final int cells;
    private final int width;
    private final int height;
    private final int map[];

    /**
     * Initialize the coordinate converter.
     * @param type is the type of conversion.
     * @param cells show how many cells we have in a row/column.
     */
    public CoordConverter (int type, int cells) {
        this (type, cells, cells);
    }

    public CoordConverter (int type, int width, int height) {
        this.type = type;
        this.width = width; 
        this.height = height; 
        this.cells = (width > height) ? width : height;
        switch (type) {
        case IOptions.TYPE_SPIRAL:
            map = initSpiral ();
            break;
        case IOptions.TYPE_PACKED_TRIANGLE:
           map = initPackedTriangle ();
           break;
        case IOptions.TYPE_TRIANGLE:
        default:
           map = initTriangle ();
        }
    }

    /**
     * Convert screen x, y coordinates into sequence number, assuming
     * cellSize = 1.
     * Note that on the screen y is growing downwards.
     * @return -1 if it is offscreen.
     */
    public int convert (int x, int y) {
        if (x < 0 || x >= cells) return 0; 
        if (y < 0 || y >= cells) return 0; 
        switch (type) {
        case IOptions.TYPE_SPIRAL:
            return map[x + (cells-width+1)/2 
                + (height-y-1 + (cells-height+1)/2) * cells];
        case IOptions.TYPE_TRIANGLE:
        case IOptions.TYPE_PACKED_TRIANGLE:
        default:
            return map[x + (height-y-1) * cells];
       } 
    }

    /**
     * @return the map that will be used in the conversion.
     */
    private int[] initSpiral () {
        int ret[] = new int [cells*cells];
        for (int i=0; i<ret.length; i++) {
            long xy[] = spiralToXY ((long)i);
            // shift xy into 0..cells-1 range
            xy[0] = xy[0] + (cells-1) / 2;
            xy[1] = xy[1] + (cells-1) / 2;
            ret [(int)xy[0] + (int)xy[1]*cells] = i;
        }
        return ret;
    }

    /**
     * @return the map that will be used in the conversion.
     */
    private int[] initTriangle () {
        int ret[] = new int [cells*cells];
        for (int i=0; i<ret.length; i++) ret[i] = -1;
        int pos = 0;
        // The real map is 4 times bigger.
        for (int i=0; i< 2 * cells; i++) {
            for (int j=0; j <= 2 * i; j++) {
                int x = 2 * i - j;
                int y = j;
                if (x < cells && y < cells) {
                    ret[x + cells*y] = pos;   
                }
                pos++;
            }
        }
        return ret;
    }
    /**
     * @return the map that will be used in the conversion.
     */
    private int[] initPackedTriangle () {
        int ret[] = new int [cells*cells];
        for (int i=0; i<ret.length; i++) ret[i] = -1;
        int pos = 0;
        // The real map is 4 times bigger.
        for (int i=0; i< cells + (cells+1)/2; i++) {
            for (int j=0; j <= i; j++) {
                int x = i - j;
                int y = j;
                if (x < cells && y < cells && pos < cells*cells) {
                    ret[x + cells*y] = pos;   
                }
                pos++;
            }
        }
        return ret;
    }

    /**
     * Convert spiral coordinates to x,y. 
     *  432
     *  501 
     * @param spiral is the spiral coordinate number.
     * @return an array of size 2. array[0] = x, array[1] = y.
     *   Origo is at 0.
     */ 
    private static long[] spiralToXY (long spiral) {
        long shell = (long) (Math.sqrt ((double) spiral / 4));
        long posInShell = spiral - 4 * shell * shell;
        // 1 3 5 7
        long quadrantSize = ((shell+1)*(shell+1) - shell*shell);
        long quadrant = posInShell / quadrantSize;
        long quadrantPos = posInShell - quadrant * quadrantSize;
        switch ((int)quadrant) {
        case 0: // left quadrant
            return new long [] {
                -quadrantSize / 2,
                quadrantSize / 2 - quadrantPos 
            };
        case 1: // bottom quadrant
            return new long [] {
                -quadrantSize / 2 + quadrantPos + 1,
                -quadrantSize / 2
            };
        case 2: // right quadrant
            return new long [] {
                quadrantSize / 2 + 1,
                -quadrantSize / 2 + quadrantPos + 1
            };
        case 3: // top quadrant
            return new long [] {
                quadrantSize / 2 - quadrantPos,
                quadrantSize / 2 + 1, 
            };
        } 
        // never happens
        return new long[] {0, 0};
    }
}

