/*
 * Copyright 2009 Benjamin Manes
 *
 * 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.googlecode.concurrentlinkedhashmap.caches;

import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A {@link ConcurrentMap} with a doubly-linked list running through its
 * entries.
 * <p>
 * This class provides the same semantics as a {@link ConcurrentHashMap} in
 * terms of iterators, acceptable keys, and concurrency characteristics, but
 * perform slightly worse due to the added expense of maintaining the linked
 * list. It differs from {@link java.util.LinkedHashMap} in that it does not
 * provide predictable iteration order.
 * <p>
 * This map is intended to be used for caches and provides the following
 * eviction policies:
 * <ul>
 * <li>First-in, First-out: Also known as insertion order. This policy has
 * excellent concurrency characteristics and an adequate hit rate.
 * <li>Second-chance: An enhanced FIFO policy that marks entries that have been
 * retrieved and saves them from being evicted until the next pass. This
 * enhances the FIFO policy by making it aware of "hot" entries, which increases
 * its hit rate to be equal to an LRU's under normal workloads. In the worst
 * case, where all entries have been saved, this policy degrades to a FIFO.
 * <li>Least Recently Used: An eviction policy based on the observation that
 * entries that have been used recently will likely be used again soon. This
 * policy provides a good approximation of an optimal algorithm, but suffers by
 * being expensive to maintain. The cost of reordering entries on the list
 * during every access operation reduces the concurrency and performance
 * characteristics of this policy.
 * </ul>
 *
 * @author <a href="mailto:ben.manes@reardencommerce.com">Ben Manes</a>
 */
@SuppressWarnings("rawtypes")
public final class ProductionMap<K, V> extends AbstractMap<K, V>
    implements ConcurrentMap<K, V>, Serializable {
  private static final EvictionListener<?, ?> nullListener =
      new EvictionListener<Object, Object>() {
        @Override public void onEviction(Object key, Object value) {
        }
      };
  private static final long serialVersionUID = 8350170357874293408L;
  final ConcurrentMap<K, Node<K, V>> data;
  final EvictionListener<K, V> listener;
  final AtomicInteger capacity;
  final EvictionPolicy policy;
  final AtomicInteger length;
  final Node<K, V> sentinel;
  final Lock lock;

  @SuppressWarnings("unchecked")
  public ProductionMap(EvictionPolicy policy, CacheFactory builder) {
    this.data = new ConcurrentHashMap<K, Node<K, V>>(
        builder.initialCapacity,  0.75f, builder.concurrencyLevel);
    this.capacity = new AtomicInteger(builder.maximumCapacity);
    this.listener = (EvictionListener<K, V>) nullListener;
    this.length = new AtomicInteger();
    this.lock = new ReentrantLock();
    this.sentinel = new Node<K, V>(lock);
    this.policy = policy;
  }

  /**
   * Determines whether the map has exceeded its capacity.
   *
   * @return Whether the map has overflowed and an entry should be evicted.
   */
  private boolean isOverflow() {
    return size() > capacity();
  }

  /**
   * Sets the maximum capacity of the map and eagerly evicts entries until it
   * shrinks to the appropriate size.
   *
   * @param capacity The maximum capacity of the map.
   */
  public void setCapacity(int capacity) {
    if (capacity < 0) {
      throw new IllegalArgumentException();
    }
    this.capacity.set(capacity);
    while (evict()) {
    }
  }

  /**
   * Retrieves the maximum capacity of the map.
   *
   * @return The maximum capacity.
   */
  public int capacity() {
    return capacity.get();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int size() {
    int size = length.get();
    return (size >= 0) ? size : 0;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void clear() {
    for (K key : keySet()) {
      remove(key);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean containsKey(Object key) {
    return data.containsKey(key);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean containsValue(Object value) {
    if (value == null) {
      throw new IllegalArgumentException();
    }
    return data.containsValue(new Node<Object, Object>(null, value, null, lock));
  }

  /**
   * Evicts a single entry if the map exceeds the maximum capacity.
   */
  private boolean evict() {
    while (isOverflow()) {
      Node<K, V> node = sentinel.getNext();
      if (node == sentinel) {
        return false;
      } else if (policy.onEvict(this, node)) {
        // Attempt to remove the node if it's still available
        if (data.remove(node.getKey(), new Identity(node))) {
          length.decrementAndGet();
          node.remove();
          listener.onEviction(node.getKey(), node.getValue());
          return true;
        }
      }
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public V get(Object key) {
    Node<K, V> node = data.get(key);
    if (node != null) {
      policy.onAccess(this, node);
      return node.getValue();
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public V put(K key, V value) {
    if (value == null) {
      throw new IllegalArgumentException();
    }
    Node<K, V> old = putIfAbsent(new Node<K, V>(key, value, sentinel, lock));
    return (old == null) ? null : old.getAndSetValue(value);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public V putIfAbsent(K key, V value) {
    if (value == null) {
      throw new IllegalArgumentException();
    }
    Node<K, V> old = putIfAbsent(new Node<K, V>(key, value, sentinel, lock));
    return (old == null) ? null : old.getValue();
  }

  /**
   * Adds a node to the list and data store if it does not already exist.
   *
   * @param node An unlinked node to add.
   * @return The previous value in the data store.
   */
  private Node<K, V> putIfAbsent(Node<K, V> node) {
    Node<K, V> old = data.putIfAbsent(node.getKey(), node);
    if (old == null) {
      length.incrementAndGet();
      node.appendToTail();
      evict();
    } else {
      policy.onAccess(this, old);
    }
    return old;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public V remove(Object key) {
    Node<K, V> node = data.remove(key);
    if (node == null) {
      return null;
    }
    length.decrementAndGet();
    node.remove();
    return node.getValue();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean remove(Object key, Object value) {
    Node<K, V> node = data.get(key);
    if ((node != null) && node.value.equals(value) && data.remove(key, new Identity(node))) {
      length.decrementAndGet();
      node.remove();
      return true;
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public V replace(K key, V value) {
    if (value == null) {
      throw new IllegalArgumentException();
    }
    Node<K, V> node = data.get(key);
    return (node == null) ? null : node.getAndSetValue(value);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean replace(K key, V oldValue, V newValue) {
    if (newValue == null) {
      throw new IllegalArgumentException();
    }
    Node<K, V> node = data.get(key);
    return (node == null) ? false : node.casValue(oldValue, newValue);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Set<K> keySet() {
    return new KeySet();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Collection<V> values() {
    return new Values();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Set<Entry<K, V>> entrySet() {
    return new EntrySet();
  }

  /**
   * A listener registered for notification when an entry is evicted.
   */
  public interface EvictionListener<K, V> {

    /**
     * A call-back notification that the entry was evicted.
     *
     * @param key The evicted key.
     * @param value The evicted value.
     */
    void onEviction(K key, V value);
  }

  /**
   * The replacement policy to apply to determine which entry to discard when
   * the capacity has been reached.
   */
  public enum EvictionPolicy {

    /**
     * Evicts entries based on insertion order.
     */
    FIFO() {
      @Override
      <K, V> void onAccess(ProductionMap<K, V> map, Node<K, V> node) {
        // do nothing
      }

      @Override
      <K, V> boolean onEvict(ProductionMap<K, V> map, Node<K, V> node) {
        return true;
      }
    },

    /**
     * Evicts entries based on insertion order, but gives an entry a "second
     * chance" if it has been requested recently.
     */
    SECOND_CHANCE() {
      @Override
      <K, V> void onAccess(ProductionMap<K, V> map, Node<K, V> node) {
        node.setMarked(true);
      }

      @Override
      <K, V> boolean onEvict(ProductionMap<K, V> map, Node<K, V> node) {
        if (node.isMarked()) {
          node.moveToTail();
          node.setMarked(false);
          return false;
        }
        return true;
      }
    },

    /**
     * Evicts entries based on how recently they are used, with the least recent
     * evicted first.
     */
    LRU() {
      @Override
      <K, V> void onAccess(ProductionMap<K, V> map, Node<K, V> node) {
        node.moveToTail();
      }

      @Override
      <K, V> boolean onEvict(ProductionMap<K, V> map, Node<K, V> node) {
        return true;
      }
    };

    /**
     * Performs any operations required by the policy after a node was
     * successfully retrieved.
     */
    abstract <K, V> void onAccess(ProductionMap<K, V> map, Node<K, V> node);

    /**
     * Determines whether to evict the node at the head of the list.
     */
    abstract <K, V> boolean onEvict(ProductionMap<K, V> map, Node<K, V> node);
  }

  /**
   * A node on the double-linked list. This list cross-cuts the data store.
   */
  @SuppressWarnings("unchecked")
  static final class Node<K, V> implements Serializable {
    private static final long serialVersionUID = 1461281468985304519L;
    private static final AtomicReferenceFieldUpdater<Node, Object> valueUpdater =
        AtomicReferenceFieldUpdater.newUpdater(Node.class, Object.class, "value");
    private static final Node UNLINKED = new Node(null);

    private final K key;
    private final Lock lock;
    private final Node<K, V> sentinel;

    private volatile V value;
    private volatile boolean marked;
    private volatile Node<K, V> prev;
    private volatile Node<K, V> next;

    /**
     * Creates a new sentinel node.
     */
    public Node(Lock lock) {
      this.sentinel = this;
      this.value = null;
      this.lock = lock;
      this.prev = this;
      this.next = this;
      this.key = null;
    }

    /**
     * Creates a new, unlinked node.
     */
    public Node(K key, V value, Node<K, V> sentinel, Lock lock) {
      this.sentinel = sentinel;
      this.next = UNLINKED;
      this.prev = UNLINKED;
      this.value = value;
      this.lock = lock;
      this.key = key;
    }

    /**
     * Appends the node to the tail of the list.
     */
    public void appendToTail() {
      lock.lock();
      try {
        // Allow moveToTail() to no-op or removal to spin-wait
        next = sentinel;

        // Read the tail on the stack to avoid unnecessary volatile reads
        final Node<K, V> tail = sentinel.prev;
        sentinel.prev = this;
        tail.next = this;
        prev = tail;
      } finally {
        lock.unlock();
      }
    }

    /**
     * Removes the node from the list.
     * <p>
     * If the node has not yet been appended to the tail it will wait for that
     * operation to complete.
     */
    public void remove() {
      for (;;) {
        if (isUnlinked()) {
          continue; // await appendToTail()
        }

        lock.lock();
        try {
          if (isUnlinked()) {
            continue; // await appendToTail()
          }
          prev.next = next;
          next.prev = prev;
          next = UNLINKED; // mark as unlinked
        } finally {
          lock.unlock();
        }
        return;
      }
    }

    /**
     * Moves the node to the tail.
     * <p>
     * If the node has been unlinked or is already at the tail, no-ops.
     */
    public void moveToTail() {
      if (isTail() || isUnlinked()) {
        return;
      }
      lock.lock();
      try {
        if (isTail() || isUnlinked()) {
          return;
        }
        // unlink
        prev.next = next;
        next.prev = prev;

        // link
        next = sentinel; // ordered for isTail()
        prev = sentinel.prev;
        sentinel.prev = this;
        prev.next = this;
      } finally {
        lock.unlock();
      }
    }

    /**
     * Checks whether the node is linked on the list chain.
     *
     * @return Whether the node has not yet been linked on the list.
     */
    public boolean isUnlinked() {
      return (next == UNLINKED);
    }

    /**
     * Checks whether the node is the last linked on the list chain.
     *
     * @return Whether the node is at the tail of the list.
     */
    public boolean isTail() {
      return (next == sentinel);
    }

    /*
     * Key operators
     */
    public K getKey() {
      return key;
    }

    /*
     * Value operators
     */
    public V getValue() {
      return (V) valueUpdater.get(this);
    }

    public V getAndSetValue(V value) {
      return (V) valueUpdater.getAndSet(this, value);
    }

    public boolean casValue(V expect, V update) {
      return valueUpdater.compareAndSet(this, expect, update);
    }

    /*
     * Previous node operators
     */
    public Node<K, V> getPrev() {
      return prev;
    }

    /*
     * Next node operators
     */
    public Node<K, V> getNext() {
      return next;
    }

    /*
     * Access frequency operators
     */
    public boolean isMarked() {
      return marked;
    }

    public void setMarked(boolean marked) {
      this.marked = marked;
    }

    /**
     * Only ensures that the values are equal, as the key may be <tt>null</tt>
     * for look-ups.
     */
    @Override
    public boolean equals(Object obj) {
      if (obj == this) {
        return true;
      } else if (!(obj instanceof Node)) {
        return false;
      }
      V value = getValue();
      Node<?, ?> node = (Node<?, ?>) obj;
      return (value == null) ? (node.getValue() == null) : value.equals(node.getValue());
    }

    @Override
    public int hashCode() {
      return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode());
    }
  }

  /**
   * Allows {@link #equals(Object)} to compare using object identity.
   */
  private static final class Identity {
    private final Object delegate;

    public Identity(Object delegate) {
      this.delegate = delegate;
    }

    @Override
    public boolean equals(Object o) {
      return (o == delegate);
    }
  }

  /**
   * An adapter to safely externalize the keys.
   */
  private final class KeySet extends AbstractSet<K> {
    private final ProductionMap<K, V> map = ProductionMap.this;

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

    @Override
    public void clear() {
      map.clear();
    }

    @Override
    public Iterator<K> iterator() {
      return new KeyIterator();
    }

    @Override
    public boolean contains(Object obj) {
      return map.containsKey(obj);
    }

    @Override
    public boolean remove(Object obj) {
      return (map.remove(obj) != null);
    }

    @Override
    public Object[] toArray() {
      return map.data.keySet().toArray();
    }

    @Override
    public <T> T[] toArray(T[] array) {
      return map.data.keySet().toArray(array);
    }
  }

  /**
   * An adapter to safely externalize the keys.
   */
  private final class KeyIterator implements Iterator<K> {
    private final EntryIterator iterator =
        new EntryIterator(ProductionMap.this.data.values().iterator());

    @Override
    public boolean hasNext() {
      return iterator.hasNext();
    }

    @Override
    public K next() {
      return iterator.next().getKey();
    }

    @Override
    public void remove() {
      iterator.remove();
    }
  }

  /**
   * An adapter to represent the data store's values in the external type.
   */
  private final class Values extends AbstractCollection<V> {
    private final ProductionMap<K, V> map = ProductionMap.this;

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

    @Override
    public void clear() {
      map.clear();
    }

    @Override
    public Iterator<V> iterator() {
      return new ValueIterator();
    }

    @Override
    public boolean contains(Object o) {
      return map.containsValue(o);
    }

    @Override
    public Object[] toArray() {
      Collection<V> values = new ArrayList<V>(size());
      for (V value : this) {
        values.add(value);
      }
      return values.toArray();
    }

    @Override
    public <T> T[] toArray(T[] array) {
      Collection<V> values = new ArrayList<V>(size());
      for (V value : this) {
        values.add(value);
      }
      return values.toArray(array);
    }
  }

  /**
   * An adapter to represent the data store's values in the external type.
   */
  private final class ValueIterator implements Iterator<V> {
    private final EntryIterator iterator =
        new EntryIterator(ProductionMap.this.data.values().iterator());

    @Override
    public boolean hasNext() {
      return iterator.hasNext();
    }

    @Override
    public V next() {
      return iterator.next().getValue();
    }

    @Override
    public void remove() {
      iterator.remove();
    }
  }

  /**
   * An adapter to represent the data store's entry set in the external type.
   */
  private final class EntrySet extends AbstractSet<Entry<K, V>> {
    private final ProductionMap<K, V> map = ProductionMap.this;

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

    @Override
    public void clear() {
      map.clear();
    }

    @Override
    public Iterator<Entry<K, V>> iterator() {
      return new EntryIterator(map.data.values().iterator());
    }

    @Override
    public boolean contains(Object obj) {
      if (!(obj instanceof Entry<?, ?>)) {
        return false;
      }
      Entry<?, ?> entry = (Entry<?, ?>) obj;
      Node<K, V> node = map.data.get(entry.getKey());
      return (node != null) && (node.value.equals(entry.getValue()));
    }

    @Override
    public boolean add(Entry<K, V> entry) {
      return (map.putIfAbsent(entry.getKey(), entry.getValue()) == null);
    }

    @Override
    public boolean remove(Object obj) {
      if (!(obj instanceof Entry<?, ?>)) {
        return false;
      }
      Entry<?, ?> entry = (Entry<?, ?>) obj;
      return map.remove(entry.getKey(), entry.getValue());
    }

    @Override
    public Object[] toArray() {
      Collection<Entry<K, V>> entries = new ArrayList<Entry<K, V>>(size());
      for (Entry<K, V> entry : this) {
        entries.add(new SimpleEntry<K, V>(entry));
      }
      return entries.toArray();
    }

    @Override
    public <T> T[] toArray(T[] array) {
      Collection<Entry<K, V>> entries = new ArrayList<Entry<K, V>>(size());
      for (Entry<K, V> entry : this) {
        entries.add(new SimpleEntry<K, V>(entry));
      }
      return entries.toArray(array);
    }
  }

  /**
   * An adapter to represent the data store's entry iterator in the external
   * type.
   */
  private final class EntryIterator implements Iterator<Entry<K, V>> {
    private final Iterator<Node<K, V>> iterator;
    private Entry<K, V> current;

    public EntryIterator(Iterator<Node<K, V>> iterator) {
      this.iterator = iterator;
    }

    @Override
    public boolean hasNext() {
      return iterator.hasNext();
    }

    @Override
    public Entry<K, V> next() {
      current = new NodeEntry(iterator.next());
      return current;
    }

    @Override
    public void remove() {
      if (current == null) {
        throw new IllegalStateException();
      }
      ProductionMap.this.remove(current.getKey(), current.getValue());
      current = null;
    }
  }

  /**
   * An entry that is tied to the map instance to allow updates through the
   * entry or the map to be visible.
   */
  private final class NodeEntry implements Entry<K, V> {
    private final ProductionMap<K, V> map = ProductionMap.this;
    private final Node<K, V> node;

    public NodeEntry(Node<K, V> node) {
      this.node = node;
    }

    @Override
    public K getKey() {
      return node.getKey();
    }

    @Override
    public V getValue() {
      if (node.isUnlinked()) {
        V value = map.get(getKey());
        if (value != null) {
          return value;
        }
      }
      return node.getValue();
    }

    @Override
    public V setValue(V value) {
      return map.replace(getKey(), value);
    }

    @Override
    public boolean equals(Object obj) {
      if (obj == this) {
        return true;
      } else if (!(obj instanceof Entry<?, ?>)) {
        return false;
      }
      Entry<?, ?> entry = (Entry<?, ?>) obj;
      return eq(getKey(), entry.getKey()) && eq(getValue(), entry.getValue());
    }

    @Override
    public int hashCode() {
      K key = getKey();
      V value = getValue();
      return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode());
    }

    @Override
    public String toString() {
      return getKey() + "=" + getValue();
    }

    private boolean eq(Object o1, Object o2) {
      return (o1 == null) ? (o2 == null) : o1.equals(o2);
    }
  }
}
