/*
 * Copyright (c) 2002-2007 TeamDev Ltd. All rights reserved.
 *
 * Use is subject to license terms.
 *
 * The complete licence text can be found at
 * http://www.teamdev.com/winpack/license.jsf
 */
package teamdev.jxcapture.samples.demo;

import org.jdesktop.application.Action;
import org.jdesktop.application.ApplicationContext;
import org.jdesktop.application.ResourceMap;
import org.jdesktop.application.Application;

import javax.swing.*;
import javax.swing.event.PopupMenuListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.List;
import java.util.LinkedList;

/**
 * @author Ikryanov Vladimir
 */
public class ImageViewer extends JFrame {

    private ActionMap actionMap;
    private ResourceMap resourceMap;
    private ImageComponent imageComponent;
    private JButton zoomToButton;

    private boolean zoomInEnabled = true;
    private boolean zoomOutEnabled = true;
    private boolean imageModified = false;

    private JPopupMenu zoomToPopupMenu = new JPopupMenu();

    public ImageViewer() throws HeadlessException {
        ApplicationContext context = Application.getInstance().getContext();
        resourceMap = context.getResourceMap(ImageViewer.class);
        actionMap = context.getActionMap(ImageViewer.class, this);

        setSize(750, 500);
        setName("ImageViewer");
        setLocationRelativeTo(null);
        setIconImage(resourceMap.getImageIcon("icon").getImage());

        createMenuBar();
        createContentPane();

        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                close();
            }
        });

        resourceMap.injectComponents(this);
    }

    private void createMenuBar() {
        String separator = getSeparatorValue();
        JMenuBar menuBar = new JMenuBar();
        menuBar.add(createMenu("FileMenu", new String[]{"open", "save", separator, "close"}));
        menuBar.add(createMenu("ViewMenu", new String[]{"zoomIn", "zoomOut", "zoomTo", separator, "actualSize", "fitToWindow"}));
        menuBar.add(createMenu("EditMenu", new String[]{"copy"}));
        menuBar.add(createMenu("SettingsMenu", new String[]{"settings"}));
        menuBar.add(createMenu("HelpMenu", new String[]{"about"}));
        setJMenuBar(menuBar);
    }

    private JMenu createMenu(String menuName, String[] actionNames) {
        JMenu menu = new JMenu();
        menu.setName(menuName);
        String separator = getSeparatorValue();
        for (String actionName : actionNames) {
            if (actionName.equals(separator)) {
                menu.add(new JSeparator());
            } else if (actionName.equals("zoomTo")) {
                JMenu zoomToMenu = new JMenu();
                configZoomToMenu(actionName, zoomToMenu);
                menu.add(zoomToMenu);
            } else {
                JMenuItem menuItem = new JMenuItem();
                configMenuItem(actionName, menuItem);
                menu.add(menuItem);
            }
        }
        return menu;
    }

    private void configMenuItem(String actionName, JMenuItem menuItem) {
        menuItem.setName(actionName);
        menuItem.setAction(actionMap.get(actionName));
        menuItem.setIcon(getIcon(actionName, "menu"));
    }

    private JToolBar createToolBar() {
        String separator = getSeparatorValue();
        String[] toolbarActionNames = {"open", "save", separator, "copy", separator,
                "zoomIn", "zoomOut", "zoomTo", separator, "settings"};

        JToolBar toolBar = new JToolBar();
        toolBar.setFloatable(false);
        toolBar.setBorderPainted(false);
        Border border = new EmptyBorder(5, 5, 5, 5);
        for (String actionName : toolbarActionNames) {
            if (actionName.equals(separator)) {
                toolBar.addSeparator();
            } else if (actionName.equals("zoomTo")) {
                toolBar.add(createZoomToButton(actionName, border));
            } else {
                toolBar.add(createToolBarButton(actionName, border));
            }
        }
        return toolBar;
    }

    private ImageComponent getImageComponent() {
        return imageComponent == null ? imageComponent = new ImageComponent() : imageComponent;
    }

    private JButton createToolBarButton(String actionName, Border border) {
        JButton button = new JButton();
        button.setName(actionName);
        button.setBorder(border);
        button.setVerticalTextPosition(JButton.BOTTOM);
        button.setHorizontalTextPosition(JButton.CENTER);
        button.setAction(actionMap.get(actionName));
        button.setIcon(getIcon(actionName, "button"));
        button.setFocusable(false);
        return button;
    }

    private JButton createZoomToButton(String actionName, Border border) {
        zoomToButton = createToolBarButton(actionName, border);

        zoomToPopupMenu.add(new JMenuItem(actionMap.get("actualSize")));
        zoomToPopupMenu.add(new JMenuItem(actionMap.get("fitToWindow")));
        zoomToPopupMenu.addSeparator();

        List<PercentAction> zoomToActions = getZoomToActions();
        for (PercentAction zoomToAction : zoomToActions) {
            zoomToPopupMenu.add(new JMenuItem(zoomToAction));
        }

        zoomToPopupMenu.addPopupMenuListener(new PopupMenuListener() {
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                zoomToButton.setSelected(true);
            }

            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                zoomToButton.setSelected(false);
            }

            public void popupMenuCanceled(PopupMenuEvent e) {
            }
        });

        return zoomToButton;
    }

    private void configZoomToMenu(String actionName, JMenu menu) {
        configMenuItem(actionName, menu);
        List<PercentAction> zoomToActions = getZoomToActions();
        for (PercentAction zoomToAction : zoomToActions) {
            menu.add(new JMenuItem(zoomToAction));
        }
    }

    private List<PercentAction> getZoomToActions() {
        List<PercentAction> actions = new LinkedList<PercentAction>();
        int[] percents = getImageComponent().getPercents();
        for (int i = 0; i < percents.length; i++) {
            actions.add(new PercentAction(percents[i], i));
        }
        return actions;
    }

    private void createContentPane() {
        JPanel contentPane = new JPanel(new BorderLayout());
        contentPane.add(createToolBar(), BorderLayout.NORTH);
        contentPane.add(new JScrollPane(getImageComponent()), BorderLayout.CENTER);
        setContentPane(contentPane);
    }

    private Icon getIcon(String actionName, String componentType) {
        return resourceMap.getIcon(actionName + "." + componentType + ".icon");
    }

    private String getSeparatorValue() {
        return resourceMap.getString("separator");
    }

    private void updateZoomActionState() {
        setZoomInEnabled(getImageComponent().canZoomIn());
        setZoomOutEnabled(getImageComponent().canZoomOut());
    }

    private boolean isImageModified() {
        return imageModified;
    }

    private void setImageModified(boolean imageModified) {
        this.imageModified = imageModified;
    }

    private boolean confirmSaveImage() {
        String title = resourceMap.getString("confirmSaveImageDialog.Title");
        String message = resourceMap.getString("confirmSaveImageDialog.Message");
        int selectedValue = JOptionPane.showConfirmDialog(this, message, title, JOptionPane.YES_NO_CANCEL_OPTION);
        switch (selectedValue) {
            case JOptionPane.YES_OPTION: return save();
            case JOptionPane.NO_OPTION: return true;
            default: return false;
        }
    }

    public void setImage(BufferedImage image) {
        getImageComponent().setImage(image);
        setImageModified(true);
    }

    public boolean setImage(File imageFile) {
        try {
            getImageComponent().setImage(imageFile);
        } catch (Exception e) {
            String title = resourceMap.getString("openImageErrorDialog.Title");
            String message = resourceMap.getString("openImageErrorDialog.Message");
            message = String.format(message, imageFile.getAbsolutePath());
            JOptionPane.showMessageDialog(this, message, title, JOptionPane.ERROR_MESSAGE);
            return false;
        }

        setImageModified(false);
        return true;
    }

    public boolean isZoomInEnabled() {
        return zoomInEnabled;
    }

    public boolean isZoomOutEnabled() {
        return zoomOutEnabled;
    }

    public void setZoomInEnabled(boolean zoomInEnabled) {
        boolean oldValue = this.zoomInEnabled;
        this.zoomInEnabled = zoomInEnabled;
        firePropertyChange("zoomInEnabled", oldValue, this.zoomInEnabled);
    }

    public void setZoomOutEnabled(boolean zoomOutEnabled) {
        boolean oldValue = this.zoomOutEnabled;
        this.zoomOutEnabled = zoomOutEnabled;
        firePropertyChange("zoomOutEnabled", oldValue, this.zoomOutEnabled);
    }

    @Action
    public boolean close() {
        if (isImageModified() && !confirmSaveImage()) return false;
        getImageComponent().dispose();
        setVisible(false);
        dispose();

        return true;
    }

    @Action
    public void open() {
        if (isImageModified() && !confirmSaveImage()) return;

        ImageFileChooser fileChooser = new ImageFileChooser(this);
        File imageFile = fileChooser.openImageFile();
        if (imageFile != null) {
            setImage(imageFile);
        }
    }

    @Action
    public boolean save() {
        ImageFileChooser fileChooser = new ImageFileChooser(this);
        File imageFile = fileChooser.saveImageFile();
        if (imageFile != null) {
            try {
                getImageComponent().saveImage(imageFile);
                setImageModified(false);
                return true;
            } catch (Exception e) {
                String title = resourceMap.getString("saveImageErrorDialog.Title");
                String message = resourceMap.getString("saveImageErrorDialog.Message");
                message = String.format(message, imageFile.getAbsolutePath());
                JOptionPane.showMessageDialog(this, message, title, JOptionPane.ERROR_MESSAGE);
                return false;
            }
        }
        return false;
    }

    @Action
    public void copy() {
        CaptureOperations.getInstance().copyToClipboard(getImageComponent().getImage());
    }

    @Action(enabledProperty = "zoomInEnabled")
    public void zoomIn() {
        ImageComponent component = getImageComponent();
        if (component.canZoomIn()) component.zoomIn();
        updateZoomActionState();
    }

    @Action(enabledProperty = "zoomOutEnabled")
    public void zoomOut() {
        ImageComponent component = getImageComponent();
        if (component.canZoomOut()) component.zoomOut();
        updateZoomActionState();
    }

    @Action
    public void zoomTo() {
        zoomToPopupMenu.show(zoomToButton, 0, zoomToButton.getBounds().height);
    }

    @Action
    public void actualSize() {
        getImageComponent().actualSize();
        updateZoomActionState();
    }

    @Action
    public void fitToWindow() {
        ImageComponent component = getImageComponent();
        Dimension parentContainerSize = component.getParent().getSize();
        component.fitToWindow(parentContainerSize);
    }

    private class PercentAction extends AbstractAction {
        private int index = 0;

        public PercentAction(int percent, int index) {
            this.index = index;
            putValue(javax.swing.Action.NAME, percent + " %");
            putValue(javax.swing.Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(String.valueOf(index + 1)));
        }

        public void actionPerformed(ActionEvent e) {
            getImageComponent().zoomTo(index);
            updateZoomActionState();
        }
    }
}
