/*
 * Copyright 2009 Google Inc.
 * 
 * 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 com.google.gwt.user.client;

import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.HasNativeEvent;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;

/**
 * <p>
 * An opaque handle to a native DOM Event. An <code>Event</code> cannot be
 * created directly. Instead, use the <code>Event</code> type when returning a
 * native DOM event from JSNI methods. An <code>Event</code> passed back into
 * JSNI becomes the original DOM event the <code>Event</code> was created from,
 * and can be accessed in JavaScript code as expected. This is typically done by
 * calling methods in the {@link com.google.gwt.user.client.DOM} class.
 * </p>
 */
public class Event extends NativeEvent {

  /**
   * Represents a preview of a native {@link Event}.
   */
  public static class NativePreviewEvent extends GwtEvent<NativePreviewHandler>
      implements HasNativeEvent {

    /**
     * Handler type.
     */
    private static Type<NativePreviewHandler> TYPE;

    /**
     * The singleton instance of {@link NativePreviewEvent}.
     */
    private static NativePreviewEvent singleton;

    /**
     * Gets the type associated with this event.
     * 
     * @return returns the handler type
     */
    public static Type<NativePreviewHandler> getType() {
      if (TYPE == null) {
        TYPE = new Type<NativePreviewHandler>();
      }
      return TYPE;
    }

    /**
     * Fire a {@link NativePreviewEvent} for the native event.
     * 
     * @param handlers the {@link HandlerManager}
     * @param nativeEvent the native event
     * @return true to fire the event normally, false to cancel the event
     */
    private static boolean fire(HandlerManager handlers, NativeEvent nativeEvent) {
      if (TYPE != null && handlers != null && handlers.isEventHandled(TYPE)) {
        // Cache the current values in the singleton in case we are in the
        // middle of handling another event.
        boolean lastIsCanceled = singleton.isCanceled;
        boolean lastIsConsumed = singleton.isConsumed;
        boolean lastIsFirstHandler = singleton.isFirstHandler;
        NativeEvent lastNativeEvent = singleton.nativeEvent;

        // Revive the event
        singleton.revive();
        singleton.setNativeEvent(nativeEvent);

        // Fire the event
        handlers.fireEvent(singleton);
        boolean ret = !(singleton.isCanceled() && !singleton.isConsumed());

        // Restore the state of the singleton.
        singleton.isCanceled = lastIsCanceled;
        singleton.isConsumed = lastIsConsumed;
        singleton.isFirstHandler = lastIsFirstHandler;
        singleton.nativeEvent = lastNativeEvent;
        return ret;
      }
      return true;
    }

    /**
     * A boolean indicating that the native event should be canceled.
     */
    private boolean isCanceled = false;

    /**
     * A boolean indicating whether or not canceling the native event should be
     * prevented. This supercedes {@link #isCanceled}.
     */
    private boolean isConsumed = false;

    /**
     * A boolean indicating that the current handler is at the top of the event
     * preview stack.
     */
    private boolean isFirstHandler = false;

    /**
     * The event being previewed.
     */
    private NativeEvent nativeEvent;

    /**
     * Cancel the native event and prevent it from firing. Note that the event
     * can still fire if another handler calls {@link #consume()}.
     * 
     * Classes overriding this method should still call super.cancel().
     */
    public void cancel() {
      isCanceled = true;
    }

    /**
     * Consume the native event and prevent it from being canceled, even if it
     * has already been canceled by another handler.
     * {@link NativePreviewHandler} that fire first have priority over later
     * handlers, so all handlers should check if the event has already been
     * canceled before calling this method.
     */
    public void consume() {
      isConsumed = true;
    }

    @Override
    public final Type<NativePreviewHandler> getAssociatedType() {
      return TYPE;
    }

    public NativeEvent getNativeEvent() {
      return nativeEvent;
    }

    /**
     * Gets the type int corresponding to the native event that triggered this
     * preview.
     * 
     * @return the type int associated with this native event
     */
    public final int getTypeInt() {
      return Event.as(getNativeEvent()).getTypeInt();
    }

    /**
     * Has the event already been canceled? Note that {@link #isConsumed()} will
     * still return true if the native event has also been consumed.
     * 
     * @return true if the event has been canceled
     * @see #cancel()
     */
    public boolean isCanceled() {
      return isCanceled;
    }

    /**
     * Has the native event been consumed? Note that {@link #isCanceled()} will
     * still return true if the native event has also been canceled.
     * 
     * @return true if the event has been consumed
     * @see #consume()
     */
    public boolean isConsumed() {
      return isConsumed;
    }

    /**
     * Is the current handler the first to preview this event?
     * 
     * @return true if the current handler is the first to preview the event
     */
    public boolean isFirstHandler() {
      return isFirstHandler;
    }

    @Override
    protected void dispatch(NativePreviewHandler handler) {
      handler.onPreviewNativeEvent(this);
      singleton.isFirstHandler = false;
    }

    @Override
    protected void revive() {
      super.revive();
      isCanceled = false;
      isConsumed = false;
      isFirstHandler = true;
      nativeEvent = null;
    }

    /**
     * Set the native event.
     * 
     * @param nativeEvent the native {@link Event} being previewed.
     */
    private void setNativeEvent(NativeEvent nativeEvent) {
      this.nativeEvent = nativeEvent;
    }
  }

  /**
   * Handler interface for {@link NativePreviewEvent} events.
   */
  public static interface NativePreviewHandler extends EventHandler {
    /**
     * Called when {@link NativePreviewEvent} is fired.
     * 
     * @param event the {@link NativePreviewEvent} that was fired
     */
    void onPreviewNativeEvent(NativePreviewEvent event);
  }

  /**
   * Fired when an element loses keyboard focus.
   */
  public static final int ONBLUR = 0x01000;

  /**
   * Fired when the value of an input element changes.
   */
  public static final int ONCHANGE = 0x00400;

  /**
   * Fired when the user clicks on an element.
   */
  public static final int ONCLICK = 0x00001;

  /**
   * Fired when the user double-clicks on an element.
   */
  public static final int ONDBLCLICK = 0x00002;

  /**
   * Fired when an image encounters an error.
   */
  public static final int ONERROR = 0x10000;

  /**
   * Fired when an element receives keyboard focus.
   */
  public static final int ONFOCUS = 0x00800;

  /**
   * Fired when the user gesture changes.
   */
  public static final int ONGESTURECHANGE = 0x2000000;

  /**
   * Fired when the user gesture ends.
   */
  public static final int ONGESTUREEND = 0x4000000;

  /**
   * Fired when the user gesture starts.
   */
  public static final int ONGESTURESTART = 0x1000000;

  /**
   * Fired when the user depresses a key.
   */
  public static final int ONKEYDOWN = 0x00080;

  /**
   * Fired when the a character is generated from a keypress (either directly or
   * through auto-repeat).
   */
  public static final int ONKEYPRESS = 0x00100;

  /**
   * Fired when the user releases a key.
   */
  public static final int ONKEYUP = 0x00200;

  /**
   * Fired when an element (normally an IMG) finishes loading.
   */
  public static final int ONLOAD = 0x08000;

  /**
   * Fired when an element that has mouse capture loses it.
   */
  public static final int ONLOSECAPTURE = 0x02000;

  /**
   * Fired when the user depresses a mouse button over an element.
   */
  public static final int ONMOUSEDOWN = 0x00004;

  /**
   * Fired when the mouse is moved within an element's area.
   */
  public static final int ONMOUSEMOVE = 0x00040;

  /**
   * Fired when the mouse is moved out of an element's area.
   */
  public static final int ONMOUSEOUT = 0x00020;

  /**
   * Fired when the mouse is moved into an element's area.
   */
  public static final int ONMOUSEOVER = 0x00010;

  /**
   * Fired when the user releases a mouse button over an element.
   */
  public static final int ONMOUSEUP = 0x00008;

  /**
   * Fired when the user scrolls the mouse wheel over an element.
   */
  public static final int ONMOUSEWHEEL = 0x20000;

  /**
   * Fired when the user pastes text into an input element.
   */
  public static final int ONPASTE = 0x80000;

  /**
   * Fired when a scrollable element's scroll offset changes.
   */
  public static final int ONSCROLL = 0x04000;

  /**
   * Fired when the user cancels touching an element.
   */
  public static final int ONTOUCHCANCEL = 0x800000;

  /**
   * Fired when the user ends touching an element.
   */
  public static final int ONTOUCHEND = 0x400000;

  /**
   * Fired when the user moves while touching an element.
   */
  public static final int ONTOUCHMOVE = 0x200000;

  /**
   * Fired when the user starts touching an element.
   */
  public static final int ONTOUCHSTART = 0x100000;
  /**
   * Fired when the user requests an element's context menu (usually by
   * right-clicking).
   */
  public static final int ONCONTEXTMENU = 0x40000;

  /**
   * A bit-mask covering both focus events (focus and blur).
   */
  public static final int FOCUSEVENTS = ONFOCUS | ONBLUR;

  /**
   * A bit-mask covering all keyboard events (down, up, and press).
   */
  public static final int KEYEVENTS = ONKEYDOWN | ONKEYPRESS | ONKEYUP;

  /**
   * A bit-mask covering all mouse events (down, up, move, over, and out), but
   * not click, dblclick, or wheel events.
   */
  public static final int MOUSEEVENTS = ONMOUSEDOWN | ONMOUSEUP | ONMOUSEMOVE
      | ONMOUSEOVER | ONMOUSEOUT;

  /**
   * A bit-mask covering all touch events (start, move, end, cancel).
   */
  public static final int TOUCHEVENTS = ONTOUCHSTART | ONTOUCHMOVE | ONTOUCHEND | ONTOUCHCANCEL;

  /**
   * A bit-mask covering all gesture events (start, change, end).
   */
  public static final int GESTUREEVENTS = ONGESTURESTART | ONGESTURECHANGE | ONGESTUREEND;

  /**
   * Value returned by accessors when the actual integer value is undefined. In
   * Development Mode, most accessors assert that the requested attribute is
   * reliable across all supported browsers.
   *
   * @see Event
   */
  @Deprecated
  public static final int UNDEFINED = 0;

  /**
   * The list of {@link NativePreviewHandler}. We use a list instead of a
   * handler manager for efficiency and because we want to fire the handlers in
   * reverse order. When the last handler is removed, handlers is reset to null.
   */
  static HandlerManager handlers;

  /**
   * Adds an event preview to the preview stack. As long as this preview remains
   * on the top of the stack, it will receive all events before they are fired
   * to their listeners. Note that the event preview will receive <u>all </u>
   * events, including those received due to bubbling, whereas normal event
   * handlers only receive explicitly sunk events.
   * 
   * @param preview the event preview to be added to the stack.
   * @deprecated replaced by
   *             {@link #addNativePreviewHandler(NativePreviewHandler)}
   */
  @Deprecated
  public static void addEventPreview(EventPreview preview) {
    DOM.addEventPreview(preview);
  }

  /**
   * <p>
   * Adds a {@link NativePreviewHandler} that will receive all events before
   * they are fired to their handlers. Note that the handler will receive
   * <u>all</u> native events, including those received due to bubbling, whereas
   * normal event handlers only receive explicitly sunk events.
   * </p>
   * 
   * <p>
   * Unlike other event handlers, {@link NativePreviewHandler} are fired in the
   * reverse order that they are added, such that the last
   * {@link NativePreviewEvent} that was added is the first to be fired.
   * </p>
   * 
   * <p>
   * Please note that nondeterministic behavior will result if more than one GWT
   * application registers preview handlers. See <a href=
   * 'http://code.google.com/p/google-web-toolkit/issues/detail?id=3892'>issue
   * 3892</a> for details.
   * </p>
   *
   * @param handler the {@link NativePreviewHandler}
   * @return {@link HandlerRegistration} used to remove this handler
   */
  public static HandlerRegistration addNativePreviewHandler(
      final NativePreviewHandler handler) {
    assert handler != null : "Cannot add a null handler";
    DOM.maybeInitializeEventSystem();

    // Initialize the type
    NativePreviewEvent.getType();
    if (handlers == null) {
      handlers = new HandlerManager(null, true);
      NativePreviewEvent.singleton = new NativePreviewEvent();
    }
    return handlers.addHandler(NativePreviewEvent.TYPE, handler);
  }

  /**
   * Converts the {@link NativeEvent} to Event. This is always safe.
   * 
   * @param event the event to downcast
   */
  public static Event as(NativeEvent event) {
    return (Event) event;
  }

  /**
   * Fire a {@link NativePreviewEvent} for the native event.
   * 
   * @param nativeEvent the native event
   * @return true to fire the event normally, false to cancel the event
   */
  public static boolean fireNativePreviewEvent(NativeEvent nativeEvent) {
    return NativePreviewEvent.fire(handlers, nativeEvent);
  }

  /**
   * Gets the current event that is being fired. The current event is only
   * available within the lifetime of the onBrowserEvent function. Once the
   * onBrowserEvent method returns, the current event is reset to null.
   * 
   * @return the current event
   */
  public static Event getCurrentEvent() {
    return DOM.eventGetCurrentEvent();
  }

  /**
   * Gets the {@link EventListener} that will receive events for the given
   * element. Only one such listener may exist for a single element.
   * 
   * @param elem the element whose listener is to be set
   * @return the element's event listener
   */
  public static EventListener getEventListener(Element elem) {
    return DOM.getEventListener(elem);
  }

  /**
   * Gets the current set of events sunk by a given element.
   * 
   * @param elem the element whose events are to be retrieved
   * @return a bitfield describing the events sunk on this element (its possible
   *         values are described in {@link Event})
   */
  public static int getEventsSunk(Element elem) {
    return DOM.getEventsSunk(elem);
  }

  /**
   * Gets the enumerated type of this event given a valid event type name.
   * 
   * @param typeName the typeName to be tested
   * @return the event's enumerated type, or -1 if not defined
   */
  public static int getTypeInt(String typeName) {
    return DOM.impl.eventGetTypeInt(typeName);
  }

  /**
   * Releases mouse capture on the given element. Calling this method has no
   * effect if the element does not currently have mouse capture.
   * 
   * @param elem the element to release capture
   * @see #setCapture(Element)
   */
  public static void releaseCapture(Element elem) {
    DOM.releaseCapture(elem);
  }

  /**
   * Removes an element from the preview stack. This element will no longer
   * capture events, though any preview underneath it will begin to do so.
   * 
   * @param preview the event preview to be removed from the stack
   * @deprecated use {@link HandlerRegistration} returned from
   *             {@link Event#addNativePreviewHandler(NativePreviewHandler)}
   */
  @Deprecated
  public static void removeEventPreview(EventPreview preview) {
    DOM.removeEventPreview(preview);
  }

  /**
   * Sets mouse-capture on the given element. This element will directly receive
   * all mouse events until {@link #releaseCapture(Element)} is called on it.
   * 
   * @param elem the element on which to set mouse capture
   */
  public static void setCapture(Element elem) {
    DOM.setCapture(elem);
  }

  /**
   * Sets the {@link EventListener} to receive events for the given element.
   * Only one such listener may exist for a single element.
   * 
   * @param elem the element whose listener is to be set
   * @param listener the listener to receive {@link Event events}
   */
  public static void setEventListener(Element elem, EventListener listener) {
    DOM.setEventListener(elem, listener);
  }

  /**
   * Sets the current set of events sunk by a given element. These events will
   * be fired to the nearest {@link EventListener} specified on any of the
   * element's parents.
   * 
   * @param elem the element whose events are to be retrieved
   * @param eventBits a bitfield describing the events sunk on this element (its
   *          possible values are described in {@link Event})
   */
  public static void sinkEvents(Element elem, int eventBits) {
    DOM.sinkEvents(elem, eventBits);
  }

  /**
   * Not directly instantiable. Subclasses should also define a protected no-arg
   * constructor to prevent client code from directly instantiating the class.
   */
  protected Event() {
  }

  /**
   * Cancels bubbling for the given event. This will stop the event from being
   * propagated to parent elements.
   * 
   * @param cancel <code>true</code> to cancel bubbling
   * @deprecated use {@link NativeEvent#stopPropagation()} instead
   */
  @Deprecated
  public final void cancelBubble(boolean cancel) {
    DOM.eventCancelBubble(this, cancel);
  }

  /**
   * Gets the current target element of this event. This is the element whose
   * listener fired last, not the element which fired the event initially.
   * 
   * @return the event's current target element
   * @deprecated use {@link NativeEvent#getCurrentEventTarget()} instead
   */
  @Deprecated
  public final Element getCurrentTarget() {
    return getCurrentEventTarget().cast();
  }

  /**
   * Gets the element from which the mouse pointer was moved (only valid for
   * {@link Event#ONMOUSEOVER}).
   * 
   * @deprecated use {@link NativeEvent#getRelatedEventTarget()} instead
   * @return the element from which the mouse pointer was moved
   */
  @Deprecated
  public final Element getFromElement() {
    return DOM.eventGetFromElement(this);
  }

  /**
   * Gets the related target for this event.
   * 
   * @return the related target
   * @deprecated use {@link NativeEvent#getRelatedEventTarget()} instead
   */
  @Deprecated
  public final Element getRelatedTarget() {
    return getRelatedEventTarget().cast();
  }

  /**
   * Gets the key-repeat state of this event.
   * 
   * @return <code>true</code> if this key event was an auto-repeat
   * @deprecated not supported on all browsers
   */
  @Deprecated
  public final boolean getRepeat() {
    return DOM.eventGetRepeat(this);
  }

  /**
   * Returns the element that was the actual target of the given event.
   * 
   * @return the target element
   * @deprecated use {@link NativeEvent#getEventTarget()} instead
   */
  @Deprecated
  public final Element getTarget() {
    return getEventTarget().cast();
  }

  /**
   * Gets the element to which the mouse pointer was moved (only valid for
   * {@link Event#ONMOUSEOUT}).
   * 
   * @deprecated use {@link NativeEvent#getRelatedEventTarget()} instead
   * @return the element to which the mouse pointer was moved
   */
  @Deprecated
  public final Element getToElement() {
    return DOM.eventGetToElement(this);
  }

  /**
   * Gets the enumerated type of this event, as defined by {@link #ONCLICK},
   * {@link #ONMOUSEDOWN}, and so forth.
   * 
   * @return the event's enumerated type
   */
  public final int getTypeInt() {
    return DOM.eventGetType(this);
  }
}
