/*******************************************************************************
 * Copyright (c) 2000, 2007 QNX Software Systems 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:
 *     QNX Software Systems - Initial API and implementation
 *     Anton Leherbauer (Wind River Systems)
 *******************************************************************************/
package org.eclipse.cdt.debug.mi.core.cdi;

import java.io.File;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.cdt.debug.core.cdi.CDIException;
import org.eclipse.cdt.debug.core.cdi.ICDIAddressLocation;
import org.eclipse.cdt.debug.core.cdi.ICDICondition;
import org.eclipse.cdt.debug.core.cdi.ICDIFunctionLocation;
import org.eclipse.cdt.debug.core.cdi.ICDILineLocation;
import org.eclipse.cdt.debug.core.cdi.ICDILocator;
import org.eclipse.cdt.debug.core.cdi.model.ICDIAddressBreakpoint;
import org.eclipse.cdt.debug.core.cdi.model.ICDIBreakpoint;
import org.eclipse.cdt.debug.core.cdi.model.ICDIEventBreakpoint;
import org.eclipse.cdt.debug.core.cdi.model.ICDIExceptionpoint;
import org.eclipse.cdt.debug.core.cdi.model.ICDIFunctionBreakpoint;
import org.eclipse.cdt.debug.core.cdi.model.ICDILineBreakpoint;
import org.eclipse.cdt.debug.core.cdi.model.ICDIWatchpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpointType;
import org.eclipse.cdt.debug.mi.core.MIException;
import org.eclipse.cdt.debug.mi.core.MIFormat;
import org.eclipse.cdt.debug.mi.core.MIPlugin;
import org.eclipse.cdt.debug.mi.core.MISession;
import org.eclipse.cdt.debug.mi.core.cdi.model.AddressBreakpoint;
import org.eclipse.cdt.debug.mi.core.cdi.model.AddressLocation;
import org.eclipse.cdt.debug.mi.core.cdi.model.Breakpoint;
import org.eclipse.cdt.debug.mi.core.cdi.model.EventBreakpoint;
import org.eclipse.cdt.debug.mi.core.cdi.model.Exceptionpoint;
import org.eclipse.cdt.debug.mi.core.cdi.model.FunctionBreakpoint;
import org.eclipse.cdt.debug.mi.core.cdi.model.FunctionLocation;
import org.eclipse.cdt.debug.mi.core.cdi.model.LineBreakpoint;
import org.eclipse.cdt.debug.mi.core.cdi.model.LocationBreakpoint;
import org.eclipse.cdt.debug.mi.core.cdi.model.Target;
import org.eclipse.cdt.debug.mi.core.cdi.model.Watchpoint;
import org.eclipse.cdt.debug.mi.core.command.CLICatch;
import org.eclipse.cdt.debug.mi.core.command.CommandFactory;
import org.eclipse.cdt.debug.mi.core.command.MIBreakAfter;
import org.eclipse.cdt.debug.mi.core.command.MIBreakCondition;
import org.eclipse.cdt.debug.mi.core.command.MIBreakDelete;
import org.eclipse.cdt.debug.mi.core.command.MIBreakDisable;
import org.eclipse.cdt.debug.mi.core.command.MIBreakEnable;
import org.eclipse.cdt.debug.mi.core.command.MIBreakInsert;
import org.eclipse.cdt.debug.mi.core.command.MIBreakList;
import org.eclipse.cdt.debug.mi.core.command.MIBreakWatch;
import org.eclipse.cdt.debug.mi.core.command.MIGDBSetBreakpointPending;
import org.eclipse.cdt.debug.mi.core.event.MIBreakpointChangedEvent;
import org.eclipse.cdt.debug.mi.core.event.MIBreakpointCreatedEvent;
import org.eclipse.cdt.debug.mi.core.event.MIBreakpointDeletedEvent;
import org.eclipse.cdt.debug.mi.core.event.MIEvent;
import org.eclipse.cdt.debug.mi.core.output.CLICatchInfo;
import org.eclipse.cdt.debug.mi.core.output.MIBreakInsertInfo;
import org.eclipse.cdt.debug.mi.core.output.MIBreakListInfo;
import org.eclipse.cdt.debug.mi.core.output.MIBreakWatchInfo;
import org.eclipse.cdt.debug.mi.core.output.MIBreakpoint;
import org.eclipse.cdt.debug.mi.core.output.MIInfo;

/**
 * Breakpoint Manager for the CDI interface.
 */
public class BreakpointManager extends Manager {

	public static ICDIBreakpoint[] EMPTY_BREAKPOINTS = {};

	Map breakMap;
	Map deferredMap;
	boolean allowInterrupt;

	public BreakpointManager(Session session) {
		super(session, false);
		breakMap = Collections.synchronizedMap(new HashMap());
		deferredMap = Collections.synchronizedMap(new HashMap());
		allowInterrupt = true;
	}

	synchronized List getBreakpointsList(Target target) {
		List bList = (List)breakMap.get(target);
		if (bList == null) {
			bList = Collections.synchronizedList(new ArrayList());
			breakMap.put(target, bList);
		}
		return bList;
	}

	MIBreakpoint[] getAllMIBreakpoints(MISession miSession) throws CDIException {
		CommandFactory factory = miSession.getCommandFactory();
		MIBreakList breakpointList = factory.createMIBreakList();
		try {
			miSession.postCommand(breakpointList);
			MIBreakListInfo info = breakpointList.getMIBreakListInfo();
			if (info == null) {
				throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
			}
			return info.getMIBreakpoints();
		} catch (MIException e) {
			throw new MI2CDIException(e);
		}
	}

	boolean hasBreakpointChanged(MIBreakpoint miBreak, MIBreakpoint miBreakpoint) {
		return miBreak.isEnabled() != miBreakpoint.isEnabled() ||
		!miBreak.getCondition().equals(miBreakpoint.getCondition()) ||
		miBreak.getIgnoreCount() != miBreakpoint.getIgnoreCount();
	}

	public Watchpoint getWatchpoint(MISession miSession, int number) {
		return (Watchpoint)getBreakpoint(miSession, number);
	}
	
	public Breakpoint getBreakpoint(MISession miSession, int number) {
		Session session = (Session)getSession();
		Target target = session.getTarget(miSession);
		if (target != null) {
			return getBreakpoint(target, number);
		}
		return null;
	}

	public Breakpoint getBreakpoint(Target target, int number) {
		List bList = (List)breakMap.get(target);
		if (bList != null) {
			Breakpoint[] bkpts = (Breakpoint[]) bList.toArray(new Breakpoint[0]);
			for (int i = 0; i < bkpts.length; i++) {
				MIBreakpoint[] miBreakpoints = bkpts[i].getMIBreakpoints();
				for (int j = 0; j < miBreakpoints.length; j++) {
					if (miBreakpoints[j].getNumber() == number) {
						return bkpts[i];
					}
				}
			}
		}
		return null;
	}

	boolean suspendInferior(Target target) throws CDIException {
		boolean shouldRestart = false;
		// Stop the program
		if (allowInterrupt && target.isRunning()) {
			// Disable events.
			((EventManager) getSession().getEventManager()).allowProcessingEvents(false);
			target.suspend();
			shouldRestart = true;
		}
		return shouldRestart;
	}

	void resumeInferior(Target target, boolean shouldRestart) throws CDIException {
		((EventManager) getSession().getEventManager()).allowProcessingEvents(true);
		if (shouldRestart) {
			target.resume();
		}
	}

	public void deleteBreakpoint(MISession miSession, int no) {
		Session session = (Session)getSession();
		Target target = session.getTarget(miSession);
		if (target != null) {
			deleteBreakpoint(target, no);
		}
	}

	/**
	 * Use in the event classes, the breakpoint is not remove from the list
	 * It is only done in DestroyedEvent class.  Since we need to keep the breakpoint
	 * type around.
	 * @param target
	 * @param no
	 */
	void deleteBreakpoint (Target target, int no) {
		List bList = (List)breakMap.get(target);
		if (bList != null) {
			Breakpoint[] points = (Breakpoint[]) bList.toArray(new Breakpoint[0]);
			for (int i = 0; i < points.length; i++) {
				MIBreakpoint[] miBreakpoints = points[i].getMIBreakpoints();
				for (int j = 0; j < miBreakpoints.length; j++) {
					if (miBreakpoints[j].getNumber() == no) {
						bList.remove(points[i]);
						break;
					}
				}
			}
		}
	}

	/**
	 * Call through the Breakpoint class Breakpoint.setEnabled(boolean)
	 * 
	 * @param breakpoint
	 * @throws CDIException
	 */
	public void enableBreakpoint(Breakpoint breakpoint) throws CDIException {
		Target target = (Target)breakpoint.getTarget();

		// Check if the breakpoint is in the deffered list
		List dList = (List)deferredMap.get(target);
		if (dList != null) {
			if (dList.contains(breakpoint)) {
				breakpoint.setEnabled0(true);
				return; // bail out here, our work is done.
			}
		}

		List bList = (List)breakMap.get(target);
		if (bList == null) {
			throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Not_a_CDT_breakpoint")); //$NON-NLS-1$			
		}
		if (!bList.contains(breakpoint)) {
			throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Not_a_CDT_breakpoint")); //$NON-NLS-1$
		}
		MIBreakpoint[] miBreakpoints = breakpoint.getMIBreakpoints();
		if (miBreakpoints == null || miBreakpoints.length == 0) {
			throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Not_a_CDT_breakpoint")); //$NON-NLS-1$
		}
		
		int[] numbers = new int[miBreakpoints.length];
		for (int i = 0; i < miBreakpoints.length; i++) {
			numbers[i] = miBreakpoints[i].getNumber();
		}

		boolean restart = false;
		MISession miSession = target.getMISession();
		CommandFactory factory = miSession.getCommandFactory();
		MIBreakEnable breakEnable = factory.createMIBreakEnable(numbers);
		try {
			restart = suspendInferior(target);
			miSession.postCommand(breakEnable);
			MIInfo info = breakEnable.getMIInfo();
			if (info == null) {
				throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
			}
			for (int i = 0; i < miBreakpoints.length; i++) {
				miBreakpoints[i].setEnabled(true);
			}
			breakpoint.setEnabled0(true);
		} catch (MIException e) {
			throw new MI2CDIException(e);
		} finally {
			try {
				// Resume the program and enable events.
				resumeInferior(target, restart);
			} finally {
				// Fire a changed Event.
				miSession.fireEvent(new MIBreakpointChangedEvent(miSession, numbers[0]));
			}
		}


	}

	/**
	 * Call through the Breakpoint class.  Breakpoint.disable
	 * 
	 * @param breakpoint
	 * @throws CDIException
	 */
	public void disableBreakpoint(Breakpoint breakpoint) throws CDIException {
		Target target = (Target)breakpoint.getTarget();
		
		// Check if the breakpoint is in the deffered list
		List dList = (List)deferredMap.get(target);
		if (dList != null) {
			if (dList.contains(breakpoint)) {
				breakpoint.setEnabled0(false);
				return; // bail out here, our work is done.
			}
		}

		List bList = (List)breakMap.get(target);
		if (bList == null) {
			throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Not_a_CDT_breakpoint")); //$NON-NLS-1$			
		}
		if (!bList.contains(breakpoint)) {
			throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Not_a_CDT_breakpoint")); //$NON-NLS-1$
		}

		MIBreakpoint[] miBreakpoints = breakpoint.getMIBreakpoints();
		if (miBreakpoints == null || miBreakpoints.length == 0) {
			throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Not_a_CDT_breakpoint")); //$NON-NLS-1$			
		}
		int[] numbers = new int[miBreakpoints.length];
		for (int i = 0; i < miBreakpoints.length; i++) {
			numbers[i] = miBreakpoints[i].getNumber();
		}

		boolean restart = false;
		MISession miSession = target.getMISession();
		CommandFactory factory = miSession.getCommandFactory();
		MIBreakDisable breakDisable = factory.createMIBreakDisable(numbers);
		try {
			restart = suspendInferior(target);
			miSession.postCommand(breakDisable);
			MIInfo info = breakDisable.getMIInfo();
			if (info == null) {
				throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
			}
			for (int i = 0; i < miBreakpoints.length; i++) {
				miBreakpoints[i].setEnabled(false);
			}
			breakpoint.setEnabled0(false);
		} catch (MIException e) {
			throw new MI2CDIException(e);
		} finally {
			try {
				resumeInferior(target, restart);
			} finally {
				// Fire a changed Event.
				miSession.fireEvent(new MIBreakpointChangedEvent(miSession, numbers[0]));
			}
		}
	}

	/**
	 * Use by the Breakpoint class, Breakpoint.setCondition(Condition cond)
	 * In this case we will not try to change the condition with -break-condition.
	 * Since condition may contains new thread-id it is simpler to remove the breakpoints
	 * and make a new breakpoints with the new conditions.
	 * @param breakpoint
	 * @param newCondition
	 * @throws CDIException
	 */
	public void setCondition(Breakpoint breakpoint, ICDICondition newCondition) throws CDIException {
		Target target = (Target)breakpoint.getTarget();

		// Check if the breakpoint is in the deffered list
		List dList = (List)deferredMap.get(target);
		if (dList != null) {
			if (dList.contains(breakpoint)) {
				breakpoint.setCondition0(newCondition);
				return; // bail out here, our work is done.
			}
		}

		List bList = (List)breakMap.get(target);
		if (bList == null) {
			throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Not_a_CDT_breakpoint")); //$NON-NLS-1$			
		}
		if (!bList.contains(breakpoint)) {
			throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Not_a_CDT_breakpoint")); //$NON-NLS-1$
		}

		MIBreakpoint[] miBreakpoints = breakpoint.getMIBreakpoints();
		deleteMIBreakpoints(target, miBreakpoints);
		ICDICondition oldCondition = breakpoint.getCondition();
		boolean success = false;
		try {
			breakpoint.setCondition0(newCondition);
			if (breakpoint instanceof LocationBreakpoint) {
				setLocationBreakpoint((LocationBreakpoint)breakpoint);
			} else if (breakpoint instanceof Watchpoint) {
				setWatchpoint((Watchpoint)breakpoint, false);
			} else {
				throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Not_a_CDT_breakpoint")); //$NON-NLS-1$
			}
			success = true;
		} finally {
			if (!success) {
				breakpoint.setCondition0(oldCondition);
				if (breakpoint instanceof LocationBreakpoint) {
					setLocationBreakpoint((LocationBreakpoint)breakpoint);
				} else if (breakpoint instanceof Watchpoint) {
					setWatchpoint((Watchpoint)breakpoint, false);
				}
			}
		}

		// Fire a changed Event.
		miBreakpoints = breakpoint.getMIBreakpoints();
		if (miBreakpoints != null && miBreakpoints.length > 0) {
			MISession miSession = target.getMISession();
			miSession.fireEvent(new MIBreakpointChangedEvent(miSession, miBreakpoints[0].getNumber()));
		}
	}

	public void update(Target target) throws CDIException {
		update(target, null);
	}

	/**
	 * Pass the event that causes this update
	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=135250
	 */
	public void update(Target target, MIEvent event) throws CDIException {
		MISession miSession = target.getMISession();
		MIBreakpoint[] allMIBreakpoints = getAllMIBreakpoints(miSession);
		List bList = getBreakpointsList(target);
		List eventList = new ArrayList(allMIBreakpoints.length);
		for (int i = 0; i < allMIBreakpoints.length; i++) {
			MIBreakpoint miBreakpoint = allMIBreakpoints[i];
			int no = miBreakpoint.getNumber();
			Breakpoint bp = getBreakpoint(target, no);
			if (bp != null) {
				MIBreakpoint[] miBps = bp.getMIBreakpoints();
				for (int j = 0; j < miBps.length; j++) {
					if (miBps[j].getNumber() == no) {
						if (hasBreakpointChanged(miBps[j], miBreakpoint)) {
							miBps[j] = miBreakpoint;
							bp.setEnabled0(miBreakpoint.isEnabled());
							// FIXME: We have a problem if the thread id change.
							ICDICondition oldCond = bp.getCondition();
							String[] tids = oldCond.getThreadIds();
							Condition newCondition = new Condition(miBreakpoint.getIgnoreCount(),
									miBreakpoint.getCondition(), tids);
							bp.setCondition0(newCondition);
							// Fire ChangedEvent
							eventList.add(new MIBreakpointChangedEvent(miSession, no)); 
						}
					}
				}
			} else {
				// add the new breakpoint and fire CreatedEvent
				int type = ICBreakpointType.REGULAR;
				if (miBreakpoint.isHardware()) {
					type = ICBreakpointType.HARDWARE;
				} else if (miBreakpoint.isTemporary()) {
					type = ICBreakpointType.TEMPORARY;
				}
				String[] tids = null;
				String tid = miBreakpoint.getThreadId();
				if (tid != null && tid.length() > 0) {
					tids = new String[] { tid };
				}
				Condition condition = new Condition(miBreakpoint.getIgnoreCount(),
						miBreakpoint.getCondition(), tids);
				
				if (miBreakpoint.isWatchpoint()) {
					int watchType = 0;
					if (miBreakpoint.isAccessWatchpoint() || miBreakpoint.isReadWatchpoint()) {
						watchType |= ICDIWatchpoint.READ;
					}
					if (miBreakpoint.isAccessWatchpoint() || miBreakpoint.isWriteWatchpoint()) {
						watchType |= ICDIWatchpoint.WRITE;
					}
					Watchpoint wpoint = new Watchpoint(target, miBreakpoint.getWhat(), type, watchType, condition, miBreakpoint.isEnabled());
					wpoint.setMIBreakpoints(new MIBreakpoint[] {miBreakpoint});
					bList.add(wpoint);
				} else {
					int hint = MIBreakpointChangedEvent.HINT_NONE;
					if (event instanceof MIBreakpointChangedEvent) {
						hint = ((MIBreakpointChangedEvent)event).getHint();
					}
					String function = miBreakpoint.getFunction();
					String file = miBreakpoint.getFile();
					int line = miBreakpoint.getLine();
					String addr = miBreakpoint.getAddress();
					boolean enabled = miBreakpoint.isEnabled();

					Breakpoint newBreakpoint = null;
					if (hint == MIBreakpointChangedEvent.HINT_NEW_LINE_BREAKPOINT || 
							(hint == MIBreakpointChangedEvent.HINT_NONE && file != null && file.length() > 0 && line > 0)) {
						LineLocation location = createLineLocation (miBreakpoint.getFile(),
								miBreakpoint.getLine());
						newBreakpoint = new LineBreakpoint(target, type, location, condition, enabled);
					} else if ((hint == MIBreakpointChangedEvent.HINT_NEW_FUNCTION_BREAKPOINT || 
							hint == MIBreakpointChangedEvent.HINT_NONE) && function != null && function.length() > 0) {
						FunctionLocation location = createFunctionLocation(file, function);
						newBreakpoint = new FunctionBreakpoint(target, type, location, condition, enabled);
					} else if (hint == MIBreakpointChangedEvent.HINT_NEW_EVENTBREAKPOINT	|| EventBreakpoint.getEventArgumentFromMI(miBreakpoint)!=null) {
						String ctype = EventBreakpoint.getEventTypeFromMI(miBreakpoint);
						if (ctype != null) {
							newBreakpoint = new EventBreakpoint(target, ctype, EventBreakpoint
									.getEventArgumentFromMI(miBreakpoint), condition, enabled);
						} else {
							MIPlugin.log("Unsupported event breakpoint: "+miBreakpoint.getWhat()); //$NON-NLS-1$ log entry not for users
						}
					} else if (addr != null && addr.length() > 0) {
						BigInteger big = MIFormat.getBigInteger(addr);
						AddressLocation location = createAddressLocation(big);
						newBreakpoint = new AddressBreakpoint(target, type, location, condition,
								enabled);
					}
					if (newBreakpoint != null) {
						newBreakpoint.setMIBreakpoints(new MIBreakpoint[] { miBreakpoint });
						bList.add(newBreakpoint);
					}
				}
				eventList.add(new MIBreakpointCreatedEvent(miSession, no)); 
			}
		}
		// Check if any breakpoint was removed.
		Breakpoint[] oldBreakpoints = (Breakpoint[]) bList.toArray(new Breakpoint[0]);
		for (int i = 0; i < oldBreakpoints.length; i++) {
			boolean found = false;
			MIBreakpoint[] miBreakpoints = oldBreakpoints[i].getMIBreakpoints();
			for (int j = 0; j < miBreakpoints.length; j++) {
				int no = miBreakpoints[j].getNumber();
				for (int k = 0; k < allMIBreakpoints.length; k++) {
					if (no == allMIBreakpoints[k].getNumber()) {
						found = true;
						break;
					}
				}
				if (!found) {
					// Fire destroyed Events.
					eventList.add(new MIBreakpointDeletedEvent(miSession, no)); 
				}
			}
		}
		MIEvent[] events = (MIEvent[])eventList.toArray(new MIEvent[0]);
		miSession.fireEvents(events);
	}

	/**
	 * @see org.eclipse.cdt.debug.core.cdi.ICDIBreakpointManager#allowProgramInterruption()
	 */
	public void allowProgramInterruption(boolean e) {
		allowInterrupt = e;
	}

	public void deleteFromDeferredList(Breakpoint bkpt) {
		List dList = (List)deferredMap.get(bkpt.getTarget());
		if (dList != null) {
			dList.remove(bkpt);
		}
	}

	/**
	 * Use by the EventManager when checking for deferred breapoints.
	 * @param bkpt
	 */
	public void addToBreakpointList(Breakpoint bkpt) {
		List bList = (List)breakMap.get(bkpt.getTarget());
		if (bList != null) {
			bList.add(bkpt);
		}
	}

	public void deleteAllBreakpoints(Target target) throws CDIException {
		List bList = (List)breakMap.get(target);
		if (bList != null) {
			ICDIBreakpoint[] bps = new ICDIBreakpoint[bList.size()];
			bList.toArray(bps);
			deleteBreakpoints(target, bps);
		}
	}
	/**
	 * @see org.eclipse.cdt.debug.core.cdi.ICDIBreakpointManager#deleteBreakpoint(ICDIBreakpoint)
	 */
	public void deleteBreakpoint(ICDIBreakpoint breakpoint) throws CDIException {
		deleteBreakpoints((Target)breakpoint.getTarget(), new ICDIBreakpoint[] { breakpoint });
	}

	public void deleteBreakpoints(Target target, ICDIBreakpoint[] breakpoints) throws CDIException {
		List bList = (List)breakMap.get(target);
		List dList = (List)deferredMap.get(target);

		// Do the sanity check first, we will accept all or none
		if (bList == null) {
			throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Not_a_CDT_breakpoint")); //$NON-NLS-1$			
		}
		for (int i = 0; i < breakpoints.length; i++) {
			if (!(breakpoints[i] instanceof Breakpoint && (bList.contains(breakpoints[i]) || (dList != null && dList.contains(breakpoints[i]))))) {
				throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Not_a_CDT_breakpoint")); //$NON-NLS-1$			
			}
		}

		MISession miSession = target.getMISession();
		List eventList = new ArrayList(breakpoints.length);
		for (int i = 0; i < breakpoints.length; i++) {
			if (!(dList != null && dList.remove(breakpoints[i]))) {
				MIBreakpoint[] miBreakpoints = ((Breakpoint)breakpoints[i]).getMIBreakpoints();
				if (miBreakpoints.length > 0) {
					deleteMIBreakpoints(target, miBreakpoints);
					eventList.add(new MIBreakpointDeletedEvent(miSession, miBreakpoints[0].getNumber()));
				}
			}
		}
		MIEvent[] events = (MIEvent[])eventList.toArray(new MIEvent[0]);
		miSession.fireEvents(events);
	}

	void deleteMIBreakpoints(Target target, MIBreakpoint[] miBreakpoints) throws CDIException {
		MISession miSession = target.getMISession();
		int[] numbers = new int[miBreakpoints.length];
		for (int i = 0; i < miBreakpoints.length; ++i) {
			numbers[i] = miBreakpoints[i].getNumber();
		}
		boolean restart = false;
		try {
			restart = suspendInferior(target);
			deleteMIBreakpoints(miSession, numbers);
		} finally {
			resumeInferior(target, restart);
		}
	}

	void deleteMIBreakpoints(MISession miSession, int[] numbers) throws CDIException {
		CommandFactory factory = miSession.getCommandFactory();
		MIBreakDelete breakDelete = factory.createMIBreakDelete(numbers);
		try {
			miSession.postCommand(breakDelete);
			MIInfo info = breakDelete.getMIInfo();
			if (info == null) {
				throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
			}
		} catch (MIException e) {
			throw new MI2CDIException(e);
		}
	}

	public ICDIBreakpoint[] getBreakpoints(Target target) throws CDIException {
		List list = (List)breakMap.get(target);
		if (list != null) {
			ICDIBreakpoint[] bps = new ICDIBreakpoint[list.size()];
			list.toArray(bps);
			return bps;
		}
		return EMPTY_BREAKPOINTS;
	}

	public ICDIBreakpoint[] getDeferredBreakpoints(Target target) throws CDIException {
		List dlist = (List)deferredMap.get(target);
		if (dlist != null) {
			ICDIBreakpoint[] bps = new ICDIBreakpoint[dlist.size()];
			dlist.toArray(bps);
			return bps;
		}
		return EMPTY_BREAKPOINTS;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.cdi.model.ICDIBreakpointManagement#setLineBreakpoint(int, org.eclipse.cdt.debug.core.cdi.ICDILineLocation, org.eclipse.cdt.debug.core.cdi.ICDICondition, boolean)
	 */
	public ICDILineBreakpoint setLineBreakpoint(Target target, int type, ICDILineLocation location,
			ICDICondition condition, boolean deferred, boolean enabled) throws CDIException {		
		LineBreakpoint bkpt = new LineBreakpoint(target, type, location, condition, enabled);
		setNewLocationBreakpoint(bkpt, deferred);
		return bkpt;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.cdi.model.ICDIBreakpointManagement#setFunctionBreakpoint(int, org.eclipse.cdt.debug.core.cdi.ICDIFunctionLocation, org.eclipse.cdt.debug.core.cdi.ICDICondition, boolean)
	 */
	public ICDIFunctionBreakpoint setFunctionBreakpoint(Target target, int type, ICDIFunctionLocation location,
			ICDICondition condition, boolean deferred, boolean enabled) throws CDIException {		
		FunctionBreakpoint bkpt = new FunctionBreakpoint(target, type, location, condition, enabled);
		setNewLocationBreakpoint(bkpt, deferred);
		return bkpt;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.cdi.model.ICDIBreakpointManagement#setAddressBreakpoint(int, org.eclipse.cdt.debug.core.cdi.ICDIAddressLocation, org.eclipse.cdt.debug.core.cdi.ICDICondition, boolean)
	 */
	public ICDIAddressBreakpoint setAddressBreakpoint(Target target, int type, ICDIAddressLocation location,
			ICDICondition condition, boolean deferred, boolean enabled) throws CDIException {		
		AddressBreakpoint bkpt = new AddressBreakpoint(target, type, location, condition, enabled);
		setNewLocationBreakpoint(bkpt, deferred);
		return bkpt;
	}


	protected void setNewLocationBreakpoint(LocationBreakpoint bkpt, boolean deferred) throws CDIException {
		Target target = (Target)bkpt.getTarget();
		MISession miSession = target.getMISession();
		try {
			setLocationBreakpoint(bkpt);
			List blist = getBreakpointsList(target);
			blist.add(bkpt);

			// Fire a created Event.
			MIBreakpoint[] miBreakpoints = bkpt.getMIBreakpoints();
			if (miBreakpoints != null && miBreakpoints.length > 0) {
				miSession.fireEvent(new MIBreakpointCreatedEvent(miSession, miBreakpoints[0].getNumber()));
			}
		} catch (CDIException e) {
			if (!deferred) {
				throw e;
			}
			Session session = (Session)target.getSession();
			SharedLibraryManager sharedMgr  = session.getSharedLibraryManager();
			if (sharedMgr.isDeferredBreakpoint(target)) {
				addDeferredBreakpoint(bkpt);
			} else {
				throw e;
			}
		}
	}

	private void addDeferredBreakpoint(Breakpoint breakpoint) {	
		Target target = (Target)breakpoint.getTarget();
		List dList = (List)deferredMap.get(target);
		if (dList == null) {
			dList = Collections.synchronizedList(new ArrayList());
			deferredMap.put(target, dList);
		}
		dList.add(breakpoint);
	}
	
	public void setLocationBreakpoint (LocationBreakpoint bkpt) throws CDIException {
		Target target = (Target)bkpt.getTarget();
		MISession miSession = target.getMISession();
		MIBreakInsert[] breakInserts = createMIBreakInsert(bkpt, miSession.isBreakpointsWithFullName());
		List pointList = new ArrayList();
		boolean restart = false;
		try {
			restart = suspendInferior(target);
			CommandFactory factory = miSession.getCommandFactory();
			boolean enable = bkpt.isEnabled();
			for (int i = 0; i < breakInserts.length; i++) {
				miSession.postCommand(breakInserts[i]);
				MIBreakInsertInfo info = breakInserts[i].getMIBreakInsertInfo();
				if (info == null) {
					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
				}
				MIBreakpoint[] points = info.getMIBreakpoints();
				if (points == null || points.length == 0) {
					throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Parsing_Error")); //$NON-NLS-1$
				}
				// Set
				if (bkpt.getFile() != null && bkpt.getFile().length() > 0)
				{
					for (int j = 0; j < points.length; j++) {
						points[j].setFile(bkpt.getFile());
					}					
				}
				// Make sure that if the breakpoint was disable we create them disable.
				if (!enable) {
					int[] numbers = new int[points.length];
					for (int j = 0; j < points.length; j++) {
						numbers[j] = points[j].getNumber();
					}
					MIBreakDisable breakDisable = factory.createMIBreakDisable(numbers);
					try {
						miSession.postCommand(breakDisable);
						MIInfo disableInfo = breakDisable.getMIInfo();
						if (disableInfo == null) {
							throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
						}
					} catch (MIException e) {
						throw new MI2CDIException(e);
					}
				}

				pointList.addAll(Arrays.asList(points));
			}
		} catch (MIException e) {
			try {
				// Things did not go well remove all the breakpoints we've set before.
				MIBreakpoint[] allPoints = (MIBreakpoint[]) pointList.toArray(new MIBreakpoint[pointList.size()]);
				if (allPoints != null && allPoints.length > 0) {
					deleteMIBreakpoints(target, allPoints);
				}
			} catch (CDIException cdie) {
				// ignore this one;
			}
			throw new MI2CDIException(e);
		} finally {
			resumeInferior(target, restart);
		}
		MIBreakpoint[] allPoints = (MIBreakpoint[]) pointList.toArray(new MIBreakpoint[pointList.size()]);
		bkpt.setMIBreakpoints(allPoints);
	}

	public void setWatchpoint(Watchpoint bkpt) throws CDIException {
		setWatchpoint(bkpt, true);
	}

	private void setWatchpoint(Watchpoint watchpoint, boolean isNew) throws CDIException {
		Target target = (Target)watchpoint.getTarget();
		boolean access = watchpoint.isReadType() && watchpoint.isWriteType();
		boolean read = ! watchpoint.isWriteType() && watchpoint.isReadType();
		String expression = watchpoint.getDerivedExpression();

		MISession miSession = target.getMISession();
		CommandFactory factory = miSession.getCommandFactory();
		MIBreakWatch breakWatch =
			factory.createMIBreakWatch(access, read, expression);
		MIBreakpoint[] points = null;
		boolean restart = false;
		try {
			restart = suspendInferior(target);
			miSession.postCommand(breakWatch);
			MIBreakWatchInfo winfo = breakWatch.getMIBreakWatchInfo();
			if (winfo == null) {
				throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
			}
			points = winfo.getMIBreakpoints();
			if (points == null || points.length == 0) {
				throw new CDIException(CdiResources.getString("cdi.BreakpointManager.Parsing_Error")); //$NON-NLS-1$
			}

			int no = points[0].getNumber();

			// Put the condition now.
			String exprCond = null;
			int ignoreCount = 0;

			ICDICondition condition = watchpoint.getCondition();
			if (condition != null) {
				exprCond = condition.getExpression();
				ignoreCount = condition.getIgnoreCount();
			}
			if (exprCond != null && exprCond.length() > 0) {
				MIBreakCondition breakCondition = factory.createMIBreakCondition(no, exprCond);				
				miSession.postCommand(breakCondition);
				MIInfo info = breakCondition.getMIInfo();
				if (info == null) {
					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
				}
			}
			if (ignoreCount > 0) {
				MIBreakAfter breakAfter = factory.createMIBreakAfter(no, ignoreCount);
				miSession.postCommand(breakAfter);
				MIInfo info = breakAfter.getMIInfo();
				if (info == null) {
					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
				}				
			}
			// how to deal with threads ???
		} catch (MIException e) {
			throw new MI2CDIException(e);
		} finally {
			resumeInferior(target, restart);
		}
		watchpoint.setMIBreakpoints(points);

		if (isNew) {
			List bList = getBreakpointsList(target);
			bList.add(watchpoint);

			// Fire a created Event.
			MIBreakpoint[] miBreakpoints = watchpoint.getMIBreakpoints();
			if (miBreakpoints != null && miBreakpoints.length > 0) {
				miSession.fireEvent(new MIBreakpointCreatedEvent(miSession, miBreakpoints[0].getNumber()));
			}
		}
	}

	Breakpoint[] exceptionBps = new Breakpoint[2];
	final int EXCEPTION_THROW_IDX = 0;
	final int EXCEPTION_CATCH_IDX = 1;
	final static String[] EXCEPTION_FUNCS = new String[] {"__cxa_throw", "__cxa_begin_catch"}; //$NON-NLS-1$ //$NON-NLS-2$


	public ICDIExceptionpoint setExceptionpoint(Target target, String clazz, boolean stopOnThrow,
			boolean stopOnCatch, boolean enabled) throws CDIException {

		if (!stopOnThrow && !stopOnCatch) {
			throw new CDIException("Must suspend on throw or catch"); //$NON-NLS-1$
		}

		MIBreakpoint[] miBreakpoints = null;

		if (stopOnThrow) {
			synchronized(exceptionBps) {
				int id = EXCEPTION_THROW_IDX;
				if (exceptionBps[EXCEPTION_THROW_IDX] == null) {
					FunctionLocation location = new FunctionLocation(null, EXCEPTION_FUNCS[id]);
					FunctionBreakpoint bp = new FunctionBreakpoint(target, ICBreakpointType.REGULAR, location, null, enabled);
					setLocationBreakpoint(bp);
					exceptionBps[id] = bp;
					miBreakpoints = bp.getMIBreakpoints();
				}
			}
		}
		if (stopOnCatch) {
			synchronized(exceptionBps) {
				int id = EXCEPTION_THROW_IDX;
				if (exceptionBps[id] == null) {
					FunctionLocation location = new FunctionLocation(null, EXCEPTION_FUNCS[id]);
					FunctionBreakpoint bp = new FunctionBreakpoint(target, ICBreakpointType.REGULAR, location, null, enabled);
					setLocationBreakpoint(bp);
					exceptionBps[id] = bp;
					if (miBreakpoints != null) {
						MIBreakpoint[] mibp = bp.getMIBreakpoints();
						MIBreakpoint[] temp = new MIBreakpoint[miBreakpoints.length + mibp.length];
						System.arraycopy(miBreakpoints, 0, temp, 0, miBreakpoints.length);
						System.arraycopy(mibp, 0, temp, miBreakpoints.length, mibp.length);
					} else {
						miBreakpoints = bp.getMIBreakpoints();
					}
				}
			}
		}

		Exceptionpoint excp = new Exceptionpoint(target, clazz, stopOnThrow, stopOnCatch, null, enabled);
		if (miBreakpoints != null && miBreakpoints.length > 0) {
			excp.setMIBreakpoints(miBreakpoints);
			List blist = getBreakpointsList(target);
			blist.add(excp);

			// Fire a created Event.
			MISession miSession = target.getMISession();
			miSession.fireEvent(new MIBreakpointCreatedEvent(miSession, miBreakpoints[0].getNumber()));
		}
		return excp;
	}

	/**
	 * Call -gdb-set breakpoint pending set
	 * @param target
	 * @param set
	 * @throws CDIException
	 */
	public void setBreakpointPending(Target target, boolean set) throws CDIException { 
		MISession miSession = target.getMISession();
		CommandFactory factory = miSession.getCommandFactory();
		MIGDBSetBreakpointPending bpp = factory.createMIGDBSetBreakpointPending(set);
		try {
			miSession.postCommand(bpp);
			MIInfo info = bpp.getMIInfo();
			if (info == null) {
				throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
			}
		} catch (MIException e) {
			throw new MI2CDIException(e);
		}
	}

	public Condition createCondition(int ignoreCount, String expression, String[] tids) {
		return new Condition(ignoreCount, expression, tids);
	}

	public LineLocation createLineLocation(String file, int line) {
		return new LineLocation(file, line);
	}
	
	public FunctionLocation createFunctionLocation(String file, String function) {
		return new FunctionLocation(file, function);
	}

	public AddressLocation createAddressLocation(BigInteger address) {
		return new AddressLocation(address);
	}
	MIBreakInsert[] createMIBreakInsert(LocationBreakpoint bkpt) throws CDIException {
		return createMIBreakInsert(bkpt, false);
	}
	MIBreakInsert[] createMIBreakInsert(LocationBreakpoint bkpt, boolean fullPath) throws CDIException {
		boolean hardware = bkpt.isHardware();
		boolean temporary = bkpt.isTemporary();
		String exprCond = null;
		int ignoreCount = 0;
		String[] threadIds = null;
		StringBuffer line = new StringBuffer();

		if (bkpt.getCondition() != null) {
			ICDICondition condition = bkpt.getCondition();
			exprCond = condition.getExpression();
			ignoreCount = condition.getIgnoreCount();
			threadIds = condition.getThreadIds();
		}

		if (bkpt.getLocator() != null) {
			ICDILocator locator = bkpt.getLocator();
			String file = locator.getFile();
			if (file != null) {
				if (fullPath==false) {
					file = new File(file).getName();
				}
			}
			String function = locator.getFunction();
			int no = locator.getLineNumber();
			if (bkpt instanceof LineBreakpoint) {
				if (file != null && file.length() > 0) {
					line.append(file).append(':');
				}
				line.append(no);				
			} else if (bkpt instanceof FunctionBreakpoint) {
				if (function != null && function.length() > 0) {
					// if the function contains :: assume the user
					// knows the exact funciton
					int colon = function.indexOf("::"); //$NON-NLS-1$
					if (colon != -1) {
						line.append(function);
					} else {
						if (file != null && file.length() > 0) {
							line.append(file).append(':');
						}
						// GDB does not seem to accept function arguments when
						// we use file name:
						// (gdb) break file.c:Test(int)
						// Will fail, altought it can accept this
						// (gdb) break file.c:main
						// so fall back to the line number or
						// just the name of the function if lineno is invalid.
						int paren = function.indexOf('(');
						if (paren != -1) {
							if (no <= 0) {
								String func = function.substring(0, paren);
								line.append(func);
							} else {
								line.append(no);
							}
						} else {
							line.append(function);
						}
					}
				} else {
					// ???
					if (file != null && file.length() > 0) {
						line.append(file).append(':');
					}
					if (no > 0) {
						line.append(no);
					}
				}
			} else if (bkpt instanceof AddressBreakpoint) {
				line.append('*').append(locator.getAddress());				
			} else {
				// ???
				if (file != null && file.length() > 0) {
					line.append(file).append(':');
				}
				line.append(no);
			}
		}

		MIBreakInsert[] miBreakInserts;
		MISession miSession = ((Target)bkpt.getTarget()).getMISession();
		CommandFactory factory = miSession.getCommandFactory();
		if (threadIds == null || threadIds.length == 0) {
			MIBreakInsert bi = factory.createMIBreakInsert(temporary, hardware, exprCond, ignoreCount, line.toString(), 0);
			miBreakInserts = new MIBreakInsert[] { bi } ;
		} else {
			List list = new ArrayList(threadIds.length);
			for (int i = 0; i < threadIds.length; i++) {
				String threadId = threadIds[i];
				int tid = 0;
				if (threadId != null && threadId.length() > 0) {
					try {
						tid = Integer.parseInt(threadId);
						list.add(factory.createMIBreakInsert(temporary, hardware, exprCond, ignoreCount, line.toString(), tid));
					} catch (NumberFormatException e) {
					}
				}
			}
			miBreakInserts = (MIBreakInsert[]) list.toArray(new MIBreakInsert[list.size()]);
		}
		return miBreakInserts;
	}
	public ICDIEventBreakpoint setEventBreakpoint(Target target, String type, String arg, ICDICondition condition, boolean enabled) throws CDIException {
		EventBreakpoint eventBkpt = new EventBreakpoint(target,type,arg,condition,enabled);
		setEventBreakpoint(eventBkpt);
		return eventBkpt;
	}
	public void setEventBreakpoint(EventBreakpoint eventBkpt) throws CDIException {
		Target target = (Target) eventBkpt.getTarget();

		MISession miSession = target.getMISession();
		CommandFactory factory = miSession.getCommandFactory();
		CLICatch breakCatch = factory.createCLICatch(eventBkpt.getGdbEvent(), eventBkpt
				.getGdbArg());
		
		eventBkpt.setMIBreakpoints(new MIBreakpoint[0]); // initialize
		boolean restart = false;
		try {
			restart = suspendInferior(target);
			miSession.postCommand(breakCatch);
			int no;
			try {
				CLICatchInfo cinfo = (CLICatchInfo) breakCatch.getMIInfo();
				if (cinfo == null) {
					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
				}
				MIBreakpoint[] 	points = cinfo.getMIBreakpoints();
				if (points == null || points.length == 0) {
					throw new CDIException(CdiResources
							.getString("cdi.BreakpointManager.Parsing_Error")); //$NON-NLS-1$
				}
				no = points[0].getNumber();
				eventBkpt.setMIBreakpoints(points);
			} catch (MIException e) {
				if (!eventBkpt.isDeferred()) {
					throw e;
				}
				addDeferredBreakpoint(eventBkpt);
				return;
			}

			// Put the condition now.
			String exprCond = null;
			int ignoreCount = 0;

			ICDICondition condition = eventBkpt.getCondition();
			if (condition != null) {
				exprCond = condition.getExpression();
				ignoreCount = condition.getIgnoreCount();
			}
			if (exprCond != null && exprCond.length() > 0) {
				MIBreakCondition breakCondition = factory.createMIBreakCondition(no, exprCond);
				miSession.postCommand(breakCondition);
				MIInfo info = breakCondition.getMIInfo();
				if (info == null) {
					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
				}
			}
			if (ignoreCount > 0) {
				MIBreakAfter breakAfter = factory.createMIBreakAfter(no, ignoreCount);
				miSession.postCommand(breakAfter);
				MIInfo info = breakAfter.getMIInfo();
				if (info == null) {
					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
				}
			}
			// how to deal with threads ???
		} catch (MIException e) {
			throw new MI2CDIException(e);
		} finally {
			resumeInferior(target, restart);
		}
	

	
		List bList = getBreakpointsList(target);
		bList.add(eventBkpt);

		// Fire a created Event.
		MIBreakpoint[] miBreakpoints = eventBkpt.getMIBreakpoints();
		if (miBreakpoints != null && miBreakpoints.length > 0) {
			miSession.fireEvent(new MIBreakpointCreatedEvent(miSession, miBreakpoints[0]
					.getNumber()));
		}
		

	}
}
