package resampler.ui;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.FloatBuffer;

import javax.imageio.ImageIO;
import javax.swing.ButtonGroup;
import javax.swing.JDesktopPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JRadioButtonMenuItem;

import resampler.AxisAlignedResampler;
import resampler.Image;
import resampler.PerspectiveResampler;
import resampler.Resampler;

import com.sun.opengl.util.BufferUtil;

/**
 * Resampler class
 * 
 * @author likan
 * 
 */

public class MainFrame extends JFrame implements ActionListener
{
    // Constants
    // No dragging boxes will be displayed
    public static final int MODE_PICTURE_VIEW = 0;
    // Keep four corners of the region in a rectangle
    public static final int MODE_AXIS_ALIGNED_TRANSFORM = 1;
    // The source and destination region can be arbitrary quadrilateral
    public static final int MODE_PERSPECTIVE_TRANSFORM = 2;
    
    // Source image tiles up repeatedly, as a texture mapping
    // and there will be no clipping
    public static final int MODE_NO_CLIPPING = 0;
    // Clip destination images to the selected region
    public static final int MODE_CLIP_TO_DESTINATION = 1;

    // menu texts
    protected static final String OPEN_MENU_TEXT = "Open";
    protected static final String SAVE_AS_MENU_TEXT = "Save As";
    protected static final String EXIT_MENU_TEXT = "Exit";
    protected static final String AXIS_ALIGNED_DRAGGING_TEXT = "Axis Aligned Dragging";
    protected static final String NON_AXIS_ALIGNED_DRAGGING_TEXT = "Non-Axis Aligned Dragging";
    protected static final String NO_DRAGGING_TEXT = "No Dragging";
    protected static final String NO_CLIPPING_TEXT = "No clipping";
    protected static final String CLIP_TO_DESTINAtION_TEXT = "Clip to destination";
    
    // UI related
    protected final JFileChooser fileChooser = new JFileChooser();
    protected final JDesktopPane desktop;
    protected final SourceImageFrame source;
    protected final ResampledImageFrame resampled;

    // the data buffer for fast access
    protected Image sourceImage = null;
    protected Image resampledImage = null;
    // the FloatBuffer sending the OpenGL for rendering
    protected FloatBuffer sourceImageBuffer = null;
    protected FloatBuffer resampledImageBuffer = null;
    
    protected int transformMode = MODE_AXIS_ALIGNED_TRANSFORM;
    protected int clippingMode = MODE_CLIP_TO_DESTINATION;
    // indicates if it is computing the resampled image
    protected boolean computing = false;
    
    /**
     * @param args
     */
    public static void main(String[] args)
    {
        MainFrame m = new MainFrame();

        m.setVisible(true);
    }

    public MainFrame()
    {
        // create the MainFrame
        super("CS4620 Resampler");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setExtendedState(JFrame.MAXIMIZED_BOTH);
        Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
        setSize(new Dimension(size.width - 200, size.height - 200));
        setLocation(100, 100);
        desktop = new JDesktopPane();
        setContentPane(desktop);

        // create the menu
        JMenuBar bar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
        fileMenu.setMnemonic('F');
        JMenu optionMenu = new JMenu("Options");
        fileMenu.setMnemonic('O');

        BasicAction open = new BasicAction(OPEN_MENU_TEXT, this);
        open.setAcceleratorKey(KeyEvent.VK_O, KeyEvent.CTRL_MASK);
        BasicAction saveAs = new BasicAction(SAVE_AS_MENU_TEXT, this);
        saveAs.setAcceleratorKey(KeyEvent.VK_A, KeyEvent.CTRL_MASK);
        BasicAction exit = new BasicAction(EXIT_MENU_TEXT, this);
        exit.setAcceleratorKey(KeyEvent.VK_Q, KeyEvent.CTRL_MASK);
        BasicAction axisAlignedDragging = new BasicAction(AXIS_ALIGNED_DRAGGING_TEXT, this);
        axisAlignedDragging.setAcceleratorKey(KeyEvent.VK_1, 0);
        JMenuItem menuAxisAligned = new JRadioButtonMenuItem(axisAlignedDragging);
        BasicAction nonAxisAlignedDragging = new BasicAction(NON_AXIS_ALIGNED_DRAGGING_TEXT, this);
        nonAxisAlignedDragging.setAcceleratorKey(KeyEvent.VK_2, 0);
        JMenuItem menuNonAxisAligned = new JRadioButtonMenuItem(nonAxisAlignedDragging);
        BasicAction nonDragging = new BasicAction(NO_DRAGGING_TEXT, this);
        nonDragging.setAcceleratorKey(KeyEvent.VK_3, 0);
        JMenuItem menuNonDragging = new JRadioButtonMenuItem(nonDragging);
        BasicAction repeatMode = new BasicAction(NO_CLIPPING_TEXT, this);
        repeatMode.setAcceleratorKey(KeyEvent.VK_4, 0);
        JMenuItem menuRepeatMode = new JRadioButtonMenuItem(repeatMode);
        BasicAction clampMode = new BasicAction(CLIP_TO_DESTINAtION_TEXT, this);
        clampMode.setAcceleratorKey(KeyEvent.VK_5, 0);
        JMenuItem menuClampMode = new JRadioButtonMenuItem(clampMode);

        fileMenu.add(new JMenuItem(open));
        fileMenu.add(new JMenuItem(saveAs));
        fileMenu.addSeparator();
        fileMenu.add(new JMenuItem(exit));
        
        optionMenu.add(menuAxisAligned);
        optionMenu.add(menuNonAxisAligned);
        optionMenu.add(menuNonDragging);
        ButtonGroup bg = new ButtonGroup();
        bg.add(menuAxisAligned);
        bg.add(menuNonAxisAligned);
        bg.add(menuNonDragging);
        menuAxisAligned.setSelected(true);
        optionMenu.addSeparator();
        optionMenu.add(menuRepeatMode);
        optionMenu.add(menuClampMode);
        bg = new ButtonGroup();
        bg.add(menuRepeatMode);
        bg.add(menuClampMode);
        menuClampMode.setSelected(true);

        bar.add(fileMenu);
        bar.add(optionMenu);
        setJMenuBar(bar);

        // create the UI components
        source = new SourceImageFrame();
        source.setVisible(true);
        desktop.add(source);
        resampled = new ResampledImageFrame();
        resampled.setVisible(true);
        desktop.add(resampled);
        
        // other works
    }

    /**
     * handle the user action
     */
    public void actionPerformed(ActionEvent e)
    {
        // process the command only when it is not busy
        if(!computing && !source.isMouseDragging()
                && !resampled.isMouseDragging())
        {
            String cmd = e.getActionCommand();
            if (cmd.equals(OPEN_MENU_TEXT))
            {
                fileChooser.resetChoosableFileFilters();
                fileChooser.setAcceptAllFileFilterUsed(true);
                if(fileChooser.showOpenDialog(this)
                        == JFileChooser.APPROVE_OPTION)
                {
                    readImageFile();
                }
            }
            else if (cmd.equals(SAVE_AS_MENU_TEXT))
            {
                if(resampledImage != null)
                {
                    fileChooser.resetChoosableFileFilters();
                    fileChooser.setAcceptAllFileFilterUsed(false);
                    if(fileChooser.showSaveDialog(this)
                            == JFileChooser.APPROVE_OPTION)
                    {
                        writeImageFile();
                    }
                }
                else
                {
                    JOptionPane.showMessageDialog(this, "Please open an image file first!");
                }
            }
            else if (cmd.equals(EXIT_MENU_TEXT))
            {
                System.exit(0);
            }
            else if(cmd.equals(AXIS_ALIGNED_DRAGGING_TEXT))
            {
                if(transformMode != MODE_AXIS_ALIGNED_TRANSFORM)
                {
                    transformMode = MODE_AXIS_ALIGNED_TRANSFORM;
                    resample();
                    refresh();
                }
            }
            else if(cmd.equals(NON_AXIS_ALIGNED_DRAGGING_TEXT))
            {
                if(transformMode != MODE_PERSPECTIVE_TRANSFORM)
                {
                    transformMode = MODE_PERSPECTIVE_TRANSFORM;
                    resample();
                    refresh();
                }
            }
            else if(cmd.equals(NO_DRAGGING_TEXT))
            {
                if(transformMode != MODE_PICTURE_VIEW)
                {
                    transformMode = MODE_PICTURE_VIEW;
                    refresh();
                }
            }
            else if(cmd.equals(NO_CLIPPING_TEXT))
            {
                if(clippingMode != MODE_NO_CLIPPING)
                {
                    clippingMode = MODE_NO_CLIPPING;
                    resample();
                    refresh();
                }
            }
            else if(cmd.equals(CLIP_TO_DESTINAtION_TEXT))
            {
                if(clippingMode != MODE_CLIP_TO_DESTINATION)
                {
                    clippingMode = MODE_CLIP_TO_DESTINATION;
                    resample();
                    refresh();
                }
            }
        }
    }

    /**
     * read the image from the file
     */
    protected void readImageFile()
    {
        try
        {
            BufferedImage bufferedImage = ImageIO.read(fileChooser.getSelectedFile());
            if(bufferedImage != null)
            {
                int imageWidth = bufferedImage.getWidth();
                int imageHeight = bufferedImage.getHeight();
                sourceImage = new Image(bufferedImage);
                resampledImage = new Image(sourceImage);
                sourceImageBuffer = BufferUtil.newFloatBuffer(3 * imageWidth * imageHeight);
                sourceImage.copyToBuffer(sourceImageBuffer);
                resampledImageBuffer = BufferUtil.newFloatBuffer(3 * imageWidth * imageHeight);
                resampledImage.copyToBuffer(resampledImageBuffer);
                synchronized (source)
                {
                    source.setImageBuffer(sourceImageBuffer, 
                            imageWidth, imageHeight);
                }
                synchronized (resampled)
                {
                    resampled.setImageBuffer(resampledImageBuffer, 
                            imageWidth, imageHeight);
                }
                refresh();
            }
            else
            {
                JOptionPane.showMessageDialog(this, 
                        "The format of the file '" + 
                        fileChooser.getSelectedFile().getPath() + 
                        "' cannot be recognized!");
            }
        }
        catch (IOException e1)
        {
            JOptionPane.showMessageDialog(this, 
                    "Error reading the file '" + 
                    fileChooser.getSelectedFile().getPath() + 
                    "'!");
        }
    }
    
    /**
     * write image to the file
     */
    protected void writeImageFile()
    {
        File file = fileChooser.getSelectedFile();
        String fileName = file.getPath();
        if(!fileName.toUpperCase().endsWith(".PNG"))
        {
            file = new File(fileName + ".PNG");
            fileChooser.setSelectedFile(file);
        }
        BufferedImage bufferedImage = resampledImage.asBufferedImage();
        try
        {
            if(!ImageIO.write(bufferedImage, "PNG", file))
            {
                JOptionPane.showMessageDialog(this, 
                        "Can't write this format of image!");
            }
        }
        catch (IOException e)
        {
            JOptionPane.showMessageDialog(this,
                    "Error writing the file '" +  
                    file.getPath() + "'!");
        }
    }
    
    /**
     * begin to do the resampling job
     */
    public void resample()
    {
        if(sourceImage == null
                || sourceImageBuffer == null
                || resampledImage == null
                || resampledImageBuffer == null
                || transformMode == MODE_PICTURE_VIEW)
        {
            return;
        }
        // set to busy state
        computing = true;
        
        // TODO change the Resampler class if you implement you own
        // Resampler class
        Resampler resampler = null;
        if(transformMode == MODE_AXIS_ALIGNED_TRANSFORM)
        {
            resampler = new AxisAlignedResampler(sourceImage, 
                    resampledImage, clippingMode == MODE_CLIP_TO_DESTINATION);
        }
        else if(transformMode == MODE_PERSPECTIVE_TRANSFORM)
        {
            resampler = new PerspectiveResampler(sourceImage, 
                    resampledImage, clippingMode == MODE_CLIP_TO_DESTINATION);
        }
        resampler.resample(source.getVertices(), resampled.getVertices());
        
        // set to idle state
        computing = false;

        // write the result to the frame buffer
        synchronized(resampled)
        {
            resampledImageBuffer.rewind();
            resampledImage.copyToBuffer(resampledImageBuffer);
        }
    }

    public void refresh()
    {
        source.repaint();
        resampled.repaint();
    }

    public int getTransformMode()
    {
        return transformMode;
    }

    public boolean isComputing()
    {
        return computing;
    }

    private class SourceImageFrame extends ImageFrame
    {
        public SourceImageFrame()
        {
            super("Source Image", MainFrame.this);
            
            setLocation(0, 0);
        }
        
        public void mouseReleased(MouseEvent e)
        {
            super.mouseReleased(e);
            if(!mainFrame.isComputing())
            {
                resampled.repaint();
            }
        }
    }

    private class ResampledImageFrame extends ImageFrame
    {
        public ResampledImageFrame()
        {
            super("Resampled Image", MainFrame.this);

            setLocation(450, 150);
        }
    }

}
