/*
 * 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 org.jetbrains.idea.svn.branchConfig;

import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.*;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.components.JBList;
import com.intellij.util.NullableFunction;
import com.intellij.util.continuation.ModalityIgnorantBackgroundableTask;
import com.intellij.util.text.DateFormatUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.RootUrlInfo;
import org.jetbrains.idea.svn.SvnBundle;
import org.jetbrains.idea.svn.SvnFileUrlMapping;
import org.jetbrains.idea.svn.SvnVcs;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;

import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * @author yole
 */
public class SelectBranchPopup {
  private final static String CONFIGURE_MESSAGE = SvnBundle.message("configure.branches.item");

  private SelectBranchPopup() {
  }

  public interface BranchSelectedCallback {
    void branchSelected(Project project, SvnBranchConfigurationNew configuration, String url, long revision);
  }

  public static void show(Project project, VirtualFile file, BranchSelectedCallback callback, final String title) {
    show(project, file, callback, title, null);
  }

  public static void show(Project project, VirtualFile file, BranchSelectedCallback callback, final String title, final Component component) {
    final SvnFileUrlMapping urlMapping = SvnVcs.getInstance(project).getSvnFileUrlMapping();
    final SVNURL svnurl = urlMapping.getUrlForFile(new File(file.getPath()));
    if (svnurl == null) {
      return;
    }
    final RootUrlInfo rootUrlInfo = urlMapping.getWcRootForUrl(svnurl.toString());
    if (rootUrlInfo == null) {
      return;
    }

    // not vcs root but wc root is ok
    showForBranchRoot(project, rootUrlInfo.getVirtualFile(), callback, title, component);
  }

  public static void showForBranchRoot(Project project, VirtualFile vcsRoot, BranchSelectedCallback callback, final String title) {
    showForBranchRoot(project, vcsRoot, callback, title, null);
  }

  public static void showForBranchRoot(Project project, VirtualFile vcsRoot, BranchSelectedCallback callback, final String title,
                                       final Component component) {
    final SvnBranchConfigurationNew configuration = SvnBranchConfigurationManager.getInstance(project).get(vcsRoot);
    final List<String> items = new ArrayList<>();
    if (! StringUtil.isEmptyOrSpaces(configuration.getTrunkUrl())) {
      items.add(getTrunkString(configuration));
    }
    for (String url : configuration.getBranchUrls()) {
      items.add(url);
    }
    items.add(CONFIGURE_MESSAGE);

    BranchBasesPopupStep step = new BranchBasesPopupStep(project, vcsRoot, configuration, callback, items, title, component);
    final ListPopup listPopup = JBPopupFactory.getInstance().createListPopup(step);
    step.showPopupAt(listPopup);
  }
  
  private static String getTrunkString(final SvnBranchConfigurationNew configuration) {
    return configuration.getTrunkUrl() + " (trunk)";
  }

  @NotNull
  private static String getBranchName(@NotNull SvnBranchItem branch) {
    return SVNPathUtil.tail(branch.getUrl());
  }

  private static class BranchBasesPopupStep extends BaseListPopupStep<String> {
    protected final Project myProject;
    private final VirtualFile myVcsRoot;
    private final SvnBranchConfigurationNew myConfiguration;
    private final boolean myTopLevel;
    private BranchSelectedCallback myCallback;
    private final Component myComponent;

    private static final String REFRESH_MESSAGE = SvnBundle.message("refresh.branches.item");
    private String myTrunkString;

    protected BranchBasesPopupStep(final Project project,
                                   final VirtualFile vcsRoot,
                                   final SvnBranchConfigurationNew configuration,
                                   boolean topLevel,
                                   final BranchSelectedCallback callback,
                                   Component component) {
      myProject = project;
      myVcsRoot = vcsRoot;
      myConfiguration = configuration;
      myTrunkString = getTrunkString(configuration);
      myTopLevel = topLevel;
      myCallback = callback;
      myComponent = component;
    }

    public BranchBasesPopupStep(final Project project, final VirtualFile vcsRoot,
                                final SvnBranchConfigurationNew configuration,
                                final BranchSelectedCallback callback,
                                final List<String> items,
                                final String title,
                                Component component) {
      this(project, vcsRoot, configuration, true, callback, component);
      init(title, items, null);
    }

    @Override
    public ListSeparator getSeparatorAbove(final String value) {
      return (CONFIGURE_MESSAGE.equals(value)) ||
             (REFRESH_MESSAGE.equals(value)) ? new ListSeparator("") : null;
    }

    @NotNull
    @Override
    public String getTextFor(final String value) {
      int pos = value.lastIndexOf('/');
      if (pos < 0) {
        return value;
      }
      if (myTopLevel && ((myConfiguration.getTrunkUrl() == null) || (! value.startsWith(myConfiguration.getTrunkUrl())))) {
        return value.substring(pos+1) + "...";
      }
      return value.substring(pos+1);
    }

    @Override
    public boolean hasSubstep(final String selectedValue) {
      return false;
    }

    @Override
    public PopupStep onChosen(final String selectedValue, final boolean finalChoice) {
      if (CONFIGURE_MESSAGE.equals(selectedValue)) {
        return doFinalStep(new Runnable() {
          public void run() {
            BranchConfigurationDialog.configureBranches(myProject, myVcsRoot);
          }
        });
      }
      else if (myTrunkString.equals(selectedValue)) {
        return doFinalStep(new Runnable() {
          public void run() {
            myCallback.branchSelected(myProject, myConfiguration, myConfiguration.getTrunkUrl(), -1);
          }
        });
      }
      else if (!myTopLevel || selectedValue.equals(myConfiguration.getTrunkUrl())) {
        return doFinalStep(new Runnable() {
          public void run() {
            myCallback.branchSelected(myProject, myConfiguration, selectedValue, -1);
          }
        });
      }
      else {
        showBranchPopup(selectedValue);
      }
      return FINAL_CHOICE;
    }

    @Nullable
    private void loadBranches(final String selectedBranchesHolder, final Runnable runnable) {
      final ProgressManager pm = ProgressManager.getInstance();
      pm.run(new ModalityIgnorantBackgroundableTask(myProject, SvnBundle.message("compare.with.branch.progress.loading.branches")) {
        @Override
        protected void doInAwtIfFail(Exception e) {
          runnable.run();
        }

        @Override
        protected void doInAwtIfCancel() {
          runnable.run();
        }

        @Override
        protected void doInAwtIfSuccess() {
          runnable.run();
        }

        @Override
        protected void runImpl(@NotNull ProgressIndicator indicator) {
          final NewRootBunch manager = SvnBranchConfigurationManager.getInstance(myProject).getSvnBranchConfigManager();

          manager.reloadBranches(myVcsRoot, selectedBranchesHolder, InfoReliability.setByUser, false);
        }
      });
    }

    private void showBranchPopup(final String selectedValue) {
      List<SvnBranchItem> branches = myConfiguration.getBranches(selectedValue);
      if (branches == null) {
        return;
      }

      final Object[] items = new Object[branches.size() + 1];
      System.arraycopy(branches.toArray(), 0, items, 0, branches.size());
      items[items.length - 1] = REFRESH_MESSAGE;

      final JList branchList = new JBList(items);
      branchList.setCellRenderer(new BranchRenderer());
      final JBPopup popup = JBPopupFactory.getInstance().createListPopupBuilder(branchList)
        .setTitle(SVNPathUtil.tail(selectedValue))
        .setResizable(true)
        //.setDimensionServiceKey("Svn.CompareWithBranchPopup")
        .setItemChoosenCallback(new Runnable() {
          public void run() {
            if (REFRESH_MESSAGE.equals(branchList.getSelectedValue())) {
              SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                  loadBranches(selectedValue, new Runnable() {
                    @Override
                    public void run() {
                      showBranchPopup(selectedValue);
                    }
                  });
                }
              });
              return;
            }
            SvnBranchItem item = (SvnBranchItem)branchList.getSelectedValue();
            if (item != null) {
              myCallback.branchSelected(myProject, myConfiguration, item.getUrl(), item.getRevision());
            }
          }
        })
        .setFilteringEnabled(new NullableFunction<Object, String>() {
          @Nullable
          @Override
          public String fun(Object item) {
            return item instanceof SvnBranchItem ? getBranchName((SvnBranchItem)item) : null;
          }
        })
        .createPopup();
      showPopupAt(popup);
    }

    public void showPopupAt(final JBPopup listPopup) {
      if (myComponent == null) {
        listPopup.showCenteredInCurrentWindow(myProject);
      } else {
        listPopup.showInCenterOf(myComponent);
      }
    }
  }

  private static class BranchRenderer extends JPanel implements ListCellRenderer {
    private final JLabel myUrlLabel = new JLabel();
    private final JLabel myDateLabel = new JLabel();

    public BranchRenderer() {
      super(new BorderLayout());
      add(myUrlLabel, BorderLayout.WEST);
      add(myDateLabel, BorderLayout.EAST);
      myUrlLabel.setBorder(JBUI.Borders.empty(1));
      myDateLabel.setHorizontalAlignment(SwingConstants.RIGHT);
      myDateLabel.setBorder(JBUI.Borders.empty(1));
      myDateLabel.setForeground(UIUtil.getInactiveTextColor());
    }

    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
      if (isSelected || cellHasFocus) {
        setBackground(UIUtil.getListSelectionBackground());
        final Color selectedForegroundColor = UIUtil.getListSelectionForeground();
        myUrlLabel.setForeground(selectedForegroundColor);
        myDateLabel.setForeground(selectedForegroundColor);
        setForeground(selectedForegroundColor);
      }
      else {
        setBackground(UIUtil.getListBackground());
        final Color foregroundColor = UIUtil.getListForeground();
        myUrlLabel.setForeground(foregroundColor);
        myDateLabel.setForeground(UIUtil.getInactiveTextColor());
        setForeground(foregroundColor);
      }
      if (value instanceof String) {
        myUrlLabel.setText((String) value);
        myDateLabel.setText("");
      } else {
        SvnBranchItem item = (SvnBranchItem) value;
        myUrlLabel.setText(getBranchName(item));
        final long creationMillis = item.getCreationDateMillis();
        myDateLabel.setText((creationMillis > 0) ? DateFormatUtil.formatDate(creationMillis) : "");
      }
      return this;
    }
  }
}
