/*
 * Copyright 2000-2017 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.config;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonShortcuts;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.ui.MasterDetailsComponent;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Ref;
import com.intellij.util.IconUtil;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.svn.SvnBundle;
import org.jetbrains.idea.svn.SvnServerFileManager;

import javax.swing.*;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.event.KeyEvent;
import java.util.*;

public class SvnConfigureProxiesComponent extends MasterDetailsComponent {
  private final SvnServerFileManager myManager;

  private final CompositeRunnable myTreeUpdaterValidator;
  private final Runnable myValidator;
  private JComponent myComponent;
  private final TestConnectionPerformer myTestConnectionPerformer;
  private ConfigureProxiesOptionsPanel myDefaultGroupPanel;

  public SvnConfigureProxiesComponent(final SvnServerFileManager manager, final GroupsValidator validator, final TestConnectionPerformer testConnectionPerformer) {
    myTestConnectionPerformer = testConnectionPerformer;
    myValidator = validator;
    myTreeUpdaterValidator = new CompositeRunnable(TREE_UPDATER, myValidator);
    initTree();
    myManager = manager;
    fillTree();
    validator.add(this);
  }

  @NotNull
  public JComponent createComponent() {
    if (myComponent == null) {
      myComponent = super.createComponent();
    }
    return myComponent;
  }

  protected void processRemovedItems() {
    // not used
  }

  protected boolean wasObjectStored(final Object editableObject) {
    return false;
  }

  public String getDisplayName() {
    return "HTTP proxies configuration";
  }

  public String getHelpTopic() {
    return null;
  }

  private String getNewName() {
    return "Unnamed";
  }

  private void addGroup(final ProxyGroup template) {
    final ProxyGroup group;
    if (template == null) {
      group = new ProxyGroup(getNewName(), "", ContainerUtil.<String, String>newHashMap());
    } else {
      group = new ProxyGroup(getNewName(), template.getPatterns(), template.getProperties());
    }
    
    addNode(createNodeForObject(group), myRoot);
    selectNodeInTree(group);
  }

  public List<String> getGlobalGroupRepositories(final Collection<String> all) {
    // i.e. all-[all used]
    final List<String> result = new LinkedList<>(all);

    for (int i = 0; i < myRoot.getChildCount(); i++) {
      final MyNode node = (MyNode) myRoot.getChildAt(i);
      final GroupConfigurable groupConfigurable = (GroupConfigurable) node.getConfigurable();
      if (! groupConfigurable.getEditableObject().isDefault()) {
        result.removeAll(groupConfigurable.getRepositories());
      }
    }

    return result;
  }

  public boolean validate(final ValidationListener listener) {
    final Ref<String> errorMessageRef = new Ref<>();
    final Set<String> checkSet = new HashSet<>();
    final AmbiguousPatternsFinder ambiguousPatternsFinder = new AmbiguousPatternsFinder();
    
    for (int i = 0; i < myRoot.getChildCount(); i++) {
      final MyNode node = (MyNode) myRoot.getChildAt(i);
      final GroupConfigurable groupConfigurable = (GroupConfigurable) node.getConfigurable();
      final String groupName = groupConfigurable.getEditableObject().getName();

      if (checkSet.contains(groupName)) {
        listener.onError(SvnBundle.message("dialog.edit.http.proxies.settings.error.same.group.names.text", groupName), myComponent, true);
        return false;
      }
      checkSet.add(groupName);
    }

    for (int i = 0; i < myRoot.getChildCount(); i++) {
      final MyNode node = (MyNode) myRoot.getChildAt(i);
      final GroupConfigurable groupConfigurable = (GroupConfigurable) node.getConfigurable();
      groupConfigurable.applyImpl();
      if(! groupConfigurable.validate(errorMessageRef)) {
        listener.onError(errorMessageRef.get(), myComponent, false);
        return false;
      }

      if (! groupConfigurable.getEditableObject().isDefault()) {
        final String groupName = groupConfigurable.getEditableObject().getName();
        final List<String> urls = groupConfigurable.getRepositories();
        ambiguousPatternsFinder.acceptUrls(groupName, urls);
      }
    }

    if(! ambiguousPatternsFinder.isValid(errorMessageRef)) {
      listener.onError(errorMessageRef.get(), myComponent, false);
      return false;
    }
    return true;
  }

  protected ArrayList<AnAction> createActions(final boolean fromPopup) {
    ArrayList<AnAction> result = new ArrayList<>();
    result.add(new AnAction("Add", "Add", IconUtil.getAddIcon()) {
        {
            registerCustomShortcutSet(CommonShortcuts.INSERT, myTree);
        }
        public void actionPerformed(AnActionEvent event) {
          addGroup(null);
        }


    });
    result.add(new MyDeleteAction(forAll(new Condition<Object>(){
      public boolean value(final Object o) {
        if (o instanceof MyNode) {
          final MyNode node = (MyNode) o;
          if (node.getConfigurable() instanceof GroupConfigurable) {
            final ProxyGroup group = ((GroupConfigurable) node.getConfigurable()).getEditableObject();
            return ! group.isDefault();
          }
        }
        return false;
      }
    })) {
      public void actionPerformed(final AnActionEvent e) {
        final TreePath path = myTree.getSelectionPath();
        final MyNode node = (MyNode)path.getLastPathComponent();
        final MyNode parentNode = (MyNode) node.getParent();
        int idx = parentNode.getIndex(node);

        super.actionPerformed(e);

        idx = (idx == parentNode.getChildCount()) ? idx - 1 : idx;
        if (parentNode.getChildCount() > 0) {
          final TreePath newSelectedPath = new TreePath(parentNode.getPath()).pathByAddingChild(parentNode.getChildAt(idx));
          myTree.setSelectionPath(newSelectedPath);
        }
      }
    });

    result.add(new AnAction("Copy", "Copy", PlatformIcons.COPY_ICON) {
        {
            registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.CTRL_MASK)), myTree);
        }
        public void actionPerformed(AnActionEvent event) {
          // apply - for update of editable object
          try {
            getSelectedConfigurable().apply();
          } catch (ConfigurationException e) {
            // suppress & wait for OK
          }
          final ProxyGroup selectedGroup = (ProxyGroup) getSelectedObject();
          if (selectedGroup != null) {
            addGroup(selectedGroup);
          }
        }

        public void update(AnActionEvent event) {
            super.update(event);
            event.getPresentation().setEnabled(getSelectedObject() != null);
        }
    });
    return result;
  }

  public void apply() throws ConfigurationException {
    final List<ProxyGroup> groups = new ArrayList<>(myRoot.getChildCount());

    for (int i = 0; i < myRoot.getChildCount(); i++) {
      final MyNode node = (MyNode) myRoot.getChildAt(i);
      final GroupConfigurable groupConfigurable = (GroupConfigurable) node.getConfigurable();
      groupConfigurable.apply();
      groups.add(groupConfigurable.getEditableObject());
    }

    myManager.updateUserServerFile(groups);
  }

  public void reset() {
    super.reset();
    myManager.updateFromFile();
    
    for (int i = 0; i < myRoot.getChildCount(); i++) {
      final MyNode node = (MyNode) myRoot.getChildAt(i);
      final GroupConfigurable groupConfigurable = (GroupConfigurable) node.getConfigurable();
      groupConfigurable.reset();
    }
  }

  private MyNode createNodeForObject(final ProxyGroup group) {
    final ConfigureProxiesOptionsPanel panel = new ConfigureProxiesOptionsPanel(myValidator, myTestConnectionPerformer);
    final GroupConfigurable gc = new GroupConfigurable(group, myTreeUpdaterValidator, panel);
    if (group.isDefault()) {
      myDefaultGroupPanel = panel;
    }

    // first added node must be global /default group. since url-fillers for other groups forwards their updates to global group
    assert myDefaultGroupPanel != null;
    panel.setPatternsListener((group.isDefault()) ? PatternsListener.Empty.instance :
                              new RepositoryUrlFilter(panel, this, myDefaultGroupPanel));

    return new MyNode(gc, group.isDefault());
  }

  private void fillTree() {
    myRoot.removeAllChildren();

    DefaultProxyGroup defaultProxyGroup = myManager.getDefaultGroup();
    defaultProxyGroup = (defaultProxyGroup == null) ? new DefaultProxyGroup(Collections.<String, String>emptyMap()) : defaultProxyGroup;
    final Map<String, ProxyGroup> userGroups = myManager.getGroups();

    myRoot.add(createNodeForObject(defaultProxyGroup));
    for (Map.Entry<String, ProxyGroup> entry : userGroups.entrySet()) {
      myRoot.add(createNodeForObject(entry.getValue()));
    }

    TreeUtil.sortRecursively(myRoot, GroupNodesComparator.getInstance());
    ((DefaultTreeModel) myTree.getModel()).reload(myRoot);
  }

  @Override
  protected Comparator<MyNode> getNodeComparator() {
    return GroupNodesComparator.getInstance();
  }

  private static class GroupNodesComparator implements Comparator<MyNode> {
    private final static GroupNodesComparator instance = new GroupNodesComparator();

    private static GroupNodesComparator getInstance() {
      return instance;
    }

    public int compare(final MyNode node1, final MyNode node2) {
      if ((node1.getConfigurable() instanceof GroupConfigurable) && (node2.getConfigurable() instanceof GroupConfigurable)) {
        final ProxyGroup group1 = ((GroupConfigurable) node1.getConfigurable()).getEditableObject();
        final ProxyGroup group2 = ((GroupConfigurable) node2.getConfigurable()).getEditableObject();
        if (group1.isDefault()) {
          return -1;
        }
        if (group2.isDefault()) {
          return 1;
        }
      }

      return node1.getDisplayName().compareToIgnoreCase(node2.getDisplayName());
    }
  }

  /**
   * total component made invalid => for test connection to be forbidden when for ex. ambiguous settings given
   */
  public void setIsValid(final boolean valid) {
    for (int i = 0; i < myRoot.getChildCount(); i++) {
      final MyNode node = (MyNode) myRoot.getChildAt(i);
      final GroupConfigurable groupConfigurable = (GroupConfigurable) node.getConfigurable();
      groupConfigurable.setIsValid(valid);
    }
  }
}
