/*
 * Copyright 2000-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 com.intellij.ide.impl;

import com.intellij.ide.SelectInContext;
import com.intellij.ide.SelectInTarget;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public abstract class SelectInTargetPsiWrapper implements SelectInTarget {
  protected final Project myProject;

  protected SelectInTargetPsiWrapper(@NotNull final Project project) {
    myProject = project;
  }

  public abstract String toString();

  protected abstract boolean canSelect(PsiFileSystemItem file);

  @Override
  public final boolean canSelect(@NotNull SelectInContext context) {
    if (!isContextValid(context)) return false;

    return canWorkWithCustomObjects() || canSelectInner(context);
  }

  protected boolean canSelectInner(@NotNull SelectInContext context) {
    PsiFileSystemItem psiFile = getContextPsiFile(context);
    return psiFile != null && canSelect(psiFile);
  }

  private boolean isContextValid(SelectInContext context) {
    if (myProject.isDisposed()) return false;

    VirtualFile virtualFile = context.getVirtualFile();
    return virtualFile.isValid();
  }

  @Nullable
  protected PsiFileSystemItem getContextPsiFile(@NotNull SelectInContext context) {
    VirtualFile virtualFile = context.getVirtualFile();
    PsiFileSystemItem psiFile = PsiManager.getInstance(myProject).findFile(virtualFile);
    if (psiFile != null) {
      return psiFile;
    }

    if (context.getSelectorInFile() instanceof PsiFile) {
      return (PsiFile)context.getSelectorInFile();
    }
    if (virtualFile.isDirectory()) {
      return PsiManager.getInstance(myProject).findDirectory(virtualFile);
    }
    return null;
  }

  @Override
  public final void selectIn(@NotNull SelectInContext context, boolean requestFocus) {
    VirtualFile file = context.getVirtualFile();
    Object selector = context.getSelectorInFile();
    if (selector == null) {
      PsiManager psiManager = PsiManager.getInstance(myProject);
      selector = file.isDirectory() ? psiManager.findDirectory(file) : psiManager.findFile(file);
    }

    if (selector instanceof PsiElement) {
      select(((PsiElement)selector).getOriginalElement(), requestFocus);
    }
    else {
      select(selector, file, requestFocus);
    }
  }

  protected abstract void select(Object selector, VirtualFile virtualFile, boolean requestFocus);

  protected abstract boolean canWorkWithCustomObjects();

  protected abstract void select(PsiElement element, boolean requestFocus);

  @Nullable
  protected static PsiElement findElementToSelect(PsiElement element, PsiElement candidate) {
    PsiElement toSelect = candidate;

    if (toSelect == null) {
      if (element instanceof PsiFile || element instanceof PsiDirectory) {
        toSelect = element;
      }
      else {
        PsiFile containingFile = element.getContainingFile();
        if (containingFile != null) {
          FileViewProvider viewProvider = containingFile.getViewProvider();
          toSelect = viewProvider.getPsi(viewProvider.getBaseLanguage());
        }
      }
    }

    if (toSelect != null) {
      PsiElement originalElement = null;
      try {
        originalElement = toSelect.getOriginalElement();
      }
      catch (IndexNotReadyException ignored) { }
      if (originalElement != null) {
        toSelect = originalElement;
      }
    }

    return toSelect;
  }
}