// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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.eclipse.che.ide.util.dom;

import elemental.client.Browser;
import elemental.events.Event;
import elemental.events.EventListener;
import elemental.events.EventTarget;
import java.util.ArrayList;
import java.util.List;

/** Utility class which allows simulation of Event Capture. */
public class MouseEventCapture {
  private interface Remover {
    public void remove();
  }

  /** Current mouse capture owner. */
  private static MouseCaptureListener captureOwner;

  /**
   * These are the Remover objects for the hooks into Window that fire to this class. We have
   * exactly one listener per event type that supports capture. When no UI component possesses
   * capture (meaning that the captureOwner stack is empty) we can disconnect these.
   */
  private static final List<Remover> mouseRemovers = new ArrayList<>();

  /**
   * Capture happens on a per listener instance basis. We throw the CaptureListener on the top of
   * the capture stack and then provide a handle to an object that can be used to
   *
   * @param listener
   */
  public static void capture(final MouseCaptureListener listener) {

    // Make sure to release the previous capture owner.
    if (captureOwner != null) {
      captureOwner.release();
    }

    // Lazily initialize event hookups (this should be below the release above
    // since the above in turn clears the capture hookups)
    if (mouseRemovers.isEmpty()) {
      registerEventCaptureHookups();
    }

    captureOwner = listener;
    listener.setCaptureReleaser(
        new CaptureReleaser() {
          @Override
          public void release() {
            // nuke the reference to this releaser in the listener (which should
            // still be the capture owner).
            listener.setCaptureReleaser(null);
            // nuke the captureOwner
            captureOwner = null;

            // Release the event listeners.
            for (int i = 0, n = mouseRemovers.size(); i < n; i++) {
              mouseRemovers.get(i).remove();
            }
            mouseRemovers.clear();
          }
        });
  }

  private static Remover addCaptureEventListener(
      final String type, final EventTarget source, final EventListener listener) {
    source.addEventListener(type, listener, true);
    return new Remover() {
      @Override
      public void remove() {
        source.removeEventListener(type, listener, true);
      }
    };
  }

  private static void forwardToCaptureOwner(Event event) {
    if (captureOwner != null) {
      captureOwner.handleEvent(event);
    }
  }

  /**
   * Registers for relevant events on the Window.
   *
   * <p>Capture should be lazily initialized, and then destroyed each time nothing has capture (as
   * to not cause useless event dispatch and handling for events like move events).
   */
  private static void registerEventCaptureHookups() {

    mouseRemovers.add(
        addCaptureEventListener(
            Event.MOUSEMOVE,
            Browser.getWindow(),
            new EventListener() {
              @Override
              public void handleEvent(Event event) {
                forwardToCaptureOwner(event);
              }
            }));

    mouseRemovers.add(
        addCaptureEventListener(
            Event.MOUSEUP,
            Browser.getWindow(),
            new EventListener() {
              @Override
              public void handleEvent(Event event) {
                forwardToCaptureOwner(event);
              }
            }));

    mouseRemovers.add(
        addCaptureEventListener(
            Event.BLUR,
            Browser.getWindow(),
            new EventListener() {
              @Override
              public void handleEvent(Event event) {
                captureOwner.release();
              }
            }));
  }
}
