/* ************************************************************************
#
#  DivConq
#
#  http://divconq.com/
#
#  Copyright:
#    Copyright 2012 eTimeline, LLC. All rights reserved.
#
#  License:
#    See the license.txt file in the project's top-level directory for details.
#
#  Authors:
#    * Andy White
#
************************************************************************ */
package divconq.script;

import java.util.ArrayList;
import java.util.List;

import divconq.lang.OperationResult;
import divconq.struct.RecordStruct;
import divconq.xml.XElement;

public abstract class BlockInstruction extends Instruction {
	protected List<Instruction> instructions = new ArrayList<Instruction>();

	protected List<Instruction> getInstructions() { 
		return this.instructions; 
    }

    public void addInstruction(Instruction inst) {
    	this.instructions.add(inst);
    }

	@Override
	public void compile(ActivityManager manager, OperationResult log) {
		super.compile(manager, log);
		
        for (XElement child : this.source.selectAll("*")) {
        	// Internal and Detail are always a special child tag, do not treat these as instructions
        	if ("Internal".equals(child.getName()) || "Detail".equals(child.getName()))
        		continue;
        	
            Instruction ni = manager.createInstruction(child);
            
            if (ni == null) {
            	log.errorTr(509, child.getName());
            	continue;
            }
            
            ni.setXml(child);
            
            this.addInstruction(ni);
            
            ni.compile(manager, log);
        }
	}

	// no need for try catch here, should always be handled in the subclass
	@Override
    public void run(final StackEntry stack, final IInstructionCallback cb) {
		final StackBlockEntry bstack = (StackBlockEntry)stack;
		
		if (bstack.getState() == ExecuteState.Ready) {
        	bstack.setState(ExecuteState.Resume);
	        cb.resume();
        	return;
		}
		
		this.alignInstruction(stack);
		
        if (bstack.getState() != ExecuteState.Resume) {
	        cb.resume();
        	return;
        }
        
        // give the debugger a more natural feel by pointing to the top instruction before executing it
        if (bstack.getTopFlag()) {
        	bstack.setTopFlag(false);
	        cb.resume();
        	return;
        }

        bstack.getChild().run(new IInstructionCallback() {
			@Override
			public void resume() {
				StackEntry child = bstack.getChild();
				
				if (child == null) {
					bstack.setState(ExecuteState.Done);
				}
				else {				
					ExecuteState cstate = child.getState();
					
					if ((cstate != ExecuteState.Ready) && (cstate != ExecuteState.Resume)) {
				        if (cstate == ExecuteState.Exit) 
				        	bstack.setState(ExecuteState.Exit);
				        else if (cstate == ExecuteState.Break)  
				        	bstack.setState(ExecuteState.Done);
				        else if (cstate == ExecuteState.Continue) 
				        	BlockInstruction.this.continueInstruction(stack);
				        else   // if Done
				        	BlockInstruction.this.nextInstruction(stack);
					}
				}
				
		        cb.resume();
			}
		});
    }

	// continue is essentially the same as reaching the end of the block
	// see alignInstruction - subclasses will treat end of block as flag
	// to either bail or continue the loop (check condition)
    public void continueInstruction(final StackEntry stack) {
		final StackBlockEntry bstack = (StackBlockEntry)stack;
		
		bstack.setPosition(this.instructions.size());
		bstack.setChild(null);
		bstack.setTopFlag(true);
    }

    public void nextInstruction(final StackEntry stack) {
		final StackBlockEntry bstack = (StackBlockEntry)stack;
		
    	bstack.setPosition(bstack.getPosition() + 1);
    	
    	// if at end of block, treat as a continue - initiate iteration logic
    	if (bstack.getPosition() >= this.instructions.size()) 
    		this.continueInstruction(stack);
    	else
    		this.alignInstruction(stack);
    }
    
    // sub classes will override this method to control instruction placement
    // and block repetition
    public void alignInstruction(final StackEntry stack) {
    	StackBlockEntry bstack = (StackBlockEntry)stack;
    	
    	if ((bstack.getState() == ExecuteState.Done) || (bstack.getState() == ExecuteState.Break) || (bstack.getState() == ExecuteState.Continue) || (bstack.getState() == ExecuteState.Exit)) {
        	bstack.setChild(null);
    	}
    	else if (bstack.getPosition() >= this.instructions.size()) {
        	bstack.setChild(null);
    		stack.setState(ExecuteState.Done);
    	}
        else {
        	Instruction inst = this.instructions.get(bstack.getPosition());        	
        	StackEntry child = bstack.getChild();
        	
        	// literally, is it the same inst?  if not then we load the current 
        	if ((child == null) || (child.getInstruction() != inst)) 
	        	bstack.setChild(inst.createStack(stack.getActivity(), bstack));
        }
    }

    @Override
	public StackEntry createStack(Activity act, StackEntry parent) {
		return new StackBlockEntry(act, parent, this);
	}	
	
    @Override
    public void collectDebugRecord(final StackEntry stack, RecordStruct rec) {
		rec.setField("Line", this.source.getLine());
		rec.setField("Column", this.source.getCol());		
    	rec.setField("Command", this.source.toLocalString());
    }	
}
