/*
 * 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 java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * A simple collection of widgets to be used by
 * {@link com.google.gwt.user.client.ui.Panel panels} and
 * {@link com.google.gwt.user.client.ui.Composite composites}.
 * 
 * <p>
 * The main purpose of this specialized collection is to implement
 * {@link java.util.Iterator#remove()} in a way that delegates removal to its
 * panel. This makes it much easier for the panel to implement an
 * {@link com.google.gwt.user.client.ui.HasWidgets#iterator() iterator} that
 * supports removal of widgets.
 * </p>
 */
public class WidgetCollection implements Iterable<Widget> {

  private class WidgetIterator implements Iterator<Widget> {

    private int index = 0;
    private Widget currentWidget;

    @Override
    public boolean hasNext() {
      return index < size;
    }

    @Override
    public Widget next() {
      if (index >= size) {
        throw new NoSuchElementException();
      }
      currentWidget = array[index];
      index++;
      return currentWidget;
    }

    @Override
    public void remove() {
      if (currentWidget == null) {
        throw new IllegalStateException();
      }
      parent.remove(currentWidget);
      index--;
      currentWidget = null;
    }
  }

  private static final int INITIAL_SIZE = 4;

  private Widget[] array;
  private HasWidgets parent;
  private int size;

  /**
   * Constructs a new widget collection.
   * 
   * @param parent the container whose {@link HasWidgets#remove(Widget)} will be
   *          delegated to by the iterator's {@link Iterator#remove()} method.
   */
  public WidgetCollection(HasWidgets parent) {
    this.parent = parent;
    array = new Widget[INITIAL_SIZE];
  }

  /**
   * Adds a widget to the end of this collection.
   * 
   * @param w the widget to be added
   */
  public void add(Widget w) {
    insert(w, size);
  }

  /**
   * Determines whether a given widget is contained in this collection.
   * 
   * @param w the widget to be searched for
   * @return <code>true</code> if the widget is present
   */
  public boolean contains(Widget w) {
    return (indexOf(w) != -1);
  }

  /**
   * Gets the widget at the given index.
   * 
   * @param index the index to be retrieved
   * @return the widget at the specified index
   * @throws IndexOutOfBoundsException if the index is out of range
   */
  public Widget get(int index) {
    if ((index < 0) || (index >= size)) {
      throw new IndexOutOfBoundsException();
    }

    return array[index];
  }

  /**
   * Gets the index of the specified index.
   * 
   * @param w the widget to be found
   * @return the index of the specified widget, or <code>-1</code> if it is
   *         not found
   */
  public int indexOf(Widget w) {
    for (int i = 0; i < size; ++i) {
      if (array[i] == w) {
        return i;
      }
    }

    return -1;
  }

  /**
   * Inserts a widget before the specified index.
   * 
   * @param w the widget to be inserted
   * @param beforeIndex the index before which the widget will be inserted
   * @throws IndexOutOfBoundsException if <code>beforeIndex</code> is out of
   *           range
   */
  public void insert(Widget w, int beforeIndex) {
    if ((beforeIndex < 0) || (beforeIndex > size)) {
      throw new IndexOutOfBoundsException();
    }

    // Realloc array if necessary (doubling).
    if (size == array.length) {
      Widget[] newArray = new Widget[array.length * 2];
      for (int i = 0; i < array.length; ++i) {
        newArray[i] = array[i];
      }
      array = newArray;
    }

    ++size;

    // Move all widgets after 'beforeIndex' back a slot.
    for (int i = size - 1; i > beforeIndex; --i) {
      array[i] = array[i - 1];
    }

    array[beforeIndex] = w;
  }

  /**
   * Gets an iterator on this widget collection. This iterator is guaranteed to
   * implement remove() in terms of its containing {@link HasWidgets}.
   * 
   * @return an iterator
   */
  public Iterator<Widget> iterator() {
    return new WidgetIterator();
  }

  /**
   * Removes the widget at the specified index.
   * 
   * @param index the index of the widget to be removed
   * @throws IndexOutOfBoundsException if <code>index</code> is out of range
   */
  public void remove(int index) {
    if ((index < 0) || (index >= size)) {
      throw new IndexOutOfBoundsException();
    }

    --size;
    for (int i = index; i < size; ++i) {
      array[i] = array[i + 1];
    }

    array[size] = null;
  }

  /**
   * Removes the specified widget.
   * 
   * @param w the widget to be removed
   * @throws NoSuchElementException if the widget is not present
   */
  public void remove(Widget w) {
    int index = indexOf(w);
    if (index == -1) {
      throw new NoSuchElementException();
    }

    remove(index);
  }

  /**
   * Gets the number of widgets in this collection.
   * 
   * @return the number of widgets
   */
  public int size() {
    return size;
  }
}
