/* ************************************************************************
#
#  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.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;

import divconq.lang.OperationResult;
import divconq.schema.DataType;
import divconq.struct.Struct;
import divconq.struct.CompositeStruct;
import divconq.struct.ListStruct;
import divconq.struct.RecordStruct;
import divconq.util.StringUtil;
import divconq.xml.XElement;

// TODO global variables need to be pushed by Collab to all activities, those vars get stored in Activity
// level variables (so be sure to use a good naming convention for globals - all start with "gbl"

// note that Activity, not Main is the root function block, little different but means exit codes are with Activity
public class Activity {
	protected XElement xml = null;
    protected String id = null;
    protected ActivityManager manager = null;
    protected boolean suspended = false;
	protected Map<String,Instruction> functions = new HashMap<String,Instruction>();
	protected StackFunctionEntry stack = null;
	protected Instruction inst = null;
	protected long runtime = 0;
	
	protected Map<String, Struct> globals = new HashMap<String, Struct>();
	
    // what we send back - errors/state  and Return variables
    protected OperationResult log = new OperationResult(true);

    public Activity(ActivityManager man) {
    	this.manager = man;
    }

    public XElement getXml() {
        return this.xml; 
    }
	
	public OperationResult getLog() {
		return this.log;
	}

    public ExecuteState getState() {
        return (this.stack != null) ? this.stack.getState() : ExecuteState.Ready;
    }
    
    public Long getExitCode() {
        return (this.stack != null) ? this.stack.getLastCode() : 0;
    }
    
    public Struct getExitResult() {
        return (this.stack != null) ? this.stack.getLastResult() : null;
    }
    
    public boolean isSuspended() {
    	return this.suspended;
    }
    
    public void setSuspended(boolean v) {
    	this.suspended = v;
    }

    public String getId() { 
    	return this.id; 
    }
    
    public void setId(String v) { 
    	this.id = v; 
    }

	public String getName() {
		if (this.xml == null)
			return "";
		
		XElement sc = this.xml.find("Script");
		
		return (sc != null) ? sc.getAttribute("Name") : "Main"; 
	}
	
	public long getRuntime() {
		return this.runtime;
	}

	// TODO figure some way to pass Parameter to Main
	
    //  TODO add run as alternative, throw activity into thread pool each call
	
    public void runSimple() {
		final Semaphore s = new Semaphore(0);
		
		final IInstructionCallback cb = new IInstructionCallback() {
			@Override
			public void resume() {
				s.release();
			}
		};
	
		long st = System.currentTimeMillis();
		
		while (!this.suspended && (this.getState() != ExecuteState.Exit)) {
			this.runSingleInstruction(cb);			
			
			try {
				s.acquire();
			} 
			catch (InterruptedException x) {
				break;
			}
		}
		
		this.runtime = (System.currentTimeMillis() - st);
    }
    
    public void resumeSimple() {
    	this.suspended = false;
    	this.runSimple();
    }

    public void runSingleInstruction(IInstructionCallback cb) {
        //try {
    	if (this.inst == null) { 
    		cb.resume();
    		return;
    	}
    	
    	if (this.stack == null) 
       		this.stack = (StackFunctionEntry)this.inst.createStack(this, null);
    	
        if (this.getState() != ExecuteState.Exit) 
        	this.stack.run(cb);
        else
        	cb.resume();
            
        /*
		catch (Exception x) {
			this.m_lasterror = x;
			this.debugOn();
			
			// TODO
			//m_plan.Controller.ProcessFailed(this);

			//this.addError("An error occurred in process: " + this.m_id);
			
			// TODO instruction exception
			//this.AddError("Error with instruction: " + x.Instruction.Source.Name.LocalName);
			//this.AddError("Error on line: " + ((IXmlLineInfo)x.Element).LineNumber);
			//this.AddError("Error message: " + x.InnerException.Message);
		}
		*/
	}
    
    public DataType getType(String type) {
    	return this.manager.getType(type);
    }

    public Struct createStruct(String type) {
        return this.manager.createVariable(this.log, type);
    }

    public CompositeStruct getDebugInfo() {
    	RecordStruct info = new RecordStruct();		// TODO type this
    	
    	info.setField("Log", this.log.getMessages());
    	
    	ListStruct list = new ListStruct();
    	
    	if (this.stack != null)
    		this.stack.debugStack(list);
    	
        info.setField("Stack", list);
        
        return info;
    }

    public OperationResult compile(XElement doc) {
        this.xml = doc;
        this.inst = null;
        
        OperationResult log = new OperationResult(true);
        
        for (XElement func : doc.selectAll("Function")) {
        	String fname = func.getAttribute("Name");
        	
        	if (StringUtil.isEmpty(fname))
        		continue;
        	
	        Instruction ni = this.manager.createInstruction(func);
	        ni.setXml(func);
	        ni.compile(this.manager, log);
	        
	        this.functions.put(fname, ni);
        }
        
        XElement node = doc.find("Main");

        if (node == null) {
        	log.errorTr(506);
        }
        else {
	        Instruction ni = this.manager.createInstruction(node);
	        ni.setXml(node);
	        ni.compile(this.manager, log);
	
	        this.inst = ni; 
        }
        
        return log;
    }

    // global variables
    public Struct queryVariable(String name) {
		if (StringUtil.isEmpty(name))
			return null;

        // do not call super - that would expose vars outside of the function
        int dotpos = name.indexOf(".");

        if (dotpos > -1) {
            String oname = name.substring(0, dotpos);

            Struct ov = this.globals.containsKey(oname) ? this.globals.get(oname) : null;

            if (ov == null) {
            	this.log.errorTr(507, oname);
            	return null;
            }
            
            if (!(ov instanceof CompositeStruct)){
            	this.log.errorTr(508, oname);
            	return null;
            }

            return ((CompositeStruct)ov).select(this.log, name.substring(dotpos + 1));
        }
        else if (this.globals.containsKey(name)) {
            return this.globals.get(name);
        }
        
        return null;
    }

	public Instruction queryFunction(String name) {
		return this.functions.get(name);
	}

	public void addVariable(String name, Struct var) {
    	this.globals.put(name, var);
	}
}
