/*
 * Copyright 2000-2014 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.favoritesTreeView.actions;

import com.intellij.ide.favoritesTreeView.FavoriteNodeProvider;
import com.intellij.ide.favoritesTreeView.FavoritesManager;
import com.intellij.ide.favoritesTreeView.FavoritesTreeViewPanel;
import com.intellij.ide.projectView.ProjectView;
import com.intellij.ide.projectView.ProjectViewNode;
import com.intellij.ide.projectView.ViewSettings;
import com.intellij.ide.projectView.impl.AbstractProjectViewPane;
import com.intellij.ide.projectView.impl.ModuleGroup;
import com.intellij.ide.projectView.impl.nodes.*;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

/**
 * User: anna
 * Date: Feb 15, 2005
 */
public class AddToFavoritesAction extends AnAction implements DumbAware {
  private static final Logger LOG = Logger.getInstance("com.intellij.ide.favoritesTreeView.actions.AddToFavoritesAction");

  private final String myFavoritesListName;

  public AddToFavoritesAction(String choosenList) {
    getTemplatePresentation().setText(choosenList, false);
    myFavoritesListName = choosenList;
  }

  @Override
  public void actionPerformed(AnActionEvent e) {
    final DataContext dataContext = e.getDataContext();

    Collection<AbstractTreeNode> nodesToAdd = getNodesToAdd(dataContext, true);

    if (!nodesToAdd.isEmpty()) {
      Project project = e.getProject();
      FavoritesManager.getInstance(project).addRoots(myFavoritesListName, nodesToAdd);
    }
  }

  @NotNull
  public static Collection<AbstractTreeNode> getNodesToAdd(final DataContext dataContext, final boolean inProjectView) {
    Project project = CommonDataKeys.PROJECT.getData(dataContext);

    if (project == null) return Collections.emptyList();

    Module moduleContext = LangDataKeys.MODULE_CONTEXT.getData(dataContext);

    Collection<AbstractTreeNode> nodesToAdd = null;
    for (FavoriteNodeProvider provider : Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, project)) {
      nodesToAdd = provider.getFavoriteNodes(dataContext, ViewSettings.DEFAULT);
      if (nodesToAdd != null) {
        break;
      }
    }

    if (nodesToAdd == null) {
      Object elements = collectSelectedElements(dataContext);
      if (elements != null) {
        nodesToAdd = createNodes(project, moduleContext, elements, inProjectView, ViewSettings.DEFAULT);
      }
    }
    return nodesToAdd == null ? Collections.<AbstractTreeNode>emptyList() : nodesToAdd;
  }

  @Override
  public void update(AnActionEvent e) {
    e.getPresentation().setEnabled(canCreateNodes(e, myFavoritesListName));
  }

  public static boolean canCreateNodes(AnActionEvent e) {
    return canCreateNodes(e, null);
  }

  public static boolean canCreateNodes(AnActionEvent e, @Nullable String listName) {
    DataContext dataContext = e.getDataContext();
    if (e.getProject() == null) {
      return false;
    }
    if (e.getPlace().equals(ActionPlaces.FAVORITES_VIEW_POPUP)
        && FavoritesTreeViewPanel.FAVORITES_LIST_NAME_DATA_KEY.getData(dataContext) == null) {
      return false;
    }
    final boolean inProjectView = e.getPlace().equals(ActionPlaces.J2EE_VIEW_POPUP) ||
                                  e.getPlace().equals(ActionPlaces.STRUCTURE_VIEW_POPUP) ||
                                  e.getPlace().equals(ActionPlaces.PROJECT_VIEW_POPUP);
    //com.intellij.openapi.actionSystem.ActionPlaces.USAGE_VIEW_TOOLBAR
    Collection<AbstractTreeNode> nodes = getNodesToAdd(dataContext, inProjectView);
    if (listName != null && !nodes.isEmpty()) {
      return FavoritesManager.getInstance(e.getProject()).canAddRoots(listName, nodes);
    }
    return !nodes.isEmpty();
  }

  static Object retrieveData(Object object, Object data) {
    return object == null ? data : object;
  }

  private static Object collectSelectedElements(final DataContext dataContext) {
    Object elements = retrieveData(null, CommonDataKeys.PSI_ELEMENT.getData(dataContext));
    elements = retrieveData(elements, LangDataKeys.PSI_ELEMENT_ARRAY.getData(dataContext));
    elements = retrieveData(elements, CommonDataKeys.PSI_FILE.getData(dataContext));
    elements = retrieveData(elements, ModuleGroup.ARRAY_DATA_KEY.getData(dataContext));
    elements = retrieveData(elements, LangDataKeys.MODULE_CONTEXT_ARRAY.getData(dataContext));
    elements = retrieveData(elements, LibraryGroupElement.ARRAY_DATA_KEY.getData(dataContext));
    elements = retrieveData(elements, NamedLibraryElement.ARRAY_DATA_KEY.getData(dataContext));
    elements = retrieveData(elements, CommonDataKeys.VIRTUAL_FILE.getData(dataContext));
    elements = retrieveData(elements, CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext));
    return elements;
  }

  public static
  @NotNull
  Collection<AbstractTreeNode> createNodes(Project project,
                                           Module moduleContext,
                                           Object object,
                                           boolean inProjectView,
                                           ViewSettings favoritesConfig) {
    if (project == null) return Collections.emptyList();
    ArrayList<AbstractTreeNode> result = new ArrayList<>();
    for (FavoriteNodeProvider provider : Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, project)) {
      final AbstractTreeNode treeNode = provider.createNode(project, object, favoritesConfig);
      if (treeNode != null) {
        result.add(treeNode);
        return result;
      }
    }
    final PsiManager psiManager = PsiManager.getInstance(project);

    final String currentViewId = ProjectView.getInstance(project).getCurrentViewId();
    AbstractProjectViewPane pane = ProjectView.getInstance(project).getProjectViewPaneById(currentViewId);


    //on psi elements
    if (object instanceof PsiElement[]) {
      for (PsiElement psiElement : (PsiElement[])object) {
        addPsiElementNode(psiElement, project, result, favoritesConfig);
      }
      return result;
    }

    //on psi element
    if (object instanceof PsiElement) {
      Module containingModule = null;
      if (inProjectView && ProjectView.getInstance(project).isShowModules(currentViewId)) {
        if (pane != null && pane.getSelectedDescriptor() != null && pane.getSelectedDescriptor().getElement() instanceof AbstractTreeNode) {
          AbstractTreeNode abstractTreeNode = ((AbstractTreeNode)pane.getSelectedDescriptor().getElement());
          while (abstractTreeNode != null && !(abstractTreeNode.getParent() instanceof AbstractModuleNode)) {
            abstractTreeNode = abstractTreeNode.getParent();
          }
          if (abstractTreeNode != null) {
            containingModule = ((AbstractModuleNode)abstractTreeNode.getParent()).getValue();
          }
        }
      }
      addPsiElementNode((PsiElement)object, project, result, favoritesConfig);
      return result;
    }

    if (object instanceof VirtualFile[]) {
      for (VirtualFile vFile : (VirtualFile[])object) {
        PsiElement element = psiManager.findFile(vFile);
        if (element == null) element = psiManager.findDirectory(vFile);
        addPsiElementNode(element,
                          project,
                          result,
                          favoritesConfig);
      }
      return result;
    }

    //on form in editor
    if (object instanceof VirtualFile) {
      final VirtualFile vFile = (VirtualFile)object;
      final PsiFile psiFile = psiManager.findFile(vFile);
      addPsiElementNode(psiFile, project, result, favoritesConfig);
      return result;
    }

    //on module groups
    if (object instanceof ModuleGroup[]) {
      for (ModuleGroup moduleGroup : (ModuleGroup[])object) {
        result.add(new ProjectViewModuleGroupNode(project, moduleGroup, favoritesConfig));
      }
      return result;
    }

    //on module nodes
    if (object instanceof Module) object = new Module[]{(Module)object};
    if (object instanceof Module[]) {
      for (Module module1 : (Module[])object) {
        result.add(new ProjectViewModuleNode(project, module1, favoritesConfig));
      }
      return result;
    }

    //on library group node
    if (object instanceof LibraryGroupElement[]) {
      for (LibraryGroupElement libraryGroup : (LibraryGroupElement[])object) {
        result.add(new LibraryGroupNode(project, libraryGroup, favoritesConfig));
      }
      return result;
    }

    //on named library node
    if (object instanceof NamedLibraryElement[]) {
      for (NamedLibraryElement namedLibrary : (NamedLibraryElement[])object) {
        result.add(new NamedLibraryElementNode(project, namedLibrary, favoritesConfig));
      }
      return result;
    }
    return result;
  }

  private static void addPsiElementNode(PsiElement psiElement,
                                        final Project project,
                                        final ArrayList<AbstractTreeNode> result,
                                        final ViewSettings favoritesConfig) {

    Class<? extends AbstractTreeNode> klass = getPsiElementNodeClass(psiElement);
    if (klass == null) {
      psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiFile.class);
      if (psiElement != null) {
        klass = PsiFileNode.class;
      }
    }

    final Object value = psiElement;
    try {
      if (klass != null && value != null) {
        result.add(ProjectViewNode.createTreeNode(klass, project, value, favoritesConfig));
      }
    }
    catch (Exception e) {
      LOG.error(e);
    }
  }


  private static Class<? extends AbstractTreeNode> getPsiElementNodeClass(PsiElement psiElement) {
    Class<? extends AbstractTreeNode> klass = null;
    if (psiElement instanceof PsiFile) {
      klass = PsiFileNode.class;
    }
    else if (psiElement instanceof PsiDirectory) {
      klass = PsiDirectoryNode.class;
    }
    return klass;
  }
}
