/*
 * Copyright 2008 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.ui;

import com.google.gwt.dom.client. Document;
import com.google.gwt.dom.client. Element;
import com.google.gwt.dom.client. Style. Overflow;
import com.google.gwt.dom.client. Style. Position;
import com.google.gwt.event.dom.client. ScrollEvent;
import com.google.gwt.event.dom.client. ScrollHandler;
import com.google.gwt.event.shared. HandlerRegistration;
import com.google.gwt.touch.client. TouchScroller;
import com.google.gwt.user.client. DOM;
import com.google.gwt.user.client. Event;

/**
 * A simple panel that wraps its contents in a scrollable area. 
 */
@SuppressWarnings("deprecation")
public class ScrollPanel extends SimplePanel implements SourcesScrollEvents,
    RequiresResize, ProvidesResize, HasScrolling {

  private final Element containerElem;
  private final Element scrollableElem;

  /**
   * The scroller used to support touch events. 
   */
  private TouchScroller touchScroller;

  /**
   * Creates an empty scroll panel. 
   */
  public ScrollPanel() {
    this.scrollableElem = getElement();
    this.containerElem = Document.get().createDivElement();
    scrollableElem.appendChild(containerElem);
    initialize();
  }

  /**
   * Creates a new scroll panel with the given child widget. 
   * 
   * @param child the widget to be wrapped by the scroll panel
   */
  public ScrollPanel(Widget child) {
    this();
    setWidget(child);
  }

  /**
   * Creates an empty scroll panel using the specified root, scrollable, and
   * container elements. 
   * 
   * @param root the root element of the Widget
   * @param scrollable the scrollable element, which can be the same as the root
   *          element
   * @param container the container element that holds the child
   */
  protected ScrollPanel(Element root, Element scrollable, Element container) {
    super(root);
    this.scrollableElem = scrollable;
    this.containerElem = container;
    initialize();
  }

  public HandlerRegistration addScrollHandler(ScrollHandler handler) {
    /*
     * Sink the event on the scrollable element, which may not be the root
     * element. 
     */
    Event.sinkEvents(getScrollableElement(), Event. ONSCROLL);
    return addHandler(handler, ScrollEvent.getType());
  }

  /**
   * @deprecated Use {@link #addScrollHandler} instead
   */
  @Deprecated
  public void addScrollListener(ScrollListener listener) {
    ListenerWrapper. WrappedScrollListener.add(this, listener);
  }

  /**
   * Ensures that the specified item is visible, by adjusting the panel's scroll
   * position. 
   * 
   * @param item the item whose visibility is to be ensured
   */
  public void ensureVisible(UIObject item) {
    Element scroll = getScrollableElement();
    Element element = item.getElement();
    ensureVisibleImpl(scroll, element);
  }

  /**
   * Gets the horizontal scroll position. 
   * 
   * @return the horizontal scroll position, in pixels
   */
  public int getHorizontalScrollPosition() {
    return getScrollableElement().getScrollLeft();
  }

  public int getMaximumHorizontalScrollPosition() {
    return ScrollImpl.get().getMaximumHorizontalScrollPosition(getScrollableElement());
  }

  public int getMaximumVerticalScrollPosition() {
    return getScrollableElement().getScrollHeight() - getScrollableElement().getClientHeight();
  }

  public int getMinimumHorizontalScrollPosition() {
    return ScrollImpl.get().getMinimumHorizontalScrollPosition(getScrollableElement());
  }

  public int getMinimumVerticalScrollPosition() {
    return 0;
  }

  /**
   * Gets the vertical scroll position. 
   * 
   * @return the vertical scroll position, in pixels
   * @deprecated as of GWT 2.3, replaced by {@link #getVerticalScrollPosition()}
   */
  @Deprecated
  public int getScrollPosition() {
    return getScrollableElement().getScrollTop();
  }

  public int getVerticalScrollPosition() {
    return getScrollPosition();
  }

  /**
   * Check whether or not touch based scrolling is disabled.  This method always
   * returns false on devices that do not support touch scrolling. 
   * 
   * @return true if disabled, false if enabled
   */
  public boolean isTouchScrollingDisabled() {
    return touchScroller == null;
  }

  public void onResize() {
    Widget child = getWidget();
    if ((child ! = null) && (child instanceof RequiresResize)) {
      ((RequiresResize) child).onResize();
    }
  }

  /**
   * @deprecated Use the {@link HandlerRegistration#removeHandler}
   * method on the object returned by {@link #addScrollHandler} instead
   */
  @Deprecated
  public void removeScrollListener(ScrollListener listener) {
    ListenerWrapper. WrappedScrollListener.remove(this, listener);
  }

  /**
   * Scroll to the bottom of this panel. 
   */
  public void scrollToBottom() {
    setVerticalScrollPosition(getMaximumVerticalScrollPosition());
  }

  /**
   * Scroll to the far left of this panel. 
   */
  public void scrollToLeft() {
    setHorizontalScrollPosition(getMinimumHorizontalScrollPosition());
  }

  /**
   * Scroll to the far right of this panel. 
   */
  public void scrollToRight() {
    setHorizontalScrollPosition(getMaximumHorizontalScrollPosition());
  }

  /**
   * Scroll to the top of this panel. 
   */
  public void scrollToTop() {
    setVerticalScrollPosition(getMinimumVerticalScrollPosition());
  }

  /**
   * Sets whether this panel always shows its scroll bars, or only when
   * necessary. 
   * 
   * @param alwaysShow <code>true</code> to show scroll bars at all times
   */
  public void setAlwaysShowScrollBars(boolean alwaysShow) {
    getScrollableElement().getStyle().setOverflow(alwaysShow ?  Overflow. SCROLL : Overflow. AUTO);
  }

  /**
   * Sets the object's height.  This height does not include decorations such as
   * border, margin, and padding. 
   * 
   * @param height the object's new height, in absolute CSS units (e.g. "10px",
   *          "1em" but not "50%")
   */
  @Override
  public void setHeight(String height) {
    super.setHeight(height);
  }

  /**
   * Sets the horizontal scroll position. 
   * 
   * @param position the new horizontal scroll position, in pixels
   */
  public void setHorizontalScrollPosition(int position) {
    getScrollableElement().setScrollLeft(position);
  }

  /**
   * Sets the vertical scroll position. 
   * 
   * @param position the new vertical scroll position, in pixels
   * @deprecated as of GWT 2.3, replaced by
   *             {@link #setVerticalScrollPosition(int)}
   */
  @Deprecated
  public void setScrollPosition(int position) {
    getScrollableElement().setScrollTop(position);
  }

  /**
   * Sets the object's size.  This size does not include decorations such as
   * border, margin, and padding. 
   * 
   * @param width the object's new width, in absolute CSS units (e.g. "10px",
   *          "1em", but not "50%")
   * @param height the object's new height, in absolute CSS units (e.g. "10px",
   *          "1em", but not "50%")
   */
  @Override
  public void setSize(String width, String height) {
    super.setSize(width, height);
  }

  /**
   * Set whether or not touch scrolling is disabled.  By default, touch scrolling
   * is enabled on devices that support touch events. 
   * 
   * @param isDisabled true to disable, false to enable
   * @return true if touch scrolling is enabled and supported, false if disabled
   *         or not supported
   */
  public boolean setTouchScrollingDisabled(boolean isDisabled) {
    if (isDisabled == isTouchScrollingDisabled()) {
      return isDisabled;
    }

    if (isDisabled) {
      // Detach the touch scroller. 
      touchScroller.setTargetWidget(null);
      touchScroller = null;
    } else {
      // Attach a new touch scroller. 
      touchScroller = TouchScroller.createIfSupported(this);
    }
    return isTouchScrollingDisabled();
  }

  public void setVerticalScrollPosition(int position) {
    setScrollPosition(position);
  }

  /**
   * Sets the object's width.  This width does not include decorations such as
   * border, margin, and padding. 
   * 
   * @param width the object's new width, in absolute CSS units (e.g. "10px",
   *          "1em", but not "50%")
   */
  @Override
  public void setWidth(String width) {
    super.setWidth(width);
  }

  @Override
  protected com.google.gwt.user.client. Element getContainerElement() {
    return DOM.asOld(containerElem);
  }

  /**
   * Get the scrollable element.  That is the element with its overflow set to
   * 'auto' or 'scroll'. 
   * 
   * @return the scrollable element
   */
  protected com.google.gwt.user.client. Element getScrollableElement() {
    return DOM.asOld(scrollableElem);
  }

  @Override
  protected void onAttach() {
    super.onAttach();

    /*
     * Attach the event listener in onAttach instead of onLoad so users cannot
     * accidentally override it.  If the scrollable element is the same as the
     * root element, then we set the event listener twice (once in
     * super.onAttach() and once here), which is fine. 
     */
    Event.setEventListener(getScrollableElement(), this);
  }

  @Override
  protected void onDetach() {
    /*
     * Detach the event listener in onDetach instead of onUnload so users cannot
     * accidentally override it. 
     */
    Event.setEventListener(getScrollableElement(), null);

    super.onDetach();
  }

  private native void ensureVisibleImpl(Element scroll, Element e) /*-{
    if (! e)
      return; 

    var item = e;
    var realOffset = 0;
    while (item && (item ! = scroll)) {
      realOffset += item.offsetTop;
      item = item.offsetParent;
    }

    scroll.scrollTop = realOffset - scroll.offsetHeight / 2;
  }-*/;

  /**
   * Initialize the widget. 
   */
  private void initialize() {
    setAlwaysShowScrollBars(false);

    // Prevent IE standard mode bug when a AbsolutePanel is contained. 
    scrollableElem.getStyle().setPosition(Position. RELATIVE);
    containerElem.getStyle().setPosition(Position. RELATIVE);

    // Hack to account for the IE6/7 scrolling bug described here:
    //   http://stackoverflow.com/questions/139000/div-with-overflowauto-and-a-100-wide-table-problem
    scrollableElem.getStyle().setProperty("zoom", "1");
    containerElem.getStyle().setProperty("zoom", "1");

    // Enable touch scrolling. 
    setTouchScrollingDisabled(false);

    // Initialize the scrollable element. 
    ScrollImpl.get().initialize(scrollableElem, containerElem);
  }
}
