package resampler;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.nio.FloatBuffer;
import java.util.Arrays;

import javax.vecmath.Color3f;
import javax.vecmath.SingularMatrixException;

import resampler.ui.MainFrame;

public class Image {
    
    /**
     * image width and height
     */
    public int width, height;
    
    /**
     * data array which contains pixel values.
     * Don't access the array directly.  Use
     * the setters and getters to access to the image.
     */
    private float[] data = null;
    
    /**
     * Creates a new image of given size.
     * @param width
     * @param height
     */
    public Image(int width, int height) {
        this.width = width;
        this.height = height;
        data = new float[3 * width * height];
    }
    
    /**
     * Creates a new image that's a copy of another image.
     * @param otherImage
     */
    public Image(Image otherImage) {
        this(otherImage.width, otherImage.height);
        System.arraycopy(otherImage.data, 0, data, 0, 3 * width * height);
    }
    
    /**
     * Get the pixel value at point (x, y).  
     * It will get wrapped around if it is outside of the image.
     * @param x
     * @param y
     * @param color Pixel value is written here
     */
    public void getPixel(int x, int y, Color3f color) {
        x = x % width;
        y = y % height;
        if (x < 0) x += width;
        if (y < 0) y += height;
        int index = 3 * (y * width + x);
        color.set(data[index], data[index + 1], data[index + 2]);
    }
    
    /**
     * Get the pixel value at point (x, y).
     * It will not wrap around, (x, y) should be 
     * inside the image.  
     * @param x
     * @param y
     * @param color
     */
    public void setPixel(int x, int y, Color3f color) {
        int index = 3 * (y * width + x);
        data[index] = color.x;
        data[index + 1] = color.y;
        data[index + 2] = color.z;
    }
    
    /**
     * Fill the whole image with certain grey value
     * @param value
     */
    public void clear(float value) {
        Arrays.fill(data, value);
    }
    
    /**
     * Copies the data to a buffer (for passing to OpenGL)
     * @param buffer
     */
    public void copyToBuffer(FloatBuffer buffer) {
        buffer.put(data);
    }
    
    // ------------------------------
    // The following is a starting point for a mipmap implementation.
    
    int numLevels = 1;
    float[][] mipmapData;
    int[] widths, heights;
    
    private void buildMipmaps() {
        // TODO Add code here to build the mipmaps

    }
    
    Color3f c00 = new Color3f();
    Color3f c10 = new Color3f();
    Color3f c01 = new Color3f();
    Color3f c11 = new Color3f();
    public void sample2d(float x, float y, int level, Color3f color) {
        // TODO use bilinear interpolation to compute
        // the interpolated pixel value at (x, y) on given mipmap level,
        // and write it to 'color'.
        
    }
    
    /**
     * Access image data of given mipmap level.  Behaves just like getPixel(x, y, color) except
     * that it accesses pixel values from the mipmapData arrays, using the image dimensions
     * for the appropriate level.
     * @param x
     * @param y
     * @param level
     * @param color
     */
    public void getPixel(int x, int y, int level, Color3f color) {
        x = x % widths[level];
        y = y % heights[level];
        if (x < 0) x += widths[level];
        if (y < 0) y += heights[level];
        int index = 3 * (y * widths[level] + x);
        color.set(mipmapData[level][index], mipmapData[level][index + 1], mipmapData[level][index + 2]);
    }
    
    
    // ------------------------------
    // Mundane AWT-related code for converting to and from BufferedImage

    public void setPixel(int x, int y, byte r, byte g, byte b) {
        int index = 3 * (y * width + x);
        data[index + 0] = (r & 0xff) / 255f;
        data[index + 1] = (g & 0xff) / 255f;
        data[index + 2] = (b & 0xff) / 255f;
    }
    
    public Image(BufferedImage bufferedImage) {
        this(bufferedImage.getWidth(), bufferedImage.getHeight());
        if (bufferedImage.getType() == BufferedImage.TYPE_3BYTE_BGR) {
            // This is much faster than waiting for getRGB to do the conversion
            DataBufferByte buffer = (DataBufferByte) bufferedImage.getData().getDataBuffer();
            for (int iy = 0; iy < height; iy++)
                for (int ix = 0; ix < width; ix++) {
                    int offset = (iy * width + ix) * 3;
                    setPixel(ix, height - iy - 1,
                            (byte) buffer.getElem(offset+2), 
                            (byte) buffer.getElem(offset+1), 
                            (byte) buffer.getElem(offset+0));
                }
        } else {
            int []imageData = new int[width * height];
            bufferedImage.getRGB(0, 0, width, height, 
                    imageData, width * (height - 1), -width);
            for (int iy = 0; iy < height; iy++)
                for (int ix = 0; ix < width; ix++) {
                    int pixelValue = imageData[iy * width + ix];
                    setPixel(ix, iy, 
                            (byte) ((pixelValue & 0x00ff0000) >> 16), 
                            (byte) ((pixelValue & 0x0000ff00) >> 8), 
                            (byte) (pixelValue & 0x000000ff));
                }
        }
    }
 
    
    public BufferedImage asBufferedImage() {
        BufferedImage bufferedImage = new BufferedImage(width, 
                height, BufferedImage.TYPE_INT_ARGB);
        int size = width * height;
        int []imageData = new int[size];
        int index = 0;
        for(int i = 0; i < size; i++)
        {
            int r = Math.max(Math.min(Math.round(data[index++] * 255), 255), 0);
            int g = Math.max(Math.min(Math.round(data[index++] * 255), 255), 0);
            int b = Math.max(Math.min(Math.round(data[index++] * 255), 255), 0);
            imageData[i] = b | (g << 8) | (r << 16) | (0xff << 24);
        }
        bufferedImage.setRGB(0, 0, width, height, imageData, size - width, -width);
        return bufferedImage;

    }

}
