package test;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

public class AnisotropyTest implements GLEventListener
{
    public static int IMAGE_WIDTH=1280;
    public static int IMAGE_HEIGHT=960;
    
    HashMap textures=new HashMap();
    TextureLoader loader=new TextureLoader();
    
    /**
     * Set to 0 or 1 to turn off anisotropic filtering.
     * Set to 16 for highest anisotropic filtering quality.
     */
    protected float maxAnisotropy=16;
    
    public AnisotropyTest()
    {    
    }
    
    protected void bindTexture(GL gl, String textureName)
    {
        Object o=textures.get(textureName);
        if(o==null)
        {
            BufferedImage bi=loader.loadTexture(textureName);
            if(bi==null) return;
            
            DirectBufferedImage image=(DirectBufferedImage)bi;
            int format;
            switch(image.getDirectType())
            {
                case DirectBufferedImage.DIRECT_RGB: format=GL.GL_RGB;
                                                      break;
                case DirectBufferedImage.DIRECT_RGBA: format=GL.GL_RGBA;
                                                      break;
                default:
                    throw new RuntimeException(
                    "bindTexture: unknown coloring type "+image.getDirectType());
            }
            
            int texName[]=new int[1];
            makeTexture(gl, bi, format, texName);
            
            textures.put(textureName, texName[0]);
        }
        else
        {
            int texture=((Integer)o).intValue();
            gl.glBindTexture(GL.GL_TEXTURE_2D, texture);
        }
    }
    
    private void makeTexture(GL gl, BufferedImage image, int format, int texName[])
    {
        gl.glGenTextures (1, texName, 0);

        gl.glBindTexture (GL.GL_TEXTURE_2D, texName[0]);
        
        gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
        gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);

        gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER,
            GL.GL_LINEAR);
        gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,
            GL.GL_LINEAR_MIPMAP_LINEAR);
        gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_GENERATE_MIPMAP, GL.GL_TRUE);
        
        if(gl.isExtensionAvailable("GL_EXT_texture_filter_anisotropic") &&
                (maxAnisotropy<0 || maxAnisotropy>1)) 
        {
            float max[] = new float[1];
            gl.glGetFloatv(GL.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, max, 0 );
            // in case a user defined maxAnisotropy is provided, use lower value of both limits
            gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAX_ANISOTROPY_EXT,
                    maxAnisotropy>0 ? Math.min(maxAnisotropy, max[0]) : max[0]);
        }
        
        DataBufferByte bb=(DataBufferByte)image.getData().getDataBuffer();
        Buffer buffer=ByteBuffer.wrap(bb.getData());
        
        if(image.getWidth()<4)
        {
            int a[]=new int[1];
            gl.glGetIntegerv(GL.GL_UNPACK_ALIGNMENT, a, 0);
            int alignment=a[0];
            
            if(alignment>1)
            {
                int bytesPerElement=format==GL.GL_RGB ? 3 : 4;
                int srcRowLength=image.getWidth()*bytesPerElement;
                
                int padding = srcRowLength % alignment;
                if(padding>0)
                {
                    int dstRowLength=srcRowLength + alignment - padding;
                    
                    byte src[]=bb.getData();
                    byte dst[]=new byte[dstRowLength*image.getHeight()];
                    
                    for(int y=0, s=0, d=0; y<image.getHeight(); y++, s+=srcRowLength, d+=dstRowLength)
                    {
                        System.arraycopy(src, s, dst, d, srcRowLength);
                    }
                    
                    buffer=ByteBuffer.wrap(dst);
                }
            }
        }
        
        gl.glTexImage2D (GL.GL_TEXTURE_2D, 0, format, image.getWidth(),
            image.getHeight(), 0, format, GL.GL_UNSIGNED_BYTE, buffer);
    }
    
    /**
     * Deletes all cached textures from the specified GL and removes
     * all objects from the cache.
     */
    protected void deleteTextures(GL gl)
    {
        int texNames[]=new int[textures.size()];
        
        int i=0;
        for(Iterator it=textures.values().iterator(); it.hasNext(); )
        {
            texNames[i++]=((Integer)it.next()).intValue();
        }
        textures.clear();
        
        if(texNames.length>0) gl.glDeleteTextures(texNames.length, texNames, 0);
    }
    
    public void display(GLAutoDrawable drawable)
    {
        GL gl=drawable.getGL();
        GLU glu=new GLU();
        
        gl.glEnable(GL.GL_DEPTH_TEST);
        
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        
        
        // glu.gluPerspective(60, 1.0 * 640 / 480, 0.1, 300.0);
        // the same ass above
        double persp[]= {1.299038, 0.0, 0.0, 0.0, 0.0, 1.7320509, 0.0, 0.0, 0.0, 0.0, -1.002002, -1.0, 0.0, 0.0, -0.2002002, 0.0};
        gl.glLoadMatrixd(persp, 0);
        
        gl.glMatrixMode(GL.GL_MODELVIEW);
        double camera[]= {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, -5.1, -10.0, 1.0};
        gl.glLoadMatrixd(camera, 0);
        
        
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

        gl.glEnable(GL.GL_TEXTURE_2D);

        // https://upload.wikimedia.org/wikipedia/commons/9/92/Cobblestones.JPG
        bindTexture(gl, "Cobblestones.JPG");
        
        double width=2272;
        double height=1704;
        
        double scale=40;
        float w=(float)(width/scale);
        float h=(float)(height/scale);
        
        gl.glBegin(GL.GL_QUADS);
        
        gl.glTexCoord2f(0.0f, 0.0f);
        gl.glVertex3f(-w/2, 0.0f, -h);
        gl.glTexCoord2f(0.0f, 10.0f);
        gl.glVertex3f(w/2, 0.0f, -h);
        gl.glTexCoord2f(10.0f, 10.0f);
        gl.glVertex3f(w/2, 0.0f, 0.0f);
        gl.glTexCoord2f(10.0f, 0.0f);
        gl.glVertex3f(-w/2, 0.0f, 0.0f);
        
        gl.glEnd();
        
        gl.glFinish();

        deleteTextures(gl);
    }

    public void displayChanged(GLAutoDrawable drawable,
        boolean modeChanged, boolean deviceChanged)
    {
    }

    public void init(GLAutoDrawable drawable)
    {
    }

    public void reshape(GLAutoDrawable drawable,
        int x, int y, int width, int height)
    {
    }
    
    public static void main(String[] args)
    {
        AnisotropyTest anisotropyTest=new AnisotropyTest();
        
        OSMesaDrawable drawable=new OSMesaDrawable();
        drawable.addGLEventListener(anisotropyTest);

        DirectBufferedImage image=DirectBufferedImage.getDirectImageRGBA(IMAGE_WIDTH, IMAGE_HEIGHT);
        drawable.setImage(image);

        drawable.display();

        try
        {
            ImageIO.write(image, "png", new File("test_aniso.png"));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }

        drawable.removeGLEventListener(anisotropyTest);
        drawable.dispose();
    }
}
