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

package org.eclipse.debug.internal.core.memory;

import java.util.ArrayList;

import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IDebugEventSetListener;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IMemoryBlockRetrieval;


/**
 * Implementation of IMemoryBlockManager
 * The manager is responsible to manage all memory blocks in the workbench.
 * 
 * @since 3.0
 * 
 */
public class MemoryBlockManager implements IMemoryBlockManager, IDebugEventSetListener {
	
	private ArrayList listeners = new ArrayList();			// list of all IMemoryBlockListener
	private ArrayList memoryBlocks = new ArrayList();	// list of all memory blocks 
	
	private static final int ADDED = 0;
	private static final int REMOVED = 1;
	
	
	/**
	 * The singleton memory block manager.
	 */
	private static MemoryBlockManager fgMemoryBlockManager;
	
	/**
	 * The singleton memory rendering manager.
	 */
	private static MemoryRenderingManager fgMemoryRenderingManager;
	
	/**
	 * Returns the memory block manager.
	 * @return the memory block manager.
	 * @see IMemoryBlockManager
	 * @since 3.0
	 */
	public static IMemoryBlockManager getMemoryBlockManager(){
		if (fgMemoryBlockManager == null)
		{
			fgMemoryBlockManager = new MemoryBlockManager();
		}
		
		return fgMemoryBlockManager;
	}
	
	/**
	 * Returns the memory rendering manager.
	 * @return the memory rendering manager.
	 * @see IMemoryRenderingManager
	 * @since 3.0
	 */
	public static IMemoryRenderingManager getMemoryRenderingManager() {
		if (fgMemoryRenderingManager == null)
		{
			fgMemoryRenderingManager = new MemoryRenderingManager();
		}
		
		return fgMemoryRenderingManager;
	}
	
	public static void pluginShutdown() {
		if (fgMemoryBlockManager != null) {
			fgMemoryBlockManager.shutdown();
		}
		if (fgMemoryRenderingManager != null) {
			fgMemoryRenderingManager.shutdown();
		}
		
	}
	
	/**
	 * Notifies a memory block listener in a safe runnable to
	 * handle exceptions.
	 */
	class MemoryBlockNotifier implements ISafeRunnable {
		
		private IMemoryBlockListener fListener;
		private int fType;
		private IMemoryBlock fMemoryBlock;
		
		/**
		 * @see org.eclipse.core.runtime.ISafeRunnable#handleException(java.lang.Throwable)
		 */
		public void handleException(Throwable exception) {
			DebugPlugin.log(exception);
		}

		/**
		 * @see org.eclipse.core.runtime.ISafeRunnable#run()
		 */
		public void run() throws Exception {
			switch (fType) {
				case ADDED:
					fListener.MemoryBlockAdded(fMemoryBlock);
					break;
				case REMOVED:
					fListener.MemoryBlockRemoved(fMemoryBlock);
					break;
			}			
		}

		/**
		 * Notify listeners of added/removed memory block events
		 */
		public void notify(IMemoryBlock memoryBlock, int update) {
			if (listeners != null) {
				fType = update;
				Object[] copiedListeners= listeners.toArray(new IMemoryBlockListener[listeners.size()]);
				for (int i= 0; i < copiedListeners.length; i++) {
					fListener = (IMemoryBlockListener)copiedListeners[i];
					fMemoryBlock = memoryBlock;
					Platform.run(this);
				}			
			}
			fListener = null;
			fMemoryBlock = null;
		}
	}
	
	private MemoryBlockNotifier getMemoryBlockNotifier() {
		return new MemoryBlockNotifier();
	}	


	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.IMemoryBlockManager#addMemoryBlock(org.eclipse.debug.core.model.IMemoryBlock)
	 */
	public void addMemoryBlock(IMemoryBlock mem, boolean addDefaultRenderings) {
		
		if (memoryBlocks == null)
			return;
		
		if (mem == null){
			DebugPlugin.logMessage("Null argument passed into IMemoryBlockManager.addMemoryBlock", null); //$NON-NLS-1$
			return;			
		}
		
		// do not allow duplicates
		if (memoryBlocks.contains(mem))
			return;
		
		MemoryRenderingManager renderingManager = (MemoryRenderingManager) getMemoryRenderingManager();
		
		if (!addDefaultRenderings)
		{
			renderingManager.setHandleMemoryBlockAddedEvent(false);
		}
		else
		{
			renderingManager.setHandleMemoryBlockAddedEvent(true);
		}
		
		if (mem == null){
			DebugPlugin.logMessage("Null argument passed into IMemoryBlockManager.addMemoryBlock", null); //$NON-NLS-1$
			return;			
		}
		
		memoryBlocks.add(mem);
		
		// add listener for the first memory block added
		if (memoryBlocks.size() == 1)
		{
			DebugPlugin.getDefault().addDebugEventListener(this);
		}
		
		notifyListeners(mem, ADDED);

		// always set it back to true
		renderingManager.setHandleMemoryBlockAddedEvent(true);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.IMemoryBlockManager#removeMemoryBlock(org.eclipse.debug.core.model.IMemoryBlock)
	 */
	public void removeMemoryBlock(IMemoryBlock mem) {
		
		if (memoryBlocks == null)
			return;
		
		if (mem == null){
			DebugPlugin.logMessage("Null argument passed into IMemoryBlockManager.removeMemoryBlock", null); //$NON-NLS-1$
			return;			
		}		
		
		memoryBlocks.remove(mem);
		
		// remove listener after the last memory block has been removed
		if (memoryBlocks.size() == 0)
		{
			DebugPlugin.getDefault().removeDebugEventListener(this);
		}
		
		if (mem instanceof IExtendedMemoryBlock)
		{ 
			((IExtendedMemoryBlock)mem).delete();
		}
		
		notifyListeners(mem, REMOVED);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.IMemoryBlockManager#addListener(org.eclipse.debug.ui.IMemoryBlockListener)
	 */
	public void addListener(IMemoryBlockListener listener) {
		
		if(listeners == null)
			return;
		
		if(listener == null){
			DebugPlugin.logMessage("Null argument passed into IMemoryBlockManager.addListener", null); //$NON-NLS-1$
			return;
		}
		
		if (!listeners.contains(listener))
			listeners.add(listener);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.IMemoryBlockManager#removeListener(org.eclipse.debug.ui.IMemoryBlockListener)
	 */
	public void removeListener(IMemoryBlockListener listener) {
		
		if(listeners == null)
			return;
		
		if(listener == null){
			DebugPlugin.logMessage("Null argument passed into IMemoryBlockManager.removeListener", null); //$NON-NLS-1$
			return;
		}
		
		if (listeners.contains(listener))
			listeners.remove(listener);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.IMemoryBlockManager#getAllMemoryBlocks()
	 */
	public IMemoryBlock[] getAllMemoryBlocks() {
		
		IMemoryBlock[] blocks = (IMemoryBlock[])memoryBlocks.toArray(new IMemoryBlock[memoryBlocks.size()]);
		
		return blocks;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.IMemoryBlockManager#getMemoryBlocks(org.eclipse.debug.core.model.IDebugTarget)
	 */
	public IMemoryBlock[] getMemoryBlocks(IDebugTarget debugTarget) {
		
		IMemoryBlock[] blocks = (IMemoryBlock[])memoryBlocks.toArray(new IMemoryBlock[memoryBlocks.size()]);
		
		ArrayList memoryBlocksList = new ArrayList();
		
		for (int i=0; i<blocks.length; i++)
		{
			if (blocks[i].getDebugTarget() == debugTarget)
				memoryBlocksList.add(blocks[i]);
		}
		
		return (IMemoryBlock[])memoryBlocksList.toArray(new IMemoryBlock[memoryBlocksList.size()]);
		
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.IMemoryBlockManager#getMemoryBlocks(org.eclipse.debug.core.model.IMemoryBlockRetrieval)
	 */
	public IMemoryBlock[] getMemoryBlocks(IMemoryBlockRetrieval retrieve) {
		IMemoryBlock[] blocks = (IMemoryBlock[])memoryBlocks.toArray(new IMemoryBlock[memoryBlocks.size()]);
		
		ArrayList memoryBlocksList = new ArrayList(blocks.length);
		
		for (int i=0; i<blocks.length; i++)
		{
			if (blocks[i] instanceof IExtendedMemoryBlock)
			{	
			
				if (((IExtendedMemoryBlock)blocks[i]).getMemoryBlockRetrieval() == retrieve)
				{	
					memoryBlocksList.add(blocks[i]);
				}
			}
			else
			{	
				// standard memory block always uses the debug target as the memory block retrieval
				if (blocks[i].getDebugTarget() == retrieve)
					memoryBlocksList.add(blocks[i]);
			}
		}
		
		return (IMemoryBlock[])memoryBlocksList.toArray(new IMemoryBlock[memoryBlocksList.size()]);
	}
	
	
	private void notifyListeners(IMemoryBlock memoryBlock, int event)
	{
		getMemoryBlockNotifier().notify(memoryBlock, event);
	}


	/* (non-Javadoc)
	 * @see org.eclipse.debug.core.IDebugEventSetListener#handleDebugEvents(org.eclipse.debug.core.DebugEvent[])
	 */
	public void handleDebugEvents(DebugEvent[] events) {
		
		for (int i=0; i < events.length; i++)
			handleDebugEvent(events[i]);
		
	}
	
	public void handleDebugEvent(DebugEvent event) {
		Object obj = event.getSource();
		IDebugTarget dt = null;
		
		if (event.getKind() == DebugEvent.TERMINATE)
		{
			// a terminate event could happen from an IThread or IDebugTarget
			// only handle a debug event from the debug target
			if (obj instanceof IDebugTarget)
			{
				dt = ((IDebugTarget)obj);
			}
			
			// getMemoryBlocks will return an empty array if dt is null
			IMemoryBlock[] deletedMemoryBlocks = getMemoryBlocks(dt);
			
			for (int i=0; i<deletedMemoryBlocks.length; i++)
			{
				removeMemoryBlock(deletedMemoryBlocks[i]);
			}
		}
	}
	
	/**
	 * Clean up when the plugin is shut down
	 */
	public void shutdown()
	{
		if (listeners != null)
		{
			listeners.clear();
			listeners = null;
		}
		
		if (memoryBlocks != null)
		{
			memoryBlocks.clear();
			memoryBlocks = null;
		}
	}
}
