/**
 * 
 */
package soottocfg.cfg.expression;

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

import soottocfg.cfg.Node;
import soottocfg.cfg.SourceLocation;
import soottocfg.cfg.expression.BinaryExpression.BinaryOperator;
import soottocfg.cfg.expression.literal.BooleanLiteral;
import soottocfg.cfg.expression.literal.IntegerLiteral;
import soottocfg.cfg.type.BoolType;
import soottocfg.cfg.type.IntType;
import soottocfg.cfg.type.Type;
import soottocfg.cfg.variable.Variable;
import soottocfg.soot.util.SootTranslationHelpers;

/**
 * @author schaef
 *
 */
public abstract class Expression implements Node, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 6933938841592139875L;

	public abstract Set<IdentifierExpression> getUseIdentifierExpressions();

	public Set<Variable> getUseVariables() {
		Set<Variable> res = new HashSet<Variable>();
		for (IdentifierExpression id : this.getUseIdentifierExpressions()) {
			res.add(id.getVariable());
		}
		return res;
	}

	private final SourceLocation sourceLocation;

	public Expression(SourceLocation loc) {
		this.sourceLocation = loc;
	}
	
	public SourceLocation getSourceLocation() {
		return this.sourceLocation;
	}

	public abstract Type getType();

	/**
	 * TODO: this one should be replaced by something that is easier to remove
	 * when restoring boolean types.
	 * 
	 * @return
	 */
	public Expression castToBoolIfNecessary() {
		if (this.getType() == IntType.instance()) {
			return new IteExpression(this.sourceLocation, new BinaryExpression(this.sourceLocation, BinaryOperator.Eq, this, IntegerLiteral.zero()),
					BooleanLiteral.falseLiteral(), BooleanLiteral.trueLiteral());
		} else if (this.getType() == BoolType.instance()) {
			return this;
		} else {
			throw new RuntimeException("Cannot cast " + this + " of type" + this.getType() + " to Boolean.");
		}
	}

	/**
	 * Returns true, if this can be assigned to other. That is, if either
	 * the types match or this is a sub-type of other.
	 * @param other
	 * @return true if this is sub- or equal-type to other.
	 */
	public boolean canBeAssignedTo(Expression other) {
		if (other.getType().getClass() != this.getType().getClass()
				&& !SootTranslationHelpers.v().getMemoryModel().isNullReference(this)) {
			return false;
		}
		return true;
	}

	public boolean canBeAssignedToType(Type t) {
		if (t.getClass() != this.getType().getClass()) {
			return false;
		}
		return true;
	}
	
	public abstract Expression deepCopy();
	
	/**
	 * Substitute variables in subs and return
	 * a clone of the original expression
	 * @param subs
	 * @return
	 */
	public abstract Expression substitute(Map<Variable, Variable> subs);
	
	/**
	 * Substitute variables with the expressions in subs and return
	 * a clone of the original expression
	 * @param subs
	 * @return
	 */
	public abstract Expression substituteVarWithExpression(Map<Variable, Expression> subs);

}
