package animation;

import java.util.Stack;

import animation.Memory.MemoryType;
import animation.StackFrame.FrameType;

public class PseudoCodeProcessor {

    public static enum CodeStatus
    {
        UNFINISHED,
        BREAKPOINT,
        FINISHED
    }
    
    private String currentDebugOutput;
    private Memory mem;
    private PseudoCodeNode programPointer;
    private Stack<ControlFlow> controlStack;
    
    public PseudoCodeProcessor( PseudoCodeNode tree )
    {
        mem = new Memory();
        mem.addFrame( new StackFrame( FrameType.FUNCTION ) );
        programPointer = tree;
        currentDebugOutput = "";
        controlStack = new Stack<>();
    }
    
    private CodeStatus selectNextNode( PseudoCodeNode next, PseudoCodeNode last )
    {
        programPointer = next;
        last.setSelected( false );
        switch( next.setSelected( true ) )
        {
        case CONTINUE:
            return CodeStatus.UNFINISHED;
        case SKIP:
            return forwardStepOver();
        case STOP:
            return CodeStatus.BREAKPOINT;
        }
        return CodeStatus.UNFINISHED;
    }
    
    public CodeStatus forwardStep()
    {
        if( programPointer == null )
            return CodeStatus.FINISHED;
        StackFrame before = mem.removeFrame();
        mem.addFrame( before );
        ControlFlow cf = null;
        if( mem.isDefined( "_call" + programPointer.getId(), MemoryType.LOCAL ) )
        {
            mem.undeclare( "_call" + programPointer.getId(), MemoryType.LOCAL );
            cf = programPointer.emptyForwardStep( mem );
            cf.setBackwardAction( (Memory m) -> {
                mem.declare( "_call" + programPointer.getId(), true, MemoryType.LOCAL );
            });
        }
        else
            cf = programPointer.forwardStep( mem );
        controlStack.push( cf );
        currentDebugOutput = programPointer.getDebugOutput( mem );
        switch( cf.getStatus() )
        {
        case ControlFlow.STEP_INTO:
            if( mem.isDefined( "_returnTo" + programPointer.getId(), MemoryType.GLOBAL ) )
            {
                mem.declare( "_returnTo" + programPointer.getId(), mem.read( "_returnTo" + programPointer.getId(), MemoryType.GLOBAL ), MemoryType.LOCAL );
                mem.undeclare( "_returnTo" + programPointer.getId(), MemoryType.GLOBAL );
                cf.setBackwardAction( (Memory m) -> {
                    mem.declare( "_returnTo" + programPointer.getId(), mem.read( "_returnTo" + programPointer.getId(), MemoryType.LOCAL ), MemoryType.GLOBAL );
                    mem.undeclare( "_returnTo" + programPointer.getId(), MemoryType.LOCAL );
                });
            }
            if( programPointer.children() == null )
                throw new IllegalStateException( "A Codeline without sublines tried to make a STEP_INTO." );
            else
                return selectNextNode( (PseudoCodeNode)programPointer.getFirstChild(), programPointer );
        case ControlFlow.STEP_OVER:
            if( programPointer.getParent() == null )
                return CodeStatus.FINISHED;
            if( before.isDefined( "_returnTo" + programPointer.getId() ) )
            {
                PseudoCodeNode nextPC = before.<PseudoCodeNode>get( "_returnTo" + programPointer.getId() );
                before.undeclare( "_returnTo" + programPointer.getId() );
                cf.setBackwardAction( (Memory m) -> {
                    before.declare( "_returnTo" + programPointer.getId(), nextPC );
                });
                return selectNextNode( nextPC, programPointer );
            }
            PseudoCodeNode nextPC = (PseudoCodeNode) ((PseudoCodeNode)programPointer.getParent()).getChildAfter( programPointer );
            if( nextPC == null )
                return selectNextNode( (PseudoCodeNode)programPointer.getParent(), programPointer );
            else
                return selectNextNode( nextPC, programPointer );
        case ControlFlow.CALL:
            PseudoCodeNode f = cf.getFunction();
            mem.declare( "_call" + programPointer.getId(), true, MemoryType.LOCAL );
            mem.declare( "_returnTo" + f.getId(), cf.getJumpBack(), MemoryType.GLOBAL );
            cf.setBackwardAction( (Memory m) -> {
                m.undeclare( "_returnTo" + f.getId(), MemoryType.GLOBAL );
                m.undeclare( "_call" + programPointer.getId(), MemoryType.LOCAL );
            });
            return selectNextNode( f, programPointer );
        }
        throw new IllegalStateException( "Unknown ControlFlow action" );
    }
    
    public CodeStatus forwardStepOver()
    {
        if( programPointer == null )
            return CodeStatus.FINISHED;
        int stackSize = mem.getSize();
        CodeStatus status = CodeStatus.UNFINISHED;
        do {
            status = forwardStep();
        } while( mem.getSize() > stackSize && status == CodeStatus.UNFINISHED );
        return status;
    }
    
    public CodeStatus forwardStepOut()
    {
        if( programPointer == null )
            return CodeStatus.FINISHED;
        int stackSize = mem.getSize();
        CodeStatus status = CodeStatus.UNFINISHED;
        do {
            status = forwardStep();
        } while( mem.getSize() >= stackSize && status == CodeStatus.UNFINISHED );
        return status;
    }
    
    private CodeStatus selectBeforeNode( PseudoCodeNode next, PseudoCodeNode last )
    {
        programPointer = next;
        last.setSelected( false );
        switch( next.setSelected( true ) )
        {
        case CONTINUE:
            return CodeStatus.UNFINISHED;
        case SKIP:
            return backwardStepOver();
        case STOP:
            return CodeStatus.BREAKPOINT;
        }
        return CodeStatus.UNFINISHED;
    }
    
    public CodeStatus backwardStep()
    {
        if( programPointer == null || controlStack.isEmpty() )
            return CodeStatus.FINISHED;
        ControlFlow cf = controlStack.pop();
        PseudoCodeNode nextPC = cf.getJumpBack();
        cf.backward( mem );
        nextPC.backwardStep( mem );
        currentDebugOutput = nextPC.getDebugOutput( mem );
        return selectBeforeNode( nextPC, programPointer );
    }
    
    public CodeStatus backwardStepOver()
    {
        if( programPointer == null )
            return CodeStatus.FINISHED;
        int stackSize = mem.getSize();
        CodeStatus status = CodeStatus.UNFINISHED;
        do {
            status = backwardStep();
        } while( mem.getSize() > stackSize && status == CodeStatus.UNFINISHED );
        return status;
    }
    
    public CodeStatus backwardStepOut()
    {
        if( programPointer == null )
            return CodeStatus.FINISHED;
        int stackSize = mem.getSize();
        CodeStatus status = CodeStatus.UNFINISHED;
        do {
            status = backwardStep();
        } while( mem.getSize() >= stackSize && status == CodeStatus.UNFINISHED );
        return status;
    }
    
    public Memory getMemory()
    {
        return mem;
    }
    
    public String getDebugOutput()
    {
        return currentDebugOutput;
    }
}
