package resampler.ui;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.nio.FloatBuffer;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;
import javax.swing.JInternalFrame;
import javax.swing.JScrollPane;
import javax.vecmath.Point2f;




/**
 * An internal frame that draws a image buffer to the viewport.
 * 
 * @author likan
 *
 */
public class ImageFrame extends JInternalFrame 
                        implements GLEventListener,
                                   MouseListener,
                                   MouseMotionListener
{
    protected static final int DEFAULT_FRAME_WIDTH = 400;
    protected static final int DEFAULT_FRAME_HEIGHT = 300;

    // UI related
    protected final MainFrame mainFrame;
    protected final GLScrollablePanel panel;
    protected final JScrollPane scroll;
    protected int width = DEFAULT_FRAME_WIDTH;
    protected int height = DEFAULT_FRAME_HEIGHT;
    protected FloatBuffer imageBuffer = null;
    protected GLU glu;
    // coordinates of the four corners, coded in OpenGL coordinate
    // system, which is y axis bottom up
    // used in axis aligned drag, left top and bottom right corner
    // of the box
    protected int left, right, top, bottom;
    // used in perspective drag mode, four vertices of the quadrilateral
    protected Point2f[] vertices = new Point2f[4];
    
    public static final int BOX_SIZE = 10;
    public static final int MAGRIN_SIZE = 10;
    public static final int LINE_WIDTH = 3;

    // handle mouse events
    // the index of the selected vertex, -1 stands for
    // not selected, 0-3 stands for top left, top right,
    // bottom right and bottom left, respectively.
    protected int selectedVertex = -1;

    /**
     * Construct the ImageFrame with indicated title and MainFrame object
     * @param title
     * @param mainFrame
     */
    public ImageFrame(String title, MainFrame mainFrame)
    {
        super(title, true, false, true, true);
        this.mainFrame = mainFrame;
        Container cp = getContentPane();
        cp.setLayout(new BorderLayout());
        
        GLCapabilities capabilities = new GLCapabilities();
        capabilities.setDoubleBuffered(true);
        panel = new GLScrollablePanel(capabilities);
        panel.addGLEventListener(this);
        panel.setPreferredSize(new Dimension(DEFAULT_FRAME_WIDTH,DEFAULT_FRAME_HEIGHT));
        panel.addMouseListener(this);
        panel.addMouseMotionListener(this);
        scroll = new JScrollPane(panel);
        cp.add(scroll, BorderLayout.CENTER);
        pack();
    }
    /**
     * update the image frame buffer
     * @param buffer
     * @param width
     * @param height
     */
    public void setImageBuffer(FloatBuffer buffer, int width, int height)
    {
        imageBuffer = buffer;
        this.width = width;
        this.height = height;
        panel.setPreferredSize(new Dimension(width + MAGRIN_SIZE * 2, height + MAGRIN_SIZE * 2));
        left = 0;
        top = height - 1;
        right = width - 1;
        bottom = 0;
        vertices[0] = new Point2f(left, top);
        vertices[1] = new Point2f(right, top);
        vertices[2] = new Point2f(right, bottom);
        vertices[3] = new Point2f(left, bottom);
        
        updateSize();
    }
    
    /**
     * handle the display event
     */
    public void display(GLAutoDrawable glDrawable)
    {
        GL gl = glDrawable.getGL();
        gl.glClear(GL.GL_COLOR_BUFFER_BIT);
        if(imageBuffer != null)
        {
            // draw the image
            synchronized (this)
            {
                gl.glRasterPos2i(MAGRIN_SIZE, MAGRIN_SIZE);
                imageBuffer.rewind();
                gl.glDrawPixels(width, 
                        height, 
                        GL.GL_RGB, 
                        GL.GL_FLOAT, 
                        imageBuffer);
            }
            gl.glColor3f(1, 1, 0);
            
            if(mainFrame.getTransformMode() == MainFrame.MODE_AXIS_ALIGNED_TRANSFORM
                    || mainFrame.getTransformMode() == MainFrame.MODE_PERSPECTIVE_TRANSFORM)
            {
                Point2f[] vertices = getVertices();
                // draw four corners
                for(int i = 0; i < 4; i++)
                {
                    gl.glRectf(vertices[i].x - BOX_SIZE + MAGRIN_SIZE, 
                            vertices[i].y - BOX_SIZE + MAGRIN_SIZE, 
                            vertices[i].x + BOX_SIZE + MAGRIN_SIZE, 
                            vertices[i].y + BOX_SIZE + MAGRIN_SIZE);
                }

                // draw the frame box
                gl.glLineWidth(LINE_WIDTH);
                gl.glBegin(GL.GL_LINE_LOOP);
                for(int i = 0; i < 4; i++)
                {
                    gl.glVertex2f(vertices[i].x + MAGRIN_SIZE, vertices[i].y + MAGRIN_SIZE);
                }
                gl.glEnd();
            }
            gl.glFlush();
        }
    }
    
    /**
     * get the positions of four vertices, depending on
     * the transformation mode.
     * These coordinates are coded in OpenGL coordinate system,
     * which is y axis bottom up.
     * @return
     */
    public Point2f[] getVertices()
    {
        if(mainFrame.getTransformMode() == MainFrame.MODE_AXIS_ALIGNED_TRANSFORM)
        {
            Point2f []vertices = new Point2f[4];
            vertices[0] = new Point2f(left, top);
            vertices[1] = new Point2f(right, top);
            vertices[2] = new Point2f(right, bottom);
            vertices[3] = new Point2f(left, bottom);
            return vertices;
        }
        else if(mainFrame.getTransformMode() == MainFrame.MODE_PERSPECTIVE_TRANSFORM)
        {
            return vertices;
        }
        else
        {
            return null;
        }
    }
    
    /**
     * indicates whether the dragging is in process
     * @return
     */
    public boolean isMouseDragging()
    {
        return selectedVertex != -1;
    }

    /**
     * update the window size
     */
    protected void updateSize()
    {
        this.pack();
        int width = Math.min(this.getWidth(), mainFrame.getWidth() * 2/3);
        int height = Math.min(this.getHeight(), mainFrame.getHeight() * 2/3);
        this.setSize(width, height);
    }

    /**
     * handle the displayChanged event
     */
    public void displayChanged(GLAutoDrawable glDrawable, 
                               boolean modeChanged, 
                               boolean deviceChanged)
    {
        // don't need to handle this event
    }

    /**
     * handle the init event
     */
    public void init(GLAutoDrawable glDrawable)
    {
        GL gl = glDrawable.getGL();
        glu = new GLU();

        gl.glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
        gl.glShadeModel(GL.GL_FLAT);
    }

    /**
     * handle the reshape event
     */
    public void reshape(GLAutoDrawable glDrawable, 
            int x, int y, 
            int width, int height)
    {
        GL gl = glDrawable.getGL();

        gl.glViewport(0, 0, width, height);
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluOrtho2D(0, width, 0, height);
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
    }

    /**
     * handle the mousePressed event
     */
    public void mousePressed(MouseEvent e)
    {
        // handle the event only when the mainFrame is not busy
        if(imageBuffer != null && !mainFrame.isComputing())
        {
            int cx = e.getX() - MAGRIN_SIZE;
            int cy = height - 1 - e.getY() + MAGRIN_SIZE;
            if(mainFrame.getTransformMode() == MainFrame.MODE_AXIS_ALIGNED_TRANSFORM
                    || mainFrame.getTransformMode() == MainFrame.MODE_PERSPECTIVE_TRANSFORM)
            {
                Point2f[] vertices = getVertices();
                selectedVertex = -1;
                for(int i = 0; i < 4; i++)
                {
                    if(Math.abs(cx - vertices[i].x) <= BOX_SIZE
                            && Math.abs(cy - vertices[i].y) <= BOX_SIZE)
                    {
                        selectedVertex = i;
                        break;
                    }
                }
            }
        }
    }

    /**
     * handle the mouseRelease event
     */
    public void mouseReleased(MouseEvent e)
    {
        // don't have to check if the mainFrame is busy
        // because the dragging is already in process
        if(selectedVertex != -1)
        {
            selectedVertex = -1;
            mainFrame.resample();
            repaint();
        }
    }

    /**
     * handle the mouseDragged event
     */
    public void mouseDragged(MouseEvent e)
    {
        if(selectedVertex != -1)
        {
            int cx = e.getX() - MAGRIN_SIZE;
            int cy = height - 1 - e.getY() + MAGRIN_SIZE;
            if (mainFrame.getTransformMode() == MainFrame.MODE_AXIS_ALIGNED_TRANSFORM)
            {
                if(selectedVertex == 0)
                {
                    top = Math.min(Math.max(cy, bottom + 1), height - 1);
                    left = Math.max(Math.min(cx, right - 1), 0);
                }
                else if(selectedVertex == 1)
                {
                    top = Math.min(Math.max(cy, bottom + 1), height - 1);
                    right = Math.min(Math.max(cx, left + 1), width - 1);
                }
                else if(selectedVertex == 2)
                {
                    bottom = Math.max(Math.min(cy, top - 1), 0);
                    right = Math.min(Math.max(cx, left + 1), width - 1);
                }
                else if(selectedVertex == 3)
                {
                    bottom = Math.max(Math.min(cy, top - 1), 0);
                    left = Math.max(Math.min(cx, right - 1), 0);
                }
            }
            else if(mainFrame.getTransformMode() == MainFrame.MODE_PERSPECTIVE_TRANSFORM)
            {
                vertices[selectedVertex].set(Math.max(Math.min(cx, width - 1), 0),
                                Math.max(Math.min(cy, height - 1), 0));
            }
            repaint();
        }
    }

    public void mouseClicked(MouseEvent e)
    {
        // nothing needs to be here
    }

    public void mouseEntered(MouseEvent e)
    {
        // nothing needs to be here
    }

    public void mouseExited(MouseEvent e)
    {
        // nothing needs to be here
    }

    public void mouseMoved(MouseEvent e)
    {
        // nothing needs to be here
    }
}
