/**
 * 
 */
package soottocfg.cfg.statement;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.base.Preconditions;

import soottocfg.cfg.SourceLocation;
import soottocfg.cfg.expression.Expression;
import soottocfg.cfg.expression.IdentifierExpression;
import soottocfg.cfg.expression.literal.BooleanLiteral;
import soottocfg.cfg.expression.literal.IntegerLiteral;
import soottocfg.cfg.expression.literal.NullLiteral;
import soottocfg.cfg.method.Method;
import soottocfg.cfg.type.BoolType;
import soottocfg.cfg.type.Type;
import soottocfg.cfg.variable.Variable;

/**
 * @author schaef
 *
 */
public class CallStatement extends Statement {

	private static final long serialVersionUID = 7267962774002374725L;
	private final Method method;
	private final List<Expression> arguments;
	private final List<Expression> returnReceiver;

	public CallStatement(SourceLocation loc, Method method, List<Expression> arguments,
			List<Expression> returnReceiver) {
		super(loc);
		Preconditions.checkArgument(method.getInParams().size() == arguments.size());

		this.method = method;
		this.arguments = new LinkedList<Expression>();
		this.returnReceiver = returnReceiver;

		for (int i = 0; i < arguments.size(); i++) {
			Expression arg = arguments.get(i);
			Type parType = method.getInParam(i).getType();

			if (!(arg instanceof NullLiteral) && !arg.canBeAssignedToType(parType)) {

				if (arg instanceof IntegerLiteral && parType == BoolType.instance()) {
					if (((IntegerLiteral) arg).getValue() == 0L) {
						this.arguments.add(BooleanLiteral.falseLiteral());
					} else {
						this.arguments.add(BooleanLiteral.trueLiteral());
					}
				} else {
					StringBuilder sb = new StringBuilder();
					sb.append("Type mismatch:\n");
					sb.append(this.toString());
					sb.append("\nAt position " + i);
					sb.append(":\nArg " + arg);
					sb.append(" of type " + arg.getType());
					sb.append(" cannot be assigned to type " + parType);
					throw new RuntimeException(sb.toString());
				}
			} else {
				this.arguments.add(arg);
			}
		}

	}

	public Method getCallTarget() {
		return method;
	}

	public List<Expression> getArguments() {
		return arguments;
	}

	public List<Expression> getReceiver() {
		return returnReceiver;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();

		if (!returnReceiver.isEmpty()) {
			String delim = "";
			for (Expression e : returnReceiver) {
				sb.append(delim);
				sb.append(e);
				delim = ", ";
			}
			sb.append(" := ");
		}
		sb.append("call ");
		sb.append(this.method.getMethodName());
		sb.append("(");
		String comma = "";
		for (Expression e : this.arguments) {
			sb.append(comma);
			sb.append(e);
			comma = ", ";
		}
		sb.append(")");
		return sb.toString();
	}

	@Override
	public Set<IdentifierExpression> getUseIdentifierExpressions() {
		Set<IdentifierExpression> used = new HashSet<IdentifierExpression>();
		for (Expression e : arguments) {
			used.addAll(e.getUseIdentifierExpressions());
		}
		return used;
	}

	@Override
	public Set<IdentifierExpression> getDefIdentifierExpressions() {
		Set<IdentifierExpression> res = new HashSet<IdentifierExpression>();
		for (Expression e : returnReceiver) {
			if (e instanceof IdentifierExpression) {
				res.add((IdentifierExpression) e);
			}
		}
		return res;
	}

	@Override
	public CallStatement deepCopy() {
		List<Expression> argCopy = new LinkedList<Expression>();
		for (Expression e : arguments) {
			argCopy.add(e.deepCopy());
		}
		List<Expression> rec = new LinkedList<Expression>();
		for (Expression e : returnReceiver) {
			rec.add(e.deepCopy());
		}
		return new CallStatement(getSourceLocation(), method, argCopy, rec);
	}

	@Override
	public CallStatement substitute(Map<Variable, Variable> subs) {
		List<Expression> argCopy = new LinkedList<Expression>();
		for (Expression e : arguments) {
			argCopy.add(e.substitute(subs));
		}
		List<Expression> rec = new LinkedList<Expression>();
		for (Expression e : returnReceiver) {
			rec.add(e.substitute(subs));
		}
		return new CallStatement(getSourceLocation(), method, argCopy, rec);
	}

	@Override
	public CallStatement substituteVarWithExpression(Map<Variable, Expression> subs) {
		List<Expression> argCopy = new LinkedList<Expression>();
		for (Expression e : arguments) {
			argCopy.add(e.substituteVarWithExpression(subs));
		}
		List<Expression> rec = new LinkedList<Expression>();
		for (Expression e : returnReceiver) {
			rec.add(e.deepCopy());
		}
		return new CallStatement(getSourceLocation(), method, argCopy, rec);
	}
	
}
