/*
 * RTLConditionalExpression.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.rtl.expressions;

import java.util.Set;

import org.jakstab.rtl.*;
import org.jakstab.ssl.Architecture;
import org.jakstab.util.FastSet;
import org.jakstab.util.Logger;

/**
 * An if-then-else expression similar to the C syntax of "condition ? trueExpression : falseExpression".
 * 
 * @author Johannes Kinder
 */
public class RTLConditionalExpression extends AbstractRTLExpression implements RTLExpression {

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

	protected Set<RTLMemoryLocation> usedMemoryLocations = null;

	private final RTLExpression condition;
	private final RTLExpression trueExpression;
	private final RTLExpression falseExpression;
	private final int size;

	/**
	 * @param condition
	 * @param trueExpression
	 * @param falseExpression
	 */
	protected RTLConditionalExpression(RTLExpression condition,
			RTLExpression trueExpression, RTLExpression falseExpression) {
		super();
		this.condition = condition;
		this.trueExpression = trueExpression;
		this.falseExpression = falseExpression;
		this.size = 1 + condition.size() + trueExpression.size() + falseExpression.size();
	}

	/**
	 * @return the condition
	 */
	public RTLExpression getCondition() {
		return condition;
	}

	/**
	 * @return the trueExpression
	 */
	public RTLExpression getTrueExpression() {
		return trueExpression;
	}

	/**
	 * @return the falseExpression
	 */
	public RTLExpression getFalseExpression() {
		return falseExpression;
	}

	@Override
	public String toString() {
		return "(" + condition.toString() + " ? " + trueExpression.toString() + 
		" : " + falseExpression.toString() + ")";
	}

	@Override
	public RTLExpression evaluate(Context context) {
		RTLExpression evaldCondition = this.condition.evaluate(context);
		
		if (evaldCondition.equals(ExpressionFactory.FALSE)) return this.falseExpression.evaluate(context);
		else if (evaldCondition.equals(ExpressionFactory.TRUE)) return this.trueExpression.evaluate(context);
		
		assert !(evaldCondition instanceof RTLNumber) : "Numeric condition result is neither TRUE nor FALSE but " + evaldCondition;
		
		RTLExpression evaldTrue = trueExpression.evaluate(context);
		RTLExpression evaldFalse = falseExpression.evaluate(context);
		
		// Collapse "(x == y) ? 1<1> : 0<1>" to (x == y)
		if (evaldTrue.equals(ExpressionFactory.TRUE) && evaldFalse.equals(ExpressionFactory.FALSE)) {
			return evaldCondition;
		} else if (evaldTrue.equals(ExpressionFactory.FALSE) && evaldFalse.equals(ExpressionFactory.TRUE)) {
				return ExpressionFactory.createOperation(Operator.NOT, evaldCondition);
		}
		
		return ExpressionFactory.createConditionalExpression(evaldCondition, evaldTrue, evaldFalse);
	}

	@Override
	public SetOfVariables getUsedVariables() {
		if (usedVariables == null) {
			usedVariables = new SetOfVariables();
			usedVariables.addAll(condition.getUsedVariables());
			usedVariables.addAll(trueExpression.getUsedVariables());
			usedVariables.addAll(falseExpression.getUsedVariables());
		}
		return usedVariables;
	}
	
	@Override
	public Set<RTLMemoryLocation> getUsedMemoryLocations() {
		if (usedMemoryLocations == null) {
			usedMemoryLocations = new FastSet<RTLMemoryLocation>();
			usedMemoryLocations.addAll(condition.getUsedMemoryLocations());
			usedMemoryLocations.addAll(trueExpression.getUsedMemoryLocations());
			usedMemoryLocations.addAll(falseExpression.getUsedMemoryLocations());
		}
		return usedMemoryLocations;
	}

	@Override
	public int size() {
		return size;
	}

	/*
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == null || obj.getClass() != this.getClass()) return false;
		RTLConditionalExpression other = (RTLConditionalExpression)obj; 
		return other.condition.equals(condition) && 
		other.trueExpression.equals(trueExpression) && 
		other.falseExpression.equals(falseExpression);
	}

	/*
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return 17 + condition.hashCode() + trueExpression.hashCode() + falseExpression.hashCode();
	}

	@Override
	public int getBitWidth() {
		return trueExpression.getBitWidth();
	}

	@Override
	public RTLExpression inferBitWidth(Architecture arch, int expectedBitWidth)
			throws TypeInferenceException {
		RTLExpression typedCondition = condition.inferBitWidth(arch, 1);
		RTLExpression typedTrueExpression = trueExpression.inferBitWidth(arch, expectedBitWidth);
		RTLExpression typedFalseExpression = falseExpression.inferBitWidth(arch, expectedBitWidth);
		if (typedCondition != condition || typedTrueExpression != trueExpression 
				|| typedFalseExpression != falseExpression)
			return ExpressionFactory.createConditionalExpression(
					typedCondition, typedTrueExpression, typedFalseExpression);
		else return this;
	}

	@Override
	public <T> T accept(ExpressionVisitor<T> visitor) {
		return visitor.visit(this);
	}
}
