package com.tagtraum.perf.gcviewer;

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.net.URLDecoder;
import java.util.ResourceBundle;
import java.util.Properties;
import java.util.Date;

/**
 *
 * Date: Jan 30, 2002
 * Time: 4:59:49 PM
 * @author <a href="mailto:hs@tagtraum.com">Hendrik Schreiber</a>
 * @version $Id: $
 */
public class GCViewer extends JFrame {

    private static ResourceBundle localStrings = ResourceBundle.getBundle("com.tagtraum.perf.gcviewer.localStrings");
    private static final String PREFERENCE_VERSION = "1.1";

    private DataReaderFactory factory;
    private DataWriterFactory writerFactory;
    private GCModel model;
    private JDesktopPane desktop;
    private ModelChart modelChart;
    private ModelPanel modelPanel;
    private JMenuItem exportMenuItem;
    private AboutDialog aboutDialog;
    private JFileChooser saveDialog;
    private JFileChooser openDialog;
    private JToolBar toolBar;
    private File file;
    private ActionListener viewActionListener;
    private JMenu fileMenu;


    public GCViewer() {
        super("GCViewer");
        setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("gcviewericon.gif")));
        factory = new DataReaderFactory();
        writerFactory = new DataWriterFactory();
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                exit();
            }
        });
        viewActionListener = new ViewMenuActionListener();
        modelChart = new ModelChart();
        modelPanel = new ModelPanel();
        aboutDialog = new AboutDialog(this);
        saveDialog = new JFileChooser();
        saveDialog.setDialogTitle(localStrings.getString("fileexport_dialog_title"));
        saveDialog.removeChoosableFileFilter(saveDialog.getAcceptAllFileFilter());
        saveDialog.addChoosableFileFilter(new ExtensionFileFilter(".csv", localStrings.getString("fileexport_dialog_csv")));
        saveDialog.addChoosableFileFilter(new ExtensionFileFilter(".txt", localStrings.getString("fileexport_dialog_txt")));
        openDialog = new JFileChooser();
        openDialog.setDialogTitle(localStrings.getString("fileopen_dialog_title"));
        setJMenuBar(initMenuBar());
        toolBar = initToolBar();
        GridBagLayout layout = new GridBagLayout();
        //getContentPane().setLayout(layout);
        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(layout);
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.fill = GridBagConstraints.BOTH;
        constraints.weightx = 2;
        constraints.weighty = 2;
        layout.setConstraints(modelChart, constraints);
        modelChart.setPreferredSize(new Dimension(800, 600));
        mainPanel.add(modelChart);
        constraints.weightx = 0;
        constraints.weighty = 2;
        constraints.fill = GridBagConstraints.NONE;
        constraints.anchor = GridBagConstraints.SOUTH;
        layout.setConstraints(modelPanel, constraints);
        mainPanel.add(modelPanel);
        getContentPane().add(toolBar, BorderLayout.NORTH);
        getContentPane().add(mainPanel, BorderLayout.CENTER);
        loadPreferences();
        pack();
        show();
    }

    private Action exitAction = new AbstractAction() {
        {
            putValue(Action.NAME, localStrings.getString("main_frame_menuitem_exit"));
            putValue(Action.SHORT_DESCRIPTION, localStrings.getString("main_frame_menuitem_hint_exit"));
            putValue(Action.MNEMONIC_KEY, new Integer(localStrings.getString("main_frame_menuitem_mnemonic_exit").charAt(0)));
            putValue(Action.ACTION_COMMAND_KEY, "exit");
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke('X', java.awt.Event.CTRL_MASK ));
            putValue(Action.SMALL_ICON, new ImageIcon(Toolkit.getDefaultToolkit().getImage(getClass().getResource("exit.gif"))));
        }
        public void actionPerformed(ActionEvent e) {
            exit();
        }
    };

    private Action aboutAction = new AbstractAction() {
        {
            putValue(Action.NAME, localStrings.getString("main_frame_menuitem_about"));
            putValue(Action.SHORT_DESCRIPTION, localStrings.getString("main_frame_menuitem_hint_about"));
            putValue(Action.MNEMONIC_KEY, new Integer(localStrings.getString("main_frame_menuitem_mnemonic_about").charAt(0)));
            putValue(Action.ACTION_COMMAND_KEY, "about");
            putValue(Action.SMALL_ICON, new ImageIcon(Toolkit.getDefaultToolkit().getImage(getClass().getResource("about.gif"))));
        }
        public void actionPerformed(ActionEvent e) {
            aboutDialog.setVisible(true);
        }
    };

    private Action openAction = new AbstractAction() {
        {
            putValue(Action.NAME, localStrings.getString("main_frame_menuitem_open_file"));
            putValue(Action.SHORT_DESCRIPTION, localStrings.getString("main_frame_menuitem_hint_open_file"));
            putValue(Action.MNEMONIC_KEY, new Integer(localStrings.getString("main_frame_menuitem_mnemonic_open_file").charAt(0)));
            putValue(Action.ACTION_COMMAND_KEY, "open");
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke('O', java.awt.Event.CTRL_MASK ));
            putValue(Action.SMALL_ICON, new ImageIcon(Toolkit.getDefaultToolkit().getImage(getClass().getResource("open.gif"))));
        }
        public void actionPerformed(ActionEvent e) {
            int val = openDialog.showOpenDialog(GCViewer.this);
            if (val == JFileChooser.APPROVE_OPTION) {
                openFile(openDialog.getSelectedFile());
            }
        }
    };

    public void openFile(File file) {
        try {
            InputStream in = new FileInputStream(file);
            DataReader reader = factory.getDataReader(in);
            model = reader.read();
            modelChart.setModel(model);
            modelPanel.setModel(model);
            setTitle("GCViewer - " + file);
            setFile(file);
            repaint();
            exportAction.setEnabled(true);
            refreshAction.setEnabled(true);
            saveDialog.setSelectedFile(openDialog.getSelectedFile());
        } catch (Exception e) {
            //e.printStackTrace();
            JOptionPane.showMessageDialog(GCViewer.this, e.getLocalizedMessage(), localStrings.getString("fileopen_dialog_read_file_failed"), JOptionPane.ERROR_MESSAGE);
        }
    }

    private Action refreshAction = new AbstractAction() {
        {
            putValue(Action.NAME, localStrings.getString("main_frame_menuitem_refresh"));
            putValue(Action.SHORT_DESCRIPTION, localStrings.getString("main_frame_menuitem_hint_refresh"));
            putValue(Action.MNEMONIC_KEY, new Integer(localStrings.getString("main_frame_menuitem_mnemonic_refresh").charAt(0)));
            putValue(Action.ACTION_COMMAND_KEY, "refresh");
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke('R', java.awt.Event.CTRL_MASK ));
            putValue(Action.SMALL_ICON, new ImageIcon(Toolkit.getDefaultToolkit().getImage(getClass().getResource("refresh.gif"))));
            setEnabled(false);
        }
        public void actionPerformed(ActionEvent e) {
            openFile(getFile());
        }
    };

    private Action exportAction = new AbstractAction() {
        {
            putValue(Action.NAME, localStrings.getString("main_frame_menuitem_export"));
            putValue(Action.MNEMONIC_KEY, new Integer(localStrings.getString("main_frame_menuitem_mnemonic_export").charAt(0)));
            putValue(Action.SHORT_DESCRIPTION, localStrings.getString("main_frame_menuitem_hint_export"));
            putValue(Action.ACTION_COMMAND_KEY, "export");
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke('E', java.awt.Event.CTRL_MASK ));
            putValue(Action.SMALL_ICON, new ImageIcon(Toolkit.getDefaultToolkit().getImage(getClass().getResource("save.gif"))));
            setEnabled(false);
        }
        public void actionPerformed(ActionEvent e) {
            int val = saveDialog.showSaveDialog(GCViewer.this);
            if (val == JFileChooser.APPROVE_OPTION) {
                exportFile(saveDialog.getSelectedFile(), ((ExtensionFileFilter)saveDialog.getFileFilter()).getExtension());
            } else if (val == JFileChooser.ERROR_OPTION) {
                JOptionPane.showMessageDialog(GCViewer.this, localStrings.getString("fileexport_dialog_error_occured"), localStrings.getString("fileexport_dialog_write_file_failed"), JOptionPane.ERROR_MESSAGE);
            }
        }
        public void exportFile(File file, String extension) {
            DataWriter writer = null;
            try {
                if (file.toString().indexOf('.') == -1) file = new File(file.toString() + extension);
                if (!file.exists() || JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(GCViewer.this, localStrings.getString("fileexport_dialog_confirm_overwrite"), localStrings.getString("fileexport_dialog_title"), JOptionPane.YES_NO_OPTION)) {
                    writer = writerFactory.getDataWriter(file, extension);
                    writer.write(model);
                }
            } catch (Exception ioe) {
                //ioe.printStackTrace();
                JOptionPane.showMessageDialog(GCViewer.this, ioe.getLocalizedMessage(), localStrings.getString("fileexport_dialog_write_file_failed"), JOptionPane.ERROR_MESSAGE);
            } finally {
                if (writer != null) {
                    writer.close();
                }
            }
        }
    };

    private Action zoomAction = new AbstractAction() {
        {
            putValue(Action.NAME, localStrings.getString("action_zoom"));
            putValue(Action.SHORT_DESCRIPTION, localStrings.getString("action_zoom_hint"));
            //putValue(Action.MNEMONIC_KEY, new Integer(localStrings.getString("main_frame_menuitem_mnemonic_export").charAt(0)));
            putValue(Action.ACTION_COMMAND_KEY, "zoom");
            //putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke('E', java.awt.Event.CTRL_MASK ));
        }

        public void actionPerformed(ActionEvent e) {
            JComboBox cb = (JComboBox)e.getSource();
            ItemSelectable is = (ItemSelectable)e.getSource();
            Object[] o = is.getSelectedObjects();
            try {
                String item = (String)o[0];
                if (item.endsWith("%")) item = item.substring(0, item.length()-1);
                int zoomFactor = Integer.parseInt(item.trim());
                modelChart.setScaleFactor(zoomFactor/100.0);
            }
            catch (NumberFormatException nfe) {
                //nfe.printStackTrace();
            }
        }
    };

    public File getFile() {
        return file;
    }

    public void setFile(File file) {
        this.file = file;
    }

    private JToolBar initToolBar() {
        JToolBar toolBar = new JToolBar();
        toolBar.add(openAction);
        toolBar.add(exportAction);
        toolBar.add(refreshAction);
        toolBar.addSeparator();
        JComboBox cb = new JComboBox(new String[] {"10%", "50%", "100%", "200%", "300%", "500%", "1000%"});
        cb.setSelectedIndex(2);
        cb.setAction(zoomAction);
        cb.setEditable(true);
        cb.setMaximumSize(cb.getPreferredSize());
        toolBar.add(cb);
        toolBar.addSeparator();
        toolBar.add(aboutAction);
        return toolBar;
    }

    private ImageIcon createMonoColoredImageIcon(Color color, int width, int height) {
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR_PRE);
        Graphics2D g = image.createGraphics();
        g.setColor(color);
        int lineHeight = 4;
        g.fill3DRect(0, (height/2)-(lineHeight/2), width, lineHeight, false);
        g.dispose();
        return new ImageIcon(image);
    }

    private JMenuBar initMenuBar() {
        JMenuBar menuBar = new JMenuBar();

        fileMenu = new JMenu(localStrings.getString("main_frame_menu_file"));
        fileMenu.setMnemonic(localStrings.getString("main_frame_menu_mnemonic_file").charAt(0));
        menuBar.add(fileMenu);

        JMenuItem menuItem = new JMenuItem(openAction);
        fileMenu.add(menuItem);

        exportMenuItem = new JMenuItem(exportAction);
        fileMenu.add(exportMenuItem);

        menuItem = new JMenuItem(refreshAction);
        fileMenu.add(menuItem);

        menuItem = new JMenuItem(exitAction);
        fileMenu.add(menuItem);

        JMenu viewMenu = new JMenu(localStrings.getString("main_frame_menu_view"));

        viewMenu.setMnemonic(localStrings.getString("main_frame_menu_mnemonic_view").charAt(0));
        menuBar.add(viewMenu);

        menuItem = new JCheckBoxMenuItem(localStrings.getString("main_frame_menuitem_full_gc_lines"), modelChart.isShowFullGCLines());
        menuItem.setMnemonic(localStrings.getString("main_frame_menuitem_mnemonic_full_gc_lines").charAt(0));
        menuItem.setToolTipText(localStrings.getString("main_frame_menuitem_hint_full_gc_lines"));
        menuItem.setIcon(createMonoColoredImageIcon(Color.black, 20, 20));
        menuItem.setActionCommand("fullgclines");
        menuItem.addActionListener(viewActionListener);
        viewMenu.add(menuItem);

        menuItem = new JCheckBoxMenuItem(localStrings.getString("main_frame_menuitem_inc_gc_lines"), modelChart.isShowIncGCLines());
        menuItem.setMnemonic(localStrings.getString("main_frame_menuitem_mnemonic_inc_gc_lines").charAt(0));
        menuItem.setToolTipText(localStrings.getString("main_frame_menuitem_hint_inc_gc_lines"));
        menuItem.setIcon(createMonoColoredImageIcon(Color.cyan, 20, 20));
        menuItem.setActionCommand("incgclines");
        menuItem.addActionListener(viewActionListener);
        viewMenu.add(menuItem);

        menuItem = new JCheckBoxMenuItem(localStrings.getString("main_frame_menuitem_gc_times_line"), modelChart.isShowGCTimesLine());
        menuItem.setMnemonic(localStrings.getString("main_frame_menuitem_mnemonic_gc_times_line").charAt(0));
        menuItem.setToolTipText(localStrings.getString("main_frame_menuitem_hint_gc_times_line"));
        menuItem.setIcon(createMonoColoredImageIcon(Color.green, 20, 20));
        menuItem.setActionCommand("gctimesline");
        menuItem.addActionListener(viewActionListener);
        viewMenu.add(menuItem);

        menuItem = new JCheckBoxMenuItem(localStrings.getString("main_frame_menuitem_gc_times_rectangles"), modelChart.isShowGCTimesRectangles());
        menuItem.setMnemonic(localStrings.getString("main_frame_menuitem_mnemonic_gc_times_rectangles").charAt(0));
        menuItem.setToolTipText(localStrings.getString("main_frame_menuitem_hint_gc_times_rectangles"));
        menuItem.setIcon(createMonoColoredImageIcon(Color.gray, 20, 20));
        menuItem.setActionCommand("gctimesrectangles");
        menuItem.addActionListener(viewActionListener);
        viewMenu.add(menuItem);

        menuItem = new JCheckBoxMenuItem(localStrings.getString("main_frame_menuitem_total_memory"), modelChart.isShowTotalMemoryLine());
        menuItem.setMnemonic(localStrings.getString("main_frame_menuitem_mnemonic_total_memory").charAt(0));
        menuItem.setToolTipText(localStrings.getString("main_frame_menuitem_hint_total_memory"));
        menuItem.setIcon(createMonoColoredImageIcon(Color.red, 20, 20));
        menuItem.setActionCommand("totalmemory");
        menuItem.addActionListener(viewActionListener);
        viewMenu.add(menuItem);

        menuItem = new JCheckBoxMenuItem(localStrings.getString("main_frame_menuitem_used_memory"), modelChart.isShowUsedMemoryLine());
        menuItem.setMnemonic(localStrings.getString("main_frame_menuitem_mnemonic_used_memory").charAt(0));
        menuItem.setToolTipText(localStrings.getString("main_frame_menuitem_hint_used_memory"));
        menuItem.setIcon(createMonoColoredImageIcon(Color.blue, 20, 20));
        menuItem.setActionCommand("usedmemory");
        menuItem.addActionListener(viewActionListener);
        viewMenu.add(menuItem);

        JMenu helpMenu = new JMenu(localStrings.getString("main_frame_menu_help"));
        helpMenu.setMnemonic(localStrings.getString("main_frame_menu_mnemonic_help").charAt(0));
        menuBar.add(helpMenu);

        menuItem = new JMenuItem(aboutAction);
        helpMenu.add(menuItem);

        return menuBar;
    }

    private class ViewMenuActionListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            if (e.getActionCommand() == "fullgclines") {
                modelChart.setShowFullGCLines(((JCheckBoxMenuItem) e.getSource()).getState());
            } else if (e.getActionCommand() == "incgclines") {
                modelChart.setShowIncGCLines(((JCheckBoxMenuItem) e.getSource()).getState());
            } else if (e.getActionCommand() == "gctimesline") {
                modelChart.setShowGCTimesLine(((JCheckBoxMenuItem) e.getSource()).getState());
            } else if (e.getActionCommand() == "gctimesrectangles") {
                modelChart.setShowGCTimesRectangles(((JCheckBoxMenuItem) e.getSource()).getState());
            } else if (e.getActionCommand() == "totalmemory") {
                modelChart.setShowTotalMemoryLine(((JCheckBoxMenuItem) e.getSource()).getState());
            } else if (e.getActionCommand() == "usedmemory") {
                modelChart.setShowUsedMemoryLine(((JCheckBoxMenuItem) e.getSource()).getState());
            }
        }
    }

    private void exit() {
        storePreferences();
        dispose();
        System.exit(0);
    }


    private void loadPreferences() {
        File preferences = getPreferencesFile();
        if (preferences.exists()) {
            FileInputStream in = null;
            try {
                in = new FileInputStream(preferences);
                Properties properties = new Properties();
                properties.load(in);
                if (PREFERENCE_VERSION.equals(properties.getProperty("preferences.version"))) {
                    JMenu menu = getJMenuBar().getMenu(1); // view menu
                    for (int i=0; i<menu.getItemCount(); i++) {
                        JCheckBoxMenuItem item = (JCheckBoxMenuItem)menu.getItem(i);
                        item.setState(properties.getProperty("view."+item.getActionCommand(), "true").equals("true"));
                        //item.;
                        viewActionListener.actionPerformed(new ActionEvent(item, 0, item.getActionCommand()));
                    }
                    //modelChart.setScaleFactor(getDoubleProperty(properties.getProperty("view.zoom"), 100.0d));
                    int width = getIntegerProperty(properties.getProperty("window.width"), 800);
                    int height = getIntegerProperty(properties.getProperty("window.height"), 600);
                    int x = getIntegerProperty(properties.getProperty("window.x"), 0);
                    int y = getIntegerProperty(properties.getProperty("window.y"), 0);
                    setBounds(x, y, width, height);
                    String lastfile = properties.getProperty("lastfile");
                    if (lastfile != null) {
                        openDialog.setSelectedFile(new File(lastfile));
                    }
                }
            }
            catch(IOException ioe) {
                ioe.printStackTrace();
            }
            finally {
                if (in != null) try { in.close(); } catch (IOException ioe) {}
            }
        }
    }

    private void storePreferences() {
        File preferences = getPreferencesFile();
        if (true) {
            FileOutputStream out = null;
            try {
                out = new FileOutputStream(preferences);
                Properties properties = new Properties();
                JMenu menu = getJMenuBar().getMenu(1); // view menu
                for (int i=0; i<menu.getItemCount(); i++) {
                    JCheckBoxMenuItem item = (JCheckBoxMenuItem)menu.getItem(i);
                    properties.setProperty("view."+item.getActionCommand(), item.getState()?Boolean.TRUE.toString():Boolean.FALSE.toString());
                }
                //properties.setProperty("view.zoom", Double.toString(modelChart.getScaleFactor()));
                properties.setProperty("window.width", Integer.toString(getWidth()));
                properties.setProperty("window.height", Integer.toString(getHeight()));
                properties.setProperty("window.x", Integer.toString(getX()));
                properties.setProperty("window.y", Integer.toString(getY()));
                properties.setProperty("preferences.version", PREFERENCE_VERSION);
                if (openDialog.getSelectedFile() != null) {
                    properties.setProperty("lastfile", openDialog.getSelectedFile().getAbsolutePath());
                }
                properties.store(out, "GCViewer preferences " + new Date());
            }
            catch(IOException ioe) {
                ioe.printStackTrace();
            }
            finally {
                if (out != null) try { out.close(); } catch (IOException ioe) {}
            }
        }
    }

    private int getIntegerProperty(String stringValue, int defaultValue) {
        int value = defaultValue;
        try {
            if (stringValue != null) value = Integer.parseInt(stringValue);
        }
        catch (NumberFormatException nfe) {
            nfe.printStackTrace();
        }
        return value;
    }

    private double getDoubleProperty(String stringValue, double defaultValue) {
        double value = defaultValue;
        try {
            if (stringValue != null) value = Double.parseDouble(stringValue);
        }
        catch (NumberFormatException nfe) {
            nfe.printStackTrace();
        }
        return value;
    }

    private File getPreferencesFile() {
        File preferences = new File(System.getProperty("user.home")+"/gcviewer.properties");
        return preferences;
    }

    public static void main(String[] args) {
        if (args.length > 1)
            usage();
        else {
            GCViewer viewer = new GCViewer();
            if (args.length == 1) {
                viewer.openFile(new File(args[0]));
            }
        }
    }

    private static void usage() {
        System.out.println("GCViewer");
        System.out.println("java com.tagtraum.perf.gcviewer.GCViewer [<gc-log-file>]");
    }

    private static class ExtensionFileFilter extends javax.swing.filechooser.FileFilter {
        private String extension;
        private String description;
        public ExtensionFileFilter(String extension, String description) {
            this.extension = extension.toLowerCase();
            this.description = description;

        }
        public boolean accept(File file) {
            return file.toString().toLowerCase().endsWith(extension);
        }

        public String getExtension() {
            return extension;
        }

        public String getDescription() {
            return description;
        }
    }
}
