/*
 * VariableValuation.java - This file is part of the Jakstab project.
 * Copyright 2007-2015 Johannes Kinder <jk@jakstab.org>
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
 */
package org.jakstab.analysis;

import java.util.*;
import java.util.Map.Entry;

import org.jakstab.rtl.Context;
import org.jakstab.rtl.expressions.*;
import org.jakstab.util.Logger;

/**
 * @author Johannes Kinder
 */
public class VariableValuation<A extends AbstractValue> implements LatticeElement, Iterable<Map.Entry<RTLVariable, A>> {

	@SuppressWarnings("unused")
	private static final Logger logger = Logger.getLogger(VariableValuation.class);

	protected final TreeMap<RTLVariable,A> aVarVal;
	protected final AbstractValueFactory<A> valueFactory;

	protected VariableValuation(TreeMap<RTLVariable,A> aVarVal, 
			AbstractValueFactory<A> valueFactory) {
		this.aVarVal = aVarVal;
		this.valueFactory = valueFactory;
	}
	
	public VariableValuation(VariableValuation<A> proto) {
		this(new TreeMap<RTLVariable, A>(proto.aVarVal), 
				proto.valueFactory);
	}
	
	public VariableValuation(AbstractValueFactory<A> valueFactory) {
		this(new TreeMap<RTLVariable, A>(),
				valueFactory);
	}
	

	public A get(RTLVariable var) {
		A e = aVarVal.get(var);
		if (e != null) {
			return e;
		} else {
			// See if we can get the value from a covering register
			RTLBitRange asParent = ExpressionFactory.getRegisterAsParent(var);

			if (asParent != null && asParent.getOperand() instanceof RTLVariable) {
				RTLVariable parent = (RTLVariable)asParent.getOperand();
				// Recursive call for al -> ax -> eax 
				A parentVal = get(parent);
				assert parentVal != null;
				
				Collection<RTLNumber> cValues = new LinkedList<RTLNumber>();
				for (RTLNumber cVal : parentVal.concretize()) {
					if (cVal == null) {
						cValues = null;
						break;
					}
					Context ctx = new Context();
					ctx.addAssignment(parent, cVal);
					RTLExpression result = asParent.evaluate(ctx);
					cValues.add((RTLNumber)result);
				}
				if (cValues != null) {
					e = valueFactory.createAbstractValue(cValues);
					//logger.debug("Generated abstract value " + e + " for " + var + " from value " + parentVal + " of " + parent);
					return e;
				}
			}

			return valueFactory.createTop(var.getBitWidth());
		}
	}
	
	private void clearCovering(RTLVariable var) {
		for (RTLVariable covering : ExpressionFactory.coveringRegisters(var)) {
			aVarVal.remove(covering);
			//clearCovering(covering);
		}
	}
	
	private void clearCovered(RTLVariable var) {
		for (RTLVariable covered : ExpressionFactory.coveredRegisters(var)) {
			aVarVal.remove(covered);
			//clearCovered(covered);
		}
	}
	
	public void set(RTLVariable var, A value) {
		
		RTLBitRange asParent = ExpressionFactory.getRegisterAsParent(var);

		// Set parent register - we only do this if the value to set represents 
		// a single concrete value. If we want to generalize this, we have to
		// build the cartesian product of concretizations
		if (asParent != null && asParent.getOperand() instanceof RTLVariable && 
				value.hasUniqueConcretization()) {
			RTLVariable parent = (RTLVariable)asParent.getOperand();
			A parentVal = get(parent);
			RTLNumber cRhs = value.concretize().iterator().next();

			Collection<RTLNumber> cValues = new LinkedList<RTLNumber>();
			for (RTLNumber cVal : parentVal.concretize()) {
				if (cVal == null) {
					cValues = null;
					break;
				}
				Context ctx = new Context();
				ctx.addAssignment(parent, cVal);
				RTLExpression result = asParent.applyInverse(cRhs).evaluate(ctx);
				cValues.add((RTLNumber)result);
			}
			if (cValues != null) {
				A e = valueFactory.createAbstractValue(cValues);
				//logger.debug("Setting parent " + parent + " of " + var + " to value " + e);
				set(parent, e);
				return;
			}
		}

		clearCovering(var);
		clearCovered(var);
		
		if (value.isTop()) {
			aVarVal.remove(var);
		} else {
			aVarVal.put(var, value);
		}
	}
	
	public void setTop(RTLVariable var) {
		clearCovering(var);
		clearCovered(var);

		aVarVal.remove(var);
	}

	@Override
	public boolean isBot() {
		return false;
	}

	@Override
	public boolean isTop() {
		return aVarVal.isEmpty();
	}

	@SuppressWarnings("unchecked")
	@Override
	public VariableValuation<A> join(LatticeElement l) {
		VariableValuation<A> other = (VariableValuation<A>)l;
		if (isTop() || other.isBot()) return this;
		if (isBot() || other.isTop()) return other;

		VariableValuation<A> joinedValuation = new VariableValuation<A>(valueFactory);
		// Join variable valuations
		for (Map.Entry<RTLVariable,A> entry : aVarVal.entrySet()) {
			RTLVariable var = entry.getKey();
			A value = entry.getValue();
			joinedValuation.set(var, (A)value.join(other.get(var)));
		}
				
		return joinedValuation;
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean lessOrEqual(LatticeElement l) {
		if (this == l) return true;
		VariableValuation<A> other = (VariableValuation<A>)l;
		if (other.isTop()) return true;
		if (isTop()) return false;

		// For all variables in other valuation, check if their
		// value in this valuation is less. Other way round is not
		// possible, as their could be variables present in the other
		// valuation but not in this one.
		for (Map.Entry<RTLVariable,A> entry : other.aVarVal.entrySet()) {
			RTLVariable var = entry.getKey();
			A value = entry.getValue();
			if (!get(var).lessOrEqual(value)) {
				return false;
			}
		}
		return true;
	}

	@Override
	public String toString() {
		return aVarVal.toString();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((aVarVal == null) ? 0 : aVarVal.hashCode());
		return result;
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		VariableValuation<A> other = (VariableValuation<A>) obj;
		if (aVarVal == null) {
			return other.aVarVal == null;
		} else {
			return aVarVal.equals(other.aVarVal);
		}
	}

	@Override
	public Iterator<Entry<RTLVariable, A>> iterator() {
		return aVarVal.entrySet().iterator();
	}
	
}
