/*
 * RTLSpecialExpression.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.Arrays;
import java.util.Set;

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

/**
 * "Special" expressions which are not (yet) supported in RTLOperation.
 * 
 * @author Johannes Kinder
 */
public class RTLSpecialExpression extends AbstractRTLExpression implements RTLExpression  {
	
	public static final String FTRUNC = "ftrunc";
	public static final String LOG2 = "log2";
	public static final String TICKCOUNT = "tickcount";
	public static final String GETPROCADDRESS = "getProcAddress";
	public static final String DBG_PRINTF = "dbgPrintf";
	
	@SuppressWarnings("unused")
	private static final Logger logger = Logger.getLogger(RTLSpecialExpression.class);
	protected Set<RTLMemoryLocation> usedMemoryLocations = null;

	private final String operation;
	private final RTLExpression[] operands;
	private final int operandCount;
	private final int size;
	private final int bitWidth;
	
	protected RTLSpecialExpression(String operation, RTLExpression... operands) {
		super();
		this.operation = operation;
		if (operation.equals(TICKCOUNT)) bitWidth = 64;
		else if (operation.equals(GETPROCADDRESS)) bitWidth = 32;
		else if (operation.equals(DBG_PRINTF)) bitWidth = 32;
		else {
			// Everything else is floating point stuff anyway
			bitWidth = 80;
			//logger.warn("Unsupported special expression: " + operation);
		}
		
		if (operands == null || operands.length == 0 || operands[0] == null ) {
			this.operandCount = 0;
			this.operands = null;
			this.size = 1;
		} else {
			this.operands = operands;
			int opCount = operands.length;
			int theSize = 1;
			// See if it's actually less real operands
			for (int i=0; i < operands.length; i++)
				if (operands[i] == null) {
					opCount = i;
					break;
				} else theSize += operands[i].size();
			this.operandCount = opCount;
			this.size = theSize;
		}
	}

	/**
	 * @return the operation
	 */
	public String getOperator() {
		return operation;
	}
	
	/**
	 * @return the operands
	 */
	public RTLExpression[] getOperands() {
		return operands;
	}

	public int getOperandCount() {
		return operandCount;
	}

	/*
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == null || obj.getClass() != this.getClass()) return false;
		RTLSpecialExpression other = (RTLSpecialExpression)obj;
		return other.operation.equals(this.operation) && Arrays.equals(this.operands, other.operands);
	}

	/*
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return 7 + operation.hashCode() + Arrays.hashCode(operands);
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		if (operandCount == 0) return operation.toString();
		StringBuilder res = new StringBuilder();
		res.append(operation.toString() + "(");
		res.append(operands[0].toString());
		for (int i = 1; i < operandCount; i++)
			res.append(", " + operands[i].toString());
		res.append(')');
		return res.toString();
	}

	@Override
	public RTLExpression evaluate(Context context) {
		RTLExpression[] evaledOperands = new RTLExpression[operandCount]; 
		for(int i=0; i<operandCount; i++)
			evaledOperands[i] = operands[i].evaluate(context);

		return ExpressionFactory.createSpecialExpression(operation, evaledOperands);
	}
	
	@Override
	public SetOfVariables getUsedVariables() {
		if (usedVariables == null) {
			usedVariables = new SetOfVariables();
			for(int i=0; i<operandCount; i++) {
				usedVariables.addAll(operands[i].getUsedVariables());
			}
		}
		return usedVariables;
	}

	@Override
	public Set<RTLMemoryLocation> getUsedMemoryLocations() {
		if (usedMemoryLocations == null) {
			usedMemoryLocations = new FastSet<RTLMemoryLocation>();
			for(int i=0; i<operandCount; i++) {
				usedMemoryLocations.addAll(operands[i].getUsedMemoryLocations());
			}
		}
		return usedMemoryLocations;
	}

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

	@Override
	public RTLExpression inferBitWidth(Architecture arch, int expectedBitWidth)
			throws TypeInferenceException {
		// TODO: implement this
		return this;
	}
	
	@Override
	public <T> T accept(ExpressionVisitor<T> visitor) {
		return visitor.visit(this);
	}
}
