/*
 * Written by Dawid Kurzyniec and released to the public domain, as explained
 * at http://creativecommons.org/licenses/publicdomain
 */

package edu.emory.mathcs.util.swing;

import java.util.*;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * Message box with the expandable detail pane at the bottom of the window.
 * Allows users to show/hide message details that may be represented
 * by any Swing component.
 * Construction and usage patterns are similar to that of {@link JOptionPane}
 * except that the "details" object is additionally required. If this
 * object is a JComponent, it is used as is;
 * otherwise, it is converted to string and displayed in a text box.
 *
 * @see JOptionPane for detailed documentation
 *
 * @author Dawid Kurzyniec
 * @version 1.0
 */

public class JDetailedMessageBox extends JDialog {

    static Color BACKGROUND_COLOR = UIManager.getColor("OptionPane.background");
    static Font MESSAGE_FONT = UIManager.getFont("OptionPane.messageFont");
    static Font BUTTON_FONT = UIManager.getFont("OptionPane.font");
    static Font LABEL_FONT = UIManager.getFont("Label.font");

    JPanel detailPane;
    JButton detailBtn;
    JButton initialFocus;
    int selectedBtn = -1;
    int defaultBtn;
    DetailButtonListener dblistener = new DetailButtonListener();

    JDetailedMessageBox(Dialog owner, String title) {
        super(owner, title, true);
    }

    JDetailedMessageBox(Frame owner, String title) {
        super(owner, title, true);
    }

    public static JDetailedMessageBox newInstance(
        Component parentComponent, String title,
        Object message, int messageType, int optionType,
        Object details)
    {
        return newInstance(parentComponent, title, message, messageType,
                           optionType, iconForMessageType(messageType), details);
    }

    public static JDetailedMessageBox newInstance(
        Component parentComponent, String title,
        Object message, int messageType, int optionType,
        Icon icon, Object details)
    {
        return newInstance(parentComponent, title, message, messageType,
                           optionType, icon, null, details);
    }

    public static JDetailedMessageBox newInstance(
        Component parentComponent, String title,
        Object message, int messageType, int optionType,
        Icon icon, Object[] options, Object details)
    {
        return newInstance(parentComponent, title, message, messageType,
                           optionType, icon, options, 0, details);
    }

    public static JDetailedMessageBox newInstance(
        Component parentComponent, String title,
        Object message, int messageType, int optionType,
        Icon icon, Object[] options, int initialIndex, Object details)
    {
        return newInstance(parentComponent, title, message, messageType,
                           optionType, (Object)icon, options, initialIndex,
                           details);
    }

    public static JDetailedMessageBox newInstance(
        Component parentComponent, String title,
        Object message, int messageType, int optionType,
        Object icon, Object[] options, int initialIndex, Object details)
    {
        final JDetailedMessageBox dialog;

        Window window = getWindowForComponent(parentComponent);
        if (window instanceof Frame) {
            dialog = new JDetailedMessageBox((Frame)window, title);
        } else {
            dialog = new JDetailedMessageBox((Dialog)window, title);
        }

        Container contentPane = dialog.getContentPane();
        contentPane.setLayout(new GridBagLayout());
        //JPanel iconPane = new JPanel();

        Component iconComponent;
        if (icon == null) {
            iconComponent = null;
        }
        else if (icon instanceof Component) {
            iconComponent = (Component)icon;
        }
        else {
            JLabel iconLabel;
            if (icon instanceof Icon) {
                iconLabel = new JLabel((Icon)icon);
            } else {
                iconLabel = new JLabel(icon.toString());
            }
            iconLabel.setBackground(BACKGROUND_COLOR);
            iconComponent = iconLabel;
        }

        if (iconComponent != null) {
            contentPane.add(iconComponent, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0
                ,GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(6, 6, 5, 5), 0, 0));
        }

        //JPanel auxMsgPane = new JPanel();
        JPanel msgPane = new JPanel();
        contentPane.add(msgPane, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.001
            ,GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(6, 6, 5, 5), 0, 0));
        //auxMsgPane.setLayout(new BorderLayout());
        //auxMsgPane.add(msgPane, BorderLayout.NORTH);
        JPanel auxBtnPane = new JPanel();
        JPanel btnPane = new JPanel();
        contentPane.add(auxBtnPane, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0
            ,GridBagConstraints.NORTHEAST, GridBagConstraints.VERTICAL, new Insets(6, 6, 5, 5), 0, 0));
        auxBtnPane.setLayout(new BorderLayout());
        auxBtnPane.add(btnPane, BorderLayout.NORTH);

        // buttons

        btnPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
        GridLayout btnLayout = new GridLayout(0, 1, 5, 5);
        btnPane.setLayout(btnLayout);
        if (options == null) {
            options = buttonsFromOptionType(optionType, dialog.getLocale());
        }
        for (int i = 0; i < options.length; i++) {
            Object btnObj = options[i];
            JButton jbutton;
            if (btnObj instanceof JButton) {
                jbutton = (JButton)btnObj;
            } else {
                jbutton = new JButton(btnObj.toString());
            }
            if (i == initialIndex) {
                dialog.getRootPane().setDefaultButton(jbutton);
                dialog.initialFocus = jbutton;
            }
            configureButton(jbutton);
            jbutton.addActionListener(dialog.new ButtonActionListener(i));
            btnPane.add(jbutton);
        }

        // message

        msgPane.setLayout(new BorderLayout());
        if (message instanceof Component) {
            msgPane.add((Component)message);
        } else {
            String msg = message.toString();
            JTextArea ta = new JTextArea(msg);
            Font font = getMessageFont(dialog);
            if (font != null) ta.setFont(font);
            ta.setEditable(false);
            ta.setBackground(BACKGROUND_COLOR);
            // check preferred witdh before word wrapping
            // try to determine best dimensions for message pane, considering
            // number of buttons and the amount of text to display
            Dimension tapref = ta.getPreferredSize();
            int width = (int)tapref.getWidth();
            int height = (int)tapref.getHeight();
            int preflines = (int)(btnPane.getPreferredSize().getHeight()/height);
            if (preflines == 0) preflines = 1;
            if (width > 250) {
                width = width / preflines;
                if (width < 250) {
                    width = 250;
                }
                else if (width > 500) {
                    width = 500;
                }
            }

            // hint for the view to correctly compute preferred height
            // (otherwise, height does not reflect wrapped lines)
            ta.setSize(width, Integer.MAX_VALUE);
            ta.setLineWrap(true);
            ta.setWrapStyleWord(true);
            msgPane.add(ta, BorderLayout.CENTER);
            ta.invalidate();
        }


        // "details" button

        JButton dbtn = new JButton();
        configureButton(dbtn);
        auxBtnPane.add(dbtn, BorderLayout.SOUTH);
        dialog.detailBtn = dbtn;
        dbtn.addActionListener(dialog.dblistener);

        // detail pane

        JPanel detailPane = new JPanel();

        //detailPane.setPreferredSize(new Dimension(0, 150));
        detailPane.setLayout(new BorderLayout());
        if (details instanceof Component) {
            detailPane.add((Component)details, BorderLayout.CENTER);
        } else {
            JScrollPane scrollPane = new JScrollPane();
            detailPane.add(scrollPane, BorderLayout.CENTER);
            JTextArea dtt = new JTextArea(details.toString());
            dtt.setEditable(false);
            dtt.setTabSize(4);
            scrollPane.setViewport(new DetailViewport());
            scrollPane.setViewportView(dtt);
        }

        Dimension dsize = detailPane.getSize();
        dsize.height = 150;
        detailPane.setSize(dsize);
        detailPane.invalidate();

        dialog.detailPane = detailPane;
        contentPane.add(detailPane, new GridBagConstraints(0, 1, 3, 1, 1.0, 1.0
            ,GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(6, 6, 5, 5), 0, 0));
        dialog.dblistener.update(false);

        if (JDialog.isDefaultLookAndFeelDecorated()) {
            int style = styleFromMessageType(messageType);
            boolean supportsWindowDecorations =
            UIManager.getLookAndFeel().getSupportsWindowDecorations();
            if (supportsWindowDecorations) {
                dialog.setUndecorated(true);
                dialog.getRootPane().setWindowDecorationStyle(style);
            }
        }

        //dialog.setResizable(false);
        dialog.pack();

        dialog.setLocationRelativeTo(parentComponent);
        dialog.addWindowListener(new WindowAdapter() {
            private boolean gotFocus = false;
            public void windowClosing(WindowEvent we) {
                dialog.selectedBtn = -1;
            }
            public void windowGainedFocus(WindowEvent we) {
                // Once window gets focus, set initial focus
                if (!gotFocus) {
                    dialog.selectedBtn = dialog.defaultBtn;
                    gotFocus = true;
                }
            }
        });
        dialog.addComponentListener(new ComponentAdapter() {
            public void componentShown(ComponentEvent ce) {
                // reset value to ensure closing works properly
                dialog.selectedBtn = -1;
            }
        });
        return dialog;
    }

    static Window getWindowForComponent(Component parentComponent)
        throws HeadlessException {
        if (parentComponent == null)
            return JOptionPane.getRootFrame();
        if (parentComponent instanceof Frame || parentComponent instanceof Dialog)
            return (Window)parentComponent;
        return JDetailedMessageBox.getWindowForComponent(parentComponent.getParent());
    }

    public void setInitialFocus() {
        if (initialFocus != null) {
            initialFocus.requestFocus();
        }
    }

    public int getValue() {
        return selectedBtn;
    }

    private static int styleFromMessageType(int messageType) {
        switch (messageType) {
        case JOptionPane.ERROR_MESSAGE:
            return JRootPane.ERROR_DIALOG;
        case JOptionPane.QUESTION_MESSAGE:
            return JRootPane.QUESTION_DIALOG;
        case JOptionPane.WARNING_MESSAGE:
            return JRootPane.WARNING_DIALOG;
        case JOptionPane.INFORMATION_MESSAGE:
            return JRootPane.INFORMATION_DIALOG;
        case JOptionPane.PLAIN_MESSAGE:
        default:
            return JRootPane.PLAIN_DIALOG;
        }
    }

    private static JButton[] buttonsFromOptionType(int optionType, Locale l) {
        JButton[] options;
        if (optionType == JOptionPane.YES_NO_OPTION) {
            options = new JButton[2];
            options[0] = new JButton(UIManager.get("OptionPane.yesButtonText",l).toString());
            options[0].setMnemonic(getMnemonic("OptionPane.yesButtonMnemonic",l));
            options[1] = new JButton(UIManager.get("OptionPane.noButtonText",l).toString());
            options[1].setMnemonic(getMnemonic("OptionPane.noButtonMnemonic", l));
        }
        else if (optionType == JOptionPane.YES_NO_CANCEL_OPTION) {
            options = new JButton[3];
            options[0] = new JButton(UIManager.get("OptionPane.yesButtonText",l).toString());
            options[0].setMnemonic(getMnemonic("OptionPane.yesButtonMnemonic",l));
            options[1] = new JButton(UIManager.get("OptionPane.noButtonText",l).toString());
            options[1].setMnemonic(getMnemonic("OptionPane.noButtonMnemonic", l));
            options[2] = new JButton(UIManager.get("OptionPane.cancelButtonText",l).toString());
            options[2].setMnemonic(getMnemonic("OptionPane.cancelButtonMnemonic", l));
        }
        else if (optionType == JOptionPane.OK_CANCEL_OPTION) {
            options = new JButton[2];
            options[0] = new JButton(UIManager.get("OptionPane.okButtonText",l).toString());
            options[0].setMnemonic(getMnemonic("OptionPane.okButtonMnemonic",l));
            options[1] = new JButton(UIManager.get("OptionPane.cancelButtonText",l).toString());
            options[1].setMnemonic(getMnemonic("OptionPane.cancelButtonMnemonic", l));
        }
        else {
            options = new JButton[1];
            options[0] = new JButton(UIManager.get("OptionPane.okButtonText",l).toString());
            options[0].setMnemonic(getMnemonic("OptionPane.okButtonMnemonic",l));
        }
        return options;
    }

    private static Icon iconForMessageType(int messageType) {
        if(messageType < 0 || messageType > 3)
            return null;
        switch(messageType) {
        case 0:
            return UIManager.getIcon("OptionPane.errorIcon");
        case 1:
            return UIManager.getIcon("OptionPane.informationIcon");
        case 2:
            return UIManager.getIcon("OptionPane.warningIcon");
        case 3:
            return UIManager.getIcon("OptionPane.questionIcon");
        }
        return null;
    }

    private static int getMnemonic(String key, Locale l) {
        String value = (String)UIManager.get(key, l);

        if (value == null) {
            return 0;
        }
        try {
            return Integer.parseInt(value);
        }
        catch (NumberFormatException nfe) { }
        return 0;
    }

    private class DetailButtonListener implements ActionListener {
        boolean shown = false;

        public void actionPerformed(ActionEvent evt) {
            update(!shown);
        }

        void update(boolean show) {
            String label = show ? ">> Details" : "Details >>";
            detailBtn.setText(label);
            detailPane.setVisible(show);
            if (show == shown) return;
            this.shown = show;
            pack();
        }
    }

    protected class ButtonActionListener implements ActionListener {
        int buttonIndex;
        public ButtonActionListener(int buttonIndex) {
            this.buttonIndex = buttonIndex;
        }
        public void actionPerformed(ActionEvent e) {
            selectedBtn = buttonIndex;
            setVisible(false);
        }
    }

    private static void configureButton(JButton btn) {
        if (BUTTON_FONT != null && btn.getFont() == null) {
            btn.setFont(BUTTON_FONT);
        }
    }

    private static Font getMessageFont(Component owner) {
        Font font = MESSAGE_FONT;
        if (font == null) font = LABEL_FONT;
        if (font == null) font = owner.getFont();
        return font;
    }

    private static Locale getLocaleForComponent(Component c) {
        return (c == null) ? Locale.getDefault() : c.getLocale();
    }

    public static void showMessageDialog(Component parentComponent,
        Object message, Object details)
    {
        showMessageDialog(parentComponent, message, UIManager.getString(
                          "OptionPane.messageDialogTitle",
                          getLocaleForComponent(parentComponent)),
                          JOptionPane.INFORMATION_MESSAGE, details);
    }

    public static void showMessageDialog(Component parentComponent,
        Object message, String title, int messageType, Object details)
    {
        showMessageDialog(parentComponent, message, title, messageType,
                          iconForMessageType(messageType), details);
    }

    public static void showMessageDialog(Component parentComponent,
        Object message, String title, int messageType, Icon icon, Object details)
    {
        showOptionDialog(parentComponent, message, title, JOptionPane.DEFAULT_OPTION,
                         messageType, icon, null, details);
    }

    public static int showConfirmDialog(Component parentComponent,
        Object message, Object details)
    {
        return showConfirmDialog(parentComponent, message,
                                 UIManager.getString("OptionPane.titleText"),
                                 JOptionPane.YES_NO_CANCEL_OPTION, details);
    }

    public static int showConfirmDialog(Component parentComponent,
        Object message, String title, int optionType, Object details)
    {
        return showConfirmDialog(parentComponent, message, title, optionType,
                                 JOptionPane.QUESTION_MESSAGE, details);
    }

    public static int showConfirmDialog(Component parentComponent,
        Object message, String title, int optionType, int messageType,
        Object details)
    {
        return showConfirmDialog(parentComponent, message, title, optionType,
                                 messageType, iconForMessageType(messageType),
                                 details);
    }

    public static int showConfirmDialog(Component parentComponent,
        Object message, String title, int optionType,
        int messageType, Icon icon, Object details)
    {
        return showOptionDialog(parentComponent, message, title, optionType,
                                messageType, icon, null, details);
    }

    public static int showOptionDialog(Component parentComponent,
        Object message, String title, int optionType, int messageType,
        Object[] options, Object details)
    {
        return showOptionDialog(parentComponent, message, title, optionType,
                                messageType, iconForMessageType(messageType),
                                options, details);
    }

    public static int showOptionDialog(Component parentComponent,
        Object message, String title, int optionType, int messageType,
        Icon icon, Object[] options, Object details)
    {
        JDetailedMessageBox mbox = newInstance(parentComponent, title, message,
                                               messageType, optionType, icon,
                                               options, details);

        mbox.setInitialFocus();
        mbox.show();
        mbox.dispose();

        int selectedValue = mbox.getValue();
        return selectedValue;
    }

    private static class DetailViewport extends JViewport {
        public Dimension getPreferredSize() {
            Dimension d = super.getPreferredSize();
            d.height = 150;
            return d;
        }
    }
}