// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.debug.core.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.BreakpointSynchronizer.Callback;
import org.chromium.debug.core.model.ChromiumLineBreakpoint.MutableProperty;
import org.chromium.debug.core.model.VmResource.Metadata;
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.DebugException;
import org.eclipse.debug.core.IBreakpointManager;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.osgi.util.NLS;

/**
 * Virtual project-supporting implementation of {@link WorkspaceBridge}.
 */
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 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());
  }

  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() {
    if (debugProject != null) {
      ChromiumDebugPluginUtil.deleteVirtualProjectAsync(debugProject);
    }
  }

  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;
  }
}
