package codeline;

import processor.ControlFlow;
import processor.Memory;
import processor.StackFrame;
import processor.Memory.ReadOnlyMemory;
import processor.Memory.Visibility;
import processor.StackFrame.FrameType;

/**
 * the initial line of a for loop
 * @author kolja
 *
 * @param <T> the type of the iteration variable
 */
public abstract class AbstractForLoop<T> extends CodeLine {

    String loopVar;

    public AbstractForLoop( String varName )
    {
        this.loopVar = varName;
    }
    
    @Override
    public ControlFlow runForward(Memory m) {
        if( !m.isSomewhereDefined( "line_" + lineId + "_index", Visibility.LOCAL ) )
        { // first loop step
            m.declare( "line_" + lineId + "_index", 0, Visibility.LOCAL );
            m.addFrame( new StackFrame( FrameType.LOOP ) );
            m.declare( loopVar, intialize( m.createReadOnlyMemory() ), Visibility.LOCAL ); // set loop variable
            if( !condition( m.createReadOnlyMemory() ) ) // prove if the loop has finished
            {
                m.removeFrame();
                actions.push( (Memory mem) -> {
                    mem.undeclare( "line_" + lineId + "_index", Visibility.LOCAL );
                    } );
                return new ControlFlow( ControlFlow.STEP_OVER ); // don't execute the loop body
            }
            actions.push( (Memory mem) -> {
                mem.removeFrame();
                mem.undeclare( "line_" + lineId + "_index", Visibility.LOCAL );
            } );
            return new ControlFlow( ControlFlow.STEP_INTO );
        }
        T next = step( m.createReadOnlyMemory() );
        T old = m.read( loopVar, Visibility.LOCAL );
        m.write( loopVar, next, Visibility.LOCAL );
        if( !condition( m.createReadOnlyMemory() ) ) // prove if loop was finished
        {
            StackFrame sf = m.removeFrame(); // remove loop stack
            actions.add( (Memory mem) -> {
                mem.addFrame( sf ); // restore last loop stack
                mem.write( loopVar, old, Visibility.LOCAL );
            });
            return new ControlFlow( ControlFlow.STEP_OVER ); // step out of the loop
        }
        StackFrame oldF = m.removeFrame(); // fresh stack frame for loop body
        m.addFrame( new StackFrame( FrameType.LOOP ) );
        m.declare( loopVar, next, Visibility.LOCAL );
        actions.push( (Memory mem) -> {
            mem.removeFrame();
            mem.addFrame( oldF );
            mem.write( loopVar, old, Visibility.LOCAL );
        });
        return new ControlFlow( ControlFlow.STEP_INTO );
    }
    
    /**
     * the initialization step of the loop.
     * only executed if the loop variable is undefined.
     * @param m the memory in which the variables are stored
     * @return the initial value of the iteration variable
     */
    protected abstract T intialize( ReadOnlyMemory m );
    
    /**
     * the statement executed after each iteration
     * @param m the memory in which the variables are stored
     * @return the next value of the iteration variable
     */
    protected abstract T step( ReadOnlyMemory m );
    
    /**
     * the exit condition of the loop
     * @param m the memory in which the variables are stored
     * @return false if the loop is over
     */
    protected abstract boolean condition( final ReadOnlyMemory m );
}
