import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.border.*;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

/**
 * ImageViewer is the main class of the image viewer application. It builds and
 * displays the application GUI and initializes all other components.
 *   @author Michael Kölling and David J. Barnes (modified by Dave Reed)
 *   @version 3/14/17
 */
public class ImageViewer {
    private static final String IMAGE_FORMAT = "jpg";
    private static final String VERSION = "Version 421";
    private static final JFileChooser fileChooser = new JFileChooser(System.getProperty("user.dir"));

    private JFrame frame;
    private ImagePanel imagePanel;
    private JLabel filenameLabel;
    private JLabel statusLabel;
    private JButton reduceButton;
    private JButton expandButton;
    private JButton invertButton;
    private JButton horizontalButton;
    private Image currentImage;

    /**
     * Create an ImageViewer and display its GUI on screen.
     */
    public ImageViewer() {
        currentImage = null;
        makeFrame();
    }

    /**
     * Make the current picture smaller.
     */
    private void reduce() {
        if (currentImage != null) {
            int width = currentImage.getWidth();
            int height = currentImage.getHeight();

            // copy pixel data into new (smaller) image
            Image newImage = new Image(width/2, height/2);
            for (int y = 0; y < height/2; y++) {
                for (int x = 0; x < width/2; x++) {
                    newImage.setPixel(x, y, currentImage.getPixel(x * 2, y * 2));
                }
            }

            // set currentImage to copy, display & resize frame
            currentImage = newImage;
            imagePanel.setImage(currentImage);
            frame.pack();
        }
    }

    /**
     * Make the current picture larger.
     */
    private void expand() {
        if (currentImage != null) {
            int width = currentImage.getWidth();
            int height = currentImage.getHeight();
            
            // copy pixel data into new (larger) image
            Image newImage = new Image(width * 2, height * 2);
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    Color col = currentImage.getPixel(x, y);
                    newImage.setPixel(x * 2, y * 2, col);
                    newImage.setPixel(x * 2 + 1, y * 2, col);
                    newImage.setPixel(x * 2, y * 2 + 1, col);
                    newImage.setPixel(x * 2 + 1, y * 2 + 1, col);
                }
            }

            // set currentImage to copy, display & resize frame
            currentImage = newImage;
            imagePanel.setImage(currentImage);
            frame.pack();
        }
    }

    /**
     * Invert all of the pixels in the image.
     */
    private void invert() {
        if (currentImage != null) {
            int width = currentImage.getWidth();
            int height = currentImage.getHeight();

            // invert each pixel
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    Color pix = currentImage.getPixel(x, y);
                    currentImage.setPixel(x, y, new Color(255 - pix.getRed(),
                            255 - pix.getGreen(),
                            255 - pix.getBlue()));
                }
            }
            
            // display the updated image
            imagePanel.setImage(currentImage);
        }
    }

    /**
     * Reflect the image along the horizontal axis.
     */
    private void horizontal() {
        if (currentImage != null) {
            int width = currentImage.getWidth();
            int height = currentImage.getHeight();

            // swap pixels along horizontal axis
            for (int y = 0; y < height / 2; y++) {
                for (int x = 0; x < width; x++) {
                    Color pix = currentImage.getPixel(x, y);
                    currentImage.setPixel(x, y, currentImage.getPixel(x, height-1-y));
                    currentImage.setPixel(x, height-1-y, pix);
                }
            }

            // display the updated image
            imagePanel.setImage(currentImage);
        }
    }

    // ---- Menu options ----
    /**
     * Open function: open a file chooser to select a new image file, and then
     * display the chosen image.
     */
    private void open() {
        int returnVal = fileChooser.showOpenDialog(frame);

        if (returnVal != JFileChooser.APPROVE_OPTION) {
            return;  // cancelled
        }

        File selectedFile = fileChooser.getSelectedFile();
        try {
            BufferedImage image = ImageIO.read(selectedFile);
            if (image != null) {
                currentImage = new Image(image);
            }
        } catch (IOException exc) {
            currentImage = null;
        }

        if (currentImage == null) {   // image file was not a valid image
            JOptionPane.showMessageDialog(frame,
                    "The file was not in a recognized image file format.",
                    "Image Load Error",
                    JOptionPane.ERROR_MESSAGE);
        }
        else {
            imagePanel.setImage(currentImage);
            setButtonsEnabled(true);
            showFilename(selectedFile.getPath());
            showStatus("File loaded.");
            frame.pack();
        }
    }

    /**
     * Close function: close the current image.
     */
    private void close() {
        currentImage = null;
        imagePanel.clearImage();
        showFilename(null);
        setButtonsEnabled(false);
    }

    /**
     * Save As function: save the current image to a file.
     */
    private void saveAs() {
        if (currentImage != null) {
            int returnVal = fileChooser.showSaveDialog(frame);

            if (returnVal != JFileChooser.APPROVE_OPTION) {
                return;  // cancelled
            }

            File selectedFile = fileChooser.getSelectedFile();
            try {
                ImageIO.write(currentImage, IMAGE_FORMAT, selectedFile);
            } catch (IOException exc) {
                JOptionPane.showMessageDialog(frame,
                        "Unable to save to the specified file.",
                        "File Write Error",
                        JOptionPane.ERROR_MESSAGE);
            }

            showFilename(selectedFile.getPath());
        }
    }

    /**
     * Quit function: quit the application.
     */
    private void quit() {
        System.exit(0);
    }

    /**
     * 'About' function: show the 'about' box.
     */
    private void showAbout() {
        JOptionPane.showMessageDialog(frame,
                "ImageViewer\n" + VERSION,
                "About ImageViewer",
                JOptionPane.INFORMATION_MESSAGE);
    }

    // ---- support methods ----
    /**
     * Show the file name of the current image in the fils display label. 'null'
     * may be used as a parameter if no file is currently loaded.
     *
     * @param filename The file name to be displayed, or null for 'no file'.
     */
    private void showFilename(String filename) {
        if (filename == null) {
            filenameLabel.setText("No file displayed.");
        } else {
            filenameLabel.setText("File: " + filename);
        }
    }

    /**
     * Show a message in the status bar at the bottom of the screen.
     *
     * @param text The status message.
     */
    private void showStatus(String text) {
        statusLabel.setText(text);
    }

    /**
     * Enable or disable all toolbar buttons.
     *
     * @param status 'true' to enable the buttons, 'false' to disable.
     */
    private void setButtonsEnabled(boolean status) {
        reduceButton.setEnabled(status);
        expandButton.setEnabled(status);
        invertButton.setEnabled(status);
        horizontalButton.setEnabled(status);
    }

    // ---- Swing stuff to build the frame and all its components and menus ----
    /**
     * Create the Swing frame and its content.
     */
    private void makeFrame() {
        frame = new JFrame("ImageViewer");
        JPanel contentPane = (JPanel) frame.getContentPane();
        contentPane.setBorder(new EmptyBorder(12, 12, 12, 12));

        makeMenuBar(frame);

        // Specify the layout manager with nice spacing
        contentPane.setLayout(new BorderLayout(6, 6));

        // Create the image pane in the center
        imagePanel = new ImagePanel();
        imagePanel.setBorder(new EtchedBorder());
        contentPane.add(imagePanel, BorderLayout.CENTER);

        // Create two labels at top and bottom for the file name and status messages
        filenameLabel = new JLabel();
        contentPane.add(filenameLabel, BorderLayout.NORTH);

        statusLabel = new JLabel(VERSION);
        contentPane.add(statusLabel, BorderLayout.SOUTH);

        // Create the toolbar with the buttons
        JPanel toolbar = new JPanel();
        toolbar.setLayout(new GridLayout(0, 1));

        reduceButton = new JButton("Reduce");
        reduceButton.addActionListener(e -> reduce());
        toolbar.add(reduceButton);

        expandButton = new JButton("Expand");
        expandButton.addActionListener(e -> expand());
        toolbar.add(expandButton);

        invertButton = new JButton("Invert");
        invertButton.addActionListener(e -> invert());
        toolbar.add(invertButton);

        horizontalButton = new JButton("Horizontal");
        horizontalButton.addActionListener(e -> horizontal());
        toolbar.add(horizontalButton);

        // Add toolbar into panel with flow layout for spacing
        JPanel flow = new JPanel();
        flow.add(toolbar);

        contentPane.add(flow, BorderLayout.WEST);

        // building is done - arrange the components      
        showFilename(null);
        setButtonsEnabled(false);
        frame.pack();

        // place the frame at the center of the screen and show
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        frame.setLocation(d.width / 2 - frame.getWidth() / 2, d.height / 2 - frame.getHeight() / 2);
        frame.setVisible(true);
    }

    /**
     * Create the main frame's menu bar.
     *
     * @param frame The frame that the menu bar should be added to.
     */
    private void makeMenuBar(JFrame frame) {
        final int SHORTCUT_MASK
                = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();

        JMenuBar menubar = new JMenuBar();
        frame.setJMenuBar(menubar);

        JMenu menu;
        JMenuItem item;

        // create the File menu
        menu = new JMenu("File");
        menubar.add(menu);

        item = new JMenuItem("Open...");
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, SHORTCUT_MASK));
        item.addActionListener(e -> open());
        menu.add(item);

        item = new JMenuItem("Close");
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, SHORTCUT_MASK));
        item.addActionListener(e -> close());
        menu.add(item);
        menu.addSeparator();

        item = new JMenuItem("Save As...");
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, SHORTCUT_MASK));
        item.addActionListener(e -> saveAs());
        menu.add(item);
        menu.addSeparator();

        item = new JMenuItem("Quit");
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, SHORTCUT_MASK));
        item.addActionListener(e -> quit());
        menu.add(item);

        // create the Help menu
        menu = new JMenu("Help");
        menubar.add(menu);

        item = new JMenuItem("About ImageViewer...");
        item.addActionListener(e -> showAbout());
        menu.add(item);

    }

    public static void main(String[] args) {
        ImageViewer viewer = new ImageViewer();
    }
}
