package processor;

import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.util.Stack;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import processor.Action;
import processor.ProcessController;
import graph.LayeredGraphNode;
import processor.Memory.Visibility;
import processor.StackFrame.FrameType;

public class PseudoCodeProcessor extends Thread {

    public static enum CodeStatus
    {
        UNFINISHED,
        BREAKPOINT,
        FINISHED
    }

    private LayeredGraphNode graph;
    private String currentDebugOutput;
    private Memory mem;
    private PseudoCodeNode programPointer;
    private Stack<ControlFlow> controlStack;
    private boolean skip = false;
    private boolean renderImage = true;
    private JFrame view;
    private ProcessController controller;
    
    public PseudoCodeProcessor( PseudoCodeNode start, LayeredGraphNode graph, JFrame view )
    {
        mem = new Memory();
        mem.addFrame( new StackFrame( FrameType.FUNCTION ) );
        mem.declare( "graph", graph, Visibility.GLOBAL );
        programPointer = start;
        currentDebugOutput = "";
        controlStack = new Stack<>();
        this.graph = graph;
        controller = new ProcessController();
        this.view = view;
    }
    
    public ProcessController getController()
    {
    	return controller;
    }
    
    private CodeStatus selectNextNode( PseudoCodeNode next, PseudoCodeNode last )
    {
        programPointer = next;
        last.setSelected( false );
        switch( next.setSelected( true ) )
        {
        case CONTINUE:
            skip = false;
            return CodeStatus.UNFINISHED;
        case SKIP:
            return forwardStepOverUntilNotSkip();
        case STOP:
            return CodeStatus.BREAKPOINT;
        }
        return CodeStatus.UNFINISHED;
    }
    
    protected CodeStatus forwardStep()
    {
        if( programPointer == null )
            return CodeStatus.FINISHED;
        StackFrame before = mem.removeFrame();
        mem.addFrame( before );
        ControlFlow cf = null;
        if( mem.isDefined( "_call" + programPointer.getId(), Visibility.LOCAL ) )
        {
            String name = "_call" + programPointer.getId();
            mem.undeclare( name, Visibility.LOCAL );
            cf = programPointer.emptyForwardStep( mem );
            cf.setBackwardAction( (Memory m) -> {
                mem.declare( name, true, Visibility.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(), Visibility.GLOBAL ) )
            {
                String name = "_returnTo" + programPointer.getId();
                mem.declare( name, mem.read( name, Visibility.GLOBAL ), Visibility.LOCAL );
                mem.undeclare( name, Visibility.GLOBAL );
                cf.setBackwardAction( (Memory m) -> {
                    mem.declare( name, mem.read( name, Visibility.LOCAL ), Visibility.GLOBAL );
                    mem.undeclare( name, Visibility.LOCAL );
                });
            }
            if( programPointer.getChildCount() == 0 )
                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() ) )
            {
                String name = "_returnTo" + programPointer.getId();
                PseudoCodeNode nextPC = before.<PseudoCodeNode>get( name );
                before.undeclare( name );
                cf.setBackwardAction( (Memory m) -> {
                    before.declare( name, 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();
            String name = "_call" + programPointer.getId();
            mem.declare( name, true, Visibility.LOCAL );
            mem.declare( "_returnTo" + f.getId(), cf.getJumpBack(), Visibility.GLOBAL );
            cf.setBackwardAction( (Memory m) -> {
                m.undeclare( "_returnTo" + f.getId(), Visibility.GLOBAL );
                m.undeclare( name, Visibility.LOCAL );
            });
            return selectNextNode( f, programPointer );
        }
        throw new IllegalStateException( "Unknown ControlFlow action" );
    }
    
    private CodeStatus forwardStepOverUntilNotSkip() {
        skip = true;
        if( programPointer == null )
            return CodeStatus.FINISHED;
        int stackSize = mem.getSize();
        CodeStatus status = CodeStatus.UNFINISHED;
        do {
            status = forwardStep();
        } while( mem.getSize() > stackSize && status == CodeStatus.UNFINISHED && skip );
        return status;
    }
    
    protected 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;
    }
    
    protected 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:
            skip = false;
            return CodeStatus.UNFINISHED;
        case SKIP:
            return backwardStepOverUntilNotSkip();
        case STOP:
            return CodeStatus.BREAKPOINT;
        }
        return CodeStatus.UNFINISHED;
    }
    
    protected 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 );
    }
    
    private CodeStatus backwardStepOverUntilNotSkip()
    {
        skip = true;
        if( programPointer == null )
            return CodeStatus.FINISHED;
        int stackSize = mem.getSize();
        CodeStatus status = CodeStatus.UNFINISHED;
        do {
            status = backwardStep();
        } while( mem.getSize() > stackSize && status == CodeStatus.UNFINISHED && skip );
        return status;
    }
    
    protected 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;
    }
    
    protected 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;
    }

    private synchronized void update()
    {
        if( renderImage )
        {
            renderImage = false;
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    for( ComponentListener l : view.getComponentListeners() )
                    {
                        l.componentResized( new ComponentEvent(view, 0) );
                        synchronized( this )
                        {
                            renderImage = true;
                        }
                    }
                }
            });
        }
    }
    
    @Override
    public void run()
    {
        while( true ) // if this loop would end we could not undo steps any more
        {
        	CodeStatus status = null;
            try {
                Action action = controller.getNextAction();
                graph.unselectGraph();
                switch( action )
                {
                case FORWARD:
                	status = forwardStep();
                    break;
                case FORWARD_OUT:
                    status = forwardStepOut();
                    graph.unselectGraph();
                    break;
                case FORWARD_OVER:
                    status = forwardStepOver();
                    graph.unselectGraph();
                    break;
                case BACKWARD:
                    status = backwardStep();
                    break;
                case BACKWARD_OUT:
                    status = backwardStepOut();
                    graph.unselectGraph();
                    break;
                case BACKWARD_OVER:
                    status = backwardStepOver();
                    graph.unselectGraph();
                    break;
                default:
                    assert false;
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            }
            update();
            if( status == CodeStatus.FINISHED )
            {
            	controller.setContinuous( false );
            	controller.setNextAction( null );
            }
        }
    }
    
    public Memory getMemory()
    {
        return mem;
    }
    
    public String getDebugOutput()
    {
        return currentDebugOutput;
    }
}
