package codelines;

import animation.CodeLine;
import animation.ControlFlow;
import animation.Memory;
import animation.StackFrame;
import animation.StackFrame.FrameType;

public abstract class ForLoop extends CodeLine {

	String loopVar;
	
	public ForLoop( String varName )
	{
		this.loopVar = varName;
	}
	
	@Override
	public ControlFlow runForward(Memory m) {
		boolean declared = false; // prove if it is the first step in the loop
		if( !m.isSomewhereDefined( "line_" + lineId + "_index", false, 1 ) )
		{ // first loop step
			m.declare( "line_" + lineId + "_index", minimum( m ), false );
			declared = true;
		}
		if( m.<Integer>read( "line_" + lineId + "_index", false ) > maximum( m ) ) // prove if the loop has finished
		{
			m.undeclare( "line_" + lineId + "_index", false );
			actions.push( (Memory mem) -> {
				return new ControlFlow( ControlFlow.STEP_OVER ); // loop was not called so nothing to reverse
			});
			return new ControlFlow( ControlFlow.STEP_OVER ); // don't execute the loop body
		}
		if( declared )
		{
			m.declare( loopVar, minimum( m ), false ); // set loop variable
			m.addFrame( new StackFrame( FrameType.LOOP ) );
			actions.push( (Memory mem) -> {
				mem.removeFrame();
				mem.undeclare( "line_" + lineId + "_index", false );
				return new ControlFlow( ControlFlow.STEP_OVER ); // step out of the loop
			} );
		}
		else
		{
			int oldIndex = m.<Integer>read( "line_" + lineId + "_index", false );
			m.write( "line_" + lineId + "_index", oldIndex + 1, false ); // count index down
			if( m.<Integer>read( "line_" + lineId + "_index", false ) > maximum( m ) ) // prove if loop was finished
			{
				StackFrame sf = m.removeFrame(); // remove loop stack
				m.undeclare( "line_" + lineId + "_index", false );
				actions.add( (Memory mem) -> {
					mem.write( "line_" + lineId + "_index", oldIndex, false );
					mem.addFrame( sf ); // restore last loop stack
					return new ControlFlow( ControlFlow.STEP_INTO ); // step into the loop body
				});
				return new ControlFlow( ControlFlow.STEP_OVER ); // step out of the loop
			}
			StackFrame old = m.removeFrame(); // fresh stack frame for loop body
			m.addFrame( new StackFrame( FrameType.LOOP ) );
			m.write( loopVar, (int)m.read( "line_" + lineId + "_index", false ), false ); // update loop variable
			actions.push( (Memory mem) -> {
				mem.removeFrame();
				mem.addFrame( old );
				mem.write( "line_" + lineId + "_index", oldIndex, false );
				return new ControlFlow( ControlFlow.STEP_INTO );
			});
		}
		return new ControlFlow( ControlFlow.STEP_INTO );
	}
	
	abstract protected int minimum( Memory m );
	
	abstract protected int maximum( Memory m );
}
