package com.knutejohnson.pi;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import javax.imageio.*;
import javax.swing.*;
import javax.swing.filechooser.*;

/**
 * ImageViewer a full screen image viewing program.
 *
 * Click the left mouse button to select an image file to display, click the
 * right mouse button to close the program.
 *
 * @author  Knute Johnson
 * @version 0.10.0beta
 */
public class ImageViewer extends JComponent {
    /** Image to display */
    private volatile BufferedImage image;

    /** Error message when fault displaying image */
    private String errorMessage = "No Image Loaded Yet!";

    /** Flag if image to be scaled */
    private boolean doScale = true;

    /** Display screen size minus any toolbars */
    private final Dimension screenSize;

    /** Map of RenderingHints */
    private final Map<Object,Object> hints = new HashMap<>();

    /**
     * Creates a new ImageViewer program
     */
    public ImageViewer() {
        GraphicsEnvironment ge =
         GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsConfiguration gc =
         ge.getDefaultScreenDevice().getDefaultConfiguration();
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        screenSize = toolkit.getScreenSize();
        Insets insets = toolkit.getScreenInsets(gc);
        // adjust the size of the screen for any task bars
        screenSize.setSize(screenSize.width - insets.left - insets.right,
         screenSize.height - insets.top - insets.bottom);
        setPreferredSize(screenSize);
        setFont(new Font(Font.SANS_SERIF,Font.BOLD,32));

        hints.put(RenderingHints.KEY_ANTIALIASING,
         RenderingHints.VALUE_ANTIALIAS_ON);
        hints.put(RenderingHints.KEY_INTERPOLATION,
         RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        hints.put(RenderingHints.KEY_COLOR_RENDERING,
         RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION,
         RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    }

    /**
     * Scales and draws the image on the display
     *
     * @param   g2d     graphics context
     */
    @Override public void paintComponent(Graphics g2d) {
        Graphics2D g = (Graphics2D)g2d;
        g.setRenderingHints(hints);

        g.setColor(Color.BLACK);
        g.fillRect(0,0,getWidth(),getHeight());

        if (image == null) {
            g.setColor(Color.WHITE);
            FontMetrics fm = g.getFontMetrics();
            int strWidth = fm.stringWidth(errorMessage);
            g.drawString(errorMessage,
             (screenSize.width-strWidth)/ 2,
             (screenSize.height / 2));
        } else {
            int width = image.getWidth();
            int height = image.getHeight();

            double screenAspectRatio =
             screenSize.getWidth() / screenSize.getHeight();
            double imageAspectRatio = (double)width / height;

            // calculate the scale factor to fit the image on the screen
            double scale;
            if (imageAspectRatio > screenAspectRatio)
                scale = screenSize.getWidth() / width;
            else
                scale = screenSize.getHeight() / height;

            // if we're not scaling put the scale back to 1.0
            if (!doScale)
                scale = 1.0;

            double x = (screenSize.getWidth() - width * scale) / 2.0;
            double y = (screenSize.getHeight() - height * scale) / 2.0;

            AffineTransform transform =
             AffineTransform.getTranslateInstance(x,y);

            transform.scale(scale,scale);

            g.drawRenderedImage(image,transform);
        }
    }

    /**
     * Displays the specified image
     *
     * @param   image   image to display
     */
    public void setImage(BufferedImage image) {
        this.image = image;
        repaint();
    }

    /**
     * Sets the error message to be displayed
     *
     * @param   error   error message
     */
    public void setError(String error) {
        errorMessage = error;
        setImage(null);
        repaint();
    }

    /**
     * Main program entry point creates a new ImageViewer object and the rest
     * of the GUI to display and control it.
     *
     * @param   args    command line arguments, not used
     */
    public static void main(String... args) {
        EventQueue.invokeLater(() -> {
            JFileChooser chooser = new JFileChooser();
            FileNameExtensionFilter filter =
             new FileNameExtensionFilter("Image Files",
             ImageIO.getReaderFileSuffixes());
            chooser.addChoosableFileFilter(filter);

            ImageViewer iviewer = new ImageViewer();

            // the JFileChooser has a BorderLayout
            // get the Component in the SOUTH panel of the JFileChooser
            // that JPanel has a BoxLayout
            JPanel panel = (JPanel)((BorderLayout)chooser.getLayout())
             .getLayoutComponent(BorderLayout.SOUTH);
            // create a new Box to hold the scale check box
            Box box = new Box(BoxLayout.X_AXIS);
            // add it to the JPanel
            panel.add(box);
            // create the scale checkbox
            JCheckBox scaleCB = new JCheckBox("Scale Image",iviewer.doScale);
            // add the scale checkbox to the Box
            box.add(scaleCB);
            // add some glue to the Box to force the scale checkbox to the left
            box.add(Box.createHorizontalGlue());

            JWindow window = new JWindow();
            window.addMouseListener(new MouseAdapter() {
                public void mousePressed(MouseEvent me) {
                    switch (me.getButton()) {
                        case MouseEvent.BUTTON1:
                            int option = chooser.showOpenDialog(window);
                            if (option == JFileChooser.APPROVE_OPTION) {
                                File file = chooser.getSelectedFile();
                                iviewer.doScale = scaleCB.isSelected();
                                Runnable readFile = () -> {
                                    try {
                                        InputStream is =
                                         new BufferedInputStream(
                                         new ProgressMonitorInputStream(window,
                                         "Reading " + file.getName(),
                                         new FileInputStream(file)));
                                        BufferedImage image = ImageIO.read(is);
                                        if (image == null)
                                            iviewer.setError(
                                             "Invalid Image File");
                                        else
                                            iviewer.setImage(image);
                                    } catch (IOException ioe) {
                                        iviewer.setError(ioe.toString());
                                    }
                                };
                                new Thread(readFile).start();
                            }
                            break;
                        case MouseEvent.BUTTON2:
                        case MouseEvent.BUTTON3:
                            option = JOptionPane.showConfirmDialog(
                             window,"End Program?","Exit",
                             JOptionPane.YES_NO_OPTION);
                            if (option == JOptionPane.YES_OPTION)
                                window.dispose();
                            break;
                        default:
                            break;
                    }
                }
            });
            window.add(iviewer);
            window.pack();
            window.setVisible(true);
        });
    }
}