/* ************************************************************************
#
#  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 divconq.lang.OperationResult;
import divconq.schema.DataType;
import divconq.struct.ListStruct;
import divconq.struct.ScalarStruct;
import divconq.struct.Struct;
import divconq.struct.RecordStruct;
import divconq.struct.scalar.BooleanStruct;
import divconq.struct.scalar.IntegerStruct;
import divconq.util.StringUtil;
import divconq.xml.XElement;

public class StackEntry {
	protected ExecuteState state = ExecuteState.Ready;
	protected StackEntry parent = null;
	protected Activity activity = null;
	protected Instruction inst = null;
	protected RecordStruct inststore = new RecordStruct();
	protected boolean doneFlag = false;
	
	public StackEntry(Activity act, StackEntry parent, Instruction inst) {
		this.activity = act;
		this.parent = parent;
		this.inst = inst;
	}
	
    public StackEntry getParent() {
    	return this.parent;
    }
    
    public void setParent(StackEntry v) {
    	this.parent = v;	
    }
    
	public RecordStruct getStore() {
		return this.inststore;
	}
	
	public Instruction getInstruction() {
		return this.inst;
	}
	
	public void setInstruction(Instruction v) {
		this.inst = v;
	}
	
	public boolean isDone() {
		return this.doneFlag;
	}
	
	public void setDone(boolean v) {
		this.doneFlag = v;
	}
	
    public ExecuteState getState() {
        return (this.state != null) ? this.state : ExecuteState.Exit;
    }
    
    public void setState(ExecuteState v) {
    	this.state = v;
    }

    public Struct queryVariable(String name) {
        return (this.parent != null) ? this.parent.queryVariable(name) : null;
    }

    public Activity getActivity() {
        return this.activity;
    }

    public StackBlockEntry queryBlockStack() {
        if (this.parent == null)
            return null;

        return this.parent.queryBlockStack();
    }

    public StackFunctionEntry queryFunctionStack() {
        if (this.parent == null)
            return null;

        return this.parent.queryFunctionStack();
    }

    public OperationResult log() {
        return this.activity.getLog();
    }    
    
    public Struct resolveValue(String val, String type) {
    	return this.resolveValue(val, this.activity.manager.getType(type));
    }
    
    public Struct resolveValue(String val, DataType type) {
    	if (StringUtil.isEmpty(val))
    		return null;
    	
    	val = this.resolveValueToString(val);
    	
    	if (val.startsWith("$")) 
    		return this.queryVariable(val.substring(1));
    	
    	// if we get this far then we must be dealing with a literal (and thus a scalar, all literals are scalars in dcScript)
    	
    	// if no type given then we cannot proceed - not an error, may be deliberate
    	if (type == null)
    		return null;
    	
    	// create the scalar to hold the value
    	Struct sref = type.create(this.log());  
    	
    	// this is an error, scripts should not create types not available
    	if ((sref == null) || !(sref instanceof ScalarStruct)) {
    		this.log().errorTr(514, type);
    		return null;
    	}
		
    	ScalarStruct ssref = (ScalarStruct) sref;
    	    	
    	// skip the literal flag
    	if (val.startsWith("`"))
    		val = val.substring(1);

    	// put the literal value into the scalar, it will adopt to the appropriate data type
    	ssref.adaptValue(val);
    	
        return ssref;
    }

	public void mutate(StackEntry inst, Struct target, XElement source) {
		this.activity.manager.mutate(this.activity, inst, target, source);
	}
    
    public Struct refFromSource(String attr) {
        return this.refFromSource(this.inst.source, attr, (DataType)null);
    }
    
    public Struct refFromSource(String attr, String type) {
        return this.refFromSource(this.inst.source, attr, type);
    }
    
    public Struct refFromSource(String attr, DataType type) {
        return this.refFromSource(this.inst.source, attr, type);
    }
    
    public Struct refFromSource(XElement el, String attr) {
        return this.refFromSource(el, attr, (DataType)null);
    }
    
    public Struct refFromSource(XElement el, String attr, String type) {
    	if ((el == null) || StringUtil.isEmpty(attr))
    		return null;
    	
        return this.resolveValue(el.getAttribute(attr), type);
    }
    
    public Struct refFromSource(XElement el, String attr, DataType type) {
    	if ((el == null) || StringUtil.isEmpty(attr))
    		return null;
    	
        return this.resolveValue(el.getAttribute(attr), type);
    }
    
    public String stringFromSource(String attr) {
        return this.stringFromSource(this.inst.source, attr, null);
    }
    
    public String stringFromSource(String attr, String def) {
        return this.stringFromSource(this.inst.source, attr, def);
    }
    
    public String stringFromSource(XElement el, String attr) {
        return this.stringFromSource(el, attr, null);
    }
    
    public String stringFromSource(XElement el, String attr, String def) {
    	if ((el == null) || StringUtil.isEmpty(attr))
    		return def;
    	
        Struct dt = this.resolveValue(el.getAttribute(attr), "String");
        
        if (dt != null) {
        	String ret = dt.toString();
        	
        	if (StringUtil.isNotEmpty(ret))
        		return ret;
        }
        
        return def;
    }
    
    public long intFromSource(String attr) {
        return this.intFromSource(this.inst.source, attr, 0);
    }
    
    public long intFromSource(String attr, int def) {
        return this.intFromSource(this.inst.source, attr, def);
    }
    
    public long intFromSource(XElement el, String attr) {
        return this.intFromSource(el, attr, 0);
    }
    
    public long intFromSource(XElement el, String attr, int def) {
    	if ((el == null) || StringUtil.isEmpty(attr))
    		return def;
    	
        Struct dt = this.resolveValue(el.getAttribute(attr), "Integer");
        
        if ((dt != null) && (dt instanceof IntegerStruct)) {
        	Long ret = ((IntegerStruct)dt).getValue();
        	
        	if (ret != null)
        		return ret;
        }
        
        return def;
    }
    
    public boolean boolFromSource(String attr) {
        return this.boolFromSource(this.inst.source, attr, false);
    }
    
    public boolean boolFromSource(String attr, boolean def) {
        return this.boolFromSource(this.inst.source, attr, def);
    }
    
    public boolean boolFromSource(XElement el, String attr) {
        return this.boolFromSource(el, attr, false);
    }
    
    public boolean boolFromSource(XElement el, String attr, boolean def) {
    	if ((el == null) || StringUtil.isEmpty(attr))
    		return def;
    	
        Struct dt = this.resolveValue(el.getAttribute(attr), "Boolean");
        
        if ((dt != null) && (dt instanceof BooleanStruct)) {
        	Boolean ret = ((BooleanStruct)dt).getValue();
        	
        	if (ret != null)
        		return ret;
        }
        
        return def;
    }

    public String resolveValueToString(String val) {
    	if (StringUtil.isEmpty(val))
    		return null;
    	
        // the expansion of variables is per Attribute Value Templates in XSLT
        // http://www.w3.org/TR/xslt#attribute-value-templates

        StringBuilder sb = new StringBuilder();

        int lpos = 0;
        int bpos = val.indexOf("{$");

        while (bpos != -1) {
            int epos = val.indexOf("}", bpos);
            if (epos == -1) 
            	break;

            sb.append(val.substring(lpos, bpos));

            lpos = epos + 1;

            String varname = val.substring(bpos + 2, epos).trim();
            String[] vparts = varname.split("\\.");

            varname = "";

            for (String vpart : vparts) {
                if (varname.length() == 0) {
                    varname = vpart;
                }
                else {
                    if (vpart.startsWith("$")) {
                        Struct qvar = this.queryVariable(vpart.substring(1));

                        if (qvar != null) {
                            varname += "." + qvar.toString();
                            continue;
                        }
                    }

                    varname += "." + vpart;
                }
            }

            Struct qvar2 = this.queryVariable(varname);

            if (qvar2 != null) 
                sb.append(qvar2.toString());
            else {
            	this.log().warnTr(500, varname);
                sb.append(val.substring(bpos, epos + 1));
            }

            bpos = val.indexOf("{$", epos);
        }

        sb.append(val.substring(lpos));

        return sb.toString();
    }

    public void addVariable(String type, String name) {
    	this.addVariable(name, this.activity.createStruct(type));
    }

    public void addVariable(String name, Struct var) {
    	if (var == null) {
    		this.log().errorTr(512);
    		return;
    	}
    		
        StackBlockEntry b = this.queryBlockStack();
        
        if (b == null) 
        	this.log().errorTr(513, name);
        else
        	b.addVariable(name, var);
    }

    // Result Code and Value
    
    public void setLastCode(Long code) {
    	StackFunctionEntry func = this.queryFunctionStack();
    	
    	if (func != null) 
	        func.setLastCode(code);
    }
    
    public Long getLastCode() {
    	StackFunctionEntry func = this.queryFunctionStack();
    	
    	if (func != null) 
	        return func.getLastCode();
    	
    	return null;
    }

    public void setLastResult(Struct val) {
    	StackFunctionEntry func = this.queryFunctionStack();
    	
    	if (func != null) 
	        func.setLastResult(val);
    }

    public void setLastResult(long code, Struct val) {
    	StackFunctionEntry func = this.queryFunctionStack();
    	
    	if (func != null) {
	        func.setLastCode(code);
	        func.setLastResult(val);
    	}
    }
    
    public void setLastResult(OperationResult v) {
    	StackFunctionEntry func = this.queryFunctionStack();
    	
    	if (func != null) {
	        func.setLastCode(v.getCode());
	        func.setLastResult(v.toMessage());
    	}
    }
    
    public Struct getLastResult() {
    	StackFunctionEntry func = this.queryFunctionStack();
    	
    	if (func != null) 
	        return func.getLastResult();
    	
    	return null;
    }
    
    // Run
    
    public void run(final IInstructionCallback cb) {
    	if (this.inst != null)
    		this.inst.run(this, cb);
    }
    
    public void debugStack(ListStruct dumpList) {
    	RecordStruct dumpRec = new RecordStruct();
    	dumpList.addItem(dumpRec);
    	
    	this.collectDebugRecord(dumpRec);
    	this.inst.collectDebugRecord(this, dumpRec);
    }
    
    public void collectDebugRecord(RecordStruct rec) {
    }

	public boolean codeHasAttribute(String attr) {
    	if (this.inst != null)
    		return this.inst.source.hasAttribute(attr);
    	
    	return false;
	}

	public void dispose() {
		this.inststore.dispose();
	}	
}
