/*
 * Copyright 2003-2016 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jetbrains.mps.idea.java.trace;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.project.facets.JavaModuleFacet;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.textgen.trace.BaseLanguageNodeLookup;
import jetbrains.mps.textgen.trace.DefaultTraceInfoProvider;
import jetbrains.mps.textgen.trace.DebugInfo;
import jetbrains.mps.textgen.trace.NodeTraceInfo;
import jetbrains.mps.textgen.trace.TraceInfoCache;
import jetbrains.mps.textgen.trace.TraceablePositionInfo;
import jetbrains.mps.util.Computable;
import jetbrains.mps.util.NameUtil;
import jetbrains.mps.vfs.IFile;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SRepository;

import java.util.Optional;
import java.util.stream.Stream;

/**
 * Use this class for mapping debugger position (type, file, line number) to
 * some stuff as: node, psi file.
 */
public class GeneratedSourcePosition {
  private final SNodeReference myNode;
  private final String myTypeName;
  private final String myFileName;
  private final int myLineNumber;

  /*package*/ GeneratedSourcePosition(@Nullable SNodeReference nodeRef, String typeName, String fileName, int lineNumber) {
    myNode = nodeRef;
    myLineNumber = lineNumber;
    myFileName = fileName;
    myTypeName = typeName;
  }

  public String getTypeName() {
    return myTypeName;
  }

  public String getFileName() {
    return myFileName;
  }

  public int getLineNumber() {
    return myLineNumber;
  }

  @Nullable
  public SNodeReference getNode() {
    return myNode;
  }

  @Nullable
  public PsiFile getPsiFile(Project project) {
    return myNode == null ? null : getPsiFile(project, myNode.getModelReference(), myFileName);
  }

  @Nullable
  public static GeneratedSourcePosition fromNode(final SNode node) {
    NodeTraceInfo nti = new NodeTraceInfo(node, TraceInfoCache.getInstance().get(node.getModel()));
    TraceablePositionInfo position = nti.getPosition();
    if (position == null) {
      return null;
    }

    return new GeneratedSourcePosition(node.getReference(), nti.getUnitName(), nti.getFileName(), position.getStartLine());
  }

  public static GeneratedSourcePosition fromLocation(Project project, String unitName, String fileName, int line) {
    Optional<DebugInfo> di = new DefaultTraceInfoProvider(ProjectHelper.getProjectRepository(project)).debugInfo(NameUtil.namespaceFromLongName(unitName)).findFirst();
    return new GeneratedSourcePosition(!di.isPresent() ? null : new BaseLanguageNodeLookup(di.get()).getNodeAt(fileName, line), unitName, fileName, line);
  }


  @Nullable
  public static PsiFile getPsiFile(final Project project, final SModelReference modelReference, final String generatedFileName) {
    SRepository repository = ProjectHelper.getProjectRepository(project);
    final String fullPath = new ModelAccessHelper(repository.getModelAccess()).runReadAction(new Computable<String>() {
      @Override
      public String compute() {
        SModel modelDescriptor = modelReference.resolve(repository);
        SModule module = modelDescriptor.getModule();
        IFile defaultOutputDir = module.getFacet(JavaModuleFacet.class).getOutputLocation(modelDescriptor);
        IFile file = defaultOutputDir.getDescendant(generatedFileName);
        if (!file.exists()) {
          return null;
        }
        return file.getPath();
      }
    });

    if (fullPath == null) {
      return null;
    }

    return ApplicationManager.getApplication().runReadAction(new com.intellij.openapi.util.Computable<PsiFile>() {
      @Override
      public PsiFile compute() {
        VirtualFile file = LocalFileSystem.getInstance().findFileByPath(fullPath);
        if (file == null) {
          return null;
        }
        return PsiManager.getInstance(project).findFile(file);
      }
    });
  }
}
