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

import java.io.Serializable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.jakstab.rtl.Context;
import org.jakstab.rtl.expressions.*;
import org.jakstab.ssl.CanonizationVisitor;
import org.jakstab.util.Logger;

/**
 * A sequence of RTL statements, only used during SSL instantiation and in stub generation. 
 * 
 * @author Johannes Kinder
 */
public class StatementSequence implements Iterable<RTLStatement>, Serializable {

	@SuppressWarnings("unused")
	private final static Logger logger = Logger.getLogger(StatementSequence.class);
	private static final long serialVersionUID = -3750283603929931899L;


	private LinkedList<RTLStatement> sequence;

	/**
	 * Creates a new empty statement sequence.
	 */
	public StatementSequence() {
		sequence = new LinkedList<RTLStatement>();
	}

	/**
	 * Inserts a statement before the start of this sequence when it is not null, 
	 * otherwise does nothing.
	 * 
	 * @param statement the statement to be added.
	 */
	public void addFirst(RTLStatement statement) {
		if (statement != null) {
			sequence.addFirst(statement);
		}
	}

	/**
	 * Inserts a sequence of statements before the start of this sequence when the 
	 * sequence object is not null, otherwise does nothing.
	 * 
	 * @param statements the statement sequence to be added
	 */
	public void addFirst(StatementSequence statements) {
		if (statements != null) {
			sequence.addAll(0, statements.sequence);
		}
	}

	/**
	 * Adds a statement to the end of this sequence when it is not null, 
	 * otherwise does nothing.
	 * 
	 * @param statement the statement to be added.
	 */
	public void addLast(RTLStatement statement) {
		if (statement != null) {
			sequence.addLast(statement);
		}
	}

	/**
	 * Adds a sequence of statements to the end of this sequence when the sequence 
	 * object is not null, otherwise does nothing.
	 * 
	 * @param statements the statement sequence to be added
	 */
	public void addLast(StatementSequence statements) {
		if (statements != null) {
			sequence.addAll(statements.sequence);
		}
	}

	public Iterator<RTLStatement> iterator() {
		return sequence.iterator();
	}

	public void removeLast() {
		sequence.removeLast();
	}


	public RTLStatement getFirst() {
		return sequence.getFirst();
	}

	public RTLStatement getLast() {
		return sequence.getLast();
	}

	/**
	 * Returns a canonical copy of this sequence. Canonization is required only
	 * once, after parsing a SSL specification. Canonical statements do not contain
	 * assignments of the special %RPT and %SKIP registers.
	 * 
	 * NOTE: Unchanged elements are not copied but the originals are returned. 
	 * 
	 * @return this statement sequence converted to canonical form. 
	 */
	public StatementSequence canonize() {
		CanonizationVisitor visitor = new CanonizationVisitor();
		
		RTLExpression skipCondition = null;
		RTLExpression repeatCondition = null;
		
		LinkedList<RTLStatement> oldSequence = sequence;
		sequence = new LinkedList<RTLStatement>();

		/* Evaluate substatements */
		for (RTLStatement statement : oldSequence) {
			RTLStatement evaldStatement;
			
			if (statement instanceof AssignmentTemplate) {
				AssignmentTemplate a = (AssignmentTemplate)statement;
				
				RTLExpression genericEvaldLHS = a.getLeftHandSide().accept(visitor);
				RTLExpression evaldRHS = a.getRightHandSide().accept(visitor);

				if (!(genericEvaldLHS instanceof Writable))
					throw new RuntimeException("Error: LHS of assignment no longer writable after canonization: " + 
							a.getLeftHandSide().toString() + " = " + genericEvaldLHS.toString());

				Writable evaldLHS = (Writable)genericEvaldLHS;

				/* Convert %SKIP assignments to conditionals */
				if (evaldLHS.equals(ExpressionFactory.SKIP)) {
					skipCondition = evaldRHS;
					continue;
				}

				/* Convert %RPT assignments to conditionals */
				if (evaldLHS.equals(ExpressionFactory.REPEAT)) {
					repeatCondition = evaldRHS;
					continue;
				}

				// if both sides are equal, remove this statement
				if (evaldLHS.equals(evaldRHS)) 
					continue;
				
				evaldStatement = new AssignmentTemplate(a.getBitWidth(), evaldLHS, evaldRHS);
				
			} else /* non AssignmentTemplate */ {
				evaldStatement = statement.evaluate(new Context());
			}

			addLast(evaldStatement);
		}
		if (sequence.size() < 1) 
			return null;

		if (skipCondition != null) {
			RTLGoto skipGoto = new RTLGoto(ExpressionFactory.pc, skipCondition, RTLGoto.Type.STRING_LENGTH_CHECK);
			addFirst(skipGoto);
		}

		if (repeatCondition != null) {
			// Create a dummy goto statement, this will point to the instruction's address after instantiation
			if (!repeatCondition.equals(ExpressionFactory.FALSE)) {
				RTLStatement condGoto = new RTLGoto(null, repeatCondition, RTLGoto.Type.REPEAT);
				addLast(condGoto);
			}
		}

		return this; 
	}
	
	/**
	 * Move all bitrange expressions from the LHS of assignments to the RHS, 
	 * and convert all assignment templates to variable or memory assignments.
	 * @return
	 */
	public StatementSequence normalizeAssignments() {
		LinkedList<RTLStatement> oldSequence = sequence;
		sequence = new LinkedList<RTLStatement>();

		for (RTLStatement statement : oldSequence) {
			if (statement instanceof AssignmentTemplate)
				sequence.addLast(((AssignmentTemplate)statement).convertToSpecificAssignmentType());
			else
				sequence.addLast(statement);
			
		}
		return this; 
	}


	public StatementSequence evaluate(Context context) {
		LinkedList<RTLStatement> oldSequence = sequence;
		sequence = new LinkedList<RTLStatement>();

		for (RTLStatement statement : oldSequence) {
			RTLStatement evaldStatement = statement.evaluate(context);
			if (evaldStatement != null) addLast(evaldStatement);
		}
		if (sequence.size() < 1) 
			return null;

		return this; 
	}

	public int getLength() {
		return sequence.size();
	}

	/**
	 * @return the statement sequence
	 */
	protected List<RTLStatement> getSequence() {
		return sequence;
	}

	public StatementSequence replace(RTLStatement statement, RTLStatement replacement) {
		LinkedList<RTLStatement> oldSequence = sequence;
		sequence = new LinkedList<RTLStatement>();
		for (RTLStatement stmt : oldSequence) {
			if (stmt.equals(statement)) {
				if (replacement != null) {
					addLast(replacement);
				}
			} else addLast(stmt);
		}
		if (sequence.size() < 1) {
			return null;
		}

		return this; 
	}

	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		res.append("{ ");
		for (RTLStatement statement : sequence) {
			res.append(statement.toString());
			res.append("; ");
		}
		res.append(" }");
		return res.toString();
	}

	public StatementSequence copy() {
		StatementSequence res = new StatementSequence();
		for (RTLStatement statement : sequence) {
			res.addLast(statement.copy());
		}
		return res;
	}

	public SetOfVariables getDefinedVariables() {
		SetOfVariables res = new SetOfVariables();
		for (RTLStatement s : sequence)
			res.addAll(s.getDefinedVariables());
		return res;
	}
	
}
