/**
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 *
 * Copyright (C)
 * <a href="http://www.inab.org">Spanish National Institute of Bioinformatics (INB)</a>
 * <a href="http://www.bsc.es">Barcelona Supercomputing Center (BSC)</a>
 * <a href="http://inb.bsc.es">Computational Node 6</a>
 */

package org.inb.swing;

import java.awt.AWTEvent;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.text.Element;
import javax.swing.text.ElementIterator;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;

/**
 * StatusBar component implementation.
 *
 * @author Dmitry Rechevsky
 */

public class StatusBar extends JPanel implements Runnable, MouseListener
{
    private final static int FADE_TIMEOUT = 5000;

    private JLabel label;
    private String message;

    private LogPanel logPanel;

    private Timer timer;
    private Fader fader;

    public StatusBar()
    {
        super(new GridBagLayout());

        GridBagConstraints c = new GridBagConstraints();
        c.insets = new Insets(0,1,0,1);

        label = new JLabel();
        label.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));

        label.setVerticalAlignment(JLabel.TOP);
        final int height = label.getFontMetrics(label.getFont()).getHeight();
        label.setMinimumSize(new Dimension(Integer.MIN_VALUE, height));
        label.setMaximumSize(new Dimension(Integer.MAX_VALUE, height));
        label.setPreferredSize(label.getMaximumSize());

        c.gridx = 1;
        c.weightx = 1.0;
        c.fill = GridBagConstraints.BOTH;

        add(label, c);

        JLabel icon = new JLabel(getIcon());
        icon.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
        icon.addMouseListener(this);

        c.gridx = 2;
        c.weightx = 0;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.CENTER;

        add(icon, c);

        logPanel = new LogPanel();
        icon.addMouseWheelListener(logPanel);
    }

    /**
     * Method sets a status bar message and logs it to the popup log panel.
     *
     * @param message - message to be logged on.
     */
    public void logMessage(String message)
    {
        logMessage(message, FADE_TIMEOUT);
    }

    /**
     * Method sets a status bar message and logs it to the popup log panel.
     *
     * @param message - message to be logged on.
     * @param timeout - timeout in milliseconds within which the message is shown.
     */
    public synchronized void logMessage(String message, int timeout)
    {
        setMessage(message, timeout);

        logPanel.addMessage(message);
    }

    /**
     * Method sets a status bar message (without logging).
     *
     * @param message - message to be shown.
     */
    public void setMessage(String message)
    {
        setMessage(message, FADE_TIMEOUT);
    }

    /**
     * Method sets a status bar message (without logging).
     *
     * @param message - message to be shown.
     * @param timeout - timeout in milliseconds within which the message is shown.
     */
    public synchronized void setMessage(String message, int timeout)
    {
        this.message = message;

        SwingUtilities.invokeLater(this);

        if (timer == null)
        {
            timer = new Timer(true);
        }

        if (fader != null)
        {
            fader.cancel();
        }

        fader = new Fader();
        timer.schedule(fader, timeout);
    }

    @Override
    public synchronized void run()
    {
        StringBuilder sb = new StringBuilder();

        sb.append("<html><span style='white-space: nowrap'>");
        sb.append(message);
        sb.append("</span></html>");

        String text = sb.toString();
        
        label.setText(text);
        label.setToolTipText(text);
    }

    private Icon getIcon()
    {
        BufferedImage image = new BufferedImage(16, 14, BufferedImage.TYPE_INT_ARGB);

        Graphics2D g2d = image.createGraphics();

        g2d.setColor(Color.LIGHT_GRAY);

        for (int i = 1; i < 14; i += 2)
        {
            g2d.drawLine(1, i, 14, i);
        }

        g2d.dispose();

        return new ImageIcon(image);
    }

    @Override public void mouseClicked(MouseEvent e) {}
    @Override public void mousePressed(MouseEvent e) {}
    @Override public void mouseReleased(MouseEvent e) {}

    @Override
    public void mouseEntered(MouseEvent e)
    {
        if (!logPanel.isVisible())
        {
            final int height = logPanel.getPreferredHeight();

            if (height > 0)
            {
                final int width = label.getWidth();


                final int x = label.getLocationOnScreen().x;

                logPanel.setPreferredSize(new Dimension(x < 0 ? width + x : width, height));
                logPanel.show(label, 0, 0 - height);
            }
        }
    }

    @Override
    public void mouseExited(MouseEvent e)
    {
        if (logPanel.isVisible())
        {
            logPanel.start();
        }
    }

    protected class Fader extends TimerTask
    {
        @Override
        public void run()
        {
            message = "";
            SwingUtilities.invokeLater(StatusBar.this);
        }
    }

    protected static class LogPanel extends JPopupMenu implements Runnable, ActionListener, MouseWheelListener, AWTEventListener
    {
        private final static int DELAY = 400;

        private StringBuilder buffer;

        private JEditorPane textPane;
        private JScrollPane scrollPane;

        private javax.swing.Timer timer;

        public LogPanel()
        {
           setLayout(new BorderLayout());

           setOpaque(false);

           StyleSheet css = new StyleSheet();
           css.addRule("div {text-align: left; color: blue}");

           HTMLEditorKit editorKit = new HTMLEditorKit();
           editorKit.setStyleSheet(css);

           textPane = new JEditorPane();
           textPane.setEditorKit(editorKit);

           textPane.setOpaque(false);
           textPane.setBackground(new Color(0,0,0, 255));

           textPane.setEditable(false);

           scrollPane = new JScrollPane(textPane);
           scrollPane.setBorder(BorderFactory.createEmptyBorder());
           scrollPane.setOpaque(false);
           scrollPane.getViewport().setOpaque(false);

           add(scrollPane, BorderLayout.CENTER);

           Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK);

           timer = new javax.swing.Timer(DELAY, this);
           timer.setRepeats(false);
        }

        void start()
        {
            timer.restart();
        }

        public int getPreferredHeight()
        {
            if (textPane.getDocument().getLength() == 0)
            {
                return 0;
            }

            Dimension d = textPane.getPreferredSize();
            Insets insets = getInsets();

            return Math.min(d.height, 100) + insets.top + insets.bottom;
        }

        public Dimension getPreferredScrollableViewportSize()
        {
            Dimension d = textPane.getPreferredSize();
            d.height = getPreferredHeight();
            return d;
        }

        public synchronized void addMessage(String message)
        {
            if (buffer == null)
            {
                buffer = new StringBuilder();
            }
            else
            {
                buffer.append('\n');
            }
            buffer.append("<div>");
            buffer.append(message);
            buffer.append("</div>");

            SwingUtilities.invokeLater(this);
        }

        @Override
        public void paint(Graphics g)
        {
            final Color bg = this.getBackground();
            final Color C1 = new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 127);
            final Color C2 = new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 255);

            Graphics2D g2d = (Graphics2D)g;

            BufferedImage image = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_ARGB);

            Graphics2D g2 = image.createGraphics();

            GradientPaint paint = new GradientPaint(0, 0, C1, 0, this.getHeight() - 1, C2);
            g2.setPaint(paint);
            g2.setComposite(AlphaComposite.Src);
            g2.fillRect(0, 0, getWidth(), getHeight());

            super.paint(g2);

            g2d.drawImage(image, 0, 0, this);
        }

        @Override
        public synchronized void run()
        {
            HTMLDocument doc = (HTMLDocument)textPane.getDocument();

            ElementIterator iter = new ElementIterator(textPane.getDocument());

            Element element;

            while((element = iter.next()) != null)
            {
                if (HTML.Tag.BODY.toString().equalsIgnoreCase(element.getName()))
                {
                    try
                    {
                        doc.insertBeforeEnd(element, buffer.toString());
                    }
                    catch (Exception ex)
                    {
                        ex.printStackTrace();
                    }
                    break;
                }
            }
            
            JViewport viewport = scrollPane.getViewport();
            viewport.validate();
            
            viewport.setViewPosition(new Point(0, Math.max(0, viewport.getView().getPreferredSize().height - viewport.getHeight())));

            buffer.setLength(0);
        }

        public void actionPerformed(ActionEvent e)
        {
            timer.stop();
            setVisible(false);
        }

        @Override
        public void mouseWheelMoved(MouseWheelEvent e)
        {
            scrollPane.dispatchEvent(e);
            setVisible(true); // prevent popup fromi hiding
        }

        public void eventDispatched(AWTEvent event)
        {
            if (event instanceof MouseEvent)
            {
                MouseEvent e = (MouseEvent)event;

                final int id = e.getID();

                if (id == MouseEvent.MOUSE_ENTERED || id == MouseEvent.MOUSE_EXITED)
                {
                    Component c = (Component) e.getSource();

                    if (SwingUtilities.isDescendingFrom(c, this))
                    {
                        Point point = SwingUtilities.convertPoint(c, e.getPoint(), this);

                        final boolean isInside = getBounds().contains(point);

                        if (id == MouseEvent.MOUSE_EXITED && !isInside)
                        {
                            setVisible(false);
                        }
                        else
                        {
                            timer.stop();
                        }
                    }
                }
            }
        }
    }
}