/*
 * org.openmicroscopy.shoola.util.ui.omeeditpane.OMEWikiComponent 
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006-2013 University of Dundee. All rights reserved.
 *
 *
 * 	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
 *  (at your option) 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.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *------------------------------------------------------------------------------
 */
package org.openmicroscopy.shoola.util.ui.omeeditpane;


//Java imports
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.border.Border;
import javax.swing.event.DocumentListener;

import org.apache.commons.lang.SystemUtils;
import org.apache.commons.lang.WordUtils;
import org.openmicroscopy.shoola.util.ui.IconManager;
import org.openmicroscopy.shoola.util.ui.UIUtilities;


/** 
 * The Wiki component.
 *
 * @author  Jean-Marie Burel &nbsp;&nbsp;&nbsp;&nbsp;
 * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @author Donald MacDonald &nbsp;&nbsp;&nbsp;&nbsp;
 * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
 * @version 3.0
 * @since 3.0-Beta4
 */
public class OMEWikiComponent 
	extends JPanel
	implements ActionListener
{

	/** Bounds property indicating that a data object has been selected. */
	public static final String WIKI_DATA_OBJECT_PROPERTY = "wikiDataObject";
	
	/** Bounds property indicating that a data object has been selected. */
	public static final String WIKI_DATA_OBJECT_ONE_CLICK_PROPERTY = 
		"wikiDataObjectOneClick";
	
	/** Regular expression for text. */
	public static final String TEXTREGEX = OMEWikiConstants.TEXTREGEX;
	
	/** Regular for a sentence. */
	public static final String SENTENCEREGEX = OMEWikiConstants.SENTENCEREGEX;
	
	/** Regular for a sequence of characters. */
	public static final String CHARACTERREGEX = OMEWikiConstants.CHARACTERREGEX;
	
	/** Regular expression for a <code>Wiki</code> link. */
	public static final String WIKILINKREGEX = OMEWikiConstants.WIKILINKREGEX;
	
	/** Regular expression defining Thumbnail. */
	public static final String THUMBNAILREGEX = OMEWikiConstants.THUMBNAILREGEX;
	
	/** Regular expression defining Dataset. */
	public static final String DATASETREGEX = OMEWikiConstants.DATASETREGEX;
	
	/** Regular expression defining Project. */
	public static final String PROJECTREGEX = OMEWikiConstants.PROJECTREGEX;
	
	/** Regular expression defining Protocol. */
	public static final String PROTOCOLREGEX = OMEWikiConstants.PROTOCOLREGEX;
	
	/** Regular expression defining Image. */
	public static final String IMAGEREGEX = OMEWikiConstants.IMAGEREGEX;
	
	/** Regular expression defining Wiki Heading. */
	public static final String HEADINGREGEX = OMEWikiConstants.HEADINGREGEX;
	
	/** Regular expression for a bullet list. */
	public static final String BULLETREGEX = OMEWikiConstants.BULLETREGEX;

	/** Regular expression for bold. */
	public static final String BOLDREGEX = OMEWikiConstants.BOLDREGEX;

	/** Italic regular expression. */
	public static final String ITALICREGEX = OMEWikiConstants.ITALICREGEX;

	/** Italic and bold regular expression. */
	public static final String ITALICBOLDREGEX = 
										OMEWikiConstants.ITALICBOLDREGEX;

	/** Indent regular expression. */
	public static final String INDENTREGEX = OMEWikiConstants.INDENTREGEX;
		
	/** regular expression defining a URL. */
	public static final String URLREGEX = OMEWikiConstants.URLREGEX;
	
	/** regular expression for names linked. */
	public static final String NAMEDLINKREGEX = OMEWikiConstants.NAMEDLINKREGEX;

	/** Action id to create an hyperlink. */
	private static final int	HYPERLINK = 0;
	
	/** Action id to create an image's entry. */
	private static final int	IMAGE = 1;
	
	/** Action id to create a protocol's entry. */
	private static final int	PROTOCOL = 2;

	/** The number of columns before splitting text.*/
    private static final int	COLUMNS = 45;
    
	/** The formatters installed by default. */
	private static Map<String, FormatSelectionAction> DEFAULT_FORMATTERS;
	
	static {
		DEFAULT_FORMATTERS = new LinkedHashMap<String, FormatSelectionAction>();
		DEFAULT_FORMATTERS.put(URLREGEX, 
				new FormatSelectionAction(
				new ColourFormatter(Formatter.DEFAULT_URL, false), 
				new URLLaunchAction()));
		/*
		DEFAULT_FORMATTERS.put(IMAGEREGEX, 
				new FormatSelectionAction(
				new ColourFormatter(Formatter.DEFAULT_LINK), 
				new ElementSelectionAction(WikiDataObject.IMAGE)));
		DEFAULT_FORMATTERS.put(PROTOCOLREGEX, 
				new FormatSelectionAction(
				new ColourFormatter(Formatter.PROTOCOL_LINK), 
				new ElementSelectionAction(WikiDataObject.PROTOCOL)));
				*/
	}
	
	/** The edit pane. */
	private OMEEditPane 	pane;
	
	/** The tool bar. */
	private JToolBar 		toolBar;
	
	/** The default components displayed in the tool bar. */
	private List<JButton>	toolBarActions;
	
	/** This text will be removed when starting typing. */
	private String			defaultText;
	
	/** Flag indicating that one click is supported if <code>true</code>. */
	private boolean			allowOneClick;
	
	/** Flag indicating to wrap or not the word.*/
	private boolean			wrapWord;
	
	/** The default number of columns before formatting the text.*/
	private int				columns;

    /**
     * Replaces the line separator by space when saving the data.
     * 
     * @param value 		The value to handle.
     * @param removeSpace 	Pass <code>true</code> to remove the spaces,
     * 						<code>false</code> otherwise.
     * @return See above.
     */
    public static String prepare(String value, boolean removeSpace)
    {
    	String v = value.replaceAll(SystemUtils.LINE_SEPARATOR, " ");
    	if (removeSpace) return v.replaceAll(" ", "");
    	return v;
    }
    
	/** Installs the default actions.  */
	private void installDefaultAction()
	{
		toolBarActions = new ArrayList<JButton>();
		IconManager icons = IconManager.getInstance();
		JButton b = new JButton(icons.getIcon(IconManager.HYPERLINK));
		b.addActionListener(this);
		b.setActionCommand(""+HYPERLINK);
		b.setToolTipText(OMEWikiConstants.HYPERLINK_TOOLTIP);
		toolBarActions.add(b);
		
		/*
		b = new JButton(icons.getIcon(IconManager.IMAGE));
		b.setToolTipText(OMEWikiConstants.IMAGE_TOOLTIP);
		b.addActionListener(this);
		b.setActionCommand(""+IMAGE);
		toolBarActions.add(b);
		b = new JButton(icons.getIcon(IconManager.FILE_EDITOR));
		b.setToolTipText(OMEWikiConstants.PROTOCOL_TOOLTIP);
		b.addActionListener(this);
		b.setActionCommand(""+PROTOCOL);
		UIUtilities.unifiedButtonLookAndFeel(b);
		toolBarActions.add(b);
		*/
	}
	
	/**
	 * Creates a new component.
	 * 
	 * @param formatters 	The formatters for the text.
	 * @param toolbar		Pass <code>true</code> to install a default toolbar, 
	 * 						<code>false</code> otherwise.
	 */
	private void initComponents(Map<String, FormatSelectionAction> formatters,
								boolean toolbar)
	{
		columns = COLUMNS;
		wrapWord = true;
		defaultText = "";
		pane = new OMEEditPane(this, formatters);
		pane.setMaximumSize(new Dimension(100,100));
		
		if (toolbar) {
			installDefaultAction();
			toolBar = new JToolBar();
			toolBar.setBackground(UIUtilities.BACKGROUND_COLOR);
			toolBar.setBorder(null);
			toolBar.setFloatable(false);
			
			for (JButton button : toolBarActions) {
				toolBar.add(button);
			}
		}
		setBackground(UIUtilities.BACKGROUND);
	}
	
	/** Builds and lays out the UI. */
	private void buildGUI()
	{
		if (toolBar != null) {
			JPanel p = new JPanel();
			p.setBackground(UIUtilities.BACKGROUND_COLOR);
			p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
			p.add(Box.createVerticalStrut(5));
			JPanel bar = UIUtilities.buildComponentPanel(toolBar, 0, 0);
			bar.setBackground(UIUtilities.BACKGROUND_COLOR);
			p.add(bar);
			p.add(Box.createVerticalStrut(2));
			add(p, BorderLayout.NORTH);
		}
		setLayout(new BorderLayout());
		add(pane, BorderLayout.CENTER);
	}
	
	/** Creates a default new instance. */
	public OMEWikiComponent()
	{
		this(DEFAULT_FORMATTERS, true);
	}
	
	/**
	 * Creates a new instance with default formatters.
	 * 
	 * @param toolBar 	Pass <code>true</code> to install a default toolbar, 
	 * 					<code>false</code> otherwise.
	 */
	public OMEWikiComponent(boolean toolBar)
	{
		this(DEFAULT_FORMATTERS, toolBar);
	}
	
	/** 
	 * Returns the default text or an empty string.
	 * 
	 * @return See above.
	 */
	String getDefaultText() { return defaultText; }
	
	/**
	 * Creates a new instance.
	 * 
	 * @param formatters 	The formatters for the text.
	 * @param toolBar		Pass <code>true</code> to install a default toolbar,
	 * 						<code>false</code> otherwise.
	 */
	public OMEWikiComponent(Map<String, FormatSelectionAction> formatters, 
			boolean toolBar)
	{
		for (Entry<String,FormatSelectionAction> formatterMap : DEFAULT_FORMATTERS.entrySet()) {
			String key = formatterMap.getKey();
			
			if (!formatters.containsKey(key))
				formatters.put(key, formatterMap.getValue());
		}

		initComponents(formatters, toolBar);
		buildGUI();
	}
	
	/**
	 * Called when the mouse is pressed and calls the actionPerformed event for
	 * the appropriate SelectionAction of the formatter.value.
	 * 
	 * @param action 	The action to handle.
	 * @param text		The selected text.
	 * @param count		The number of count.
	 */
	void onSelection(SelectionAction action, String text, int count)
	{
		if (action == null) return;
		int ref = 2;
		if (!isEnabled()) {
			if (action instanceof URLLaunchAction && count == 1) {
				action.onSelection(text);
			} else {
				if ((action instanceof ElementSelectionAction)) {
					action.onSelection(text);
					ElementSelectionAction a = (ElementSelectionAction) action;
					int index = a.getWikiDataObjectIndex();
					long id = a.getObjectID();
					if (id >= 0) {
						WikiDataObject object = new WikiDataObject(index, id);
						if (allowOneClick) {
							if (count == 1) {
								
								firePropertyChange(
										WIKI_DATA_OBJECT_ONE_CLICK_PROPERTY, 
										null, object);
							}
							else if (count == 2) 
								firePropertyChange(WIKI_DATA_OBJECT_PROPERTY, 
										null, object);
							
						} else {
							if (count == 1) 
								firePropertyChange(WIKI_DATA_OBJECT_PROPERTY, 
										null, object);
						}
					}
				}
			}
		} else {
			if (action instanceof ElementSelectionAction && count == ref) {
				action.onSelection(text);
				ElementSelectionAction a = (ElementSelectionAction) action;
				int index = a.getWikiDataObjectIndex();
				long id = a.getObjectID();
				if (id >= 0) {
					WikiDataObject object = new WikiDataObject(index, id);
					firePropertyChange(WIKI_DATA_OBJECT_PROPERTY, null, object);
				}
			} else if (action instanceof URLLaunchAction) {
			    if (allowOneClick) ref = 1;
			    if (count == ref) action.onSelection(text);
			}
		}
	}

	/**
	 * Returns <code>true</code> if the passed text is the default text,
	 * <code>false</code> otherwise.
	 * 
	 * @param text The value to check.
	 * @return See above.
	 */
	boolean isDefaultText(String text)
	{
		if (text == null) return false;
		return (defaultText.equals(text.trim()));
	}
	
	/**
	 * Installs formatter for various objects. 
	 */
	public void installObjectFormatters()
	{
		pane.addFormatter(IMAGEREGEX, 
				new FormatSelectionAction(
						new ColourFormatter(Formatter.DEFAULT_LINK, false), 
						new ElementSelectionAction(WikiDataObject.IMAGE)));
		pane.addFormatter(PROTOCOLREGEX, 
				new FormatSelectionAction(
						new ColourFormatter(Formatter.DEFAULT_LINK, false), 
						new ElementSelectionAction(WikiDataObject.PROTOCOL)));
		pane.addFormatter(DATASETREGEX, 
				new FormatSelectionAction(
						new ColourFormatter(Formatter.DEFAULT_LINK, false), 
						new ElementSelectionAction(WikiDataObject.DATASET)));
		pane.addFormatter(PROJECTREGEX, 
				new FormatSelectionAction(
						new ColourFormatter(Formatter.DEFAULT_LINK, false), 
						new ElementSelectionAction(WikiDataObject.PROJECT)));
	}
	
	/**
	 * Sets the default text.
	 * 
	 * @param text The value to set.
	 */
	public void setDefaultText(String text)
	{
		if (text == null) return;
		defaultText = text.trim();
	}
	
	/**
	 * Selects all the text in the <code>TextComponent</code>.
     * Does nothing on a <code>null</code> or empty document.
	 */
	public void selectAll() { pane.selectAll(); }
	
	/**
	 * Sets the position of the text insertion caret for the 
     * <code>TextComponent</code>.
     *
	 * @param n The position.
	 */
	public void setCaretPosition(int n) { pane.setCaretPosition(n); }
	
	/**
	 * Sets the text.
	 * 
	 * @param text The value to set.
	 */
	public void setText(String text) { pane.setText(text); }
	
	/**
	 * Returns the text.
	 * 
	 * @return See above.
	 */
	public String getText() { return pane.getText(); }
	
	/**
	 * Adds the specified listener to the {@link #pane}.
	 * 
	 * @param listener The listener to add.
	 */
	public void addDocumentListener(DocumentListener listener)
	{
		if (listener != null) pane.getDocument().addDocumentListener(listener);
	}
	
	/**
	 * Removes the specified listener from the {@link #pane}.
	 * 
	 * @param listener The listener to add.
	 */
	public void removeDocumentListener(DocumentListener listener)
	{
		if (listener != null) 
			pane.getDocument().removeDocumentListener(listener);
	}
	
	/**
	 * Sets the border of the component.
	 * 
	 * @param border The component to set.
	 */
	public void setComponentBorder(Border border)
	{
		pane.setBorder(border);
	}
	
	/**
	 * Sets the value indicating that the one click is supported if the 
	 * <code>enabled</code> flag is <code>false</code>.
	 * 
	 * @param allowOneClick Pass <code>true</code> to allow one click event,
	 * 						<code>false</code> otherwise.
	 */
	public void setAllowOneClick(boolean allowOneClick)
	{
		this.allowOneClick = allowOneClick;
	}
	
	/**
	 * Indicates to wrap the text or not.
	 * 
	 * @param wrapWord Pass <code>true</code> to wrap the text,
	 * 				   <code>false</code> otherwise.
	 */
	public void setWrapWord(boolean wrapWord)
	{
		this.wrapWord = wrapWord;
	}
	
	/** 
	 * Invokes when the text needs to be wrapped.
	 * 
	 * @param width The width to use.
	 * @param newLineStr The string used for new line.
	 */
	public void wrapText(int width, String newLineStr)
	{
		if (!wrapWord || pane == null) return;
		String value = getText();
		if (value == null) return;
		value = prepare(value, false);
		FontMetrics fm = getFontMetrics(getFont());
		int charWidth = fm.charWidth('m');
		columns = (int) (1.5 * width) / charWidth;
		setText(WordUtils.wrap(value, columns, newLineStr, false));
	}
	
	/**
	 * Overridden to set the flag for all components.
	 * @see JPanel#setEnabled(boolean)
	 */
	public void setEnabled(boolean enabled)
	{
		super.setEnabled(enabled);
		if (pane != null) pane.setEditable(enabled);
		
		if(toolBarActions != null)
		{
			for (JButton toolBarButton : toolBarActions) {
				toolBarButton.setEnabled(enabled);
			}
		}
	}
	
	/**
	 * Overridden to set the background color of all the components.
	 * @see JPanel#setBackground(Color)
	 */
	public void setBackground(Color color)
	{
		super.setBackground(color);
		Component[] comp = getComponents();
		if (comp != null) {
			for (int i = 0; i < comp.length; i++) {
				if (comp[i] instanceof JComponent) {
					((JComponent) comp[i]).setBackground(color);
				}
			}
		}
		if (pane != null) pane.setBackground(color);
		if (toolBar != null) {
			toolBar.setBackground(color);
			comp = toolBar.getComponents();
			for (int i = 0; i < comp.length; i++) {
				if (comp[i] instanceof JComponent) {
					((JComponent) comp[i]).setBackground(color);
				}
			}
		}
	}
	
	/**
	 * Overridden to set the foreground color of the {@link #pane}.
	 * @see JPanel#setForeground(Color)
	 */
	public void setForeground(Color color)
	{
		if (pane != null) pane.setForeground(color);
	}

	/**
	 * Overridden to set the font of {@link #pane}.
	 * @see JPanel#setFont(Font)
	 */
	public void setFont(Font font)
	{
		if (pane != null) pane.setFont(font);
	}
	
	/**
	 * Overridden to set the font of {@link #pane}.
	 * @see JPanel#getFont()
	 */
	public Font getFont()
	{
		if (pane == null) return super.getFont();
		return pane.getFont();
	}
	
	/**
	 * Inserts text depending on the action.
	 * @see ActionListener#actionPerformed(ActionEvent)
	 */
	public void actionPerformed(ActionEvent e)
	{
		int index = Integer.parseInt(e.getActionCommand());
		String s = getText();
		switch (index) {
			case IMAGE:
				if (isDefaultText(s))
					setText(OMEWikiConstants.DEFAULT_IMAGE);
				else setText(s+" "+OMEWikiConstants.DEFAULT_IMAGE);
				pane.requestFocus();
				break;
			case HYPERLINK:
				if (isDefaultText(s))
					setText(OMEWikiConstants.DEFAULT_HYPERLINK);
				else setText(s+" "+OMEWikiConstants.DEFAULT_HYPERLINK);
				pane.requestFocus();
				break;
			case PROTOCOL:
				if (isDefaultText(s))
					setText(OMEWikiConstants.DEFAULT_PROTOCOL);
				else setText(s+" "+OMEWikiConstants.DEFAULT_PROTOCOL);
				pane.requestFocus();
		}
	}
	
}
