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

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

/**
 * A memory location with a specified data type/bit width and an address defined by another 
 * expression. IA-32 addresses can also contain a segment register, but the implementation
 * is not yet complete.
 * 
 * @author Johannes Kinder
 */
public class RTLMemoryLocation extends AbstractRTLExpression implements RTLExpression, Writable {

	@SuppressWarnings("unused")
	private final static Logger logger = Logger.getLogger(RTLMemoryLocation.class);
			
	private Set<RTLMemoryLocation> usedMemoryLocations = null;
	private SetOfVariables usedVariablesOnWrite;
	private final RTLExpression segmentRegister;
	private final RTLExpression address;
	private final int bitWidth;
	private final int size;
	private final int memoryState;

	protected RTLMemoryLocation(int memoryState, RTLExpression segmentRegister, RTLExpression address, int bitWidth) {
		super();
		this.memoryState = memoryState;
		this.address = address;
		this.segmentRegister = segmentRegister;
		this.bitWidth = bitWidth;
		this.size = 1 + (address != null ? address.size() : 0 ) 
				+ (segmentRegister != null ? segmentRegister.size() : 0 );
	}

	/**
	 * @return the address
	 */
	public RTLExpression getAddress() {
		return address;
	}

	public int getBitWidth() {
		return bitWidth;
	}

	/**
	 * @return the segment register for this memory access, if specified,
	 *         otherwise null.
	 */
	public RTLExpression getSegmentRegister() {
		return segmentRegister;
	}

	/**
	 * Return the logical state this memory location refers to. Usually 0, but can
	 * be higher to allow expressions to refer to different memory states, e.g. as in
	 * mem32'[10] = mem32[10] + 5; 
	 * 
	 * @return the memory state (number of 'primes')
	 */
	public int getMemoryState() {
		return memoryState;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		//if (getUsedVariables().contains(ExpressionFactory.getInstance().stackPointer))
		//		sb.append("stack");
		//else 
		sb.append("mem");
		if (bitWidth > 0) sb.append(bitWidth);
		for (int i = 0; i < memoryState; i++)
			sb.append('\'');
		sb.append("[");
		if (segmentRegister != null) {
			sb.append(segmentRegister.toString());
			sb.append(":");
		}
		sb.append(address.toHexString());
		sb.append("]");
		return sb.toString();
	}

	@Override
	public RTLExpression evaluate(Context context) {
		RTLExpression subst = context.getSubstitution(this);
		
		// We also need to whether this location has been assigned a value (needed in VPC constant propagation, 31.1.13)
		subst = context.getAssignment(this);		
		
		if (subst instanceof RTLMemoryLocation) {
			RTLMemoryLocation m = (RTLMemoryLocation)subst;
			RTLExpression evaldAddress = m.address.evaluate(context);
			RTLMemoryLocation evaldMemLoc = evaldAddress == m.address ? 
					m : ExpressionFactory.createMemoryLocation(m.segmentRegister, evaldAddress, m.bitWidth);

			RTLExpression value = context.getAssignment(evaldMemLoc);
			if (value != null)
				return value;
			else 
				return evaldMemLoc;
		} else {
			return subst.evaluate(context);
		}
	}

	@Override
	public SetOfVariables getUsedVariables() {
		if (usedVariables == null) {
			usedVariables = new SetOfVariables();
			if (segmentRegister != null) 
				usedVariables.addAll(segmentRegister.getUsedVariables());
			if (address != null)
				usedVariables.addAll(address.getUsedVariables());
			//usedVariables.add(this);
		}
		return usedVariables;
	}

	@Override
	public SetOfVariables getUsedVariablesOnWrite() {
		if (usedVariablesOnWrite == null) {
			usedVariablesOnWrite = new SetOfVariables();
			if (segmentRegister != null) 
				usedVariablesOnWrite.addAll(segmentRegister.getUsedVariables());
			if (address != null)
				usedVariablesOnWrite.addAll(address.getUsedVariables());
		}
		return usedVariablesOnWrite;
	}
	
	@Override
	public Set<RTLMemoryLocation> getUsedMemoryLocations() {
		if (usedMemoryLocations == null) {
			usedMemoryLocations = new FastSet<RTLMemoryLocation>();
			usedMemoryLocations.addAll(address.getUsedMemoryLocations());
			usedMemoryLocations.add(this);
		}
		return usedMemoryLocations;
	}
	
	@Override
	public Set<RTLMemoryLocation> getDefinedMemoryLocationsOnWrite() {
		return Collections.singleton(this);
	}

	@Override
	public Set<RTLMemoryLocation> getUsedMemoryLocationsOnWrite() {
		return address.getUsedMemoryLocations();
	}

	@Override
	public SetOfVariables getDefinedVariablesOnWrite() {
		//return setOfThis;
		return SetOfVariables.EMPTY_SET;
	}

	@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;
		RTLMemoryLocation other = (RTLMemoryLocation)obj;
		return other.address.equals(address) && 
		other.bitWidth == bitWidth && 
		other.memoryState == memoryState && 
		(other.segmentRegister == segmentRegister || 
				(segmentRegister != null && segmentRegister.equals(other.segmentRegister)));
	}

	/*
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return 59 + address.hashCode() + bitWidth;
	}

	@Override
	public RTLExpression inferBitWidth(Architecture arch, int expectedBitWidth)
			throws TypeInferenceException {
		if (bitWidth != expectedBitWidth)
			throw new TypeInferenceException("Expected bitwidth of " + expectedBitWidth + ", but memory reference is " + bitWidth + " bit.");
		
		//return this;
		// Inferring address bitwidth currently does not work b/c of multiplication.
		// For MUL instructions, bitwidth gets doubled, in index-scale addressing, there is an implicit truncation

		RTLExpression typedAddress = address.inferBitWidth(arch, arch.getAddressBitWidth());
		if (typedAddress != address)
			return ExpressionFactory.createMemoryLocation(
					this.segmentRegister, typedAddress, bitWidth);
		else return this;
	}
	
	@Override
	public <T> T accept(ExpressionVisitor<T> visitor) {
		return visitor.visit(this);
	}
}
