/*
 * Copyright 2000-2015 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.vcs.log.ui.filter;

import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.vcs.log.*;
import com.intellij.vcs.log.impl.SingletonRefGroup;
import com.intellij.vcs.log.impl.VcsLogUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

public abstract class BranchPopupBuilder {
  @NotNull private final VcsLogDataPack myDataPack;
  @Nullable private final Collection<VirtualFile> myVisibleRoots;
  @Nullable private final List<List<String>> myRecentItems;

  protected BranchPopupBuilder(@NotNull VcsLogDataPack dataPack,
                               @Nullable Collection<VirtualFile> visibleRoots,
                               @Nullable List<List<String>> recentItems) {
    myDataPack = dataPack;
    myVisibleRoots = visibleRoots;
    myRecentItems = recentItems;
  }

  @NotNull
  protected abstract AnAction createAction(@NotNull String name);

  protected void createRecentAction(@NotNull DefaultActionGroup actionGroup, @NotNull List<String> recentItem) {
    assert myRecentItems == null;
  }

  @NotNull
  protected AnAction createCollapsedAction(String actionName) {
    return createAction(actionName);
  }

  public ActionGroup build() {
    return createActions(prepareGroups(myDataPack, myVisibleRoots, myRecentItems));
  }

  private static Groups prepareGroups(@NotNull VcsLogDataPack dataPack,
                                      @Nullable Collection<VirtualFile> visibleRoots,
                                      @Nullable List<List<String>> recentItems) {
    Groups filteredGroups = new Groups();
    Collection<VcsRef> allRefs = dataPack.getRefs().getBranches();
    for (Map.Entry<VirtualFile, Set<VcsRef>> entry : VcsLogUtil.groupRefsByRoot(allRefs).entrySet()) {
      VirtualFile root = entry.getKey();
      if (visibleRoots != null && !visibleRoots.contains(root)) continue;
      Collection<VcsRef> refs = entry.getValue();
      VcsLogProvider provider = dataPack.getLogProviders().get(root);
      VcsLogRefManager refManager = provider.getReferenceManager();
      List<RefGroup> refGroups = refManager.groupForBranchFilter(refs);

      putActionsForReferences(refGroups, filteredGroups);
    }

    if (recentItems != null) {
      for (List<String> recentItem : recentItems) {
        if (recentItem.size() == 1) {
          final String item = ContainerUtil.getFirstItem(recentItem);
          if (filteredGroups.singletonGroups.contains(item) ||
              ContainerUtil.find(filteredGroups.expandedGroups.values(), strings -> strings.contains(item)) != null) {
            continue;
          }
        }
        filteredGroups.recentGroups.add(recentItem);
      }
    }

    return filteredGroups;
  }

  private DefaultActionGroup createActions(@NotNull Groups groups) {
    DefaultActionGroup actionGroup = new DefaultActionGroup();
    for (String actionName : groups.singletonGroups) {
      actionGroup.add(createAction(actionName));
    }
    if (!groups.recentGroups.isEmpty()) {
      actionGroup.addSeparator("Recent");
      for (List<String> recentItem : groups.recentGroups) {
        createRecentAction(actionGroup, recentItem);
      }
    }
    for (Map.Entry<String, TreeSet<String>> group : groups.expandedGroups.entrySet()) {
      actionGroup.addSeparator(group.getKey());
      for (String actionName : group.getValue()) {
        actionGroup.add(createAction(actionName));
      }
    }
    actionGroup.addSeparator();
    for (Map.Entry<String, TreeSet<String>> group : groups.collapsedGroups.entrySet()) {
      DefaultActionGroup popupGroup = new DefaultActionGroup(group.getKey(), true);
      for (String actionName : group.getValue()) {
        popupGroup.add(createCollapsedAction(actionName));
      }
      actionGroup.add(popupGroup);
    }
    return actionGroup;
  }

  private static class Groups {
    private final TreeSet<String> singletonGroups = ContainerUtil.newTreeSet();
    private final List<List<String>> recentGroups = ContainerUtil.newArrayList();
    private final TreeMap<String, TreeSet<String>> expandedGroups = ContainerUtil.newTreeMap();
    private final TreeMap<String, TreeSet<String>> collapsedGroups = ContainerUtil.newTreeMap();
  }

  private static void putActionsForReferences(List<RefGroup> references, Groups actions) {
    for (final RefGroup refGroup : references) {
      if (refGroup instanceof SingletonRefGroup) {
        actions.singletonGroups.add(refGroup.getName());
      }
      else if (refGroup.isExpanded()) {
        addToGroup(refGroup, actions.expandedGroups);
      }
      else {
        addToGroup(refGroup, actions.collapsedGroups);
      }
    }
  }

  private static void addToGroup(final RefGroup refGroup, TreeMap<String, TreeSet<String>> groupToAdd) {
    TreeSet<String> groupActions = groupToAdd.computeIfAbsent(refGroup.getName(), key -> new TreeSet<>());
    for (VcsRef ref : refGroup.getRefs()) {
      groupActions.add(ref.getName());
    }
  }
}
