/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.jikesrvm.util;

import java.util.Iterator;
import java.util.NoSuchElementException;

import org.jikesrvm.VM;
import org.jikesrvm.mm.mminterface.MemoryManager;

/**
 * Common super class for all VM hash maps
 */
abstract class AbstractHashMapRVM<K, V> {

  protected static final int DEFAULT_SIZE = 7;
  private static final float LOAD = 3;
  protected AbstractBucket<K, V>[] buckets;
  protected int numElems = 0;

  abstract static class AbstractBucket<K, V> {
    abstract AbstractBucket<K, V> getNext();

    /**
     * Change the next bucket after this bucket, possibly constructing a new
     * abstract bucket
     */
    abstract AbstractBucket<K, V> setNext(AbstractBucket<K, V> n);

    abstract K getKey();

    abstract V getValue();

    abstract void setValue(V v);
  }

  /**
   * Are two keys the same?
   */
  abstract boolean same(K key1, K key2);

  abstract int hashTheKey(K key);

  abstract AbstractBucket<K,V> createNewBucket(K k, V v, AbstractBucket<K,V> n);

  AbstractHashMapRVM(int size) {
    buckets = newBucketArray(size);
  }

  public final int size() {
    return numElems;
  }

  public final V get(K key) {
    int bucketIdx = bucketIndex(key, buckets.length);
    AbstractBucket<K, V> cur = buckets[bucketIdx];
    while (cur != null && !same(cur.getKey(), key)) {
      cur = cur.getNext();
    }
    if (cur == null) {
      return null;
    } else {
      return cur.getValue();
    }
  }

  /**
   * Advise against growing the buckets if they are immortal, as it will lead
   * to multiple sets of buckets that will be scanned
   */
  private boolean growMapAllowed() {
    return !VM.runningVM || !MemoryManager.isImmortal(buckets);
  }

  public final V put(K key, V value) {
    if (VM.VerifyAssertions) VM._assert(key != null);
    if (growMapAllowed() && numElems > (buckets.length * LOAD)) {
      growMap();
    }

    int bucketIdx = bucketIndex(key, buckets.length);
    AbstractBucket<K, V> cur = buckets[bucketIdx];
    while (cur != null && !same(cur.getKey(), key)) {
      cur = cur.getNext();
    }
    if (cur != null) {
      // replacing existing <key,value> pair
      V tmp = cur.getValue();
      cur.setValue(value);
      return tmp;
    } else {
      buckets[bucketIdx] = createNewBucket(key, value, buckets[bucketIdx]);
      numElems++;
      return null;
    }
  }

  @SuppressWarnings("unchecked")
  private AbstractBucket<K, V>[] newBucketArray(int size) {
    return new AbstractBucket[size];
  }

  private void growMap() {
    AbstractBucket<K, V>[] newBuckets = newBucketArray(buckets.length * 2 + 1);
    // Iterate over all buckets
    for (AbstractBucket<K, V> cur : buckets) {
      // Iterate over all values in a bucket
      while (cur != null) {
        AbstractBucket<K, V> next = cur.getNext();
        int newIdx = bucketIndex(cur.getKey(), newBuckets.length);
        cur = cur.setNext(newBuckets[newIdx]);
        newBuckets[newIdx] = cur;
        cur = next;
      }
    }
    buckets = newBuckets;
  }

  public final V remove(K key) {
    int bucketIdx = bucketIndex(key, buckets.length);
    AbstractBucket<K, V> cur = buckets[bucketIdx];
    AbstractBucket<K, V> prev = null;
    while (cur != null && !same(cur.getKey(), key)) {
      prev = cur;
      cur = cur.getNext();
    }
    if (cur != null) {
      if (prev == null) {
        // removing first bucket in chain.
        buckets[bucketIdx] = cur.getNext();
      } else {
        AbstractBucket<K, V> newPrev = prev.setNext(cur.getNext());
        if (newPrev != prev) {
          throw new UnsupportedOperationException();
        }
      }
      numElems--;
      return cur.getValue();
    } else {
      return null;
    }
  }

  public final Iterator<V> valueIterator() {
    return new ValueIterator();
  }

  public final Iterator<K> keyIterator() {
    return new KeyIterator();
  }

  private int bucketIndex(K key, int divisor) {
    if (key == null) {
      return 0;
    } else {
      return (hashTheKey(key) & 0x7fffffff) % divisor;
    }
  }

  /**
   * @return a java.lang.Iterable for the values in the hash map
   */
  public final Iterable<V> values() {
    return new Iterable<V>() {
      public Iterator<V> iterator() {
        return AbstractHashMapRVM.this.valueIterator();
      }
    };
  }

  /**
   *
   * @return a java.lang.Iterable for the values in the hash map
   */
  public final Iterable<K> keys() {
    return new Iterable<K>() {
      public Iterator<K> iterator() {
        return AbstractHashMapRVM.this.keyIterator();
      }
    };
  }

  /**
   * Iterator types for key and value
   */
  private class BucketIterator {
    private int bucketIndex = 0;
    private AbstractBucket<K, V> next = null;
    private int numVisited = 0;

    public AbstractBucket<K, V> nextBucket() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }

      while (next == null) {
        next = buckets[bucketIndex++];
      }
      AbstractBucket<K, V> ans = next;
      next = ans.getNext();
      numVisited++;
      return ans;
    }

    public boolean hasNext() {
      return numVisited < numElems;
    }

    public void remove() {
      throw new UnsupportedOperationException();
    }
  }

  private final class KeyIterator extends BucketIterator implements Iterator<K> {
    public K next() {
      AbstractBucket<K, V> cur = nextBucket();
      return cur.getKey();
    }
  }

  private final class ValueIterator extends BucketIterator implements Iterator<V> {
    public V next() {
      AbstractBucket<K, V> cur = nextBucket();
      return cur.getValue();
    }
  }
}
