/*
 * Copyright (c) 2019 Patrick Scheibe, Dmitry Kashin, Athiele.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package de.halirutan.keypromoterx;

import com.intellij.application.Topics;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.ex.AnActionListener;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.impl.StripeButton;
import de.halirutan.keypromoterx.statistic.KeyPromoterStatistics;
import org.jetbrains.annotations.NotNull;

import java.awt.*;
import java.awt.event.AWTEventListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * The main component that is registered in plugin.xml. It will take care of catching UI events
 * and transfers the them to {@link KeyPromoterAction} for inspection. Depending on the type of action (tool-window button,
 * menu entry, etc.) a balloon is shown and the statistic is updated.
 *
 * @author Patrick Scheibe, Dmitry Kashin
 */
public class KeyPromoter implements AWTEventListener, AnActionListener, Disposable {

  private final Map<String, Integer> withoutShortcutStats = Collections.synchronizedMap(new HashMap<>());
  private final KeyPromoterStatistics statsService = ServiceManager.getService(KeyPromoterStatistics.class);
  // Presentation and stats fields.
  private final KeyPromoterSettings keyPromoterSettings = ServiceManager.getService(KeyPromoterSettings.class);
  private static final String distractionFreeModeKey = "editor.distraction.free.mode";
  private long lastEventTime = -1;


  public KeyPromoter() {
    Topics.subscribe(AnActionListener.TOPIC, this, this);
    long eventMask = AWTEvent.MOUSE_EVENT_MASK | AWTEvent.WINDOW_EVENT_MASK | AWTEvent.WINDOW_STATE_EVENT_MASK;
    Toolkit.getDefaultToolkit().addAWTEventListener(this, eventMask);
  }

  @Override
  public void dispose() {
    Toolkit.getDefaultToolkit().removeAWTEventListener(this);
  }

  /**
   * Catches all UI events from the main IDEA AWT making it possible to inspect all mouse-clicks.
   * Note that on OSX this will not catch clicks on the (detached) menu bar.
   *
   * @param e event that is caught
   */
  @Override
  public void eventDispatched(AWTEvent e) {
    if (e.getID() == MouseEvent.MOUSE_RELEASED && ((MouseEvent) e).getButton() == MouseEvent.BUTTON1) {
      handleMouseEvent(e);
    }
  }

  /**
   * Transfers the event to {@link KeyPromoterAction} and inspects the results. Then, depending on the result and the
   * Key Promoter X settings, a balloon is shown with the shortcut tip and the statistic is updated.
   *
   * @param e event that is handled
   */
  private void handleMouseEvent(AWTEvent e) {
    if (e.getSource() instanceof StripeButton && keyPromoterSettings.isToolWindowButtonsEnabled()) {
      KeyPromoterAction action = new KeyPromoterAction(e);
      showTip(action);
    }
  }

  @Override
  public void beforeActionPerformed(@NotNull AnAction action, @NotNull DataContext dataContext, AnActionEvent event) {
    final InputEvent input = event.getInputEvent();
    if (input instanceof MouseEvent) {
      // The following is a hack to work around an issue with IDEA, where certain events arrive
      // twice. See https://youtrack.jetbrains.com/issue/IDEA-219133
      if (input.getWhen() != 0 && lastEventTime == input.getWhen()) {
        return;
      }
      lastEventTime = input.getWhen();

      final String place = event.getPlace();
      KeyPromoterAction kpAction;
      if ("MainMenu".equals(place)) {
        if (keyPromoterSettings.isMenusEnabled()) {
          kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.MENU_ENTRY);
          showTip(kpAction);
        }
      } else if ("MainToolbar".equals(place)) {
        if (keyPromoterSettings.isToolbarButtonsEnabled()) {
          kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.MAIN_TOOLBAR);
          showTip(kpAction);
        }
      } else if (place.matches(".*Popup")) {
        if (keyPromoterSettings.isEditorPopupEnabled()) {
          kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.POPUP);
          showTip(kpAction);
        }
      } else if (keyPromoterSettings.isAllButtonsEnabled()) {
        kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.OTHER);
        showTip(kpAction);
      }
    }
  }

  private boolean disabledInPresentationMode() {
    boolean isPresentationMode = UISettings.getInstance().getPresentationMode();
    return isPresentationMode && keyPromoterSettings.isDisabledInPresentationMode();
  }

  private boolean disabledInDistractionFreeMode() {
    final boolean isDistractionFreeMode = Registry.get(distractionFreeModeKey).asBoolean();
    return isDistractionFreeMode && keyPromoterSettings.isDisabledInDistractionFreeMode();
  }

  private void showTip(KeyPromoterAction action) {
    if (action == null
        || !action.isValid()
        || statsService.isSuppressed(action)
        || disabledInPresentationMode()
        || disabledInDistractionFreeMode()
        || SnoozeNotifier.isSnoozed()
    ) { return; }

    final String shortcut = action.getShortcut();
    if (!StringUtil.isEmpty(shortcut)) {
      statsService.registerAction(action);
      int count = statsService.get(action).count;
      if (count % keyPromoterSettings.getShowTipsClickCount() == 0) {
        KeyPromoterNotification.showTip(action, statsService.get(action).getCount());
      }

    } else {
      final String ideaActionID = action.getIdeaActionID();
      withoutShortcutStats.putIfAbsent(ideaActionID, 0);
      withoutShortcutStats.put(ideaActionID, withoutShortcutStats.get(ideaActionID) + 1);
      if (keyPromoterSettings.getProposeToCreateShortcutCount() > 0
          &&
          withoutShortcutStats.get(ideaActionID) % keyPromoterSettings.getProposeToCreateShortcutCount() == 0) {
        KeyPromoterNotification.askToCreateShortcut(action);

      }
    }
  }
}