package edu.stanford.nlp.util;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A factory class for vending different sorts of Maps.
 *
 * @author Dan Klein (klein@cs.stanford.edu)
 * @author Kayur Patel (kdpatel@cs)
 */
public abstract class MapFactory<K,V> implements Serializable {

  // allow people to write subclasses
  protected MapFactory() {
  }

  private static final long serialVersionUID = 4529666940763477360L;

  @SuppressWarnings("unchecked")
  public static final MapFactory HASH_MAP_FACTORY = new HashMapFactory();

  @SuppressWarnings("unchecked")
  public static final MapFactory IDENTITY_HASH_MAP_FACTORY = new IdentityHashMapFactory();

  @SuppressWarnings("unchecked")
  private static final MapFactory WEAK_HASH_MAP_FACTORY = new WeakHashMapFactory();

  @SuppressWarnings("unchecked")
  private static final MapFactory TREE_MAP_FACTORY = new TreeMapFactory();

  @SuppressWarnings("unchecked")
  private static final MapFactory LINKED_HASH_MAP_FACTORY = new LinkedHashMapFactory();

  @SuppressWarnings("unchecked")
  private static final MapFactory ARRAY_MAP_FACTORY = new ArrayMapFactory();

  public static final MapFactory CONCURRENT_MAP_FACTORY = new ConcurrentMapFactory();

  /** Return a MapFactory that returns a HashMap.
   *  <i>Implementation note: This method uses the same trick as the methods
   *  like emptyMap() introduced in the Collections class in JDK1.5 where
   *  callers can call this method with apparent type safety because this
   *  method takes the hit for the cast.
   *
   *  @return A MapFactory that makes a HashMap.
   */
  @SuppressWarnings("unchecked")
  public static <K,V> MapFactory<K,V> hashMapFactory() {
    return HASH_MAP_FACTORY;
  }

  /** Return a MapFactory that returns an IdentityHashMap.
   *  <i>Implementation note: This method uses the same trick as the methods
   *  like emptyMap() introduced in the Collections class in JDK1.5 where
   *  callers can call this method with apparent type safety because this
   *  method takes the hit for the cast.
   *
   *  @return A MapFactory that makes a HashMap.
   */
  @SuppressWarnings("unchecked")
  public static <K,V> MapFactory<K,V> identityHashMapFactory() {
    return IDENTITY_HASH_MAP_FACTORY;
  }

  /** Return a MapFactory that returns a WeakHashMap.
   *  <i>Implementation note: This method uses the same trick as the methods
   *  like emptyMap() introduced in the Collections class in JDK1.5 where
   *  callers can call this method with apparent type safety because this
   *  method takes the hit for the cast.
   *
   *  @return A MapFactory that makes a WeakHashMap.
   */
  @SuppressWarnings("unchecked")
  public static <K,V> MapFactory<K,V> weakHashMapFactory() {
    return WEAK_HASH_MAP_FACTORY;
  }

  /** Return a MapFactory that returns a TreeMap.
   *  <i>Implementation note: This method uses the same trick as the methods
   *  like emptyMap() introduced in the Collections class in JDK1.5 where
   *  callers can call this method with apparent type safety because this
   *  method takes the hit for the cast.
   *
   *  @return A MapFactory that makes an TreeMap.
   */
  @SuppressWarnings("unchecked")
  public static <K,V> MapFactory<K,V> treeMapFactory() {
    return TREE_MAP_FACTORY;
  }

  /**
   * Return a MapFactory that returns a TreeMap with the given Comparator.
   */
  public static <K,V> MapFactory<K,V> treeMapFactory(Comparator<? super K> comparator) {
    return new TreeMapFactory<>(comparator);
  }

  /** Return a MapFactory that returns an LinkedHashMap.
   *  <i>Implementation note: This method uses the same trick as the methods
   *  like emptyMap() introduced in the Collections class in JDK1.5 where
   *  callers can call this method with apparent type safety because this
   *  method takes the hit for the cast.
   *
   *  @return A MapFactory that makes an LinkedHashMap.
   */
  @SuppressWarnings("unchecked")
  public static <K,V> MapFactory<K,V> linkedHashMapFactory() {
    return LINKED_HASH_MAP_FACTORY;
  }

  /** Return a MapFactory that returns an ArrayMap.
   *  <i>Implementation note: This method uses the same trick as the methods
   *  like emptyMap() introduced in the Collections class in JDK1.5 where
   *  callers can call this method with apparent type safety because this
   *  method takes the hit for the cast.
   *
   *  @return A MapFactory that makes an ArrayMap.
   */
  @SuppressWarnings("unchecked")
  public static <K,V> MapFactory<K,V> arrayMapFactory() {
    return ARRAY_MAP_FACTORY;
  }



  private static class HashMapFactory<K,V> extends MapFactory<K,V> {

    private static final long serialVersionUID = -9222344631596580863L;

    @Override
    public Map<K,V> newMap() {
      return Generics.newHashMap();
    }

    @Override
    public Map<K,V> newMap(int initCapacity) {
      return Generics.newHashMap(initCapacity);
    }

    @Override
    public Set<K> newSet() {
      return Generics.newHashSet();
    }

    @Override
    public Set<K> newSet(Collection<K> init) {
      return Generics.newHashSet(init);
    }

    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map) {
      map = Generics.newHashMap();
      return map;
    }

    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map, int initCapacity) {
      map = Generics.newHashMap(initCapacity);
      return map;
    }

  } // end class HashMapFactory


  private static class IdentityHashMapFactory<K,V> extends MapFactory<K,V> {

    private static final long serialVersionUID = -9222344631596580863L;

    @Override
    public Map<K,V> newMap() {
      return new IdentityHashMap<>();
    }

    @Override
    public Map<K,V> newMap(int initCapacity) {
      return new IdentityHashMap<>(initCapacity);
    }

    @Override
    public Set<K> newSet() {
      return Collections.newSetFromMap(new IdentityHashMap<>());
    }

    @Override
    public Set<K> newSet(Collection<K> init) {
      Set<K> set =  Collections.newSetFromMap(new IdentityHashMap<>());  // nothing more efficient to be done here...
      set.addAll(init);
      return set;
    }

    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map) {
      map = new IdentityHashMap<>();
      return map;
    }

    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map, int initCapacity) {
      map = new IdentityHashMap<>(initCapacity);
      return map;
    }

  } // end class IdentityHashMapFactory


  private static class WeakHashMapFactory<K,V> extends MapFactory<K,V> {

    private static final long serialVersionUID = 4790014244304941000L;

    @Override
    public Map<K,V> newMap() {
      return new WeakHashMap<>();
    }

    @Override
    public Map<K,V> newMap(int initCapacity) {
      return new WeakHashMap<>(initCapacity);
    }

    @Override
    public Set<K> newSet() {
      return Collections.newSetFromMap(new WeakHashMap<>());
    }

    @Override
    public Set<K> newSet(Collection<K> init) {
      Set<K> set = Collections.newSetFromMap(new WeakHashMap<>());
      set.addAll(init);
      return set;
    }


    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map) {
      map = new WeakHashMap<>();
      return map;
    }

    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map, int initCapacity) {
      map = new WeakHashMap<>(initCapacity);
      return map;
    }

  } // end class WeakHashMapFactory


  private static class TreeMapFactory<K,V> extends MapFactory<K,V> {

    private static final long serialVersionUID = -9138736068025818670L;

    private final Comparator<? super K> comparator;

    public TreeMapFactory() {
      this.comparator = null;
    }

    public TreeMapFactory(Comparator<? super K> comparator) {
      this.comparator = comparator;
    }

    @Override
    public Map<K,V> newMap() {
      return comparator == null ? new TreeMap<>() : new TreeMap<>(comparator);
    }

    @Override
    public Map<K,V> newMap(int initCapacity) {
      return newMap();
    }

    @Override
    public Set<K> newSet() {
      return comparator == null ? new TreeSet<>() : new TreeSet<>(comparator);
    }

    @Override
    public Set<K> newSet(Collection<K> init) {
      return new TreeSet<>(init);
    }


    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map) {
      if (comparator == null) {
        throw new UnsupportedOperationException();
      }
      map = new TreeMap<>();
      return map;
    }

    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map, int initCapacity) {
      if (comparator == null) {
        throw new UnsupportedOperationException();
      }
      map = new TreeMap<>();
      return map;
    }

  } // end class TreeMapFactory

  private static class LinkedHashMapFactory<K,V> extends MapFactory<K,V> {

    private static final long serialVersionUID = -9138736068025818671L;

    @Override
    public Map<K,V> newMap() {
      return new LinkedHashMap<>();
    }

    @Override
    public Map<K,V> newMap(int initCapacity) {
      return newMap();
    }

    @Override
    public Set<K> newSet() {
      return new LinkedHashSet<>();
    }

    @Override
    public Set<K> newSet(Collection<K> init) {
      return new LinkedHashSet<>(init);
    }


    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map) {
      map = new LinkedHashMap<>();
      return map;
    }

    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map, int initCapacity) {
      map = new LinkedHashMap<>();
      return map;
    }

  } // end class LinkedHashMapFactory


  private static class ArrayMapFactory<K,V> extends MapFactory<K,V> {

    private static final long serialVersionUID = -5855812734715185523L;

    @Override
    public Map<K,V> newMap() {
      return new ArrayMap<>();
    }

    @Override
    public Map<K,V> newMap(int initCapacity) {
      return new ArrayMap<>(initCapacity);
    }

    @Override
    public Set<K> newSet() {
      return new ArraySet<>();
    }

    @Override
    public Set<K> newSet(Collection<K> init) {
      return new ArraySet<>();
    }

    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1, V1> map) {
      return new ArrayMap<>();
    }

    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map, int initCapacity) {
      map = new ArrayMap<>(initCapacity);
      return map;
    }

  } // end class ArrayMapFactory


  private static class ConcurrentMapFactory<K,V> extends MapFactory<K,V> {

    private static final long serialVersionUID = -5855812734715185523L;

    @Override
    public Map<K,V> newMap() {
      return new ConcurrentHashMap<>();
    }

    @Override
    public Map<K,V> newMap(int initCapacity) {
      return new ConcurrentHashMap<>(initCapacity);
    }

    @Override
    public Set<K> newSet() {
      return Collections.newSetFromMap(new ConcurrentHashMap<>());
    }

    @Override
    public Set<K> newSet(Collection<K> init) {
      Set<K> set = Collections.newSetFromMap(new ConcurrentHashMap<>());
      set.addAll(init);
      return set;
    }

    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1, V1> map) {
      return new ConcurrentHashMap<>();
    }

    @Override
    public <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map, int initCapacity) {
      map = new ConcurrentHashMap<>(initCapacity);
      return map;
    }

  } // end class ConcurrentMapFactory

  /**
   * Returns a new non-parameterized map of a particular sort.
   *
   * @return A new non-parameterized map of a particular sort
   */
  public abstract Map<K,V> newMap();

  /**
   * Returns a new non-parameterized map of a particular sort with an initial capacity.
   *
   * @param initCapacity initial capacity of the map
   * @return A new non-parameterized map of a particular sort with an initial capacity
   */
  public abstract Map<K,V> newMap(int initCapacity);

  /**
   * A set with the same {@code K} parameterization of the Maps.
   */
  public abstract Set<K> newSet();

  /**
   * A set with the same {@code K} parameterization, but initialized to the given collection.
   */
  public abstract Set<K> newSet(Collection<K> init);

  /**
   * A method to get a parameterized (genericized) map out.
   *
   * @param map A type-parameterized {@link Map} argument
   * @return A {@link Map} with type-parameterization identical to that of
   *         the argument.
   */
  public abstract <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map);

  public abstract <K1, V1> Map<K1, V1> setMap(Map<K1,V1> map, int initCapacity);

}
