/*******************************************************************************
 * Copyright (c) 2000, 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.console;

import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.IBasicPropertyConstants;
import org.eclipse.ui.internal.console.ConsoleMessages;

/**
 * Common function for consoles.
 * <p>
 * Clients implementing consoles should subclass this class.
 * </p>
 * @since 3.0
 */
public abstract class AbstractConsole implements IConsole {
	
	// property listeners
	private ListenerList fListeners;
	
	/**
	 * Console name
	 */
	private String fName = null;
	
	/**
	 * Console image descriptor
	 */
	private ImageDescriptor fImageDescriptor = null;
	
	/**
	 * Console type identifier
	 */
	private String fType = null;
	
	/**
	 * Used to notify this console of lifecycle methods <code>init()</code>
	 * and <code>dispose()</code>.
	 */
	class Lifecycle implements IConsoleListener {
		
		/* (non-Javadoc)
		 * @see org.eclipse.ui.console.IConsoleListener#consolesAdded(org.eclipse.ui.console.IConsole[])
		 */
		public void consolesAdded(IConsole[] consoles) {
			for (int i = 0; i < consoles.length; i++) {
				IConsole console = consoles[i];
				if (console == AbstractConsole.this) {
					initialize();
				}
			}

		}

		/* (non-Javadoc)
		 * @see org.eclipse.ui.console.IConsoleListener#consolesRemoved(org.eclipse.ui.console.IConsole[])
		 */
		public void consolesRemoved(IConsole[] consoles) {
			for (int i = 0; i < consoles.length; i++) {
				IConsole console = consoles[i];
				if (console == AbstractConsole.this) {
					ConsolePlugin.getDefault().getConsoleManager().removeConsoleListener(this);
					destroy();
				}
			}
		}
	}
	
	/**
	 * Notifies listeners of property changes, handling any exceptions
	 */
	class PropertyNotifier implements ISafeRunnable {
		
		private IPropertyChangeListener fListener;
		private PropertyChangeEvent fEvent;
		
		/**
		 * @see org.eclipse.core.runtime.ISafeRunnable#handleException(java.lang.Throwable)
		 */
		public void handleException(Throwable exception) {
			IStatus status = new Status(IStatus.ERROR, ConsolePlugin.getUniqueIdentifier(), IConsoleConstants.INTERNAL_ERROR, ConsoleMessages.AbstractConsole_0, exception); 
			ConsolePlugin.log(status);
		}

		/**
		 * @see org.eclipse.core.runtime.ISafeRunnable#run()
		 */
		public void run() throws Exception {
			fListener.propertyChange(fEvent);
		}

		/**
		 * Notifies listeners of the property change
		 * 
		 * @param event the event that describes the property that has changed
		 */
		public void notify(PropertyChangeEvent event) {
			if (fListeners == null) {
				return;
			}
			fEvent = event;
			Object[] copiedListeners= fListeners.getListeners();
			for (int i= 0; i < copiedListeners.length; i++) {
				fListener = (IPropertyChangeListener)copiedListeners[i];
                SafeRunner.run(this);
			}	
			fListener = null;			
		}
	}	
	
	/**
	 * Constructs a new console with the given name and image.
	 * 
	 * @param name console name, cannot be <code>null</code>
	 * @param imageDescriptor image descriptor, or <code>null</code> if none
	 * @param autoLifecycle whether this console's lifecycle methods should be called
	 *  automatically when it is added (<code>initialize()</code>) and removed
	 *  (<code>destroy()</code>) from the console manager. When <code>false</code>,
	 *  clients are responsible for calling the lifecycle methods.
	 * @since 3.1
	 */
	public AbstractConsole(String name, ImageDescriptor imageDescriptor, boolean autoLifecycle) {
	    this(name, null, imageDescriptor, autoLifecycle);
	}
	
	/**
	 * Constructs a new console with the given name, type, image and lifecycle.
	 * 
	 * @param name console name, cannot be <code>null</code>
	 * @param type console type identifier or <code>null</code>
	 * @param imageDescriptor image descriptor, or <code>null</code> if none
	 * @param autoLifecycle whether this console's lifecycle methods should be called
	 *  automatically when it is added (<code>initialize()</code>) and removed
	 *  (<code>destroy()</code>) from the console manager. When <code>false</code>,
	 *  clients are responsible for calling the lifecycle methods.
	 * @since 3.1
	 */
	public AbstractConsole(String name, String type, ImageDescriptor imageDescriptor, boolean autoLifecycle) {
		setName(name);
		setType(type);
		setImageDescriptor(imageDescriptor);
		if (autoLifecycle) {
		    ConsolePlugin.getDefault().getConsoleManager().addConsoleListener(new Lifecycle());
		}
	}	
	
	/**
	 * Constructs a new console with the given name and image. The console's lifecycle
	 * methods <code>init()</code> and <code>dispose()</code> will be called when the
	 * console is added and removed from the console manager.
	 * 
	 * @param name console name, cannot be <code>null</code>
	 * @param imageDescriptor image descriptor, or <code>null</code> if none
	 */
	public AbstractConsole(String name, ImageDescriptor imageDescriptor) {
		this(name, imageDescriptor, true);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.console.IConsole#getName()
	 */
	public String getName() {
		return fName;
	}

	/**
	 * Sets the name of this console to the specified value and notifies
	 * property listeners of the change.
	 * 
	 * @param name the new name
	 */
	protected void setName(String name) {
        if (!name.equals(fName)) {
            String old = fName;
            fName = name;
            firePropertyChange(this, IBasicPropertyConstants.P_TEXT, old, name);
        }
    }
	
	/* (non-Javadoc)
	 * @see org.eclipse.ui.console.IConsole#getImageDescriptor()
	 */
	public ImageDescriptor getImageDescriptor() {
		return fImageDescriptor;
	}
	
	/**
	 * Sets the image descriptor for this console to the specified value and notifies
	 * property listeners of the change.
	 * 
	 * @param imageDescriptor the new image descriptor
	 */
	protected void setImageDescriptor(ImageDescriptor imageDescriptor) {
		ImageDescriptor old = fImageDescriptor;
		fImageDescriptor =imageDescriptor;
		firePropertyChange(this, IBasicPropertyConstants.P_IMAGE, old, imageDescriptor);
	}	

	/* (non-Javadoc)
	 * @see org.eclipse.ui.console.IConsole#addPropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener)
	 */
	public void addPropertyChangeListener(IPropertyChangeListener listener) {
		if (fListeners == null) {
			fListeners = new ListenerList();
		}
		fListeners.add(listener);		
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.console.IConsole#removePropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener)
	 */
	public void removePropertyChangeListener(IPropertyChangeListener listener) {
		if (fListeners != null) {
			fListeners.remove(listener);
		}
	}

	/**
	 * Notify all listeners that the given property has changed.
	 * 
	 * @param source the object on which a property has changed 
	 * @param property identifier of the property that has changed
	 * @param oldValue the old value of the property, or <code>null</code>
	 * @param newValue the new value of the property, or <code>null</code>
	 */
	public void firePropertyChange(Object source, String property, Object oldValue, Object newValue) {
		if (fListeners == null) {
			return;
		}
		PropertyNotifier notifier = new PropertyNotifier();
		notifier.notify(new PropertyChangeEvent(source, property, oldValue, newValue));
	}
	
	/**
	 * Initializes this console. This method should only be called by clients managing a
	 * console's lifecycle, otherwise this method will be called automatically when this console
	 * is added to the console manager. The method is called once to initialize this console,
	 * marking the beginning of its lifecycle.
	 * 
	 * @since 3.1
	 */
	public final void initialize() {
	    init();
	}
	
	/**
	 * Called when this console is added to the console manager. Default
	 * implementation does nothing. Subclasses may override.
	 * <p>
	 * Since 3.1, this method is only called automatically if this console was
	 * created with an automatic lifecycle.
	 * </p>
	 */
	protected void init() {
	}
	
	/**
	 * Disposes this console. This method should only be called by clients managing a
	 * console's lifecycle, otherwise this method will be called automatically when this
	 * console is removed from the console manager. The method is called once to dispose
	 * this console, after which this console will no longer be used. 
	 * 
	 * @since 3.1
	 */
	public final void destroy() {
	    dispose();
	}
	
	/**
	 * Called when this console is removed from the console manager. Default
	 * implementation does nothing. Subclasses may override.
	 * <p>
	 * Since 3.1, this methods is only called automatically if this console was
	 * created with an automatic lifecycle.
	 * </p>
	 */
	protected void dispose() {
	}
	
	/**
	 * Shows this console in all console views. This console will be become visible
	 * if another console is currently pinned. 
	 * 
	 * @since 3.1
	 */
    public void activate() {
        ConsolePlugin.getDefault().getConsoleManager().showConsoleView(this);
    }
    
    /**
     * Sets this console's type identifier.
     * 
     * @param typeIdentifier the type identifier for this console 
     * @since 3.1
     */
    protected void setType(String typeIdentifier) {
        fType = typeIdentifier;
    }
    
    /**
     * @see org.eclipse.ui.console.IConsole#getType()
     * @since 3.1
     */
    public String getType() {
        return fType;
    }
    
    /**
     * Returns the help context identifier for this console, or <code>null</code>
     * if none. When a non-<code>null</code> value is returned the associated help
     * will be installed for this console.
     * 
     * @return help context id or <code>null</code>
     * @since 3.2
     */
    public String getHelpContextId() {
    	return null;
    }
    
}
