package org.mvel2.util;

import java.util.*;

/**
 * As most use-cases of the VariableResolverFactory's rely on Maps, this is meant to implement a simple wrapper
 * which records index positions for use by the optimizing facilities.
 *
 * This wrapper also ensures that the Map is only additive.  You cannot remove an element once it's been added.
 * While this may seem like an odd limitation, it is consistent with the language semantics. (ie. it's not possible
 * to delete a variable at runtime once it's been declared).
 *
 * @author Mike Brock
 */
public class SimpleIndexHashMapWrapper<K, V> implements Map<K, V> {
  private int indexCounter;
  private final Map<K, ValueContainer<K, V>> wrappedMap;
  private final ArrayList<ValueContainer<K, V>> indexBasedLookup;

  public SimpleIndexHashMapWrapper() {
    this.wrappedMap = new HashMap<K, ValueContainer<K, V>>();
    this.indexBasedLookup = new ArrayList<ValueContainer<K, V>>();
  }

  public SimpleIndexHashMapWrapper(SimpleIndexHashMapWrapper<K, V> wrapper, boolean allocateOnly) {
    this.indexBasedLookup = new ArrayList<ValueContainer<K, V>>(wrapper.indexBasedLookup.size());
    this.wrappedMap = new HashMap<K, ValueContainer<K, V>>();

    ValueContainer<K, V> vc;
    int index = 0;
    if (allocateOnly) {
      for (ValueContainer<K, V> key : wrapper.indexBasedLookup) {
        vc = new ValueContainer<K, V>(index++, key.getKey(), null);
        indexBasedLookup.add(vc);
        wrappedMap.put(key.getKey(), vc);
      }
    }
    else {
      for (ValueContainer<K, V> key : wrapper.indexBasedLookup) {
        vc = new ValueContainer<K, V>(index++, key.getKey(), key.getValue());
        indexBasedLookup.add(vc);
        wrappedMap.put(key.getKey(), vc);
      }
    }
  }


  public SimpleIndexHashMapWrapper(K[] keys) {
    this.wrappedMap = new HashMap<K, ValueContainer<K, V>>(keys.length * 2);
    this.indexBasedLookup = new ArrayList<ValueContainer<K, V>>(keys.length);

    initWithKeys(keys);
  }

  public SimpleIndexHashMapWrapper(K[] keys, int initialCapacity, float load) {
    this.wrappedMap = new HashMap<K, ValueContainer<K, V>>(initialCapacity * 2, load);
    this.indexBasedLookup = new ArrayList<ValueContainer<K, V>>(initialCapacity);

    initWithKeys(keys);
  }

  public void initWithKeys(K[] keys) {
    int index = 0;
    ValueContainer<K, V> vc;
    for (K key : keys) {
      vc = new ValueContainer<K, V>(index++, key, null);
      wrappedMap.put(key, vc);
      indexBasedLookup.add(vc);
    }
  }

  public void addKey(K key) {
    ValueContainer<K, V> vc = new ValueContainer<K, V>(indexCounter++, key, null);
    this.indexBasedLookup.add(vc);
    this.wrappedMap.put(key, vc);
  }

  public void addKey(K key, V value) {
    ValueContainer<K, V> vc = new ValueContainer<K, V>(indexCounter++, key, value);
    this.indexBasedLookup.add(vc);
    this.wrappedMap.put(key, vc);
  }

  public int size() {
    return wrappedMap.size();
  }

  public boolean isEmpty() {
    return wrappedMap.isEmpty();
  }

  public boolean containsKey(Object key) {
    return wrappedMap.containsKey(key);
  }

  public boolean containsValue(Object value) {
    return wrappedMap.containsValue(value);
  }

  public V get(Object key) {
    return wrappedMap.get(key).getValue();
  }

  public V getByIndex(int index) {
    return indexBasedLookup.get(index).getValue();
  }

  public K getKeyAtIndex(int index) {
    return indexBasedLookup.get(index).getKey();
  }

  public int indexOf(K key) {
    return wrappedMap.get(key).getIndex();
  }

  public V put(K key, V value) {
    ValueContainer<K, V> vc = wrappedMap.get(key);
    if (vc == null)
      throw new RuntimeException("cannot add a new entry.  you must allocate a new key with addKey() first.");

    indexBasedLookup.add(vc);
    return wrappedMap.put(key, vc).getValue();
  }

  public void putAtIndex(int index, V value) {
    ValueContainer<K, V> vc = indexBasedLookup.get(index);
    vc.setValue(value);
  }

  public V remove(Object key) {
    throw new UnsupportedOperationException("cannot remove keys");
  }

  public void putAll(Map<? extends K, ? extends V> m) {
    //   wrappedMap.put
  }

  public void clear() {
    throw new UnsupportedOperationException("cannot clear map");
  }

  public Set<K> keySet() {
    return wrappedMap.keySet();
  }

  public Collection<V> values() {
    throw new UnsupportedOperationException();
  }

  public Set<Entry<K, V>> entrySet() {
    throw new UnsupportedOperationException();
  }

  private class ValueContainer<K, V> {
    private int index;
    private K key;
    private V value;

    public ValueContainer(int index, K key, V value) {
      this.index = index;
      this.key = key;
      this.value = value;
    }

    public int getIndex() {
      return index;
    }

    public K getKey() {
      return key;
    }

    public V getValue() {
      return value;
    }

    void setKey(K key) {
      this.key = key;
    }

    void setValue(V value) {
      this.value = value;
    }
  }
}
