// Copyright (c) 2010 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;

import org.chromium.debug.core.model.VmResourceId;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.sourcelookup.ISourceContainer;
import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
import org.eclipse.debug.core.sourcelookup.containers.ContainerSourceContainer;
import org.eclipse.debug.core.sourcelookup.containers.DefaultSourceContainer;

/**
 * Eclipse has a standard facility for looking up source file for a debug artifact.
 * LiveEdit feature has an opposite problem: find script in remote VM for a particular js file.
 * This class implements some approach to this problem. An instance of this class corresponds
 * to a particular debug launch.
 */
public class ReverseSourceLookup {
  private final ISourceLookupDirector sourceDirector;

  public ReverseSourceLookup(ISourceLookupDirector sourceDirector) {
    this.sourceDirector = sourceDirector;
  }

  /**
   * Tries to find a corresponding script for a file from a user workspace. The method uses
   * the file name and current source lookup rules to retrieve a resource id, regardless of
   * whether the resource has actually been loaded into the VM (you may want to set a breakpoint
   * on resource before it is actually loaded).
   */
  public VmResourceId findVmResource(IFile sourceFile) throws CoreException {
    for (ISourceContainer container : sourceDirector.getSourceContainers()) {
      VmResourceId scriptName = tryForContainer(sourceFile, container);
      if (scriptName != null) {
        return scriptName;
      }
    }
    return null;
  }

  private VmResourceId tryForContainer(IFile sourceFile, ISourceContainer container)
      throws CoreException {
    if (container.isComposite() && isSupportedCompositeContainer(container)) {
      ISourceContainer[] subContainers = container.getSourceContainers();
      for (ISourceContainer subContainer : subContainers) {
        VmResourceId res = tryForContainer(sourceFile, subContainer);
        if (res != null) {
          return res;
        }
      }
      return null;
    } else if (container instanceof VProjectSourceContainer) {
      VProjectSourceContainer projectSourceContainer = (VProjectSourceContainer) container;
      return projectSourceContainer.findScriptId(sourceFile);
    } else {
      String name = tryForNonVirtualContainer(sourceFile, container);
      if (name == null) {
        return null;
      }
      return VmResourceId.forName(name);
    }
  }

  /**
   * We use {@link ISourceContainer#getSourceContainers()} method to unwrap internal containers.
   * However it doesn't make sense for all composite containers (some of them may return their
   * subdirectories as containers, which is not what we need).
   */
  private boolean isSupportedCompositeContainer(ISourceContainer container) {
    return container instanceof DefaultSourceContainer;
  }

  /**
   * @param container that may not wrap VProjectSourceContainer
   */
  private String tryForNonVirtualContainer(IFile resource, ISourceContainer container) {
    if (container instanceof ContainerSourceContainer) {
      ContainerSourceContainer containerSourceContainer = (ContainerSourceContainer) container;
      IContainer resourceContainer = containerSourceContainer.getContainer();
      if (resourceContainer.getFullPath().isPrefixOf(resource.getFullPath())) {
        String name = resource.getFullPath().makeRelativeTo(
            resourceContainer.getFullPath()).toPortableString();
        return name;
      }
    } else if (container instanceof SourceNameMapperContainer) {
      SourceNameMapperContainer mappingContainer =
          (SourceNameMapperContainer) container;
      String subResult = tryForNonVirtualContainer(resource, mappingContainer.getTargetContainer());
      if (subResult != null) {
        return mappingContainer.getPrefix() + subResult;
      }
    }

    return null;
  }
}
