
package uk.co.wingpath.gui;

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import uk.co.wingpath.event.*;
import uk.co.wingpath.event.Event;
import uk.co.wingpath.util.*;

public class StatusBar
    extends JPanel
    implements Reporter
{
    private final JLabel icon;
    private final JLabel text;
    private final Icon errorIcon;
    private final Icon warningIcon;
    private final Icon informationIcon;
    private final Border border;
    private ValueListener clearListener;
    private Action cancelAction;
    private Action clearAction;
    private final String id;
    private boolean hasError;
    private boolean hasWarning;
    private final ValueEventSource statusListeners;
    private final HelpViewer helpViewer;
    private String helpId;
    private final Action helpAction;

    public StatusBar (String id, final HelpViewer helpViewer)
    {
        Event.checkIsEventDispatchThread ();
        this.id = id;
        this.helpViewer = helpViewer;
        border = BorderFactory.createLoweredBevelBorder ();
        errorIcon = UIManager.getIcon ("OptionPane.errorIcon");
        warningIcon = UIManager.getIcon ("OptionPane.warningIcon");
        informationIcon = UIManager.getIcon ("OptionPane.informationIcon");
        setLayout (new BoxLayout (this, BoxLayout.X_AXIS));
        icon = new JLabel ();
        String exampleLine =
            "This_is_an_example_line_that_is_45_chars_long<br>";
        text = new JLabel ("<html>" + exampleLine + exampleLine + exampleLine);

        clearListener = null;
        cancelAction = null;
        clearAction = null;
        hasError = false;
        hasWarning = false;
        statusListeners = new ValueEventSource ();
        helpId = null;

        add (Box.createHorizontalStrut (10));
        add (icon);
        add (text);

        helpAction =
            new AbstractAction ("Error Help")
            {
                public void actionPerformed (ActionEvent e)
                {
                    Event.checkIsEventDispatchThread ();
                    if (helpViewer != null && helpId != null)
                        helpViewer.viewError (helpId);
                }
            };
        helpAction.putValue (Action.SHORT_DESCRIPTION,
            "Get help on this error message");
        InputMap inputMap = getInputMap (JComponent.WHEN_IN_FOCUSED_WINDOW);
        inputMap.put (KeyStroke.getKeyStroke (KeyEvent.VK_F4, 0),
            "error_help");
        getActionMap ().put ("error_help", helpAction);

        Dimension textSize = text.getMinimumSize ();
        int minHeight = errorIcon == null ? 0 : errorIcon.getIconHeight ();
        if (textSize.height > minHeight)
            minHeight = textSize.height;
        int minWidth = errorIcon == null ? 0 : errorIcon.getIconWidth ();
        minWidth += textSize.width;
        setPreferredSize (new Dimension (minWidth + 2, minHeight + 2));
        clear ();
    }

    public StatusBar (String id)
    {
        this (id, null);
    }

    public ValueListener getClearListener (final String cid)
    {
        Event.checkIsEventDispatchThread ();
        if (clearListener == null)
        {
            clearListener = new ValueListener ()
                {
                    public void valueChanged (ValueEvent e)
                    {
                        clear (cid);
                    }
                };
        }
        return clearListener;
    }

    private void reset ()
    {
        removeAll ();
        helpId = null;
        InputMap inputMap = getInputMap (JComponent.WHEN_IN_FOCUSED_WINDOW);
        inputMap.remove (KeyStroke.getKeyStroke (KeyEvent.VK_F4, 0));
        setBorder (null);
        text.setText ("");
        icon.setIcon (null);
        add (Box.createHorizontalStrut (10));
        add (icon);
        add (text);
    }

    private void redisplay (boolean err, boolean warn)
    {
        revalidate ();
        repaint ();
        if (err != hasError)
        {
            hasError = err;
            statusListeners.fireValueChanged (this);
        }
        if (warn != hasWarning)
        {
            hasWarning = warn;
            statusListeners.fireValueChanged (this);
        }
    }

    private void escapeString (StringBuilder sb, String str)
    {
        for (int i = 0 ; i < str.length () ; i++)
        {
            char c = str.charAt (i);
            switch (c)
            {
            case '>':
                sb.append ("&gt;");
                break;
            case '<':
                sb.append ("&lt;");
                break;
            case '&':
                sb.append ("&amp;");
                break;
            case '"':
                sb.append ("&quot;");
                break;
            default:
                sb.append (c);
                break;
            }
        }
    }

    private void showText (String message)
    {
        String [] lines = message.split ("\n");
        StringBuilder sb = new StringBuilder ();
        sb.append ("<html>");
        boolean first = true;

        for (int i = 0 ; i < lines.length ; i++)
        {
            if (lines [i].length () != 0)
            {
                if (!first)
                    sb.append ("<br>");
                escapeString (sb, lines [i]);
                first = false;
            }
        }

        text.setText (sb.toString ());
    }

    private void addButton (Action action)
    {
        if (action == null)
            return;
        JButton button = new JButton (action);
        button.setRequestFocusEnabled (false);
        add (button);
    }

    private void addButtons (Action [] actions)
    {
        for (Action action : actions)
            addButton (action);
        if (helpViewer != null && helpId != null)
        {
            addButton (helpAction);
            InputMap inputMap = getInputMap (JComponent.WHEN_IN_FOCUSED_WINDOW);
            inputMap.put (KeyStroke.getKeyStroke (KeyEvent.VK_F4, 0),
                "error_help");
        }
    }

    public final void clear ()
    {
        if (!EventQueue.isDispatchThread ())
        {
            EventQueue.invokeLater (new Runnable ()
                {
                    public void run ()
                    {
                        clear ();
                    }
                });
            return;
        }
        reset ();
        redisplay (false, false);
    }

    public final void clear (final String cid)
    {
        if (!EventQueue.isDispatchThread ())
        {
            EventQueue.invokeLater (new Runnable ()
                {
                    public void run ()
                    {
                        clear (cid);
                    }
                });
            return;
        }
        reset ();
        redisplay (false, false);
    }

    public void showMessage (final String helpId, final String message,
        final Action... actions)
    {
        if (!EventQueue.isDispatchThread ())
        {
            EventQueue.invokeLater (new Runnable ()
                {
                    public void run ()
                    {
                        showMessage (helpId, message, actions);
                    }
                });
            return;
        }
        if (hasWarning || hasError)
            return;
        reset ();
        this.helpId = helpId;
        setBorder (border);
        showText (message);
        addButtons (actions);
        redisplay (false, false);
    }

    public void showMessage (final String message, final Action... actions)
    {
        showMessage (null, message, actions);
    }

    public void showMessage (String fmt, Object... args)
    {
        String msg = String.format (fmt, args);
        showMessage (msg);
    }

    public void showError (final String helpId, final String message,
        final Action... actions)
    {
        if (!EventQueue.isDispatchThread ())
        {
            EventQueue.invokeLater (new Runnable ()
                {
                    public void run ()
                    {
                        showError (helpId, message, actions);
                    }
                });
            return;
        }
        reset ();
        this.helpId = helpId;
        setBorder (border);
        showText (message);
        icon.setIcon (errorIcon);
        addButtons (actions);
        redisplay (true, false);
    }

    public void showError (final String message, final Action... actions)
    {
        showError (null, message, actions);
    }

    public void showError (String fmt, Object... args)
    {
        String msg = String.format (fmt, args);
        showError (msg);
    }

    public void showWarning (final String helpId, final String message,
        final Action... actions)
    {
        if (!EventQueue.isDispatchThread ())
        {
            EventQueue.invokeLater (new Runnable ()
                {
                    public void run ()
                    {
                        showWarning (helpId, message, actions);
                    }
                });
            return;
        }
        if (hasError)
            return;
        reset ();
        this.helpId = helpId;
        setBorder (border);
        showText (message);
        icon.setIcon (warningIcon);
        addButtons (actions);
        redisplay (false, true);
    }

    public void showWarning (final String message, final Action... actions)
    {
        showWarning (null, message, actions);
    }

    public Action getClearAction ()
    {
        if (clearAction == null)
        {
            clearAction = new AbstractAction ("Clear")
            {
                public void actionPerformed (ActionEvent e)
                {
                    clear ();
                }
            };
            clearAction.putValue (Action.MNEMONIC_KEY, KeyEvent.VK_C);
            clearAction.putValue (Action.SHORT_DESCRIPTION,
                "Clear status bar message");
        }
        return clearAction;
    }

    public Action getCancelAction ()
    {
        if (cancelAction == null)
        {
            cancelAction = new AbstractAction ("Cancel")
            {
                public void actionPerformed (ActionEvent e)
                {
                    clear ();
                }
            };
            cancelAction.putValue (Action.MNEMONIC_KEY, KeyEvent.VK_C);
        }
        return cancelAction;
    }

    public void showInfo (final String helpId, final String message,
        final Action... actions)
    {
        if (!EventQueue.isDispatchThread ())
        {
            EventQueue.invokeLater (new Runnable ()
                {
                    public void run ()
                    {
                        showInfo (helpId, message, actions);
                    }
                });
            return;
        }
        if (hasWarning || hasError)
            return;
        reset ();
        this.helpId = helpId;
        setBorder (border);
        showText (message);
        icon.setIcon (informationIcon);
        addButtons (actions);
        redisplay (false, false);
    }

    public void showInfo (final String message, final Action... actions)
    {
        showInfo (null, message, actions);
    }

    public void showException (Throwable ex, Action... actions)
    {
        String msg = Exceptions.getMessage (ex);
        if (ex instanceof Helpful)
            showError (((Helpful) ex).getHelpId (), msg, actions);
        else
            showError (msg, actions);
    }

    public boolean hasError ()
    {
        return hasError;
    }

    public boolean hasWarning ()
    {
        return hasWarning;
    }

    @Override
    public void warn (String helpId, String fmt, Object... args)
    {
        String msg = String.format (fmt, args);
        showWarning (helpId, msg, getClearAction ());
    }

    @Override
    public boolean fatal (String fmt, Object... args)
    {
        String msg = String.format (fmt, args);
        showError (null, msg, getClearAction ());
        return false;
    }

    @Override
    public boolean fatal (Throwable t, String fmt, Object... args)
    {
        String msg = String.format (fmt, args);
        showError (null, msg, getClearAction ());
        return false;
    }

    @Override
    public void error (String helpId, String fmt, Object... args)
    {
        String msg = String.format (fmt, args);
        showError (helpId, msg, getClearAction ());
    }

    @Override
    public void info (String helpId, String fmt, Object... args)
    {
        String msg = String.format (fmt, args);
        showInfo (helpId, msg, getClearAction ());
    }

    @Override
    public void trace (String helpId, String fmt, Object... args)
    {
    }

    @Override
    public boolean debug (String fmt, Object... args)
    {
        return true;
    }

    public void addStatusListener (ValueListener l)
    {
        statusListeners.addListener (l);
    }

    public void removeStatusListener (ValueListener l)
    {
        statusListeners.removeListener (l);
    }
}

