/*
 * Copyright 2003-2011 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 jetbrains.mps.plugins.actions;

import com.intellij.openapi.actionSystem.Shortcut;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapManager;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public abstract class BaseKeymapChanges {
  private static final Logger LOG = LogManager.getLogger(BaseKeymapChanges.class);

  private static Map<Keymap, Set<String>> ourClearedActions = new THashMap<>();
  private Map<String, Set<Shortcut>> myRemovedShortcuts = new THashMap<>();

  private Map<String, Set<Shortcut>> myAddedComplexShortcuts = new THashMap<>();

  private Map<String, Set<ShortcutWrapper>> mySimpleShortcuts = new THashMap<>();
  private Map<String, Set<ComplexShortcut>> myComplexShortcuts = new THashMap<>();

  private Keymap myKeymap;

  public abstract String getScheme();

  //shortId is an id w/o parameter ids
  public void parameterizedActionCreated(String shortId, String longId, Object... params) {
    Keymap keymap = getKeymap();
    if (keymap == null) {
      return;
    }

    Set<ComplexShortcut> complexShortcuts = myComplexShortcuts.get(shortId);
    if (complexShortcuts == null) {
      return;
    }

    for (ComplexShortcut cs : complexShortcuts) {
      for (Shortcut s : cs.getShortcutsFor(params)) {
        addShortcutToKeymap(longId, keymap, s);

        Set<Shortcut> added = myAddedComplexShortcuts.get(longId);
        if (added == null) {
          added = new THashSet<>();
          myAddedComplexShortcuts.put(longId, added);
        }
        added.add(s);
      }
    }
  }

  public final void init() {
    Keymap keymap = getKeymap();
    if (keymap == null) {
      return;
    }

    for (Entry<String, Set<ShortcutWrapper>> e : mySimpleShortcuts.entrySet()) {
      String key = e.getKey();
      for (ShortcutWrapper s : e.getValue()) {
        addShortcutToKeymap(key, keymap, s.myShortcut, s.myRemove, s.myReplaceAll);
      }
    }
  }

  public final void dispose() {
    Keymap keymap = getKeymap();
    if (keymap == null) {
      return;
    }

    //complex
    for (Entry<String, Set<Shortcut>> e : myAddedComplexShortcuts.entrySet()) {
      String key = e.getKey();
      for (Shortcut s : e.getValue()) {
        keymap.removeShortcut(key, s);
      }
    }
    myAddedComplexShortcuts.clear();

    //simple
    for (Entry<String, Set<ShortcutWrapper>> e : mySimpleShortcuts.entrySet()) {
      String key = e.getKey();
      for (ShortcutWrapper s : e.getValue()) {
        keymap.removeShortcut(key, s.myShortcut);
      }
    }
    mySimpleShortcuts.clear();

    //register old
    for (Entry<String, Set<Shortcut>> e : myRemovedShortcuts.entrySet()) {
      String key = e.getKey();
      for (Shortcut s : e.getValue()) {
        keymap.addShortcut(key, s);
      }
    }
    myRemovedShortcuts.clear();

    ourClearedActions.clear();
  }

  protected void addSimpleShortcut(String id, ShortcutWrapper... s) {
    Set<ShortcutWrapper> shortcuts = mySimpleShortcuts.get(id);
    if (shortcuts == null) {
      shortcuts = new THashSet<>();
      mySimpleShortcuts.put(id, shortcuts);
    }
    shortcuts.addAll(Arrays.asList(s));
  }

  protected void addSimpleShortcut(String id, Shortcut... s) {
    ShortcutWrapper[] sw = new ShortcutWrapper[s.length];
    for (int i = 0; i < s.length; i++) {
      sw[i] = new ShortcutWrapper(s[i]);
    }
    addSimpleShortcut(id, sw);
  }

  protected void addComplexShortcut(String id, ComplexShortcut... s) {
    Set<ComplexShortcut> shortcuts = myComplexShortcuts.get(id);
    if (shortcuts == null) {
      shortcuts = new THashSet<>();
      myComplexShortcuts.put(id, shortcuts);
    }
    shortcuts.addAll(Arrays.asList(s));
  }

  private void addShortcutToKeymap(String longId, Keymap keymap, Shortcut s) {
    addShortcutToKeymap(longId, keymap, s, false, false);
  }

  private void addShortcutToKeymap(String longId, Keymap keymap, Shortcut s, boolean remove, boolean replaceAll) {
    Shortcut[] oldShortcuts = keymap.getShortcuts(longId);

    boolean isClear = oldShortcuts.length == 0;
    if (!isClear) {
      for (Set<String> set : ourClearedActions.values()) {
        if (set.contains(longId)) {
          isClear = true;
          break;
        }
      }
    }

    if (!isClear) {
      if (oldShortcuts.length != 0) {
        myRemovedShortcuts.put(longId, new THashSet<>(Arrays.asList(oldShortcuts)));
        keymap.removeAllActionShortcuts(longId);
      }
    }

    Set<String> actions = ourClearedActions.get(keymap);
    if (actions == null) {
      actions = new THashSet<>();
      ourClearedActions.put(keymap, actions);
    }
    actions.add(longId);

    // Proceed as in class ActionManagerImpl in method processKeyboardShortcutNode
    if (remove) {
      keymap.removeShortcut(longId, s);
    }
    if (replaceAll) {
      keymap.removeAllActionShortcuts(longId);
    }
    if (!remove) {
      keymap.addShortcut(longId, s);
    }
  }

  private Keymap getKeymap() {
    if (myKeymap == null) {
      myKeymap = KeymapManager.getInstance().getKeymap(getScheme());
      if (myKeymap == null) {
        LOG.error("keymap " + getScheme() + " is not found");
        return null;
      }
    }
    return myKeymap;
  }

  protected static abstract class ComplexShortcut {
    public abstract List<Shortcut> getShortcutsFor(Object... params);

    public static class ParameterizedSimpleShortcut extends ComplexShortcut {
      private final List<Shortcut> myShortcut;

      public ParameterizedSimpleShortcut(Shortcut... shortcut) {
        myShortcut = Arrays.asList(shortcut);
      }

      @Override
      public List<Shortcut> getShortcutsFor(Object... params) {
        return myShortcut;
      }
    }
  }

  protected static class ShortcutWrapper {
    public final Shortcut myShortcut;
    public final boolean myRemove;
    public final boolean myReplaceAll;

    public ShortcutWrapper(Shortcut shortcut) {
      this(shortcut, false, false);
    }

    public ShortcutWrapper(Shortcut shortcut, boolean remove, boolean replaceAll) {
      myShortcut = shortcut;
      myRemove = remove;
      myReplaceAll = replaceAll;
    }
  }
}
