/**
 * 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 org.waveprotocol.wave.model.util;

import com.google.common.annotations.VisibleForTesting;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;

/**
 * A container implementation that uses copy-on-write semantics to ensure that it is
 * safe to mutate the set while iterating.  The iterator semantics are
 * equivalent to iterating though a snapshot taking at the time of calling
 * {@link #iterator()}; however, a copy-on-write strategy is better suited for
 * infrequently-modified but frequently-iterated container.
 *
 * A minor optimization to copy-on-write is that the underlying container is only
 * copied if an iterator has previously been created for it.  This means that,
 * during a single iteration over a set of size N, a sequence of M operations
 * only costs O(N + M) rather than O(NM) (i.e., the first mutation does the
 * O(N) copy, and subsequent mutations do direct O(1) operations until another
 * iterator is created).
 *
 * NOTE(user): This class is not synchronized.
 *
 */
public final class CopyOnWriteSet<T> implements Iterable<T> {

  /** Factory for blah. */
  interface CollectionFactory {
    /** @return a copy of a collection. */
    <T> Collection<T> copy(Collection<T> source);
  }

  private final static CollectionFactory HASH_SET = new CollectionFactory() {
    @Override
    public <T> Collection<T> copy(Collection<T> source) {
      // HACK(zdwang): We should use a light weight container that does
      // not assume order when we feel confident enough that no one
      // have implicit reliance on order of iteration.
      return new LinkedHashSet<T>(source);
    }
  };

  private final static CollectionFactory LIST_SET = new CollectionFactory() {
    @Override
    public <T> Collection<T> copy(Collection<T> source) {
      return CollectionUtils.newArrayList(source);
    }
  };

  /** Factory for the underlying collection object. */
  private final CollectionFactory factory;

  /** The base collection. Initially refers to a shared empty collection. */
  private Collection<T> contents = Collections.emptySet();

  /** True iff a copy is to be made on the next mutation. */
  private boolean stale = true;

  @VisibleForTesting
  CopyOnWriteSet(CollectionFactory factory) {
    this.factory = factory;
  }

  /** @return a new copy-on-write set, with the default implementation. */
  public static <T> CopyOnWriteSet<T> create() {
    return createHashSet();
  }

  /** @return a new copy-on-write set, backed by a hash set. */
  public static <T> CopyOnWriteSet<T> createHashSet() {
    return new CopyOnWriteSet<T>(HASH_SET);
  }

  /** @return a new copy-on-write set, backed by an array list. */
  public static <T> CopyOnWriteSet<T> createListSet() {
    return new CopyOnWriteSet<T>(LIST_SET);
  }

  /**
   * Replaces the current set with a copy of it.
   */
  private void copy() {
    assert stale;
    contents = factory.copy(contents);
    stale = false;
  }

  /**
   * Adds an item to this set.
   *
   * @param o  object to add
   * @return whether the container changed due to the addition
   */
  public boolean add(T o) {
    if (!stale) {
      return contents.add(o);
    } else {
      if (!contains(o)) {
        copy();
        return contents.add(o);
      } else {
        return false;
      }
    }
  }

  /**
   * Removes an item from this set.
   *
   * @param o  object to remove
   * @return whether the container changed due to the removal
   */
  public boolean remove(T o) {
    if (!stale) {
      return contents.remove(o);
    } else {
      if (contains(o)) {
        copy();
        return contents.remove(o);
      } else {
        return false;
      }
    }
  }

  /**
   * Checks whether an object exists in this collection.
   *
   * @param o  object to check for existence
   */
  public boolean contains(T o) {
    return contents.contains(o);
  }

  @Override
  public Iterator<T> iterator() {
    stale = true;
    return contents.iterator();
  }

  /**
   * Clears this collection.
   */
  public void clear() {
    contents = Collections.emptySet();
    stale = true;
  }

  /**
   * @return true if this collection is empty.
   */
  public boolean isEmpty() {
    return contents.isEmpty();
  }

  /**
   * @return the size of this collection.
   */
  public int size() {
    return contents.size();
  }

  @Override
  public String toString() {
    return contents.toString();
  }
}
