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

import soottocfg.cfg.SourceLocation;
import soottocfg.cfg.expression.Expression;
import soottocfg.cfg.expression.IdentifierExpression;
import soottocfg.cfg.variable.ClassVariable;
import soottocfg.cfg.variable.Variable;

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

	private static final long serialVersionUID = 5776310555422969945L;
	private final ClassVariable classConstant;
	private IdentifierExpression object;
	private final List<Expression> right;
	
	private final List<Expression> ghostExpressions;
	
	private final int id;	
	private static int nextID = 0;
	
	/**
	 * @param loc
	 */
	public PushStatement(SourceLocation loc, ClassVariable c, IdentifierExpression obj, List<Expression> rhs, int id) {
		this(loc, c, obj, rhs, null, id);
	}
	
	public PushStatement(SourceLocation loc, ClassVariable c, IdentifierExpression obj, List<Expression> rhs, List<Expression> ghostExpressions, int id) {
		super(loc);
		this.ghostExpressions = new LinkedList<Expression>();
		if (ghostExpressions!=null) {
			this.ghostExpressions.addAll(ghostExpressions);
		}
		classConstant = c;
		object = obj;
		right = new LinkedList<Expression>(rhs);
		this.id = id;
		verifySize();
	}
	
	private void verifySize() {
		if (classConstant.getAssociatedFields().length != right.size()) {
			StringBuilder err = new StringBuilder();
			err.append(object);
			err.append(" has fields ");
			String comma = "";
			err.append("[");
			for (Variable cv : classConstant.getAssociatedFields()) {
				err.append(comma);
				comma = ", ";
				err.append(cv.getName()+":"+cv.getType());
			}
			err.append("] but is assigned to ");
			err.append("[");
			comma = "";
			for (Expression e : right) {
				err.append(comma);
				comma = ", ";
				err.append(e+":"+e.getType());				
			}
			err.append("]");
			Verify.verify(false, err.toString());
		}
	}
	
	public PushStatement(SourceLocation loc, ClassVariable c, IdentifierExpression obj, List<Expression> rhs) {
		this(loc, c, obj, rhs, nextID());
	}

    public List<Expression> getGhostExpressions() {
    	return this.ghostExpressions;
    }

	
	// had to put this in a method to silence findBugs...
	private static int nextID() {
		return ++nextID;
	}
	
    public ClassVariable getClassSignature() {
        return classConstant;
    }

    public Expression getObject() {
        return object;
    }

    public List<Expression> getRight() {
        return right;
    }
    
    public int getID() {
    	return id;
    }
    
    
	@Override
	public Set<IdentifierExpression> getUseIdentifierExpressions() {
		Set<IdentifierExpression> used = new HashSet<IdentifierExpression>();
		for (Expression e : right) {
			used.addAll(e.getUseIdentifierExpressions());	
		}
		for (Expression e: ghostExpressions) {
			used.addAll(e.getUseIdentifierExpressions());
		}
//TODO: I'm not sure if a push is a use or a def of 'object' ... or both?
        used.add(object);
		return used;
	}

	@Override
	public Set<IdentifierExpression> getDefIdentifierExpressions() {
		Set<IdentifierExpression> res = new HashSet<IdentifierExpression>();
//		res.add(object);
		return res;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("push_");
                sb.append(id);
		sb.append("(");
		sb.append(classConstant.getName());
		sb.append(", ");
		sb.append(object);
		for (Expression e : this.ghostExpressions) {
			sb.append(", ");
			sb.append(e);
		}

		sb.append(", [");
		String comma = "";
		for (Expression v : right) {
			sb.append(comma);
			sb.append(v);
			comma = ", ";
		}
		sb.append("]");
		sb.append(")");
		return sb.toString();
	}

	// TODO check where this is used and what to do with ID
	@Override
	public Statement deepCopy() {
		List<Expression> rightCopy = new LinkedList<Expression>();
		for (Expression e : right) {
			rightCopy.add(e.deepCopy());
		}
		List<Expression> ghostCopy = new LinkedList<Expression>();
		for (Expression e : ghostExpressions) {
			ghostCopy.add(e.deepCopy());
		}
		return new PushStatement(getSourceLocation(), classConstant, object.deepCopy(), rightCopy, ghostCopy, this.id);
	}

	@Override
	public PushStatement substitute(Map<Variable, Variable> subs) {
		List<Expression> rightCopy = new LinkedList<Expression>();
		for (Expression e : right) {
			rightCopy.add(e.substitute(subs));
		}
		List<Expression> ghostCopy = new LinkedList<Expression>();
		for (Expression e : ghostExpressions) {
			ghostCopy.add(e.substitute(subs));
		}

		return new PushStatement(getSourceLocation(), classConstant, object.substitute(subs), rightCopy, ghostCopy, this.id);
	}
	
	@Override
	public PushStatement substituteVarWithExpression(Map<Variable, Expression> subs) {
		List<Expression> rightCopy = new LinkedList<Expression>();
		for (Expression e : right) {
			rightCopy.add(e.substituteVarWithExpression(subs));
		}
		List<Expression> ghostCopy = new LinkedList<Expression>();
		for (Expression e : ghostExpressions) {
			ghostCopy.add(e.substituteVarWithExpression(subs));
		}
//		Verify.verify(!subs.containsKey(object.getVariable()));
		return new PushStatement(getSourceLocation(), classConstant, object.deepCopy(), rightCopy, ghostCopy, this.id);
	}	
	
	
//	@Override
//	public boolean equals(Object o) {
//		if (!(o instanceof PushStatement))
//			return false;
//		PushStatement other = (PushStatement) o;
//		return this.id == other.id;
//	}
//	
//	@Override
//	public int hashCode() {
//		return id;
//	}
}
