package zemberek.core.collections;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


public class FloatValueMap<T> extends HashBase<T> implements Iterable<T> {

  // Carries count values.
  private float[] values;

  public FloatValueMap() {
    this(INITIAL_SIZE);
  }

  public FloatValueMap(int size) {
    super(size);
    values = new float[keys.length];
  }

  private FloatValueMap(FloatValueMap<T> other, T[] keys, float[] values) {
    super(other, keys);
    this.values = values;
  }

  /**
   * Returns the count of the key. If key does not exist, returns 0.
   *
   * @param key key
   * @return count of the key
   */
  public float get(T key) {
    if (key == null) {
      throw new IllegalArgumentException("Key cannot be null.");
    }
    int slot = hash(key) & modulo;
    while (true) {
      final T t = keys[slot];
      if (t == null) {
        return 0;
      }
      if (t == TOMB_STONE) {
        slot = (slot + 1) & modulo;
        continue;
      }
      if (t.equals(key)) {
        return values[slot];
      }
      slot = (slot + 1) & modulo;
    }
  }

  /**
   * addOrIncrement the value by "amount". If value does not exist, it a applies set() operation.
   *
   * @param key key
   * @param amount amount to addOrIncrement
   * @return incremented value
   */
  public float incrementByAmount(T key, float amount) {
    if (key == null) {
      throw new IllegalArgumentException("Key cannot be null.");
    }
    if (keyCount + removeCount == threshold) {
      expand();
    }
    int l = locate(key);
    if (l < 0) {
      l = -l - 1;
      values[l] = amount;
      keys[l] = key;
      keyCount++;
      return values[l];
    } else {
      values[l] += amount;
      return values[l];
    }
  }

  private void expand() {
    FloatValueMap<T> h = new FloatValueMap<>(newSize());
    for (int i = 0; i < keys.length; i++) {
      if (hasValidKey(i)) {
        h.set(keys[i], values[i]);
      }
    }
    expandCopyParameters(h);
    this.values = h.values;
  }

  /**
   * Sets the key with the value. If there is a matching key, it overwrites it (key and the value).
   *
   * @param key key
   * @param value value
   */
  public void set(T key, float value) {
    if (key == null) {
      throw new IllegalArgumentException("Key cannot be null.");
    }
    if (keyCount + removeCount == threshold) {
      expand();
    }
    int loc = locate(key);
    if (loc >= 0) {
      keys[loc] = key;
      values[loc] = value;
    } else {
      loc = -loc - 1;
      keys[loc] = key;
      values[loc] = value;
      keyCount++;
    }
  }

  public float[] values() {
    float[] result = new float[size()];
    int j = 0;
    for (int i = 0; i < keys.length; i++) {
      if (hasValidKey(i)) {
        result[j++] = values[i];
      }
    }
    return result;
  }

  public List<Entry<T>> getAsEntryList() {
    List<Entry<T>> res = new ArrayList<>(keyCount);
    for (int i = 0; i < keys.length; i++) {
      if (hasValidKey(i)) {
        res.add(new Entry<>(keys[i], values[i]));
      }
    }
    return res;
  }

  public FloatValueMap<T> copy() {
    return new FloatValueMap<>(this, keys.clone(), values.clone());
  }

  public Iterator<Entry<T>> entryIterator() {
    return new EntryIterator();
  }

  public Iterable<Entry<T>> iterableEntries() {
    return EntryIterator::new;
  }

  public static class Entry<T> implements Comparable<Entry<T>> {

    public final T key;
    public final float value;

    public Entry(T key, float value) {
      this.key = key;
      this.value = value;
    }

    @Override
    public int compareTo(Entry<T> o) {
      return Double.compare(value, o.value);
    }
  }

  private class EntryIterator implements Iterator<Entry<T>> {

    int i;
    int k;

    @Override
    public boolean hasNext() {
      return k < keyCount;
    }

    @Override
    public Entry<T> next() {
      while (!hasValidKey(i)) {
        i++;
      }
      Entry<T> te = new Entry<>(keys[i], values[i]);
      i++;
      k++;
      return te;
    }

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