package edu.berkeley.nlp.PCFGLA;

import java.io.Serializable;
import java.util.Map;
import java.util.Set;

import edu.berkeley.nlp.util.ArrayUtil;
import edu.berkeley.nlp.util.Counter;
import edu.berkeley.nlp.util.MapFactory;

public class BinaryCounterTable implements Serializable {
	/**
	 * Based on Counter.
	 * 
	 * A map from objects to doubles. Includes convenience methods for getting,
	 * setting, and incrementing element counts. Objects not in the counter will
	 * return a count of zero. The counter is backed by a HashMap (unless
	 * specified otherwise with the MapFactory constructor).
	 * 
	 * @author Slav Petrov
	 */
	private static final long serialVersionUID = 1L;
	Map<BinaryRule, double[][][]> entries;
	short[] numSubStates;
	BinaryRule searchKey;

	/**
	 * The elements in the counter.
	 * 
	 * @return set of keys
	 */
	public Set<BinaryRule> keySet() {
		return entries.keySet();
	}

	/**
	 * The number of entries in the counter (not the total count -- use
	 * totalCount() instead).
	 */
	public int size() {
		return entries.size();
	}

	/**
	 * True if there are no entries in the counter (false does not mean
	 * totalCount > 0)
	 */
	public boolean isEmpty() {
		return size() == 0;
	}

	/**
	 * Returns whether the counter contains the given key. Note that this is the
	 * way to distinguish keys which are in the counter with count zero, and
	 * those which are not in the counter (and will therefore return count zero
	 * from getCount().
	 * 
	 * @param key
	 * @return whether the counter contains the key
	 */
	public boolean containsKey(BinaryRule key) {
		return entries.containsKey(key);
	}

	/**
	 * Get the count of the element, or zero if the element is not in the
	 * counter. Can return null!
	 * 
	 * @param key
	 * @return
	 */
	public double[][][] getCount(BinaryRule key) {
		double[][][] value = entries.get(key);
		return value;
	}

	public double[][][] getCount(short pState, short lState, short rState) {
		searchKey.setNodes(pState, lState, rState);
		double[][][] value = entries.get(searchKey);
		return value;
	}

	/**
	 * Set the count for the given key, clobbering any previous count.
	 * 
	 * @param key
	 * @param count
	 */
	public void setCount(BinaryRule key, double[][][] counts) {
		entries.put(key, counts);
	}

	/**
	 * Increment a key's count by the given amount. Assumes for efficiency that
	 * the arrays have the same size.
	 * 
	 * @param key
	 * @param increment
	 */
	public void incrementCount(BinaryRule key, double[][][] increment) {
		double[][][] current = getCount(key);
		if (current == null) {
			setCount(key, increment);
			return;
		}
		for (int i = 0; i < current.length; i++) {
			for (int j = 0; j < current[i].length; j++) {
				// test if increment[i][j] is null or zero, in which case
				// we needn't add it
				if (increment[i][j] == null)
					continue;
				// allocate more space as needed
				if (current[i][j] == null)
					current[i][j] = new double[increment[i][j].length];
				// if we've gotten here, then both current and increment
				// have correct arrays in index i
				for (int k = 0; k < current[i][j].length; k++) {
					current[i][j][k] += increment[i][j][k];
				}
			}
		}
		setCount(key, current);
	}

	public void incrementCount(BinaryRule key, double increment) {
		double[][][] current = getCount(key);
		if (current == null) {
			double[][][] tmp = key.getScores2();
			current = new double[tmp.length][tmp[0].length][tmp[0][0].length];
			ArrayUtil.fill(current, increment);
			setCount(key, current);
			return;
		}
		for (int i = 0; i < current.length; i++) {
			for (int j = 0; j < current[i].length; j++) {
				if (current[i][j] == null)
					current[i][j] = new double[numSubStates[key
							.getParentState()]];
				for (int k = 0; k < current[i][j].length; k++) {
					current[i][j][k] += increment;
				}
			}
		}
		setCount(key, current);
	}

	public BinaryCounterTable(short[] numSubStates) {
		this(new MapFactory.HashMapFactory<BinaryRule, double[][][]>(),
				numSubStates);
	}

	public BinaryCounterTable(MapFactory<BinaryRule, double[][][]> mf,
			short[] numSubStates) {
		entries = mf.buildMap();
		searchKey = new BinaryRule((short) 0, (short) 0, (short) 0);
		this.numSubStates = numSubStates;
	}

	public static void main(String[] args) {
		Counter<String> counter = new Counter<String>();
		System.out.println(counter);
		counter.incrementCount("planets", 7);
		System.out.println(counter);
		counter.incrementCount("planets", 1);
		System.out.println(counter);
		counter.setCount("suns", 1);
		System.out.println(counter);
		counter.setCount("aliens", 0);
		System.out.println(counter);
		System.out.println(counter.toString(2));
		System.out.println("Total: " + counter.totalCount());
	}
}
