// CommonConsole.java
//
// Created: September 2005
//
// This file is a component of the BioMoby project.
// Copyright Martin Senger (martin.senger@gmail.com).
//

package org.biomoby.service.dashboard;

import org.biomoby.shared.MobyException;
import org.biomoby.shared.Utils;

import org.tulsoft.tools.gui.SwingUtils;
import org.tulsoft.shared.UUtils;
import org.tulsoft.shared.PrefsUtils;

import javax.swing.JButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.Icon;
import javax.swing.Box;
import javax.swing.SwingUtilities;
import javax.swing.JFileChooser;

import java.awt.Font;
import java.awt.GridBagLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

import java.util.prefs.Preferences;
import java.io.File;

/**
 * A simple console component. It allows:
 * <ul>
 * <li> to display text in a text area (not-editable) - but see {@link
 * SimpleClientPanel} how to make it editable easily}
 * <li> to do it in an append or override mode,
 * <li> to increase and decrease used font size,
 * <li> to listen to events and display their contents if in verbose mode,
 * <li> to clean the area, and
 * <li> to save its contents to a file
 * </ul> <p>
 *
 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: CommonConsole.java,v 1.11 2008/03/02 12:45:26 senger Exp $
 */

public class CommonConsole
    extends JPanel
    implements PropertyChangeListener {

    /** A default name of property change event that is considered to
	carry verbose messages. It is used unless a different name is
	set by {@link #setVerboseEventName}.
    */
    public static final String VERBOSE_EVENT = "verbose-msg";

    protected static final String SAVE_ERROR =
	"Sorry, an error happened when saving...\n\n";

    // components
    JTextArea textArea;
    JCheckBox appendModeBox, verboseModeBox;
    JButton cleanButton, saveButton;
    boolean appendMode = true;
    boolean verboseMode = false;
    JFileChooser saveChooser;

    // sharing events
    PropertyChannel propertyChannel;
    String verboseEventName = VERBOSE_EVENT;

    // shared icons
    static Icon clearIcon, clearIconDis;
    static Icon saveIcon, saveIconDis;
    static Icon zoomInIcon, zoomInIconDis;
    static Icon zoomOutIcon, zoomOutIconDis;

    /*********************************************************************
     * Constructor.
     ********************************************************************/
    public CommonConsole() {
	super();
	loadIcons();
	setLayout (new GridBagLayout());

        textArea = new JTextArea();
        textArea.setFont (new Font ("Courier", Font.PLAIN, 10));
	textArea.setEditable (false);
	JScrollPane scroller = new JScrollPane (textArea);

	cleanButton = AbstractPanel.createButton
	    ("",
	     "Remove all messages from the console area",
	     -1,
	     new ActionListener() {
		 public void actionPerformed (ActionEvent e) {
		     if (! "".equals (textArea.getText()) &&
			 AbstractPanel.confirm (CommonConsole.this,
						"Remove all messages?")) {
			 textArea.setText ("");
			 cleanButton.setEnabled (false);
			 saveButton.setEnabled (false);
		     }
		 }
	     });
	cleanButton.setIcon (clearIcon);
	cleanButton.setDisabledIcon (clearIconDis);
	cleanButton.setEnabled (false);
	SwingUtils.compact (cleanButton);

	saveChooser = new JFileChooser();
	saveChooser.setDialogTitle ("Save console contents");

	saveButton = AbstractPanel.createButton
	    ("",
	     "Save the contents of the console area to a file",
	     -1,
	     new ActionListener() {
		 public void actionPerformed (ActionEvent e) {
		     onSave();
		 }
	     });
	saveButton.setIcon (saveIcon);
	saveButton.setDisabledIcon (saveIconDis);
	saveButton.setEnabled (false);
	SwingUtils.compact (saveButton);

	JButton zoomInButton = AbstractPanel.createButton
	    ("",
	     "Increase font in the console window",
	     -1,
	     new ActionListener() {
		 public void actionPerformed (ActionEvent e) {
		     Font font = textArea.getFont();
		     textArea.setFont (font.deriveFont (font.getSize2D() + 1));
		 }
	     });
	zoomInButton.setIcon (zoomInIcon);
	zoomInButton.setDisabledIcon (zoomInIconDis);
	SwingUtils.compact (zoomInButton);

	JButton zoomOutButton = AbstractPanel.createButton
	    ("",
	     "Decrease font in the console window",
	     -1,
	     new ActionListener() {
		 public void actionPerformed (ActionEvent e) {
		     Font font = textArea.getFont();
		     textArea.setFont (font.deriveFont (Math.max (1, font.getSize2D() - 1)));
		 }
	     });
	zoomOutButton.setIcon (zoomOutIcon);
	zoomOutButton.setDisabledIcon (zoomOutIconDis);
	SwingUtils.compact (zoomOutButton);

	appendModeBox = AbstractPanel.createCheckBox
	    ("append mode", appendMode, -1,
	     new ItemListener() {
		 public void itemStateChanged (ItemEvent e) {
		     appendMode = (e.getStateChange() == ItemEvent.SELECTED);
		 }
	     });

	verboseModeBox = AbstractPanel.createCheckBox
	    ("verbose", verboseMode, -1,
	     new ItemListener() {
		 public void itemStateChanged (ItemEvent e) {
		     verboseMode = (e.getStateChange() == ItemEvent.SELECTED);
		     if (propertyChannel != null) {
			 if (verboseMode)
			     propertyChannel.addPropertyChangeListener (CommonConsole.this);
			 else
			     propertyChannel.removePropertyChangeListener (CommonConsole.this);
		     }
		 }
	     });

	Component glue = Box.createHorizontalGlue();

	// put it together
 	SwingUtils.addComponent (this, scroller,
				 0, 0, 7, 1, AbstractPanel.BOTH, AbstractPanel.NWEST, 1.0, 1.0);
	if (hasCleanButton())
 	SwingUtils.addComponent (this, cleanButton,
				 0, 1, 1, 1, AbstractPanel.NONE, AbstractPanel.NWEST, 0.0, 0.0);
	if (hasSaveButton())
 	SwingUtils.addComponent (this, saveButton,
				 1, 1, 1, 1, AbstractPanel.NONE, AbstractPanel.NWEST, 0.0, 0.0);
	if (hasAppendModeSwitcher())
 	SwingUtils.addComponent (this, appendModeBox,
				 2, 1, 1, 1, AbstractPanel.NONE, AbstractPanel.NWEST, 0.0, 0.0);
	if (hasVerboseModeSwitcher())
 	SwingUtils.addComponent (this, verboseModeBox,
				 3, 1, 1, 1, AbstractPanel.NONE, AbstractPanel.NWEST, 0.0, 0.0);
 	SwingUtils.addComponent (this, glue,
				 4, 1, 1, 1, AbstractPanel.HORI, AbstractPanel.NWEST, 1.0, 0.0);
 	SwingUtils.addComponent (this, zoomInButton,
				 5, 1, 1, 1, AbstractPanel.NONE, AbstractPanel.NEAST, 0.0, 0.0);
 	SwingUtils.addComponent (this, zoomOutButton,
				 6, 1, 1, 1, AbstractPanel.NONE, AbstractPanel.NEAST, 0.0, 0.0);
    }

    /*********************************************************************
     * Show...
     ********************************************************************/
    protected String showSaveDialog() {

	// set an init value (if any)
	Preferences node = PrefsUtils.getNode (this.getClass());
	String initValue = node.get (DashboardProperties.DP_CONSOLE_FILE,
				     System.getProperty ("user.dir"));
	if (UUtils.notEmpty (initValue)) {
	    File file = new File (initValue);
	    saveChooser.setSelectedFile (file);
	}

	// show chooser
	if (saveChooser.showSaveDialog (this) != JFileChooser.APPROVE_OPTION)
	    return null;

	// store the chosen value
	String fileName = saveChooser.getSelectedFile().getAbsolutePath();
	node.put (DashboardProperties.DP_CONSOLE_FILE,
		  fileName);

	return fileName;
    }

    /*********************************************************************
     * Save contents...
     ********************************************************************/
    protected void onSave() {

	final String fileName = showSaveDialog();
	if (UUtils.isEmpty (fileName ))
	    return;

	final SwingWorker worker = new SwingWorker() {
		MobyException exception = null;
		public Object construct() {
		    try {
			Utils.createFile (new File (fileName), getArea().getText());
		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}
		    
		// runs on the event-dispatching thread
		public void finished() {
		    if (exception != null)
			AbstractPanel.error (SAVE_ERROR, exception);
		}
	    };
	worker.start(); 
    }

    /*********************************************************************
     * Direct access to the text area.
     ********************************************************************/
    public JTextArea getArea() {
	return textArea;
    }

    /*********************************************************************
     *
     ********************************************************************/
    public void setText (String value) {
 	realSetText (value);
// 	if (SwingUtilities.isEventDispatchThread()) {
// 	    realSetText (value);
// 	} else {
// 	    final String hereValue = value;
// 	    SwingUtilities.invokeLater (new Runnable() {
// 		    public void run() {
// 			realSetText (hereValue);
// 		    }
// 		});
// 	}
    }
    private void realSetText (String value) {
	if (appendMode || ! appendModeBox.isEnabled()) {
	    textArea.append (value);
	    textArea.setCaretPosition (textArea.getDocument().getLength());
	} else {
	    textArea.setText (value);
	}
	cleanButton.setEnabled (true);
	saveButton.setEnabled (true);
    }

    /*********************************************************************
     * Clean the console (disregading of any current modes).
     ********************************************************************/
    public void clean() {
	textArea.setText ("");
	cleanButton.setEnabled (true);
	saveButton.setEnabled (true);
    }

    /*********************************************************************
     *
     ********************************************************************/
    public boolean setAppendMode (boolean enabled) {
	boolean oldMode = appendMode;
	appendMode = enabled;
	appendModeBox.setSelected (appendMode);
	return oldMode;
    }

    /*********************************************************************
     *
     ********************************************************************/
    public boolean isAppendMode() {
	return appendMode;
    }

    /*********************************************************************
     * This allows to disable and enable append-mode check box - so
     * user cannot change the append more. This is useful for a
     * sequence of messages that we do not want to be cleaned in the
     * middle.
     ********************************************************************/
    public void setEnabledAppendMode (boolean enabled) {
	appendModeBox.setEnabled (enabled);
    }

    /*********************************************************************
     *
     ********************************************************************/
    public boolean setVerboseMode (boolean enabled) {
	boolean oldMode = verboseMode;
	verboseMode = enabled;
	verboseModeBox.setSelected (verboseMode);
	return oldMode;
    }

    /*********************************************************************
     *
     ********************************************************************/
    public boolean isVerboseMode() {
	return verboseMode;
    }

    /**************************************************************************
     * Remember the property channel. It will be used for listening to the
     * the verbose messages.
     **************************************************************************/
    public void setPropertyChannel (PropertyChannel propertyChannel) {
	this.propertyChannel = propertyChannel;
    }

    /**************************************************************************
     * Remember what event name will be considered by this instance
     * for its verbose messages.
     **************************************************************************/
    public void setVerboseEventName (String eventName) {
	verboseEventName = eventName;
    }

    /**************************************************************************
     * Here this instance is notified when somebody fires a verbose
     * message. Only events carrying verbose messages are used here.
     * The verbose messages are those with name set by {@link
     * #setVerboseEventName} (default is defined by {@link
     * #VERBOSE_EVENT}).
     **************************************************************************/
    public void propertyChange (PropertyChangeEvent event) {
        String prop = event.getPropertyName();
        if (prop == null) return;     // no interest in non-specific changes
	Object obj = event.getNewValue();
	if (obj == null || ! (obj instanceof String)) return;
        final String value = (String)obj;

	// only interested in a verbose message
	if (verboseEventName.equals (prop))
	    setText (value);
    }

    /*********************************************************************
     * Load all menu icons.
     ********************************************************************/
    protected void loadIcons() {
	if (clearIcon == null) clearIcon = loadIcon ("images/smallClear.gif");
	if (clearIconDis == null) clearIconDis = loadIcon ("images/smallClear_dis.gif");

	if (saveIcon == null) saveIcon = loadIcon ("images/smallSave.gif");
	if (saveIconDis == null) saveIconDis = loadIcon ("images/smallSave_dis.gif");

	if (zoomInIcon == null) zoomInIcon = loadIcon ("images/smallZoomIn.gif");
	if (zoomInIconDis == null) zoomInIconDis = loadIcon ("images/smallZoomIn_dis.gif");

	if (zoomOutIcon == null) zoomOutIcon = loadIcon ("images/smallZoomOut.gif");
	if (zoomOutIconDis == null) zoomOutIconDis = loadIcon ("images/smallZoomOut_dis.gif");
    }

    /*********************************************************************
     * Load shared icon.
     ********************************************************************/
    protected static Icon loadIcon (String path) {
	return SwingUtils.createIcon (path, Dashboard.class);
    }

    public boolean hasCleanButton() { return true; }
    public boolean hasSaveButton() {return true; }
    public boolean hasAppendModeSwitcher() {return true; }
    public boolean hasVerboseModeSwitcher() {return true; }


}
