package org.nodeclipse.debug.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import org.chromium.debug.core.ChromiumDebugPlugin;
import org.chromium.debug.core.ChromiumSourceDirector;
import org.chromium.debug.core.ScriptNameManipulator.ScriptNamePattern;
import org.chromium.debug.core.model.BreakpointInTargetMap;
import org.chromium.debug.core.model.BreakpointSynchronizer;
import org.chromium.debug.core.model.BreakpointSynchronizer.Callback;
import org.chromium.debug.core.model.ChromiumBreakpointAdapter;
import org.chromium.debug.core.model.ChromiumExceptionBreakpoint;
import org.chromium.debug.core.model.ChromiumLineBreakpoint;
import org.chromium.debug.core.model.ChromiumLineBreakpoint.MutableProperty;
import org.chromium.debug.core.model.ConnectedTargetData;
import org.chromium.debug.core.model.DebugTargetImpl;
import org.chromium.debug.core.model.JavascriptThread;
import org.chromium.debug.core.model.Messages;
import org.chromium.debug.core.model.StackFrame;
import org.chromium.debug.core.model.VmResource;
import org.chromium.debug.core.model.VmResource.Metadata;
import org.chromium.debug.core.model.VmResourceId;
import org.chromium.debug.core.model.VmResourceRef;
import org.chromium.debug.core.model.WorkspaceBridge;
import org.chromium.debug.core.util.ChromiumDebugPluginUtil;
import org.chromium.debug.core.util.JavaScriptRegExpSupport;
import org.chromium.sdk.Breakpoint;
import org.chromium.sdk.CallFrame;
import org.chromium.sdk.ExceptionData;
import org.chromium.sdk.JavascriptVm;
import org.chromium.sdk.JavascriptVm.ScriptsCallback;
import org.chromium.sdk.RelayOk;
import org.chromium.sdk.Script;
import org.chromium.sdk.SyncCallback;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointManager;
import org.eclipse.debug.core.IDebugEventSetListener;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.osgi.util.NLS;
import org.nodeclipse.debug.launch.LaunchConfigurationDelegate;
import org.nodeclipse.debug.util.NodeDebugUtil;

public class VProjectWorkspaceBridge implements WorkspaceBridge {
	/** The debug model ID. */
	public static final String DEBUG_MODEL_ID = "org.chromium.debug"; //$NON-NLS-1$

	public static class FactoryImpl implements Factory {
		private final String projectNameBase;

		public FactoryImpl(String projectNameBase) {
			this.projectNameBase = projectNameBase;
		}

		public WorkspaceBridge attachedToVm(
				ConnectedTargetData connectedTargetData,
				JavascriptVm javascriptVm) {
			// We might want to add URL or something to project name.
			return new VProjectWorkspaceBridge(projectNameBase,
					connectedTargetData, javascriptVm);
		}

		public String getDebugModelIdentifier() {
			return DEBUG_MODEL_ID;
		}

		public JsLabelProvider getLabelProvider() {
			return LABEL_PROVIDER;
		}
	}

	private IDebugEventSetListener debugListener = new IDebugEventSetListener() {
		@Override
		public void handleDebugEvents(DebugEvent[] events) {
			for (DebugEvent e : events) {
				switch (e.getKind()) {
				case DebugEvent.TERMINATE:
					// final IProject project = ResourcesPlugin
					// .getWorkspace().getRoot()
					// .getProject("STANDALONE_V8");
					if (debugProject != null && debugProject.exists()) {
						NodeDebugUtil.deleteConfig();
						ChromiumDebugPluginUtil
								.deleteVirtualProjectAsync(debugProject);
						DebugPlugin.getDefault().removeDebugEventListener(debugListener);
					}
					LaunchConfigurationDelegate.terminateNodeProcess();
					break;
				}
			}
		}
	};
	private final IProject debugProject;
	private final JavascriptVm javascriptVm;
	private final ResourceManager resourceManager;
	private final ConnectedTargetData connectedTargetData;
	private final ChromiumSourceDirector sourceDirector;

	public VProjectWorkspaceBridge(String projectName,
			ConnectedTargetData connectedTargetData, JavascriptVm javascriptVm) {
		this.connectedTargetData = connectedTargetData;
		this.javascriptVm = javascriptVm;
		this.debugProject = ChromiumDebugPluginUtil
				.createEmptyProject(projectName);
		this.resourceManager = new ResourceManager(debugProject);

		ILaunch launch = connectedTargetData.getDebugTarget().getLaunch();

		sourceDirector = (ChromiumSourceDirector) launch.getSourceLocator();
		sourceDirector.initializeVProjectContainers(debugProject,
				resourceManager, connectedTargetData.getJavascriptEmbedder());
		DebugPlugin.getDefault().addDebugEventListener(debugListener);
	}

	public BreakpointSynchronizer getBreakpointSynchronizer() {
		return new BreakpointSynchronizer(javascriptVm, sourceDirector,
				breakpointHandler, DEBUG_MODEL_ID);
	}

	public void synchronizeBreakpoints(
			BreakpointSynchronizer.Direction direction, Callback callback) {
		getBreakpointSynchronizer().syncBreakpoints(direction, callback);
	}

	public void startInitialization() {
		LaunchInitializationProcedure.startAsync(this, connectedTargetData
				.getDebugTarget().getPresetSyncDirection());
	}

	public void launchRemoved() {
	}

	public void beforeDetach() {
	}

	public void handleVmResetEvent() {
		resourceManager.clear();
	}

	public void scriptLoaded(Script newScript) {
		resourceManager.addScript(newScript);
	}

	public void scriptCollected(Script script) {
		resourceManager.scriptCollected(script);
	}

	public void reloadScriptsAtStart() {
		javascriptVm.getScripts(new ScriptsCallback() {
			public void failure(String errorMessage) {
				ChromiumDebugPlugin.logError(errorMessage);
			}

			public void success(Collection<Script> scripts) {
				if (!javascriptVm.isAttached()) {
					return;
				}
				for (Script script : scripts) {
					resourceManager.addScript(script);
				}
			}
		});
	}

	public Collection<? extends VmResource> findVmResourcesFromWorkspaceFile(
			IFile resource) throws CoreException {
		VmResourceRef vmResourceRef = findVmResourceRefFromWorkspaceFile(resource);
		if (vmResourceRef == null) {
			return null;
		}
		return vmResourceRef.accept(RESOLVE_RESOURCE_VISITOR);
	}

	private final VmResourceRef.Visitor<Collection<? extends VmResource>> RESOLVE_RESOURCE_VISITOR = new VmResourceRef.Visitor<Collection<? extends VmResource>>() {
		@Override
		public java.util.Collection<? extends VmResource> visitRegExpBased(
				ScriptNamePattern scriptNamePattern) {
			Pattern pattern = JavaScriptRegExpSupport
					.convertToJavaPattern(scriptNamePattern);
			return resourceManager.findVmResources(pattern);
		}

		@Override
		public Collection<? extends VmResource> visitResourceId(
				VmResourceId resourceId) {
			VmResource vmResource = resourceManager.getVmResource(resourceId);
			if (vmResource == null) {
				return Collections.emptyList();
			} else {
				return Collections.singletonList(vmResource);
			}
		}
	};

	public VmResource getVProjectVmResource(IFile file) {
		VmResourceId resourceId = resourceManager.getResourceId(file);
		if (resourceId == null) {
			return null;
		}
		return resourceManager.getVmResource(resourceId);
	}

	public VmResource createTemporaryFile(Metadata metadata,
			String proposedFileName) {
		return resourceManager.createTemporaryFile(metadata, proposedFileName);
	}

	private VmResourceRef findVmResourceRefFromWorkspaceFile(IFile resource)
			throws CoreException {
		return sourceDirector.findVmResourceRef(resource);
	}

	public void reloadScript(Script script) {
		resourceManager.reloadScript(script);
	}

	public BreakpointHandler getBreakpointHandler() {
		return breakpointHandler;
	}

	private final BreakpointHandlerImpl breakpointHandler = new BreakpointHandlerImpl();

	private class BreakpointHandlerImpl implements BreakpointHandler,
			BreakpointSynchronizer.BreakpointHelper {

		private volatile JavascriptVm.ExceptionCatchMode breakExceptionMode = null;

		private final LineBreakpointHandler lineBreakpointHandler = new LineBreakpointHandler();
		private final ExceptionBreakpointHandler exceptionBreakpointHandler = new ExceptionBreakpointHandler();
		private final List<BreakpointMapperBase<?, ?>> allHandlers;
		{
			allHandlers = new ArrayList<BreakpointMapperBase<?, ?>>(2);
			allHandlers.add(lineBreakpointHandler);
			allHandlers.add(exceptionBreakpointHandler);
		}

		public void initBreakpointManagerListenerState(
				IBreakpointManager breakpointManager) {
			for (BreakpointMapperBase<?, ?> handler : allHandlers) {
				handler.initEnablement(breakpointManager);
			}
		}

		public boolean supportsBreakpoint(IBreakpoint breakpoint) {
			for (BreakpointMapperBase<?, ?> handler : allHandlers) {
				Object res = handler.tryCastBreakpoint(breakpoint);
				if (res != null) {
					return true;
				}
			}
			return false;
		}

		@Override
		public Breakpoint getSdkBreakpoint(
				ChromiumLineBreakpoint chromiumLineBreakpoint) {
			return lineBreakpointHandler
					.getSdkBreakpoint(chromiumLineBreakpoint);
		}

		public void breakpointAdded(IBreakpoint breakpoint) {
			for (BreakpointMapperBase<?, ?> handler : allHandlers) {
				boolean res = handler.breakpointAdded(breakpoint);
				if (res) {
					return;
				}
			}
		}

		public RelayOk createBreakpointOnRemote(
				final ChromiumLineBreakpoint lineBreakpoint,
				final VmResourceRef vmResourceRef,
				final CreateCallback createCallback, SyncCallback syncCallback)
				throws CoreException {
			return lineBreakpointHandler
					.createBreakpointOnRemote(lineBreakpoint, vmResourceRef,
							createCallback, syncCallback);
		}

		@Override
		public BreakpointInTargetMap<Breakpoint, ChromiumLineBreakpoint> getLineBreakpointMap() {
			return lineBreakpointHandler.getMap();
		}

		@Override
		public void registerExceptionBreakpoint(
				Collection<ChromiumExceptionBreakpoint> collection) {
			exceptionBreakpointHandler.registerLocalBreakpoints(collection);
		}

		public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) {
			for (BreakpointMapperBase<?, ?> handler : allHandlers) {
				boolean res = handler.breakpointChanged(breakpoint, delta);
				if (res) {
					return;
				}
			}
		}

		public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) {
			for (BreakpointMapperBase<?, ?> handler : allHandlers) {
				boolean res = handler.breakpointRemoved(breakpoint, delta);
				if (res) {
					return;
				}
			}
		}

		public synchronized void breakpointManagerEnablementChanged(
				boolean enabled) {
			for (BreakpointMapperBase<?, ?> handler : allHandlers) {
				handler.breakpointManagerEnablementChanged(enabled);
			}
		}

		public Collection<? extends IBreakpoint> breakpointsHit(
				Collection<? extends Breakpoint> breakpointsHit) {
			return lineBreakpointHandler.handleBreakpointsHit(breakpointsHit);
		}

		@Override
		public Collection<? extends IBreakpoint> exceptionBreakpointHit(
				boolean isUncaught) {
			return exceptionBreakpointHandler.getHitBreakpoints(isUncaught);
		}

		private String getTargetNameSafe() {
			try {
				return connectedTargetData.getDebugTarget().getLaunch()
						.getLaunchConfiguration().getName();
			} catch (RuntimeException e) {
				return "<unknown>"; //$NON-NLS-1$
			}
		}

		private class LineBreakpointHandler extends
				BreakpointMapperBase<Breakpoint, ChromiumLineBreakpoint> {
			private final EnablementMonitor enablementMonitor = new EnablementMonitor();

			private class EnablementMonitor {
				synchronized void init(IBreakpointManager breakpointManager) {
					sendRequest(breakpointManager.isEnabled());
				}

				synchronized void setState(boolean enabled) {
					sendRequest(enabled);
				}

				private void sendRequest(boolean enabled) {
					javascriptVm.enableBreakpoints(enabled, null, null);
				}
			}

			void initEnablement(IBreakpointManager breakpointManager) {
				enablementMonitor.init(breakpointManager);
			}

			@Override
			void breakpointAdded(ChromiumLineBreakpoint lineBreakpoint) {
				if (ChromiumLineBreakpoint.getIgnoreList().contains(
						lineBreakpoint)) {
					return;
				}
				IFile file = (IFile) lineBreakpoint.getMarker().getResource();
				VmResourceRef vmResourceRef;
				try {
					vmResourceRef = findVmResourceRefFromWorkspaceFile(file);
				} catch (CoreException e) {
					ChromiumDebugPlugin
							.log(new Exception(
									"Failed to resolve script for the file " + file, e)); //$NON-NLS-1$
					return;
				}
				if (vmResourceRef == null) {
					// Might be a script from a different debug target
					return;
				}

				try {
					createBreakpointOnRemote(lineBreakpoint, vmResourceRef,
							null, null);
				} catch (CoreException e) {
					ChromiumDebugPlugin.log(new Exception(
							"Failed to create breakpoint in " + //$NON-NLS-1$
									getTargetNameSafe(), e));
					throw new RuntimeException(e);
				}
			}

			public Collection<? extends IBreakpoint> handleBreakpointsHit(
					Collection<? extends Breakpoint> sdkBreakpoints) {
				if (sdkBreakpoints.isEmpty()) {
					return Collections.emptyList();
				}

				Collection<IBreakpoint> uiBreakpoints = new ArrayList<IBreakpoint>(
						sdkBreakpoints.size());

				for (Breakpoint sdkBreakpoint : sdkBreakpoints) {
					ChromiumLineBreakpoint uiBreakpoint = getMap()
							.getUiBreakpoint(sdkBreakpoint);
					if (uiBreakpoint != null) {
						try {
							uiBreakpoint.silentlyResetIgnoreCount(); // reset
																		// ignore
																		// count
																		// as
																		// we've
																		// hit
																		// it
						} catch (CoreException e) {
							ChromiumDebugPlugin.log(new Exception(
									"Failed to reset breakpoint ignore count",
									e));
						}

						uiBreakpoints.add(uiBreakpoint);
					}
				}
				return uiBreakpoints;
			}

			public Breakpoint getSdkBreakpoint(
					ChromiumLineBreakpoint chromiumLineBreakpoint) {
				return getMap().getSdkBreakpoint(chromiumLineBreakpoint);
			}

			public RelayOk createBreakpointOnRemote(
					final ChromiumLineBreakpoint lineBreakpoint,
					final VmResourceRef vmResourceRef,
					final CreateCallback createCallback,
					SyncCallback syncCallback) throws CoreException {
				ChromiumLineBreakpoint.Helper.CreateOnRemoveCallback callback = new ChromiumLineBreakpoint.Helper.CreateOnRemoveCallback() {
					public void success(Breakpoint breakpoint) {
						getMap().add(breakpoint, lineBreakpoint);
						if (createCallback != null) {
							createCallback.success();
						}
					}

					public void failure(String errorMessage) {
						if (createCallback == null) {
							ChromiumDebugPlugin.logError(errorMessage);
						} else {
							createCallback.failure(new Exception(errorMessage));
						}
					}
				};
				return ChromiumLineBreakpoint.Helper.createOnRemote(
						lineBreakpoint, vmResourceRef, connectedTargetData,
						callback, syncCallback);
			}

			@Override
			void breakpointChanged(ChromiumLineBreakpoint lineBreakpoint,
					IMarkerDelta delta) {
				if (ChromiumLineBreakpoint.getIgnoreList().contains(
						lineBreakpoint)) {
					return;
				}
				Breakpoint sdkBreakpoint = getMap().getSdkBreakpoint(
						lineBreakpoint);
				if (sdkBreakpoint == null) {
					return;
				}

				Set<MutableProperty> propertyDelta = lineBreakpoint
						.getChangedProperty(delta);

				if (propertyDelta.isEmpty()) {
					return;
				}

				try {
					ChromiumLineBreakpoint.Helper.updateOnRemote(sdkBreakpoint,
							lineBreakpoint, propertyDelta);
				} catch (RuntimeException e) {
					ChromiumDebugPlugin.log(new Exception(
							"Failed to change breakpoint in " + //$NON-NLS-1$
									getTargetNameSafe(), e));
				} catch (CoreException e) {
					ChromiumDebugPlugin.log(new Exception(
							"Failed to change breakpoint in " + //$NON-NLS-1$
									getTargetNameSafe(), e));
				}
			}

			@Override
			void breakpointRemoved(ChromiumLineBreakpoint lineBreakpoint,
					IMarkerDelta delta) {
				if (ChromiumLineBreakpoint.getIgnoreList().contains(
						lineBreakpoint)) {
					return;
				}

				Breakpoint sdkBreakpoint = getMap().getSdkBreakpoint(
						lineBreakpoint);
				if (sdkBreakpoint == null) {
					return;
				}

				if (!lineBreakpoint.isEnabled()) {
					return;
				}
				JavascriptVm.BreakpointCallback callback = new JavascriptVm.BreakpointCallback() {
					public void failure(String errorMessage) {
						ChromiumDebugPlugin.log(new Exception(
								"Failed to remove breakpoint in " + //$NON-NLS-1$
										getTargetNameSafe()
										+ ": " + errorMessage)); //$NON-NLS-1$
					}

					public void success(Breakpoint breakpoint) {
					}
				};
				try {
					sdkBreakpoint.clear(callback, null);
				} catch (RuntimeException e) {
					ChromiumDebugPlugin.log(new Exception(
							"Failed to remove breakpoint in " + //$NON-NLS-1$
									getTargetNameSafe(), e));
				}
				getMap().remove(lineBreakpoint);
			}

			@Override
			void breakpointManagerEnablementChanged(boolean enabled) {
				enablementMonitor.setState(enabled);
			}

			@Override
			ChromiumLineBreakpoint tryCastBreakpoint(IBreakpoint breakpoint) {
				if (connectedTargetData.getDebugTarget().isDisconnected()) {
					return null;
				}
				return ChromiumBreakpointAdapter.tryCastBreakpoint(breakpoint);
			}
		}

		private class ExceptionBreakpointHandler
				extends
				BreakpointMapperBase<ExceptionBreakpointHandler.FakeSdkBreakpoint, ChromiumExceptionBreakpoint> {
			private final List<FakeSdkBreakpoint> breakpoints = new ArrayList<FakeSdkBreakpoint>(
					2);
			private JavascriptVm.ExceptionCatchMode currentRemoteFlag = null;
			private boolean breakpointsEnabled;

			/**
			 * Represents an SDK breakpoint object similar to
			 * {@link IBreakpoint} for line breakpoints. There is no real SDK
			 * exception breakpoints, we use
			 * {@link JavascriptVm#enableBreakpoints} directly.
			 */
			class FakeSdkBreakpoint {
				boolean includeCaught;
				boolean enabled;

				void initProperties(ChromiumExceptionBreakpoint uiBreakpoint) {
					includeCaught = uiBreakpoint.getIncludeCaught();
					try {
						enabled = uiBreakpoint.isEnabled();
					} catch (CoreException e) {
						ChromiumDebugPlugin.log(e);
					}
				}
			}

			@Override
			synchronized void breakpointManagerEnablementChanged(boolean enabled) {
				breakpointsEnabled = enabled;
				updateRemoteState();
			}

			public Collection<? extends IBreakpoint> getHitBreakpoints(
					boolean isUncaught) {
				List<ChromiumExceptionBreakpoint> result = new ArrayList<ChromiumExceptionBreakpoint>(
						2);
				synchronized (this) {
					for (FakeSdkBreakpoint sdkBreakpoint : breakpoints) {
						if (!isUncaught && !sdkBreakpoint.includeCaught) {
							continue;
						}
						ChromiumExceptionBreakpoint uiBreakpoint = getMap()
								.getUiBreakpoint(sdkBreakpoint);
						if (uiBreakpoint == null) {
							continue;
						}
						result.add(uiBreakpoint);
					}
				}
				return result;
			}

			@Override
			synchronized void initEnablement(
					IBreakpointManager breakpointManager) {
				breakpointsEnabled = breakpointManager.isEnabled();
			}

			public void registerLocalBreakpoints(
					Collection<ChromiumExceptionBreakpoint> collection) {
				synchronized (this) {
					for (ChromiumExceptionBreakpoint uiBreakpoint : collection) {
						FakeSdkBreakpoint sdkBreakpoint = new FakeSdkBreakpoint();
						sdkBreakpoint.initProperties(uiBreakpoint);
						breakpoints.add(sdkBreakpoint);
						getMap().add(sdkBreakpoint, uiBreakpoint);
					}
				}
				updateRemoteState();
			}

			@Override
			void breakpointAdded(ChromiumExceptionBreakpoint uiBreakpoint) {
				FakeSdkBreakpoint sdkBreakpoint = new FakeSdkBreakpoint();
				sdkBreakpoint.initProperties(uiBreakpoint);
				synchronized (this) {
					breakpoints.add(sdkBreakpoint);
					getMap().add(sdkBreakpoint, uiBreakpoint);
				}
				updateRemoteState();
			}

			@Override
			void breakpointChanged(ChromiumExceptionBreakpoint uiBreakpoint,
					IMarkerDelta delta) {
				FakeSdkBreakpoint sdkBreakpoint = getMap().getSdkBreakpoint(
						uiBreakpoint);
				if (sdkBreakpoint == null) {
					return;
				}
				boolean includeCaught = uiBreakpoint.getIncludeCaught();
				boolean enabled;
				try {
					enabled = uiBreakpoint.isEnabled();
				} catch (CoreException e) {
					throw new RuntimeException(e);
				}
				boolean changed = false;
				synchronized (this) {
					if (includeCaught != sdkBreakpoint.includeCaught) {
						changed = true;
						sdkBreakpoint.includeCaught = includeCaught;
					}
					if (enabled != sdkBreakpoint.enabled) {
						changed = true;
						sdkBreakpoint.enabled = enabled;
					}
				}
				if (changed) {
					updateRemoteState();
				}
			}

			@Override
			void breakpointRemoved(ChromiumExceptionBreakpoint uiBreakpoint,
					IMarkerDelta delta) {
				FakeSdkBreakpoint sdkBreakpoint = getMap().getSdkBreakpoint(
						uiBreakpoint);
				if (sdkBreakpoint == null) {
					return;
				}
				synchronized (this) {
					breakpoints.remove(sdkBreakpoint);
					getMap().remove(uiBreakpoint);
				}
				updateRemoteState();
			}

			void updateRemoteState() {
				JavascriptVm.ExceptionCatchMode newRemoteFlag = JavascriptVm.ExceptionCatchMode.NONE;

				synchronized (this) {
					if (breakpointsEnabled) {
						for (FakeSdkBreakpoint sdkBreakpoint : breakpoints) {
							if (!sdkBreakpoint.enabled) {
								continue;
							}
							if (sdkBreakpoint.includeCaught) {
								newRemoteFlag = JavascriptVm.ExceptionCatchMode.ALL;
								break;
							}
							newRemoteFlag = JavascriptVm.ExceptionCatchMode.UNCAUGHT;
						}
					}
					if (newRemoteFlag != currentRemoteFlag) {
						javascriptVm.setBreakOnException(newRemoteFlag, null,
								null);
						currentRemoteFlag = newRemoteFlag;
					}
				}
			}

			@Override
			ChromiumExceptionBreakpoint tryCastBreakpoint(IBreakpoint breakpoint) {
				if (connectedTargetData.getDebugTarget().isDisconnected()) {
					return null;
				}
				return ChromiumExceptionBreakpoint
						.tryCastBreakpoint(breakpoint);
			}
		}

		private abstract class BreakpointMapperBase<SDK, UI> {
			boolean breakpointAdded(IBreakpoint breakpoint) {
				UI castBreakpoint = tryCastBreakpoint(breakpoint);
				if (castBreakpoint == null) {
					return false;
				}
				breakpointAdded(castBreakpoint);
				return true;
			}

			boolean breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) {
				UI castBreakpoint = tryCastBreakpoint(breakpoint);
				if (castBreakpoint == null) {
					return false;
				}
				breakpointChanged(castBreakpoint, delta);
				return true;
			}

			boolean breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) {
				UI castBreakpoint = tryCastBreakpoint(breakpoint);
				if (castBreakpoint == null) {
					return false;
				}
				breakpointRemoved(castBreakpoint, delta);
				return true;
			}

			abstract void initEnablement(IBreakpointManager breakpointManager);

			abstract void breakpointManagerEnablementChanged(boolean enabled);

			abstract void breakpointAdded(UI breakpoint);

			abstract void breakpointChanged(UI breakpoint, IMarkerDelta delta);

			abstract void breakpointRemoved(UI breakpoint, IMarkerDelta delta);

			abstract UI tryCastBreakpoint(IBreakpoint breakpoint);

			protected BreakpointInTargetMap<SDK, UI> getMap() {
				return map;
			}

			private final BreakpointInTargetMap<SDK, UI> map = new BreakpointInTargetMap<SDK, UI>();
		}
	}

	protected final static JsLabelProvider LABEL_PROVIDER = new JsLabelProvider() {
		public String getTargetLabel(DebugTargetImpl debugTarget) {
			String name = debugTarget.getName();
			String status = debugTarget.getVmStatus();
			if (status == null) {
				return name;
			} else {
				return NLS.bind(Messages.DebugTargetImpl_TARGET_NAME_PATTERN,
						name, status);
			}
		}

		public String getThreadLabel(JavascriptThread thread) {
			String url = thread.getConnectedData().getJavascriptEmbedder()
					.getThreadName();
			return NLS.bind(Messages.JsThread_ThreadLabelFormat,
					getThreadStateLabel(thread),
					(url.length() > 0 ? (" : " + url) : "")); //$NON-NLS-1$ //$NON-NLS-2$
		}

		private String getThreadStateLabel(JavascriptThread thread) {
			return thread.describeState(THREAD_DESCRIBE_VISITOR);
		}

		private final JavascriptThread.StateVisitor<String> THREAD_DESCRIBE_VISITOR = new JavascriptThread.StateVisitor<String>() {
			@Override
			public String visitResumed(
					JavascriptThread.ResumeReason resumeReason) {
				return Messages.JsThread_ThreadLabelRunning;
			}

			@Override
			public String visitSuspended(IBreakpoint[] breakpoints,
					ExceptionData exceptionData) {
				if (exceptionData != null) {
					return NLS
							.bind(Messages.JsThread_ThreadLabelSuspendedExceptionFormat,
									exceptionData.getExceptionMessage());
				} else {
					return Messages.JsThread_ThreadLabelSuspended;
				}
			}
		};

		public String getStackFrameLabel(StackFrame stackFrame)
				throws DebugException {
			CallFrame callFrame = stackFrame.getCallFrame();
			String name = callFrame.getFunctionName();
			Script script = callFrame.getScript();
			String scriptName;
			if (script == null) {
				scriptName = Messages.StackFrame_UnknownScriptName;
			} else {
				scriptName = VmResourceId.forScript(script).getVisibleName();
			}
			int line = stackFrame.getLineNumber();
			if (line != -1) {
				name = NLS.bind(Messages.StackFrame_NameFormat, new Object[] {
						name, scriptName, line });
			}
			return name;
		}
	};

	public ConnectedTargetData getConnectedTargetData() {
		return connectedTargetData;
	}
}
