
package uk.co.wingpath.modbusgui;

import uk.co.wingpath.util.*;
import uk.co.wingpath.modbus.*;
import uk.co.wingpath.gui.*;
import uk.co.wingpath.event.Event;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.Timer;
import java.util.*;
import java.io.*;

public class WindowReporter
    implements Reporter
{
    private final static int MAX_LINES = 1000;

    private final Variable<Reporter.Level> maxLevel;
    private boolean includeLevel;
    private boolean includeMillis;
    private SimpleDialog dialog;
    private JTextArea textArea;
    private final Frontend frontend;
    private final String lineSeparator;
    private long addedLineTime;
    private JScrollPane scrollPane;
    private JButton clearButton;
    private int size;
    private LinkedList<String> helpIds;
    private Highlighter.HighlightPainter painter;
    private Object highlight;


    private void buildDialog ()
    {
        dialog = SimpleDialog.createFrame (frontend.getMainFrame ());
        dialog.setTitle (frontend.getProduct ().getName () + " - Log Output");
        clearButton = dialog.addButton ("Clear", "Clear log output",
            new ActionListener ()
            {
                public void actionPerformed (ActionEvent w)
                {
                    textArea.setText ("");
                    size = 0;
                    helpIds.clear ();
                }
            });
        clearButton.setMnemonic (KeyEvent.VK_C);
        dialog.addCloseButton ();
        dialog.addHelpButton (frontend.getHelpAction ("logging"));
        textArea = new JTextArea ();
        textArea.setEditable (false);
        textArea.setDragEnabled (true);
        scrollPane = new JScrollPane (textArea);
        scrollPane.setPreferredSize (new Dimension (580, 480));
        dialog.setContent (scrollPane);

        painter = new DefaultHighlighter.DefaultHighlightPainter (
            Gui.COLOUR_BACKGROUND_ERROR);
        highlight = null;

        textArea.addMouseListener (
            new MouseAdapter ()
            {
                public void mouseClicked (MouseEvent e)
                {
                    showHelp ();
                }
            });

        textArea.addMouseMotionListener (
            new MouseMotionAdapter ()
            {
                public void mouseMoved (MouseEvent e)
                {
                    int pos = textArea.viewToModel (e.getPoint ());
                    String helpId = getHelpId (pos);
                    dialog.setCursor (
                        helpId == null ? Cursor.getDefaultCursor () :
                        Cursor.getPredefinedCursor (Cursor.HAND_CURSOR));
                }
            });

        textArea.addCaretListener (
            new CaretListener ()
            {
                public void caretUpdate (CaretEvent e)
                {
                    try
                    {
                        int pos = e.getDot ();
                        String helpId = getHelpId (pos);
                        Highlighter highlighter = textArea.getHighlighter ();
                        if (highlight != null)
                        {
                            highlighter.removeHighlight (highlight);
                            highlight = null;
                        }

                        if (helpId != null)
                        {
                            int line = textArea.getLineOfOffset (pos);
                            int start = textArea.getLineStartOffset (line);
                            int end = textArea.getLineStartOffset (line + 1);
                            highlight = highlighter.addHighlight (start, end,
                                painter);
                        }
                    }
                    catch (BadLocationException ex)
                    {
                    }
                }
            });

        Gui.addShortCut (textArea, "ERROR_HELP", KeyEvent.VK_SPACE, 0,
            new AbstractAction ()
            {
                public void actionPerformed (ActionEvent e)
                {
                    showHelp ();
                }
            });

        // The following focus listener is a workaround for a Swing bug
        // (see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4512626).
        // It ensures that the caret is visible when the text area has focus.
        // This is needed for keyboard users to navigate to a line in order
        // to get help (and even just to know that the text area has focus).
        textArea.addFocusListener (new FocusListener ()
        {
            public void focusGained (FocusEvent e)
            {
                textArea.getCaret ().setVisible (true);
            }

            public void focusLost (FocusEvent e)
            {
                textArea.getCaret ().setVisible (false);
            }
        });
    }

    private String getHelpId (int pos)
    {
        // System.out.println ("pos " + pos);
        try
        {
            int line = textArea.getLineOfOffset (pos);
            // System.out.println ("line " + line);
            if (line < 0 || line >= helpIds.size ())
                return null;
            return helpIds.get (line);
        }
        catch (BadLocationException ex)
        {
        }

        return null;
    }

    private void showHelp ()
    {
        int pos = textArea.getCaretPosition ();
        // System.out.println ("showhelp pos " + pos);
        String helpId = getHelpId (pos);
        // System.out.println ("helpId " + helpId);
        if (helpId != null)
            frontend.getHelpViewer ().viewError (helpId);
    }

    public WindowReporter (Frontend frontend, Variable<Reporter.Level> maxLevel)
    {
        Event.checkIsEventDispatchThread ();
        this.frontend = frontend;
        this.maxLevel = maxLevel;
        includeLevel = false;
        includeMillis = false;
        lineSeparator = System.getProperty ("line.separator");
        addedLineTime = 0;
        size = 0;
        helpIds = new LinkedList<String> ();
        buildDialog ();
        Timer t = new Timer (100, new ActionListener ()
            {
                public void actionPerformed (ActionEvent e)
                {
                    Event.checkIsEventDispatchThread ();
                    if (System.currentTimeMillis () < addedLineTime + 500)
                    {
                        JScrollBar scrollBar =
                            scrollPane.getVerticalScrollBar ();
                        if (!scrollBar.getValueIsAdjusting ())
                            scrollBar.setValue (scrollBar.getMaximum ());
                    }
                }
            });
        t.start ();
    }

    /**
    * Sets whether to include milliseconds in the output.
    * @param include whether to include milliseconds.
    */
    public void setIncludeMillis (boolean include)
    {
        includeMillis = include;
    }

    /**
    * Sets whether to include the log level in the output.
    * @param include whether to include the log level.
    */
    public void setIncludeLevel (boolean include)
    {
        includeLevel = include;
    }

    private void writeLine (final String helpId, final String line)
    {
        EventQueue.invokeLater (new Runnable ()
            {
                public void run ()
                {
                    if (size >= MAX_LINES)
                    {
                        try
                        {
                            textArea.replaceRange ("", 0,
                                textArea.getLineStartOffset (1));
                        }
                        catch (BadLocationException e)
                        {
                        }
                        helpIds.removeFirst ();
                    }
                    textArea.append (line + lineSeparator);
                    helpIds.add (helpId);
                    size++;
                    addedLineTime = System.currentTimeMillis ();
                }
            });
    }

    private void writeLines (Reporter.Level level, String helpId,
        String fmt, Object... args)
    {
        if (level.compareTo (maxLevel.getValue ()) > 0)
            return;

        String msg = String.format (fmt, args);
        long ms = System.currentTimeMillis ();
        String prefix = String.format ("%1$tH:%1$tM:%1$tS", ms);
        if (includeMillis)
            prefix += String.format (".%1$tL", ms);
        prefix += ":";
        if (includeLevel)
            prefix += " " + String.format ("%-5s", level);
        prefix += " ";

        String [] lines = msg.split ("\n");

        for (int i = 0 ; i < lines.length ; i++)
        {
            writeLine (helpId, prefix + lines [i]);
        }
    }

    @Override
    public boolean fatal (String fmt, Object... args)
    {
        writeLines (Reporter.Level.FATAL, null, fmt, args);
        return false;
    }

    @Override
    public boolean fatal (Throwable t, String fmt, Object... args)
    {
        writeLines (Reporter.Level.FATAL, null, fmt, args);
        return false;
    }

    @Override
    public void error (String helpId, String fmt, Object... args)
    {
        writeLines (Reporter.Level.ERROR, helpId, fmt, args);
    }

    @Override
    public void warn (String helpId, String fmt, Object... args)
    {
        writeLines (Reporter.Level.WARN, helpId, fmt, args);
    }

    @Override
    public void info (String helpId, String fmt, Object... args)
    {
        writeLines (Reporter.Level.INFO, helpId, fmt, args);
    }

    @Override
    public void trace (String helpId, String fmt, Object... args)
    {
        writeLines (Reporter.Level.TRACE, helpId, fmt, args);
    }

    @Override
    public boolean debug (String fmt, Object... args)
    {
        writeLines (Reporter.Level.DEBUG, null, fmt, args);
        return true;
    }

    @Override
    public void clear ()
    {
    }

    public void showDialog ()
    {
        Event.checkIsEventDispatchThread ();
        dialog.showDialog ();
        clearButton.requestFocusInWindow ();
    }
}

