package zemberek.core.collections;

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

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

  // Carries unsigned integer values.
  private int[] values;

  public UIntValueMap() {
    this(INITIAL_SIZE);
  }

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

  /**
   * Returns the count of the key. If key does not exist, returns -1.
   *
   * @param key key
   * @return count of the key
   */
  public int 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 -1;
      }
      if (t == TOMB_STONE) {
        slot = (slot + 1) & modulo;
        continue;
      }
      if (t.equals(key)) {
        return values[slot];
      }
      slot = (slot + 1) & modulo;
    }
  }

  private void expand() {
    UIntValueMap<T> h = new UIntValueMap<>(newSize());
    for (int i = 0; i < keys.length; i++) {
      if (hasValidKey(i)) {
        h.put(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 put(T key, int value) {
    if (key == null) {
      throw new IllegalArgumentException("Key cannot be null.");
    }
    if (value < 0) {
      throw new IllegalArgumentException("Value cannot be negative : " + value);
    }
    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++;
    }
  }

  /**
   * If key does not exist, it adds it with count value 1. Otherwise, it increments the count value
   * by 1.
   *
   * @param key key
   * @return the new value after addOrIncrement
   */
  public int increment(T key) {
    return incrementByAmount(key, 1);
  }

  /**
   * Adds all keys in Iterable. If key does not exist, it adds it with count value 1. Otherwise, it
   * increments the count value by 1.
   *
   * @param keys key
   */
  public void incrementAll(Iterable<T> keys) {
    for (T t : keys) {
      incrementByAmount(t, 1);
    }
  }

  public int decrement(T key) {
    return incrementByAmount(key, -1);
  }

  /**
   * 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 int incrementByAmount(T key, int 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++;
      if (values[l] < 0) {
        throw new IllegalStateException("Value reached to negative.");
      }
      return values[l];
    } else {
      values[l] += amount;
      if (values[l] < 0) {
        throw new IllegalStateException("Value reached to negative.");
      }
      return values[l];
    }
  }

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

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

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

  /**
   * counts the items those values are smaller than amount
   *
   * @param amount amount to check size
   * @return count.
   */
  public int sizeLarger(int amount) {
    int count = 0;
    for (int i = 0; i < keys.length; i++) {
      if (hasValidKey(i) && values[i] > amount) {
        count++;
      }
    }
    return count;
  }

  /**
   * counts the items those values are smaller than amount
   *
   * @param amount amount to check size
   * @return count.
   */
  public int sizeSmaller(int amount) {
    int count = 0;
    for (int i = 0; i < keys.length; i++) {
      if (hasValidKey(i) && values[i] < amount) {
        count++;
      }
    }
    return count;
  }

  /**
   * returns the max value.
   *
   * @return the max value in the map. If map is empty, returns 0.
   */
  public int maxValue() {
    int max = 0;
    for (int i = 0; i < keys.length; i++) {
      if (hasValidKey(i) && values[i] > max) {
        max = values[i];
      }
    }
    return max;
  }

  /**
   * returns the max value.
   *
   * @return the max value in the map. If map is empty, returns 0.
   */
  public int minValue() {
    int min = Integer.MAX_VALUE;
    for (int i = 0; i < keys.length; i++) {
      if (hasValidKey(i) && values[i] < min) {
        min = values[i];
      }
    }
    return min;
  }

  public long sumOfValues(int minValue, int maxValue) {
    long sum = 0;
    for (int i = 0; i < keys.length; i++) {
      int value = values[i];
      if (hasValidKey(i) && value >= minValue && value <= maxValue) {
        sum += value;
      }
    }
    return sum;
  }

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

  public long sumOfValues() {
    long sum = 0;
    for (int i = 0; i < keys.length; i++) {
      if (hasValidKey(i)) {
        sum += values[i];
      }
    }
    return sum;
  }

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

    public final T key;
    public final int count;

    public Entry(T key, int count) {
      this.key = key;
      this.count = count;
    }

    @Override
    public int compareTo(IntValueMap.Entry<T> o) {
      return Integer.compare(o.count, count);
    }

    public String toString() {
      return key.toString() + ":" + count;
    }
  }

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

    int i;
    int k;

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

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

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

}