/*
 * 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.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.Event;

import java.util.Iterator;

/**
 * Abstract base class for {@link HorizontalSplitPanel} and
 * {@link VerticalSplitPanel}.
 * 
 * @deprecated Use {@link SplitLayoutPanel} instead, but understand that it is
 *             not a drop in replacement for this class. It requires standards
 *             mode, and is most easily used under a {@link RootLayoutPanel} (as
 *             opposed to a {@link RootPanel}
 */
@Deprecated
abstract class SplitPanel extends Panel {
  /**
   * The element that masks the screen so we can catch mouse events over
   * iframes.
   */
  private static Element glassElem = null;

  /**
   * Sets an elements positioning to absolute.
   * 
   * @param elem the element
   */
  static void addAbsolutePositoning(Element elem) {
    DOM.setStyleAttribute(elem, "position", "absolute");
  }

  /**
   * Adds clipping to an element.
   * 
   * @param elem the element
   */
  static final void addClipping(final Element elem) {
    DOM.setStyleAttribute(elem, "overflow", "hidden");
  }

  /**
   * Adds as-needed scrolling to an element.
   * 
   * @param elem the element
   */
  static final void addScrolling(final Element elem) {
    DOM.setStyleAttribute(elem, "overflow", "auto");
  }

  /**
   * Sizes and element to consume the full area of its parent using the CSS
   * properties left, right, top, and bottom. This method is used for all
   * browsers except IE6/7.
   * 
   * @param elem the element
   */
  static final void expandToFitParentUsingCssOffsets(Element elem) {
    final String zeroSize = "0px";

    addAbsolutePositoning(elem);
    setLeft(elem, zeroSize);
    setRight(elem, zeroSize);
    setTop(elem, zeroSize);
    setBottom(elem, zeroSize);
  }

  /**
   * Sizes an element to consume the full areas of its parent using 100% width
   * and height. This method is used on IE6/7 where CSS offsets don't work
   * reliably.
   * 
   * @param elem the element
   */
  static final void expandToFitParentUsingPercentages(Element elem) {
    final String zeroSize = "0px";
    final String fullSize = "100%";

    addAbsolutePositoning(elem);
    setTop(elem, zeroSize);
    setLeft(elem, zeroSize);
    setWidth(elem, fullSize);
    setHeight(elem, fullSize);
  }

  /**
   * Returns the offsetHeight element property.
   * 
   * @param elem the element
   * @return the offsetHeight property
   */
  static final int getOffsetHeight(Element elem) {
    return DOM.getElementPropertyInt(elem, "offsetHeight");
  }

  /**
   * Returns the offsetWidth element property.
   * 
   * @param elem the element
   * @return the offsetWidth property
   */
  static final int getOffsetWidth(Element elem) {
    return DOM.getElementPropertyInt(elem, "offsetWidth");
  }

  /**
   * Adds zero or none CSS values for padding, margin and border to prevent
   * stylesheet overrides. Returns the element for convenience to support
   * builder pattern.
   * 
   * @param elem the element
   * @return the element
   */
  static final Element preventBoxStyles(final Element elem) {
    DOM.setIntStyleAttribute(elem, "padding", 0);
    DOM.setIntStyleAttribute(elem, "margin", 0);
    DOM.setStyleAttribute(elem, "border", "none");
    return elem;
  }

  /**
   * Convenience method to set bottom offset of an element.
   * 
   * @param elem the element
   * @param size a CSS length value for bottom
   */
  static void setBottom(Element elem, String size) {
    DOM.setStyleAttribute(elem, "bottom", size);
  }

  /**
   * Sets the elements css class name.
   * 
   * @param elem the element
   * @param className the class name
   */
  static final void setClassname(final Element elem, final String className) {
    DOM.setElementProperty(elem, "className", className);
  }

  /**
   * Convenience method to set the height of an element.
   * 
   * @param elem the element
   * @param height a CSS length value for the height
   */
  static final void setHeight(Element elem, String height) {
    DOM.setStyleAttribute(elem, "height", height);
  }

  /**
   * Convenience method to set the left offset of an element.
   * 
   * @param elem the element
   * @param left a CSS length value for left
   */
  static final void setLeft(Element elem, String left) {
    DOM.setStyleAttribute(elem, "left", left);
  }

  /**
   * Convenience method to set the right offset of an element.
   * 
   * @param elem the element
   * @param right a CSS length value for right
   */
  static final void setRight(Element elem, String right) {
    DOM.setStyleAttribute(elem, "right", right);
  }

  /**
   * Convenience method to set the top offset of an element.
   * 
   * @param elem the element
   * @param top a CSS length value for top
   */
  static final void setTop(Element elem, String top) {
    DOM.setStyleAttribute(elem, "top", top);
  }

  /**
   * Convenience method to set the width of an element.
   * 
   * @param elem the element
   * @param width a CSS length value for the width
   */
  static final void setWidth(Element elem, String width) {
    DOM.setStyleAttribute(elem, "width", width);
  }

  // The enclosed widgets.
  private final Widget[] widgets = new Widget[2];

  // The elements containing the widgets.
  private final Element[] elements = new Element[2];

  // The element that acts as the splitter.
  private final Element splitElem;

  // Indicates whether drag resizing is active.
  private boolean isResizing = false;

  /**
   * Initializes the split panel.
   * 
   * @param mainElem the root element for the split panel
   * @param splitElem the element that acts as the splitter
   * @param headElem the element to contain the top or left most widget
   * @param tailElem the element to contain the bottom or right most widget
   */
  SplitPanel(Element mainElem, Element splitElem, Element headElem,
      Element tailElem) {
    setElement(mainElem);
    this.splitElem = splitElem;
    elements[0] = headElem;
    elements[1] = tailElem;
    sinkEvents(Event.MOUSEEVENTS | Event.ONLOSECAPTURE);

    if (glassElem == null) {
      glassElem = DOM.createDiv();
      glassElem.getStyle().setProperty("position", "absolute");
      glassElem.getStyle().setProperty("top", "0px");
      glassElem.getStyle().setProperty("left", "0px");
      glassElem.getStyle().setProperty("margin", "0px");
      glassElem.getStyle().setProperty("padding", "0px");
      glassElem.getStyle().setProperty("border", "0px");

      // We need to set the background color or mouse events will go right
      // through the glassElem. If the SplitPanel contains an iframe, the
      // iframe will capture the event and the slider will stop moving.
      glassElem.getStyle().setProperty("background", "white");
      glassElem.getStyle().setProperty("opacity", "0.0");
      glassElem.getStyle().setProperty("filter", "alpha(opacity=0)");
    }
  }

  @Override
  public void add(Widget w) {
    if (getWidget(0) == null) {
      setWidget(0, w);
    } else if (getWidget(1) == null) {
      setWidget(1, w);
    } else {
      throw new IllegalStateException(
          "A Splitter can only contain two Widgets.");
    }
  }

  /**
   * Indicates whether the split panel is being resized.
   * 
   * @return <code>true</code> if the user is dragging the splitter,
   *         <code>false</code> otherwise
   */
  public boolean isResizing() {
    return isResizing;
  }

  public Iterator<Widget> iterator() {
    return WidgetIterators.createWidgetIterator(this, widgets);
  }

  @Override
  public void onBrowserEvent(Event event) {
    switch (DOM.eventGetType(event)) {

      case Event.ONMOUSEDOWN: {
        Element target = DOM.eventGetTarget(event);
        if (DOM.isOrHasChild(splitElem, target)) {
          startResizingFrom(DOM.eventGetClientX(event) - getAbsoluteLeft(),
              DOM.eventGetClientY(event) - getAbsoluteTop());
          DOM.setCapture(getElement());
          DOM.eventPreventDefault(event);
        }
        break;
      }

      case Event.ONMOUSEUP: {
        if (isResizing()) {
          // The order of these two lines is important. If we release capture
          // first, then we might trigger an onLoseCapture event before we set
          // isResizing to false.
          stopResizing();
          DOM.releaseCapture(getElement());
        }
        break;
      }

      case Event.ONMOUSEMOVE: {
        if (isResizing()) {
          assert DOM.getCaptureElement() != null;
          onSplitterResize(DOM.eventGetClientX(event) - getAbsoluteLeft(),
              DOM.eventGetClientY(event) - getAbsoluteTop());
          DOM.eventPreventDefault(event);
        }
        break;
      }

      // IE automatically releases capture if the user switches windows, so we
      // need to catch the event and stop resizing.
      case Event.ONLOSECAPTURE: {
        if (isResizing()) {
          stopResizing();
        }
        break;
      }
    }
    super.onBrowserEvent(event);
  }

  @Override
  public boolean remove(Widget widget) {
    if (widgets[0] == widget) {
      setWidget(0, null);
      return true;
    } else if (widgets[1] == widget) {
      setWidget(1, null);
      return true;
    }
    return false;
  }

  /**
   * Moves the position of the splitter.
   * 
   * @param size the new size of the left region in CSS units (e.g. "10px",
   *            "1em")
   */
  public abstract void setSplitPosition(String size);

  /**
   * Gets the content element for the given index.
   * 
   * @param index the index of the element, only 0 and 1 are valid.
   * @return the element
   */
  protected Element getElement(int index) {
    return elements[index];
  }

  /**
   * Gets the element that is acting as the splitter.
   * 
   * @return the element
   */
  protected Element getSplitElement() {
    return splitElem;
  }

  /**
   * Gets one of the contained widgets.
   * 
   * @param index the index of the widget, only 0 and 1 are valid.
   * @return the widget
   */
  protected Widget getWidget(int index) {
    return widgets[index];
  }

  /**
   * <b>Affected Elements:</b>
   * <ul>
   * <li>-splitter = the container containing the splitter element.</li>
   * </ul>
   * 
   * @see UIObject#onEnsureDebugId(String)
   */
  @Override
  protected void onEnsureDebugId(String baseID) {
    super.onEnsureDebugId(baseID);
    ensureDebugId(splitElem, baseID, "splitter");
  }

  /**
   * Sets one of the contained widgets.
   * 
   * @param index the index, only 0 and 1 are valid
   * @param w the widget
   */
  protected final void setWidget(int index, Widget w) {
    Widget oldWidget = widgets[index];

    // Validate.
    if (oldWidget == w) {
      return;
    }

    // Detach the new child.
    if (w != null) {
      w.removeFromParent();
    }

    // Remove the old child.
    if (oldWidget != null) {
      // Orphan old.
      try {
        orphan(oldWidget);
      } finally {
        // Physical detach old.
        DOM.removeChild(elements[index], oldWidget.getElement());
        widgets[index] = null;
      }
    }

    // Logical attach new.
    widgets[index] = w;

    if (w != null) {
      // Physical attach new.
      DOM.appendChild(elements[index], w.getElement());

      // Adopt new.
      adopt(w);
    }
  }

  /**
   * Called on each mouse drag event as the user is dragging the splitter.
   * 
   * @param x the x coordinate of the mouse relative to the panel's extent
   * @param y the y coordinate of the mosue relative to the panel's extent
   */
  abstract void onSplitterResize(int x, int y);

  /**
   * Called when the user starts dragging the splitter.
   * 
   * @param x the x coordinate of the mouse relative to the panel's extent
   * @param y the y coordinate of the mouse relative to the panel's extent
   */
  abstract void onSplitterResizeStarted(int x, int y);

  private void startResizingFrom(int x, int y) {
    fireEvent(new SplitterBeforeResizeEvent());
    isResizing = true;
    onSplitterResizeStarted(x, y);

    // Resize glassElem to take up the entire scrollable window area
    int height = RootPanel.getBodyElement().getScrollHeight() - 1;
    int width = RootPanel.getBodyElement().getScrollWidth() - 1;
    glassElem.getStyle().setProperty("height", height + "px");
    glassElem.getStyle().setProperty("width", width + "px");
    RootPanel.getBodyElement().appendChild(glassElem);
  }

  private void stopResizing() {
    isResizing = false;
    RootPanel.getBodyElement().removeChild(glassElem);
    fireEvent(new SplitterResizedEvent());
  }

  public HandlerRegistration addSplitterBeforeResizeHandler(SplitterBeforeResizeHandler handler)
  {
    return addHandler(handler, SplitterBeforeResizeEvent.TYPE);
  }

  public HandlerRegistration addSplitterResizedHandler(SplitterResizedHandler handler)
  {
    return addHandler(handler, SplitterResizedEvent.TYPE);
  }
}
