package railo.runtime.interpreter;

import railo.commons.lang.ParserString;
import railo.commons.lang.StringList;
import railo.commons.lang.StringUtil;
import railo.runtime.PageContext;
import railo.runtime.PageContextImpl;
import railo.runtime.config.NullSupportHelper;
import railo.runtime.exp.PageException;
import railo.runtime.op.Caster;
import railo.runtime.type.Collection;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.ref.VariableReference;
import railo.runtime.type.scope.Argument;
import railo.runtime.type.scope.CallerImpl;
import railo.runtime.type.scope.Local;
import railo.runtime.type.scope.Scope;
import railo.runtime.type.scope.ScopeSupport;
import railo.runtime.type.scope.Undefined;
import railo.runtime.type.scope.Variables;
import railo.runtime.type.util.KeyConstants;
/**
 * Class to check and interpret Variable Strings
 */
public final class VariableInterpreter {
    
	private static final Object NULL = new Object();

	/**
	 * reads a subelement from a struct
	 * @param pc
	 * @param collection
	 * @param var
	 * @return matching Object
	 * @throws PageException
	 */
	public static Object getVariable(PageContext pc, Collection collection,String var) throws PageException {			
	    StringList list = parse(pc,new ParserString(var),false);
        if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
        
        while(list.hasNextNext()) {
            collection=Caster.toCollection(collection.get(KeyImpl.init(list.next())));
        }
        return collection.get(KeyImpl.init(list.next()));
	}
	
	public static String scopeInt2String(int type) {
		switch(type) {
			case Scope.SCOPE_APPLICATION:	return "application";
			case Scope.SCOPE_ARGUMENTS:		return "arguments";
			case Scope.SCOPE_CGI:			return "cgi";
			case Scope.SCOPE_COOKIE:		return "cookie";
			case Scope.SCOPE_CLIENT:		return "client";
			case Scope.SCOPE_FORM:			return "form";
			case Scope.SCOPE_REQUEST:		return "request";
			case Scope.SCOPE_SESSION:		return "session";
			case Scope.SCOPE_SERVER:		return "server";
			case Scope.SCOPE_URL:			return "url";
			case Scope.SCOPE_VARIABLES:		return "variables";
			case Scope.SCOPE_CLUSTER:		return "cluster";
		}
		return null;
	}
	

	public static Object getVariableEL(PageContext pc, Collection collection,String var) {			
	    StringList list = parse(pc,new ParserString(var),false);
        if(list==null) return null;
       
        while(list.hasNextNext()) {
        	collection=Caster.toCollection(collection.get(list.next(),null),null);
        	if(collection==null) return null;
        }
        return collection.get(list.next(),null);
	}
    
    /**
	 * get a variable from page context
	 * @param pc Page Context
	 * @param var variable string to get value to
	 * @return the value
     * @throws PageException
	 */
	public static Object getVariable(PageContext pc,String var) throws PageException {
        StringList list = parse(pc,new ParserString(var),false);
        if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
        
		int scope=scopeString2Int(list.next());
		Object coll =null; 
		if(scope==Scope.SCOPE_UNDEFINED) {
		    coll=pc.undefinedScope().get(list.current());
		}
		else {
			coll=VariableInterpreter.scope(pc, scope, list.hasNext());
		}
		
		while(list.hasNext()) {
			coll=pc.getVariableUtil().get(pc,coll,list.next());
		}
		return coll;
    }
	


	public static Object getVariableAsCollection(PageContext pc,String var) throws PageException {
        StringList list = parse(pc,new ParserString(var),false);
        if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
        
		int scope=scopeString2Int(list.next());
		Object coll =null; 
		if(scope==Scope.SCOPE_UNDEFINED) {
		    coll=pc.undefinedScope().getCollection(list.current());
		}
		else {
		    coll=VariableInterpreter.scope(pc, scope, list.hasNext());
		}
		
		while(list.hasNext()) {
			coll=pc.getVariableUtil().getCollection(pc,coll,list.next());
		}
		return coll;
    }
	

	public static Object getVariable(PageContext pc,String str,Scope scope) throws PageException {
		return _variable(pc, str,NULL, scope);
	}
	public static Object setVariable(PageContext pc,String str,Object value,Scope scope) throws PageException {
		return _variable(pc, str,value, scope);
	}
	
	public static Object _variable(PageContext pc,String str,Object value,Scope scope) throws PageException {
		// define a ohter enviroment for the function
		if(scope!=null){
			
			// Variables Scope
			Variables var=null;
			if(scope instanceof Variables){
				var=(Variables) scope;
			}
			else if(scope instanceof CallerImpl){
				var=((CallerImpl) scope).getVariablesScope();
			}
			if(var!=null){
				Variables current=pc.variablesScope();
				pc.setVariablesScope(var);
		        try{
		        	if(value!=NULL) return setVariable(pc, str,value);
		        	return getVariable(pc, str);
		        }
		        finally{
		        	pc.setVariablesScope(current);
		        }
			}
			
			// Undefined Scope
			else if(scope instanceof Undefined) {
				PageContextImpl pci=(PageContextImpl) pc;
				Undefined undefined=(Undefined) scope;
				
				boolean check=undefined.getCheckArguments();
				Variables orgVar=pc.variablesScope();
				Argument orgArgs=pc.argumentsScope();
		        Local orgLocal=pc.localScope();
				
				pci.setVariablesScope(undefined.variablesScope());
				if(check)pci.setFunctionScopes(undefined.localScope(), undefined.argumentsScope());
		        try{
		        	if(value!=NULL) return setVariable(pc, str,value);
		        	return getVariable(pc, str);
		        }
		        finally{
		        	pc.setVariablesScope(orgVar);
		        	if(check)pci.setFunctionScopes(orgLocal,orgArgs);
		        }
			}
		}
		if(value!=NULL) return setVariable(pc, str,value);
		return getVariable(pc, str);
	}
	
	/**
	 * get a variable from page context
	 * @param pc Page Context
	 * @param var variable string to get value to
	 * @param defaultValue value returnded if variable was not found
	 * @return the value or default value if not found
	 */
	public static Object getVariableEL(PageContext pc,String var, Object defaultValue) {
        StringList list = parse(pc,new ParserString(var),false);
        if(list==null) return defaultValue;
        
		int scope=scopeString2Int(list.next());
		Object coll =null; 
		if(scope==Scope.SCOPE_UNDEFINED) {
		    coll=pc.undefinedScope().get(KeyImpl.init(list.current()),NullSupportHelper.NULL());
		    if(coll==NullSupportHelper.NULL()) return defaultValue;
		}
		else {
		    try {
                coll=VariableInterpreter.scope(pc, scope, list.hasNext());
		    	//coll=pc.scope(scope);
            } 
		    catch (PageException e) {
                return defaultValue;
            }
		}
		
		while(list.hasNext()) {
			coll=pc.getVariableUtil().get(pc,coll,KeyImpl.init(list.next()),NullSupportHelper.NULL());
			if(coll==NullSupportHelper.NULL()) return defaultValue;
		}
		return coll;
    }
	
	public static Object getVariableELAsCollection(PageContext pc,String var, Object defaultValue) {
        StringList list = parse(pc,new ParserString(var),false);
        if(list==null) return defaultValue;
        
		int scope=scopeString2Int(list.next());
		Object coll =null; 
		if(scope==Scope.SCOPE_UNDEFINED) {
		    try {
				coll=pc.undefinedScope().getCollection(list.current());
			} 
		    catch (PageException e) {
				coll=null;
			}
		    if(coll==null) return defaultValue;
		}
		else {
		    try {
                coll=VariableInterpreter.scope(pc, scope, list.hasNext());
		    	//coll=pc.scope(scope);
            } 
		    catch (PageException e) {
                return defaultValue;
            }
		}
		
		while(list.hasNext()) {
			coll=pc.getVariableUtil().getCollection(pc,coll,list.next(),null);
			if(coll==null) return defaultValue;
		}
		return coll;
    }
	/**
	 * return a variable reference by string syntax ("scopename.key.key" -> "url.name")
	 * a variable reference, references to variable, to modifed it, with global effect.
	 * @param pc
	 * @param var variable name to get
	 * @return variable as Reference
	 * @throws PageException
	 */
	public static VariableReference getVariableReference(PageContext pc,String var) throws PageException { 
	    StringList list = parse(pc,new ParserString(var),false);
        if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
        
		if(list.size()==1) {
			return new VariableReference(pc.undefinedScope(),list.next()); 
		}
		int scope=scopeString2Int(list.next());
		
		Object coll;
		if(scope==Scope.SCOPE_UNDEFINED){
			coll=pc.touch(pc.undefinedScope(),list.current());
		}
		else{
			coll=VariableInterpreter.scope(pc, scope, list.hasNext());
			//coll=pc.scope(scope);
		}
		
		
		while(list.hasNextNext()) {
			coll=pc.touch(coll,list.next());
		}

		if(!(coll instanceof Collection))
			throw new InterpreterException("invalid variable ["+var+"]");
		return new VariableReference((Collection)coll,list.next());
	} 
	
	/**
	 * sets a variable to page Context
	 * @param pc pagecontext of the new variable
	 * @param var String of variable definition
	 * @param value value to set to variable
	 * @return value setted
	 * @throws PageException
	 */
	public static Object setVariable(PageContext pc,String var, Object value) throws PageException {			
	    StringList list = parse(pc,new ParserString(var),false);
        if(list==null) throw new InterpreterException("invalid variable name declaration ["+var+"]");

		if(list.size()==1) {
			return pc.undefinedScope().set(list.next(),value);
		}
		
		// min 2 elements
		int scope=scopeString2Int(list.next());
		Object coll;
		if(scope==Scope.SCOPE_UNDEFINED){
			coll=pc.touch(pc.undefinedScope(),list.current());
		}
		else {
			coll=VariableInterpreter.scope(pc, scope, true);
			//coll=pc.scope(scope);
		}
		
		
		while(list.hasNextNext()) {
		    coll=pc.touch(coll,list.next());
		}
		return pc.set(coll,list.next(),value);
	}
	
	/**
	 * removes a variable eith matching name from page context
	 * @param pc
	 * @param var
	 * @return has removed or not
	 * @throws PageException
	 */
	public static Object removeVariable(PageContext pc,String var) throws PageException {	
	    //print.ln("var:"+var);
	    StringList list = parse(pc,new ParserString(var),false);
        if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
        
		if(list.size()==1) {
			return pc.undefinedScope().remove(KeyImpl.init(list.next()));
		}
        
		int scope=scopeString2Int(list.next());
		
		Object coll;
		if(scope==Scope.SCOPE_UNDEFINED){
			coll=pc.undefinedScope().get(list.current());
		}
		else {
			coll=VariableInterpreter.scope(pc, scope, true);
			//coll=pc.scope(scope);
		}
		
		while(list.hasNextNext()) {
		    coll=pc.get(coll,list.next());
		}
		return Caster.toCollection(coll).remove(KeyImpl.init(list.next()));
	}

	

	/**
	 * check if a variable is defined in Page Context
	 * @param pc PageContext to check
	 * @param var variable String
	 * @return exists or not
	 */
	public static boolean isDefined(PageContext pc,String var) {
		StringList list = parse(pc,new ParserString(var),false);
		if(list==null) return false;
        try {
			int scope=scopeString2Int(list.next());
			Object coll =NULL; 
			if(scope==Scope.SCOPE_UNDEFINED) {
				coll=pc.undefinedScope().get(list.current(),null);
				if(coll==null)return false;
			}
			else {
				coll=VariableInterpreter.scope(pc, scope, list.hasNext());
				//coll=pc.scope(scope);
			}
			
			while(list.hasNext()) {
				coll=pc.getVariableUtil().getCollection(pc,coll,list.next(),null);
				if(coll==null)return false;
			}
		} catch (PageException e) {
	        return false;
	    }
		return true;
    }
		

	
	
	/*
	public static boolean isDefined(PageContext pc,String var) {
        StringList list = parse(pc,new ParserString(var));
        if(list==null) return false;
        
		int scope=scopeString2Int(list.next());
		Object coll =NULL; 
		if(scope==Scope.SCOPE_UNDEFINED) {
		    coll=pc.undefinedScope().get(list.current(),NULL);
		    if(coll==NULL) return false;
		}
		else {
		    try {
                coll=pc.scope(scope);
            } catch (PageException e) {
                return false;
            }
		}
		
		while(list.hasNext()) {
			coll=pc.getVariableUtil().get(pc,coll,list.next(),NULL);
			//print.out(coll);
			if(coll==NULL) return false;
		}
       
		return true;
    }
	 */
	
    
    /**
     * parse a Literal variable String and return result as String List
     * @param pc Page Context
     * @param ps ParserString to read
     * @return Variable Definition in a String List
     */
    private static StringList parse(PageContext pc,ParserString ps, boolean doLowerCase) {
        String id=readIdentifier(ps,doLowerCase);
        if(id==null)return null;
        StringList list=new StringList(id);
        CFMLExpressionInterpreter interpreter=null;
        
        while(true) {
            if(ps.forwardIfCurrent('.')) {
	            id=readIdentifier(ps,doLowerCase);
	            if(id==null)return null;
	            list.add(id);
            }
            else if(ps.forwardIfCurrent('[')) {
                if(interpreter==null)interpreter=new CFMLExpressionInterpreter();
                try {
                    list.add(Caster.toString(interpreter.interpretPart(pc,ps)));
                } catch (PageException e) {
                    return null;
                }
                if(!ps.forwardIfCurrent(']')) return null;
                ps.removeSpace();
            }
            else break;
        }
        if(ps.isValidIndex()) return null;
        list.reset();
        return list;
    }
    
    public static StringList parse(String var, boolean doLowerCase) {
    	ParserString ps = new ParserString(var);
        String id=readIdentifier(ps,doLowerCase);
        if(id==null)return null;
        StringList list=new StringList(id);
        
        while(true) {
            if(ps.forwardIfCurrent('.')) {
	            id=readIdentifier(ps,doLowerCase);
	            if(id==null)return null;
	            list.add(id);
            }
            else break;
        }
        if(ps.isValidIndex()) return null;
        list.reset();
        return list;
    }

	/**
	 * translate a string type definition to its int representation
	 * @param type type to translate
	 * @return int representation matching to given string
	 */
	public static int scopeString2Int(String type) {
		type=StringUtil.toLowerCase(type);
		char c=type.charAt(0);
		if('a'==c) {
			if("application".equals(type)) 		return Scope.SCOPE_APPLICATION;
			else if("arguments".equals(type))	return Scope.SCOPE_ARGUMENTS;
		}
		else if('c'==c) {
			if("cgi".equals(type))				return Scope.SCOPE_CGI;
			if("cookie".equals(type))			return Scope.SCOPE_COOKIE;
			if("client".equals(type))			return Scope.SCOPE_CLIENT;
			if("cluster".equals(type))			return Scope.SCOPE_CLUSTER;
		}
		else if('f'==c) {
			if("form".equals(type))				return Scope.SCOPE_FORM;
		}
		else if('l'==c) {
			if("local".equals(type))				return Scope.SCOPE_LOCAL;// LLL
		}
		else if('r'==c) {
			if("request".equals(type))			return Scope.SCOPE_REQUEST;
		}
		else if('s'==c) {
			if("session".equals(type))			return Scope.SCOPE_SESSION;
			if("server".equals(type))			return Scope.SCOPE_SERVER;
		}
		else if('u'==c) {
			if("url".equals(type))				return Scope.SCOPE_URL;
		}
		else if('v'==c) {
			if("variables".equals(type))		return Scope.SCOPE_VARIABLES;
		}
		return Scope.SCOPE_UNDEFINED;
	}
	
	public static int scopeKey2Int(Collection.Key type) {
		char c=type.lowerCharAt(0);
		if('a'==c) {
			if(KeyConstants._application.equalsIgnoreCase(type)) 		return Scope.SCOPE_APPLICATION;
			else if(KeyConstants._arguments.equalsIgnoreCase(type))	return Scope.SCOPE_ARGUMENTS;
		}
		else if('c'==c) {
			if(KeyConstants._cgi.equalsIgnoreCase(type))				return Scope.SCOPE_CGI;
			if(KeyConstants._cookie.equalsIgnoreCase(type))			return Scope.SCOPE_COOKIE;
			if(KeyConstants._client.equalsIgnoreCase(type))			return Scope.SCOPE_CLIENT;
			if(KeyConstants._cluster.equalsIgnoreCase(type))			return Scope.SCOPE_CLUSTER; 
		}
		else if('f'==c) {
			if(KeyConstants._form.equalsIgnoreCase(type))				return Scope.SCOPE_FORM;
		}
		else if('r'==c) {
			if(KeyConstants._request.equalsIgnoreCase(type))			return Scope.SCOPE_REQUEST;
		}
		else if('s'==c) {
			if(KeyConstants._session.equalsIgnoreCase(type))			return Scope.SCOPE_SESSION;
			if(KeyConstants._server.equalsIgnoreCase(type))			return Scope.SCOPE_SERVER;
		}
		else if('u'==c) {
			if(KeyConstants._url.equalsIgnoreCase(type))				return Scope.SCOPE_URL;
		}
		else if('v'==c) {
			if(KeyConstants._variables.equalsIgnoreCase(type))		return Scope.SCOPE_VARIABLES;
		}
		return Scope.SCOPE_UNDEFINED;
	}
    
    private static String readIdentifier(ParserString ps, boolean doLowerCase) {
        
        ps.removeSpace();
        if(ps.isAfterLast())return null;
        int start=ps.getPos();
        if(!isFirstVarLetter(ps.getCurrentLower())) return null;
        ps.next();
        
        while(ps.isValidIndex()) {
            if(isVarLetter(ps.getCurrentLower()))ps.next();
            else break;
        }
        ps.removeSpace();
        return doLowerCase?ps.substringLower(start,ps.getPos()-start):ps.substring(start,ps.getPos()-start);
    }


    private static boolean isFirstVarLetter(char c) {
        return (c>='a' && c<='z') || c=='_' || c=='$';
    }

    private static boolean isVarLetter(char c) {
        return (c>='a' && c<='z') || (c>='0' && c<='9') || c=='_' || c=='$';
    }

	public static Object scope(PageContext pc, int scope, boolean touch) throws PageException {
		switch(scope) {
	        case Scope.SCOPE_UNDEFINED:     return pc.undefinedScope();
	        case Scope.SCOPE_URL:           return pc.urlScope();
	        case Scope.SCOPE_FORM:          return pc.formScope();
	        case Scope.SCOPE_VARIABLES:     return pc.variablesScope();
	        case Scope.SCOPE_REQUEST:       return pc.requestScope();
	        case Scope.SCOPE_CGI:           return pc.cgiScope();
	        case Scope.SCOPE_APPLICATION:   return pc.applicationScope();
	        case Scope.SCOPE_ARGUMENTS:     return pc.argumentsScope();
	        case Scope.SCOPE_SESSION:       return pc.sessionScope();
	        case Scope.SCOPE_SERVER:        return pc.serverScope();
	        case Scope.SCOPE_COOKIE:        return pc.cookieScope();
	        case Scope.SCOPE_CLIENT:        return pc.clientScope();
	        case ScopeSupport.SCOPE_VAR:         	return pc.localScope();
	        case Scope.SCOPE_CLUSTER:		return pc.clusterScope();
	        
	        case Scope.SCOPE_LOCAL:         
				if(touch) return ((PageContextImpl)pc).localTouch();
				return ((PageContextImpl)pc).localGet();
	    }
	    return pc.variablesScope();
	}
    
}