package processor;

import java.awt.Rectangle;

import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;

import codeline.CodeLine;
import lib.TextLayoutHelper;

/**
 * represents a line of pseudocode
 * @author kolja
 *
 */
public class PseudoCodeNode extends DefaultMutableTreeNode {
    private static final long serialVersionUID = 7366822030747482541L;

    public static enum CodeAction
    {
        SKIP,
        STOP,
        CONTINUE
    }

    private static int nextNodeId = 0;
    private final int nodeId;
    private ProcessController controller;
    private JTree tree;
    private CodeLine code;
    private boolean selected;
    private boolean breakPoint;
    
    public PseudoCodeNode( String description, String[] vars, JTree tree, CodeLine line )
    {
        super( TextLayoutHelper.setupPseudoCode( description, vars ) );
        synchronized( PseudoCodeNode.class )
        {
        	nodeId = nextNodeId++;
        }
        selected = false;
        this.tree = tree;
        breakPoint = false;
        code = line;
    }

    public int getId()
    {
        return nodeId;
    }
    
    public void setController( ProcessController c )
    {
        if( children != null )
        {
            for( Object ch : children )
            {
                ((PseudoCodeNode)ch).setController( c );
            }
        }
        controller = c;
    }
    
    @Override
    public void add( MutableTreeNode node )
    {
        ((PseudoCodeNode)node).setController( controller );
        super.add( node );
    }
    
    /**
     * 
     * @return the tree that this node belongs to
     */
    public JTree getTree()
    {
        return tree;
    }
    
    /**
     * checks if this node should be highlighted
     * @return true if it should, false otherwise
     */
    public boolean isSelected()
    {
        return selected;
    }
    
    /**
     * checks if one of the subnodes of this node is selected.
     * @return true if one is, false otherwise
     */
    public boolean hasSelectedSubnode()
    {
    	if( children != null )
    	{
	    	for( Object ch : children )
	        {
	            if( ((PseudoCodeNode)ch).isSelected() || ((PseudoCodeNode)ch).hasSelectedSubnode() )
	            	return true;
	        }
    	}
    	return false;
    }
    
    private void expandToRoot()
    {
    	if( parent != null )
    		((PseudoCodeNode)parent).expandToRoot();
        tree.expandPath( new TreePath( this.getPath() ) );
    }

    /**
     * highlight this line of pseudocode.
     * should be called when the line is entered, as it triggers breakpoints
     * @param selected whether to select or deselect this line
     * @return if the automatic execution should continue.
     */
    public CodeAction setSelected( boolean selected )
    {
        if( selected && breakPoint )
            controller.setContinuous( false );
        this.selected = selected;
        if( selected )
        {
            if( tree != null ) {
                TreePath path = new TreePath( getPath() );
                Rectangle bounds = tree.getPathBounds(path);
                if( bounds!= null )
                {
                    bounds.height = (int) (tree.getVisibleRect().height - tree.getVisibleRect().getHeight() / 2);
                    bounds.x = 0;
                    SwingUtilities.invokeLater( new Runnable() {
                        @Override
                        public void run() {
                            tree.scrollRectToVisible(bounds);
                        }
                    });
                }
            }
            if( controller == null || controller.getStepOption() != 1 || breakPoint )
            {
                SwingUtilities.invokeLater( new Runnable() {
                    @Override
                    public void run() {
                        expandToRoot();
                    }
                });
            }
        }
        else
        {
            if( controller == null || controller.getStepOption() != 1 )
            {
                SwingUtilities.invokeLater( new Runnable() {
                    @Override
                    public void run() {
                        tree.collapsePath( new TreePath( getPath() ) );
                    }
                });
            }
        }
        if( breakPoint && selected )
            return CodeAction.STOP; // Breakpoint
        if( controller != null && controller.getStepOption() == 1 && !tree.isVisible( new TreePath( this.getPath() ) ) )
            return CodeAction.SKIP; // Step would be to detailed
        return CodeAction.CONTINUE; // Normal
    }
    
    /**
     * set a breakpoint at this line of code
     * @param breakPoint whether there should be a breakpoint or node
     */
    public void setBreakPoint( boolean breakPoint )
    {
        this.breakPoint = breakPoint;
    }
    
    /**
     * check if there is a breakpoint set at this line of code
     * @return true, iff there is a breakpoint
     */
    public boolean hasBreakPoint()
    {
        return breakPoint;
    }

    public ControlFlow forwardStep( Memory m )
    {
        ControlFlow cf = code.runForward( m );
        cf.setJumpBack( this );
        return cf;
    }

    public ControlFlow emptyForwardStep( Memory m )
    {
        code.createEmptyBackwardsAction();
        ControlFlow cf = new ControlFlow( ControlFlow.STEP_OVER );
        cf.setJumpBack( this );
        return cf;
    }

    public void backwardStep( Memory m )
    {
        code.runBackward( m );
    }

    public String getDebugOutput( Memory m )
    {
        if( parent == null )
            return "";
        return ((PseudoCodeNode)parent).getDebugOutput( m );
    }
}
