/*
 * Copyright 2007 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.Element;
import com.google.gwt.user.client.DOM;

import java.util.Iterator;

/**
 * Abstract base class for panels that can contain multiple child widgets.
 */
public abstract class ComplexPanel extends Panel implements IndexedPanel.ForIsWidget {

  private WidgetCollection children = new WidgetCollection(this);

  /**
   * The command used to orphan children. 
   */
  private AttachDetachException.Command orphanCommand;

  public Widget getWidget(int index) {
    return getChildren().get(index);
  }

  public int getWidgetCount() {
    return getChildren().size();
  }

  public int getWidgetIndex(Widget child) {
    return getChildren().indexOf(child);
  }
  
  public int getWidgetIndex(IsWidget child) {
    return getWidgetIndex(asWidgetOrNull(child));
  }

  public Iterator<Widget> iterator() {
    return getChildren().iterator();
  }

  public boolean remove(int index) {
    return remove(getWidget(index));
  }

  @Override
  public boolean remove(Widget w) {
    // Validate.
    if (w.getParent() != this) {
      return false;
    }
    // Orphan.
    try {
      orphan(w);
    } finally {
      // Physical detach.
      Element elem = w.getElement();
      DOM.getParent(elem).removeChild(elem);
  
      // Logical detach.
      getChildren().remove(w);
    }
    return true;
  }

  /**
   * Adds a new child widget to the panel, attaching its Element to the
   * specified container Element.
   * 
   * @param child the child widget to be added
   * @param container the element within which the child will be contained
   */
  protected void add(Widget child, Element container) {
    add(child, DOM.asOld(container));
  }

  /**
   * @deprecated Call and override {@link #add(Widget, Element)} instead.
   */
  @Deprecated
  protected void add(Widget child, com.google.gwt.user.client.Element container) {
    // Detach new child.
    child.removeFromParent();

    // Logical attach.
    getChildren().add(child);

    // Physical attach.
    DOM.appendChild(container, child.getElement());

    // Adopt.
    adopt(child);
  }

  /**
   * Adjusts beforeIndex to account for the possibility that the given widget is
   * already a child of this panel.
   * 
   * @param child the widget that might be an existing child
   * @param beforeIndex the index at which it will be added to this panel
   * @return the modified index
   */
  protected int adjustIndex(Widget child, int beforeIndex) {
    checkIndexBoundsForInsertion(beforeIndex);

    // Check to see if this widget is already a direct child.
    if (child.getParent() == this) {
      // If the Widget's previous position was left of the desired new position
      // shift the desired position left to reflect the removal
      int idx = getWidgetIndex(child);
      if (idx < beforeIndex) {
        beforeIndex--;
      }
    }

    return beforeIndex;
  }

  /**
   * Checks that <code>index</code> is in the range [0, getWidgetCount()), which
   * is the valid range on accessible indexes.
   * 
   * @param index the index being accessed
   */
  protected void checkIndexBoundsForAccess(int index) {
    if (index < 0 || index >= getWidgetCount()) {
      throw new IndexOutOfBoundsException();
    }
  }

  /**
   * Checks that <code>index</code> is in the range [0, getWidgetCount()], which
   * is the valid range for indexes on an insertion.
   * 
   * @param index the index where insertion will occur
   */
  protected void checkIndexBoundsForInsertion(int index) {
    if (index < 0 || index > getWidgetCount()) {
      throw new IndexOutOfBoundsException();
    }
  }

  /**
   * Gets the list of children contained in this panel.
   * 
   * @return a collection of child widgets
   */
  protected WidgetCollection getChildren() {
    return children;
  }

  /**
   * Insert a new child Widget into this Panel at a specified index, attaching
   * its Element to the specified container Element. The child Element will
   * either be attached to the container at the same index, or simply appended
   * to the container, depending on the value of <code>domInsert</code>.
   * 
   * @param child the child Widget to be added
   * @param container the Element within which <code>child</code> will be
   *          contained
   * @param beforeIndex the index before which <code>child</code> will be
   *          inserted
   * @param domInsert if <code>true</code>, insert <code>child</code> into
   *          <code>container</code> at <code>beforeIndex</code>; otherwise
   *          append <code>child</code> to the end of <code>container</code>.
   */
  protected void insert(Widget child, Element container, int beforeIndex,
      boolean domInsert) {
    insert(child, DOM.asOld(container), beforeIndex, domInsert);
  }

  /**
   * @deprecated Call and override {@link insert(Widget, Element, int, boolean)} instead.
   */
  @Deprecated
  protected void insert(Widget child, com.google.gwt.user.client.Element container,
      int beforeIndex, boolean domInsert) {
    // Validate index; adjust if the widget is already a child of this panel.
    beforeIndex = adjustIndex(child, beforeIndex);

    // Detach new child.
    child.removeFromParent();

    // Logical attach.
    getChildren().insert(child, beforeIndex);

    // Physical attach.
    if (domInsert) {
      DOM.insertChild(container, child.getElement(), beforeIndex);
    } else {
      DOM.appendChild(container, child.getElement());
    }

    // Adopt.
    adopt(child);
  }

  void doLogicalClear() {
    // TODO(jgw): When Layout work has landed, deprecate FlowPanel (the only
    // caller of this method in our code), and deprecate this method with an eye
    // to making it private down the road.

    // Only use one orphan command per panel to avoid object creation.
    if (orphanCommand == null) {
      orphanCommand = new AttachDetachException.Command() {
        public void execute(Widget w) {
          orphan(w);
        }
      };
    }
    try {
      AttachDetachException.tryCommand(this, orphanCommand);
    } finally {
      children = new WidgetCollection(this);
    }
  }
}
