package railo.runtime.type.scope;

import java.io.UnsupportedEncodingException;

import railo.commons.lang.StringList;
import railo.commons.lang.StringUtil;
import railo.commons.net.URLDecoder;
import railo.commons.net.URLItem;
import railo.runtime.PageContext;
import railo.runtime.dump.DumpData;
import railo.runtime.dump.DumpProperties;
import railo.runtime.exp.ExpressionException;
import railo.runtime.op.Caster;
import railo.runtime.security.ScriptProtect;
import railo.runtime.type.Array;
import railo.runtime.type.ArrayImpl;
import railo.runtime.type.CastableStruct;
import railo.runtime.type.Collection;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.Sizeable;
import railo.runtime.type.Struct;
import railo.runtime.type.StructImpl;
import railo.runtime.type.util.ListUtil;
import railo.runtime.type.util.StructUtil;



/**
 * Simple standart implementation of a Scope, for standart use.
 */
public abstract class ScopeSupport extends StructImpl implements Scope,Sizeable {
	
	private static final long serialVersionUID = -4185219623238374574L;
	
	private String name;
    private String dspName;
	private static int _id=0;
	private int id=0;
    private static final byte[] EMPTY="".getBytes();
	
	
	/**
	 * Field <code>isInit</code>
	 */
	protected boolean isInit;
    private int type;

    /**
     * constructor for the Simple class
     * @param name name of the scope
     * @param type scope type (SCOPE_APPLICATION,SCOPE_COOKIE use)
     */
    public ScopeSupport(boolean sync,String name, int type) {
        super(sync?StructImpl.TYPE_SYNC:StructImpl.TYPE_REGULAR);
        this.name=name;
        this.type=type;
        
        id=++_id;
    }
    /**
     * constructor for ScopeSupport
     * @param name name of the scope
     * @param type scope type (SCOPE_APPLICATION,SCOPE_COOKIE use)
     * @param doubleLinked mean that the struct has predictable iteration order this make the input order fix
     */
    public ScopeSupport(String name, int type, int mapType) {
        super(mapType);
        this.name=name;
        this.type=type;
        
        id=++_id;
    }
	
    @Override

	public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
		return toDumpData(pageContext, maxlevel, dp, this, dspName);
	}
	
	public static DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp, Struct sct,String dspName) {
		if(StringUtil.isEmpty(dspName))dspName="Scope";
		
		return StructUtil.toDumpTable(sct, dspName, pageContext, maxlevel, dp);
		
	}
		
	protected ExpressionException invalidKey(String key) {
		return new ExpressionException("variable ["+key+"] doesn't exist in "+StringUtil.ucFirst(name)+" Scope (keys:"+ListUtil.arrayToList(keys(), ",")+")");
	}

    /**
     * write parameter defined in a query string (name1=value1&name2=value2) to the scope
     * @param qs Query String
     * @return parsed name value pair
     */
	protected static URLItem[] setFromQueryString(String str) {
		return setFrom___(str, '&');
	}

	protected static URLItem[] setFromTextPlain(String str) {
		return setFrom___(str, '\n');
	}
	protected static URLItem[] setFrom___(String tp,char delimiter) {
        if(tp==null) return new URLItem[0];
        Array arr=ListUtil.listToArrayRemoveEmpty(tp,delimiter);
        URLItem[] pairs=new URLItem[arr.size()];
        
        //Array item;
        int index;
        String name;
        
        for(int i=1;i<=pairs.length;i++) {
            name=Caster.toString(arr.get(i,""),"");
            //if(name.length()==0) continue;
            
            index=name.indexOf('=');
            if(index!=-1) pairs[i-1]=new URLItem(name.substring(0,index),name.substring(index+1),true);
            else pairs[i-1]=new URLItem(name,"",true);
          
        }
        return pairs;
    }

    protected static byte[] getBytes(String str) {
        return str.getBytes();
    }
    protected static byte[] getBytes(String str,String encoding) {
        try {
            return str.getBytes(encoding);
        } catch (UnsupportedEncodingException e) {
            return EMPTY;
        }
    }
    
    protected void fillDecodedEL(URLItem[] raw, String encoding, boolean scriptProteced, boolean sameAsArray) {
    	try {
			fillDecoded(raw, encoding,scriptProteced,sameAsArray);
		} catch (UnsupportedEncodingException e) {
			try {
				fillDecoded(raw, "iso-8859-1",scriptProteced,sameAsArray);
			} catch (UnsupportedEncodingException e1) {}
		}
    }
        
    
    /**
     * fill th data from given strut and decode it
     * @param raw
     * @param encoding
     * @throws UnsupportedEncodingException
     */
    protected void fillDecoded(URLItem[] raw, String encoding, boolean scriptProteced, boolean sameAsArray) throws UnsupportedEncodingException {
    	clear();
    	String name,value;
        //Object curr;
        for(int i=0;i<raw.length;i++) {
            name=raw[i].getName();
            value=raw[i].getValue();
            if(raw[i].isUrlEncoded()) {
            	name=URLDecoder.decode(name,encoding,true);
            	value=URLDecoder.decode(value,encoding,true);
            }
            // MUST valueStruct
            if(name.indexOf('.')!=-1) {
                StringList list=ListUtil.listToStringListRemoveEmpty(name,'.');
                Struct parent=this;
                while(list.hasNextNext()) {
                    parent=_fill(parent,list.next(),new CastableStruct(),false,scriptProteced,sameAsArray);
                }
                _fill(parent,list.next(),value,true,scriptProteced,sameAsArray);
            } 
            //else 
                _fill(this,name,value,true,scriptProteced,sameAsArray);
        }
    }
    
    private Struct _fill(Struct parent, String name, Object value, boolean isLast, boolean scriptProteced, boolean sameAsArray) {
    	Object curr;
        boolean isArrayDef=sameAsArray;
        Collection.Key key=KeyImpl.init(name);
        
        // script protect
        if(scriptProteced && value instanceof String) {
        	value=ScriptProtect.translate((String)value);
        }
        
        if(name.length() >2 && name.endsWith("[]")) {
            isArrayDef=true;
            name=name.substring(0,name.length()-2);
            key=KeyImpl.getInstance(name);
            curr=parent.get(key,null);                
        }
        else {
            curr=parent.get(key,null);
        }
        
        if(curr==null) {
        	if(isArrayDef) {
        		Array arr = new ArrayImpl();
        		arr.appendEL(value);
        		parent.setEL(key,arr);
        	}
            else parent.setEL(key,value); 
        }
        else if(curr instanceof Array){
            ((Array) curr).appendEL(value);
        }
        else if(curr instanceof CastableStruct){
        	if(isLast) ((CastableStruct)curr).setValue(value);
            else return (Struct) curr;
        	
        }
        else if(curr instanceof Struct){
            if(isLast) parent.setEL(key,value);
            else return (Struct) curr;
        }
        else if(curr instanceof String){
            if(isArrayDef) {
            	Array arr = new ArrayImpl();
            	arr.appendEL(curr);
            	arr.appendEL(value);
                parent.setEL(key,arr);
            }
            else if(value instanceof Struct) {
                parent.setEL(key,value);
            }
            else {
            	if(!StringUtil.isEmpty(value)) {
            		String existing=Caster.toString(curr,"");
            		if(StringUtil.isEmpty(existing))
            			parent.setEL(key,value);
            		else 
            			parent.setEL(key,Caster.toString(curr,"")+','+value);
            	}
            }
        }
        if(!isLast) {
            return (Struct)value;
        }
        return null;
    }
    
    /*
    private String decode(Object value,String encoding) throws UnsupportedEncodingException {
        return URLDecoder.decode(new String(Caster.toString(value,"").getBytes("ISO-8859-1"),encoding),encoding);
    }*/
    
    @Override
	public boolean isInitalized() {
		return isInit;
	}

	@Override
	public void initialize(PageContext pc) {
        isInit=true;
	}

	@Override
	public void release() {
		clear();
		isInit=false;
	}
	
	@Override
	public void release(PageContext pc) {
		clear();
		isInit=false;
	}


    /**
     * @return Returns the id.
     */
    public int _getId() {
        return id;
    }
    
    /**
     * display name for dump
     * @param dspName
     */
    protected void setDisplayName(String dspName) {
        this.dspName=dspName;
    }
    
    @Override
    public int getType() {
        return type;
    }
    
    @Override
    public String getTypeAsString() {
        return name;
    }
	public long sizeOf() {
		return StructUtil.sizeOf(this);
	}
    
}