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

import com.google.gwt.lang.Array;

/**
 * A {@link java.util.Set} of {@link Enum}s. <a
 * href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/EnumSet.html">[Sun
 * docs]</a>
 * 
 * @param <E> enumeration type
 */
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> {

  /**
   * Implemented via sparse array since the set size is finite. Iteration takes
   * linear time with respect to the set of the enum rather than the number of
   * items in the set.
   * 
   * Note: Implemented as a subclass instead of a concrete final EnumSet class.
   * This is because declaring an EnumSet.add(E) causes hosted mode to bind to
   * the tighter method rather than the bridge method; but the tighter method
   * isn't available in the real JRE.
   */
  static final class EnumSetImpl<E extends Enum<E>> extends EnumSet<E> {
    private class IteratorImpl implements Iterator<E> {
      /*
       * i is the index of the item that will be returned on the next call to
       * next() last is the index of the item that was returned on the previous
       * call to next(), -1 if no such item exists.
       */

      int i = -1, last = -1;

      {
        findNext();
      }

      public boolean hasNext() {
        return i < capacity();
      }

      public E next() {
        if (!hasNext()) {
          throw new NoSuchElementException();
        }
        last = i;
        findNext();
        return set[last];
      }

      public void remove() {
        if (last < 0) {
          throw new IllegalStateException();
        }
        assert (set[last] != null);
        set[last] = null;
        --size;
        last = -1;
      }

      private void findNext() {
        ++i;
        for (int c = capacity(); i < c; ++i) {
          if (set[i] != null) {
            return;
          }
        }
      }
    }

    /**
     * All enums; reference to the class's copy; must not be modified.
     */
    private final E[] all;

    /**
     * Live enums in the set.
     */
    private E[] set;

    /**
     * Count of enums in the set.
     */
    // TODO e4 remove private to prevent private access emulation:
    // private int size;
    int size;

    /**
     * Constructs an empty set.
     */
    public EnumSetImpl(E[] all) {
      this(all, Array.createFrom(all), 0);
    }

    /**
     * Constructs a set taking ownership of the specified set. The size must
     * accurately reflect the number of non-null items in set.
     */
    public EnumSetImpl(E[] all, E[] set, int size) {
      this.all = all;
      this.set = set;
      this.size = size;
    }

    @Override
    public boolean add(E e) {
      if (e == null) {
        throw new NullPointerException();
      }
      int ordinal = e.ordinal();
      if (set[ordinal] == null) {
        set[ordinal] = e;
        ++size;
        return true;
      }
      return false;
    }

    public EnumSet<E> clone() {
      E[] clonedSet = Array.clone(set);
      return new EnumSetImpl<E>(all, clonedSet, size);
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean contains(Object o) {
      if (o instanceof Enum) {
        Enum e = (Enum) o;
        return set[e.ordinal()] == e;
      }
      return false;
    }

    @Override
    public Iterator<E> iterator() {
      return new IteratorImpl();
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean remove(Object o) {
      if (o instanceof Enum) {
        Enum e = (Enum) o;
        if (set[e.ordinal()] == e) {
          set[e.ordinal()] = null;
          --size;
          return true;
        }
      }
      return false;
    }

    @Override
    public int size() {
      return size;
    }

    @Override
    int capacity() {
      return all.length;
    }
  }

  public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
    E[] all = elementType.getEnumConstants();
    E[] set = Array.clone(all);
    return new EnumSetImpl<E>(all, set, all.length);
  }

  public static <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> other) {
    EnumSetImpl<E> s = (EnumSetImpl<E>) other;
    E[] all = s.all;
    E[] oldSet = s.set;
    E[] newSet = Array.createFrom(oldSet);
    for (int i = 0, c = oldSet.length; i < c; ++i) {
      if (oldSet[i] == null) {
        newSet[i] = all[i];
      }
    }
    return new EnumSetImpl<E>(all, newSet, all.length - s.size);
  }

  public static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c) {
    if (c instanceof EnumSet) {
      return EnumSet.copyOf((EnumSet<E>) c);
    }

    Iterator<E> it = c.iterator();
    E first = it.next();
    Class<E> clazz = first.getDeclaringClass();
    EnumSet<E> set = EnumSet.noneOf(clazz);
    set.add(first);
    while (it.hasNext()) {
      set.add(it.next());
    }
    return set;
  }

  public static <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s) {
    return s.clone();
  }

  public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
    E[] all = elementType.getEnumConstants();
    return new EnumSetImpl<E>(all, Array.createFrom(all), 0);
  }

  public static <E extends Enum<E>> EnumSet<E> of(E first) {
    E[] all = first.getDeclaringClass().getEnumConstants();
    E[] set = Array.createFrom(all);
    set[first.ordinal()] = first;
    return new EnumSetImpl<E>(all, set, 1);
  }

  public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest) {
    E[] all = first.getDeclaringClass().getEnumConstants();
    E[] set = Array.createFrom(all);
    set[first.ordinal()] = first;
    for (E e : rest) {
      set[e.ordinal()] = e;
    }
    return new EnumSetImpl<E>(all, set, rest.length + 1);
  }

  public static <E extends Enum<E>> EnumSet<E> range(E from, E to) {
    if (from.compareTo(to) > 0) {
      throw new IllegalArgumentException(from + " > " + to);
    }
    E[] all = from.getDeclaringClass().getEnumConstants();
    E[] set = Array.createFrom(all);

    // Inclusive
    int start = from.ordinal();
    int end = to.ordinal() + 1;
    for (int i = start; i < end; ++i) {
      set[i] = all[i];
    }
    return new EnumSetImpl<E>(all, set, end - start);
  }

  /**
   * Single implementation only.
   */
  EnumSet() {
  }

  public abstract EnumSet<E> clone();

  abstract int capacity();
}
