/*
 * StatusBar.java - The status bar displayed at the bottom of views
 * :tabSize=4:indentSize=4:noTabs=false:
 * :folding=explicit:collapseFolds=1:
 *
 * Copyright (C) 2001, 2004 Slava Pestov
 * Portions copyright (C) 2001 Mike Dillon
 * Portions copyright (C) 2008 Matthieu Casanova
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package org.gjt.sp.jedit.gui;

//{{{ Imports
import javax.swing.border.*;
import javax.swing.text.Segment;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.util.Objects;
import java.util.StringTokenizer;
import org.gjt.sp.jedit.textarea.*;
import org.gjt.sp.jedit.*;
import org.gjt.sp.jedit.gui.statusbar.StatusWidgetFactory;
import org.gjt.sp.jedit.gui.statusbar.Widget;
import org.gjt.sp.jedit.gui.statusbar.ToolTipLabel;
import org.gjt.sp.util.*;
//}}}

/** The status bar used to display various information to the user.
 *
 * Currently, it is used for the following:
 * <ul>
 * <li>Displaying caret position information
 * <li>Displaying {@link InputHandler#readNextChar(String,String)} prompts
 * <li>Displaying {@link #setMessage(String)} messages
 * <li>Displaying I/O progress
 * <li>Displaying various editor settings
 * <li>Displaying memory status
 * </ul>
 *
 * @version $Id: StatusBar.java 23410 2014-02-09 19:23:35Z ezust $
 * @author Slava Pestov
 * @since jEdit 3.2pre2
 */
public class StatusBar extends JPanel
{
	//{{{ StatusBar constructor
	public StatusBar(View view)
	{
		super(new BorderLayout());
		setName("StatusBar");
		setBorder(new CompoundBorder(new EmptyBorder(4,0,0,
			(OperatingSystem.isMacOS() ? 18 : 0)),
			UIManager.getBorder("TextField.border")));

		this.view = view;

		panel = new JPanel(new BorderLayout());
		box = new Box(BoxLayout.X_AXIS);
		panel.add(BorderLayout.EAST,box);
		add(BorderLayout.CENTER,panel);

		MouseHandler mouseHandler = new MouseHandler();

		caretStatus = new ToolTipLabel();
		caretStatus.setName("caretStatus");
		caretStatus.setToolTipText(jEdit.getProperty("view.status.caret-tooltip"));
		caretStatus.addMouseListener(mouseHandler);

		message = new JLabel(" ");
		setMessageComponent(message);

		modeWidget = _getWidget("mode");
		foldWidget = _getWidget("fold");
		encodingWidget = _getWidget("encoding");
		wrapWidget = _getWidget("wrap");
		indentWidget = _getWidget("indent");
		multiSelectWidget = _getWidget("multiSelect");
		rectSelectWidget = _getWidget("rectSelect");
		overwriteWidget = _getWidget("overwrite");
		lineSepWidget = _getWidget("lineSep");

		taskHandler = new TaskHandler();
	} //}}}

	//{{{ propertiesChanged() method
	public void propertiesChanged()
	{
		Color fg = jEdit.getColorProperty("view.status.foreground");
		Color bg = jEdit.getColorProperty("view.status.background");

		showCaretStatus = jEdit.getBooleanProperty("view.status.show-caret-status");

		panel.setBackground(bg);
		panel.setForeground(fg);
		caretStatus.setBackground(bg);
		caretStatus.setForeground(fg);
		message.setBackground(bg);
		message.setForeground(fg);

		// retarded GTK look and feel!
		Font font = new JLabel().getFont();
		//UIManager.getFont("Label.font");
		FontMetrics fm = getFontMetrics(font);

		if (showCaretStatus)
		{
			panel.add(BorderLayout.WEST,caretStatus);

			caretStatus.setFont(font);

			Dimension dim = new Dimension(fm.stringWidth(caretTestStr),
					fm.getHeight());
			caretStatus.setPreferredSize(dim);
			updateCaretStatus();
		}
		else
			panel.remove(caretStatus);

		String statusBar = jEdit.getProperty("view.status");
		if (!Objects.equals(currentBar, statusBar))
		{
			box.removeAll();
			StringTokenizer tokenizer = new StringTokenizer(statusBar);
			while (tokenizer.hasMoreTokens())
			{
				String token = tokenizer.nextToken();
				if (Character.isLetter(token.charAt(0)))
				{
					Widget widget = getWidget(token);
					if (widget == null)
					{
						JLabel label = new JLabel(token);
						label.setBackground(bg);
						label.setForeground(fg);
						box.add(label);
						continue;
					}
					Component c = widget.getComponent();
					c.setBackground(bg);
					c.setForeground(fg);
					box.add(c);
					widget.update();
					widget.propertiesChanged();
				}
				else
				{
					JLabel label = new JLabel(token);
					label.setBackground(bg);
					label.setForeground(fg);
					box.add(label);
				}
			}
			currentBar = statusBar;
		}
		updateBufferStatus();
		updateMiscStatus();
	} //}}}

	//{{{ addNotify() method
	@Override
	public void addNotify()
	{
		super.addNotify();
		TaskManager.instance.addTaskListener(taskHandler);
	} //}}}

	//{{{ removeNotify() method
	@Override
	public void removeNotify()
	{
		super.removeNotify();
		TaskManager.instance.removeTaskListener(taskHandler);
	} //}}}

	//{{{ TaskListener implementation
	private class TaskHandler implements TaskListener
	{
		private final Runnable statusLineIo = new Runnable()
		{
			public void run()
			{
				// don't obscure existing message
				if(!currentMessageIsIO && message != null && !"".equals(message.getText().trim()))
					return;

				int requestCount = TaskManager.instance.countIoTasks();
				if(requestCount == 0)
				{
					setMessageAndClear(jEdit.getProperty(
						"view.status.io.done"));
					currentMessageIsIO = true;
				}
				else if(requestCount == 1)
				{
					setMessage(jEdit.getProperty(
						"view.status.io-1"));
					currentMessageIsIO = true;
				}
				else
				{
					Object[] args = {requestCount};
					setMessage(jEdit.getProperty(
						"view.status.io",args));
					currentMessageIsIO = true;
				}
			}
		};

		//{{{ waiting() method
		public void waiting(Task task)
		{
			SwingUtilities.invokeLater(statusLineIo);
		} //}}}
	
		//{{{ running() method
		public void running(Task task)
		{
		} //}}}
	
		//{{{ done() method
		public void done(Task task)
		{
			SwingUtilities.invokeLater(statusLineIo);
		} //}}}
	
		//{{{ statusUpdate() method
		public void statusUpdated(Task task)
		{
		} //}}}

		//{{{ maximumUpdated() method
		public void maximumUpdated(Task task)
		{
		} //}}}

		//{{{ valueUpdated() method
		public void valueUpdated(Task task)
		{
		} //}}}
	} //}}}

	//}}}

	//{{{ getMessage() method
	/**
	 * Returns the current message.
	 *
	 * @return the current message
	 * @since jEdit 4.4pre1
	 */
	public String getMessage()
	{
		return message.getText();
	} //}}}

	//{{{ setMessageAndClear() method
	/**
	 * Show a message for a short period of time.
	 * @param message The message
	 * @since jEdit 3.2pre5
	 */
	public void setMessageAndClear(String message)
	{
		setMessage(message);

		tempTimer = new Timer(0,new ActionListener()
		{
			public void actionPerformed(ActionEvent evt)
			{
				// so if view is closed in the meantime...
				if(isShowing())
					setMessage(null);
			}
		});

		tempTimer.setInitialDelay(10000);
		tempTimer.setRepeats(false);
		tempTimer.start();
	} //}}}

	//{{{ setMessage() method
	/**
	 * Displays a status message.
	 * @param message the message to display, it can be null
	 */
	public void setMessage(String message)
	{
		if(tempTimer != null)
		{
			tempTimer.stop();
			tempTimer = null;
		}

		setMessageComponent(this.message);

		if(message == null)
		{
			if(view.getMacroRecorder() != null)
				this.message.setText(jEdit.getProperty("view.status.recording"));
			else
				this.message.setText(" ");
		}
		else
			this.message.setText(message);
	} //}}}

	//{{{ setMessageComponent() method
	public void setMessageComponent(Component comp)
	{
		currentMessageIsIO = false;

		if (comp == null || messageComp == comp)
		{
			return;
		}

		messageComp = comp;
		panel.add(BorderLayout.CENTER, messageComp);
	} //}}}

	//{{{ updateCaretStatus() method
	/** Updates the status bar with information about the caret position, line number, etc */
	public void updateCaretStatus()
	{
		if (showCaretStatus)
		{
			Buffer buffer = view.getBuffer();

			if(!buffer.isLoaded() ||
				/* can happen when switching buffers sometimes */
				buffer != view.getTextArea().getBuffer())
			{
				caretStatus.setText(" ");
				return;
			}

			JEditTextArea textArea = view.getTextArea();

			int caretPosition = textArea.getCaretPosition();
			int currLine = textArea.getCaretLine();

			// there must be a better way of fixing this...
			// the problem is that this method can sometimes
			// be called as a result of a text area scroll
			// event, in which case the caret position has
			// not been updated yet.
			if(currLine >= buffer.getLineCount())
				return; // hopefully another caret update will come?

			int start = textArea.getLineStartOffset(currLine);
			int dot = caretPosition - start;

			if(dot < 0)
				return;

			int bufferLength = buffer.getLength();

			buffer.getText(start,dot,seg);
			int virtualPosition = StandardUtilities.getVirtualWidth(seg,
				buffer.getTabSize());
			// for GC
			seg.array = null;
			seg.count = 0;

			if (jEdit.getBooleanProperty("view.status.show-caret-linenumber", true))
			{
				buf.append(currLine + 1);
				buf.append(',');
			}
			if (jEdit.getBooleanProperty("view.status.show-caret-dot", true))
			{
				buf.append(dot + 1);
			}
			if (jEdit.getBooleanProperty("view.status.show-caret-virtual", true) &&
				virtualPosition != dot)
			{
				buf.append('-');
				buf.append(virtualPosition + 1);
			}
			if (buf.length() > 0)
			{
				buf.append(' ');
			}
			if (jEdit.getBooleanProperty("view.status.show-caret-offset", true) &&
				jEdit.getBooleanProperty("view.status.show-caret-bufferlength", true))
			{
				buf.append('(');
				buf.append(caretPosition);
				buf.append('/');
				buf.append(bufferLength);
				buf.append(')');
			}
			else if (jEdit.getBooleanProperty("view.status.show-caret-offset", true))
			{
				buf.append('(');
				buf.append(caretPosition);
				buf.append(')');
			}
			else if (jEdit.getBooleanProperty("view.status.show-caret-bufferlength", true))
			{
				buf.append('(');
				buf.append(bufferLength);
				buf.append(')');
			}

			caretStatus.setText(buf.toString());
			buf.setLength(0);
		}
	} //}}}

	//{{{ updateBufferStatus() method
	public void updateBufferStatus()
	{
		wrapWidget.update();
		indentWidget.update();
		lineSepWidget.update();
		modeWidget.update();
		foldWidget.update();
		encodingWidget.update();
	} //}}}

	//{{{ updateMiscStatus() method
	public void updateMiscStatus()
	{
		multiSelectWidget.update();
		rectSelectWidget.update();
		overwriteWidget.update();
	} //}}}

	//{{{ Private members
	private String currentBar;
	private final TaskHandler taskHandler;
	private final View view;
	private final JPanel panel;
	private final Box box;
	private final ToolTipLabel caretStatus;
	private Component messageComp;
	private final JLabel message;
	private final Widget modeWidget;
	private final Widget foldWidget;
	private final Widget encodingWidget;
	private final Widget wrapWidget;
	private final Widget indentWidget;
	private final Widget multiSelectWidget;
	private final Widget rectSelectWidget;
	private final Widget overwriteWidget;
	private final Widget lineSepWidget;
	/* package-private for speed */ StringBuilder buf = new StringBuilder();
	private Timer tempTimer;
	private boolean currentMessageIsIO;

	private final Segment seg = new Segment();

	private boolean showCaretStatus;
	//}}}

	static final String caretTestStr = "9999,999-999 (99999999/99999999)";

	//{{{ getWidget() method
	private Widget getWidget(String name)
	{
		if ("mode".equals(name))
			return modeWidget;
		if ("fold".equals(name))
			return foldWidget;
		if ("encoding".equals(name))
			return encodingWidget;
		if ("wrap".equals(name))
			return wrapWidget;
		if ("indent".equals(name))
			return indentWidget;
		if ("multiSelect".equals(name))
			return multiSelectWidget;
		if ("rectSelect".equals(name))
			return rectSelectWidget;
		if ("overwrite".equals(name))
			return overwriteWidget;
		if ("lineSep".equals(name))
			return lineSepWidget;

		return _getWidget(name);
	} //}}}

	//{{{ _getWidget() method
	private Widget _getWidget(String name)
	{
		StatusWidgetFactory widgetFactory =
		(StatusWidgetFactory) ServiceManager.getService("org.gjt.sp.jedit.gui.statusbar.StatusWidgetFactory", name);
		if (widgetFactory == null)
		{
			return null;
		}
		return widgetFactory.getWidget(view);
	} //}}}

	//{{{ MouseHandler class
	private class MouseHandler extends MouseAdapter
	{
		@Override
		public void mouseClicked(MouseEvent evt)
		{
			Object source = evt.getSource();
			if(source == caretStatus && evt.getClickCount() == 2)
			{
				view.getTextArea().showGoToLineDialog();
			}
		}
	} //}}}
}
