/*
 * TraceReplayState.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.tracereplay;

import java.util.Set;

import org.jakstab.analysis.AbstractState;
import org.jakstab.analysis.LatticeElement;
import org.jakstab.analysis.UnderApproximateState;
import org.jakstab.asm.AbsoluteAddress;
import org.jakstab.cfa.Location;
import org.jakstab.rtl.expressions.ExpressionFactory;
import org.jakstab.rtl.expressions.RTLExpression;
import org.jakstab.rtl.expressions.RTLNumber;
import org.jakstab.util.FastSet;
import org.jakstab.util.Logger;
import org.jakstab.util.Tuple;

import com.google.common.collect.SetMultimap;

/**
 * States corresponding to a line number within a pre-recorded trace. Each state
 * stores a reference to the trace to allow direct access to recorded PC values.
 */
public class TraceReplayState implements UnderApproximateState {

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

	public static TraceReplayState BOT = new TraceReplayState();
	
	private final AbsoluteAddress cur;
	private final SetMultimap<AbsoluteAddress,AbsoluteAddress> succ;
	
	private TraceReplayState() {
		super();
		cur = new AbsoluteAddress(0xF0000B07L);
		succ = null;
	}
	
	public TraceReplayState(SetMultimap<AbsoluteAddress,AbsoluteAddress> succ, AbsoluteAddress cur) {
		this.succ = succ;
		this.cur = cur;
	}
	
	@Override
	public String getIdentifier() {
		return cur.toString();
	}

	@Override
	public Location getLocation() {
		throw new UnsupportedOperationException();
	}
	
	/**
	 * Get the value of the program counter at the current state. 
	 * 
	 * @return an AbsoluteAddress corresponding to the PC value for the analyzed module. 
	 */
	public AbsoluteAddress getCurrentPC() {
		return cur;
	}

	/**
	 * Get the value of the program counter at the current state's successor state. 
	 * 
	 * @return an AbsoluteAddress corresponding to the next PC value for the analyzed module. 
	 */
	public Set<AbsoluteAddress> getNextPC() {
		return succ.get(cur);
	}
	
	@Override
	public AbstractState join(LatticeElement l) {
		throw new UnsupportedOperationException();
	}
	
	@Override
	public Set<Tuple<RTLNumber>> projectionFromConcretization(RTLExpression... expressions) {

		// Only concretize expression requests from transformerFactory
		// Warning: If this method is invoked with 2 parameters for other reasons, it will 
		//          likely fail!
		if (expressions.length != 2) return null;
		
		// If not on trace, don't concretize
		if (isBot()) return null;

		RTLExpression condition = expressions[0];
		RTLExpression target = expressions[1];
		RTLNumber cCondition;
		RTLNumber cTarget;
		
		Set<Tuple<RTLNumber>> res = new FastSet<Tuple<RTLNumber>>();
		
		for (AbsoluteAddress successor : getNextPC()) {
			RTLNumber nextPC = successor.toNumericConstant();

			if (target instanceof RTLNumber) {
				// If target is a number, this is a direct jump, and maybe conditional

				cTarget = (RTLNumber)target;

				if (condition instanceof RTLNumber) {
					// Direct, unconditional jump
					cCondition = (RTLNumber)condition;
				} else if (target.equals(nextPC)) {
					// Conditional jump that is taken according to the trace
					cCondition = ExpressionFactory.TRUE;
				} else { 
					// Conditional jump that is not taken
					cCondition = ExpressionFactory.FALSE;
				}

			} else {
				// Target is not a number, so this is an indirect jump

				assert (condition instanceof RTLNumber) : "There should be no conditional indirect jumps in x86!";
				cCondition = (RTLNumber)condition;
				cTarget = nextPC;
			}
			res.add(Tuple.create(cCondition, cTarget));
		}
		
		return res;
	}

	@Override
	public boolean isBot() {
		return this == BOT;
	}

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

	@Override
	public int hashCode() {
		return cur.hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		
		return cur.equals(((TraceReplayState)obj).cur);
	}

	@Override
	public boolean lessOrEqual(LatticeElement l) {
		TraceReplayState other = (TraceReplayState)l;
		if (other.isTop() || this.isBot()) return true;
		return this.equals(l); 
	}
	
	public String toString() {
		if (isBot()) return "Trace@BOT";
		return "Trace@" + getCurrentPC() + ": Next: " + getNextPC();
	}
}
	