package railo.runtime.type.scope;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;

import railo.commons.collection.MapFactory;
import railo.commons.io.IOUtil;
import railo.commons.io.res.Resource;
import railo.commons.lang.ByteNameValuePair;
import railo.commons.lang.StringUtil;
import railo.commons.net.URLItem;
import railo.runtime.PageContext;
import railo.runtime.config.ConfigImpl;
import railo.runtime.engine.ThreadLocalPageContext;
import railo.runtime.exp.PageException;
import railo.runtime.listener.ApplicationContext;
import railo.runtime.net.http.ServletInputStreamDummy;
import railo.runtime.op.Caster;
import railo.runtime.type.Array;
import railo.runtime.type.util.ArrayUtil;
import railo.runtime.type.util.KeyConstants;
import railo.runtime.type.util.ListUtil;


/**
 * Form Scope
 */
public final class FormImpl extends ScopeSupport implements Form,ScriptProtected {
	

	private byte EQL=61;
	private byte NL=10;
	private byte AMP=38;
	
	
	private Map<String,Item> fileItems=MapFactory.<String,Item>getConcurrentMap();
	private Exception initException=null;

    private String encoding=null;
    private int scriptProtected=ScriptProtected.UNDEFINED;
    private static final URLItem[] empty=new URLItem[0];
	//private static final ResourceFilter FILTER = new ExtensionResourceFilter(".upload",false);
    private URLItem[] raw=empty;
    private static int count=1;

    private static final int HEADER_TEXT_PLAIN=0;
    private static final int HEADER_MULTIPART_FORM_DATA=1;
    private static final int HEADER_APP_URL_ENC=2;
	private int headerType=-1;
    
	/**
	 * standart class Constructor
	 */
	public FormImpl() {
		super(true,"form",SCOPE_FORM);
	}

    @Override
    public String getEncoding() {
        return encoding;
    }
    
    @Override
    public void setEncoding(ApplicationContext ac,String encoding) throws UnsupportedEncodingException {
        encoding=encoding.trim().toUpperCase();
        if(encoding.equals(this.encoding)) return;
        this.encoding = encoding;
        if(!isInitalized()) return;
        fillDecoded(raw,encoding,isScriptProtected(),ac.getSameFieldAsArray(Scope.SCOPE_FORM));
        setFieldNames();
    }

	@Override
	public void initialize(PageContext pc) {
		if(encoding==null)encoding=pc.getConfig().getWebCharset();
		
		if(scriptProtected==ScriptProtected.UNDEFINED) {
			scriptProtected=((pc.getApplicationContext().getScriptProtect()&ApplicationContext.SCRIPT_PROTECT_FORM)>0)?
					ScriptProtected.YES:ScriptProtected.NO;
		}
        super.initialize(pc);
		
        String contentType=pc. getHttpServletRequest().getContentType();
        
		if(contentType==null) return;
		contentType=StringUtil.toLowerCase(contentType);
		if(contentType.startsWith("multipart/form-data")) {
			headerType=HEADER_MULTIPART_FORM_DATA;
			initializeMultiPart(pc,isScriptProtected());
		}
		else if(contentType.startsWith("text/plain")) {
			headerType=HEADER_TEXT_PLAIN;
			initializeUrlEncodedOrTextPlain(pc,'\n',isScriptProtected());
		}
		else if(contentType.startsWith("application/x-www-form-urlencoded")) {
			headerType=HEADER_APP_URL_ENC;
			initializeUrlEncodedOrTextPlain(pc,'&',isScriptProtected());
		}
		setFieldNames();
	}

    void setFieldNames() {
    	if(size()>0) {
    		setEL(KeyConstants._fieldnames,ListUtil.arrayToList(keys(), ","));
        }
    }


    private void initializeMultiPart(PageContext pc, boolean scriptProteced) {
    	// get temp directory
    	Resource tempDir = ((ConfigImpl)pc.getConfig()).getTempDirectory();
    	Resource tempFile;
    	
    	// Create a new file upload handler
    	final String encoding=getEncoding();
    	FileItemFactory factory = tempDir instanceof File? 
    			new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD,(File)tempDir):
    				new DiskFileItemFactory();
    	
    	ServletFileUpload upload = new ServletFileUpload(factory);
    	upload.setHeaderEncoding(encoding);
    	//ServletRequestContext c = new ServletRequestContext(pc.getHttpServletRequest());
    	
    	
    	HttpServletRequest req = pc.getHttpServletRequest();
    	ServletRequestContext context = new ServletRequestContext(req) {
    		public String getCharacterEncoding() {
    			return encoding;
    		}
    	};
    	
    	// Parse the request
    	try {
    		FileItemIterator iter = upload.getItemIterator(context);
        	//byte[] value;
        	InputStream is;
        	ArrayList<URLItem> list=new ArrayList<URLItem>();
			while (iter.hasNext()) {
			    FileItemStream item = iter.next();

			    is=IOUtil.toBufferedInputStream(item.openStream());
			    if (item.getContentType()==null || StringUtil.isEmpty(item.getName())) {
			    	list.add(new URLItem(item.getFieldName(),new String(IOUtil.toBytes(is),encoding),false));	     
			    } 
			    else {
			    	tempFile=tempDir.getRealResource(getFileName());
			    	fileItems.put(item.getFieldName().toLowerCase(),
			    			new Item(tempFile,item.getContentType(),item.getName(),item.getFieldName()));
					String value=tempFile.toString();
			    	IOUtil.copy(is, tempFile,true);
			    	
				    list.add(new URLItem(item.getFieldName(),value,false));	     
			    }       
			}
			
			raw= list.toArray(new URLItem[list.size()]);
			fillDecoded(raw,encoding,scriptProteced,pc.getApplicationContext().getSameFieldAsArray(SCOPE_FORM));
		} 
    	catch (Exception e) {
			
        	//throw new PageRuntimeException(Caster.toPageException(e));
        	fillDecodedEL(new URLItem[0],encoding,scriptProteced,pc.getApplicationContext().getSameFieldAsArray(SCOPE_FORM));
			initException=e;
		}
	}
    
    private static String getFileName() { 
		return "tmp-"+(count++)+".upload";
	}

	/*private void initializeMultiPart(PageContext pc, boolean scriptProteced) {

    	File tempDir=FileWrapper.toFile(pc.getConfig().getTempDirectory());
    	
    	// Create a factory for disk-based file items
    	DiskFileItemFactory factory = new DiskFileItemFactory(-1,tempDir);

    	// Create a new file upload handler
    	ServletFileUpload upload = new ServletFileUpload(factory);
    	
    	upload.setHeaderEncoding(getEncoding());
    	
		//FileUpload fileUpload=new FileUpload(new DiskFileItemFactory(0,tempDir));
		java.util.List list;
		try {
			list = upload.parseRequest(pc.getHttpServletRequest());
            raw=new ByteNameValuePair[list.size()];
                        
			for(int i=0;i<raw.length;i++) {
				DiskFileItem val=(DiskFileItem) list.get(i);
				if(val.isFormField()) {
                    raw[i]=new ByteNameValuePair(getBytes(val.getFieldName()),val.get(),false);
                }
				else {
					print.out("-------------------------------");
					print.out("fieldname:"+val.getFieldName());
					print.out("name:"+val.getName());
					print.out("formfield:"+val.isFormField());
					print.out("memory:"+val.isInMemory());
					print.out("exist:"+val.getStoreLocation().getCanonicalFile().exists());
					
					fileItems.put(val.getFieldName().toLowerCase(),val);
					
                    raw[i]=new ByteNameValuePair(getBytes(val.getFieldName()),val.getStoreLocation().getCanonicalFile().toString().getBytes(),false);
                    //raw.put(val.getFieldName(),val.getStoreLocation().getCanonicalFile().toString());
				}
			}
            fillDecoded(raw,encoding,scriptProteced);
		} 
        catch (Exception e) {
        	
        	//throw new PageRuntimeException(Caster.toPageException(e));
        	fillDecodedEL(new ByteNameValuePair[0],encoding,scriptProteced);
			initException=e;
		}
	}*/
    
	private void initializeUrlEncodedOrTextPlain(PageContext pc, char delimiter, boolean scriptProteced) {
		BufferedReader reader=null;
		try {
			reader = pc.getHttpServletRequest().getReader();
			raw=setFrom___(IOUtil.toString(reader,false),delimiter);
			fillDecoded(raw,encoding,scriptProteced,pc.getApplicationContext().getSameFieldAsArray(SCOPE_FORM));
		} 
        catch (Exception e) {
        	
        	fillDecodedEL(new URLItem[0],encoding,scriptProteced,pc.getApplicationContext().getSameFieldAsArray(SCOPE_FORM));
			initException=e;
        }
        finally {
        	IOUtil.closeEL(reader);
        }
	}
	

	@Override
	public void release() {
		release(ThreadLocalPageContext.get());
	}

	@Override
	public void release(PageContext pc) {
		super.release(pc);
        encoding=null;
        scriptProtected=ScriptProtected.UNDEFINED;
        raw=empty;
		
		if(!fileItems.isEmpty()) {
			Iterator<Entry<String, Item>> it = fileItems.entrySet().iterator();
			Item item;
			while(it.hasNext()) {
				item=it.next().getValue();
				item.getResource().delete();
			}
			fileItems.clear();
		}
		initException=null;
		
	}

	@Override
	public FormItem[] getFileItems() {
		if(fileItems==null || fileItems.isEmpty()) return new FormImpl.Item[0];
		
		Iterator<Entry<String, Item>> it = fileItems.entrySet().iterator();
		Map.Entry<String, Item> entry;
		FormImpl.Item[] rtn=new FormImpl.Item[fileItems.size()];
		int index=0;
		while(it.hasNext()){
			entry=it.next();
			rtn[index++]=entry.getValue();
		}
		return rtn;
	}
	
	
	public DiskFileItem getFileUpload(String key) {
		return null;
	}
	
	public FormItem getUploadResource(String key) {
		key=key.trim();
		String lcKey = StringUtil.toLowerCase(key);
		
		// x
		Item item = fileItems.get(lcKey);
		if(item!=null)return item;
		
		// form.x
		if(lcKey.startsWith("form.")) {
			lcKey=lcKey.substring(5).trim();
			item = fileItems.get(lcKey);
			if(item!=null)return item;
		}
		
		// form . x
		try {
			Array array = ListUtil.listToArray(lcKey, '.');
			if(array.size()>1 && array.getE(1).toString().trim().equals("form")) {
				array.removeE(1);
				lcKey=ListUtil.arrayToList(array, ".").trim();
				item = fileItems.get(lcKey);
				if(item!=null)return item;
			}
		} 
		catch (PageException e) {}
		
		// /file.tmp
		Iterator<Entry<String, Item>> it = fileItems.entrySet().iterator();
		while(it.hasNext()) {
			item=it.next().getValue();
			if(item.getResource().getAbsolutePath().equalsIgnoreCase(key))return item;
		}
		
		return null;
	}

	@Override
	public PageException getInitException() {
		if(initException!=null)
			return Caster.toPageException(initException);
		return null;
	}

	@Override
	public void setScriptProtecting(ApplicationContext ac,boolean scriptProtected) {
		int _scriptProtected = scriptProtected?ScriptProtected.YES:ScriptProtected.NO;
		if(isInitalized() && _scriptProtected!=this.scriptProtected) {
			fillDecodedEL(raw,encoding,scriptProtected,ac.getSameFieldAsArray(SCOPE_FORM));
			setFieldNames();
		}
		this.scriptProtected=_scriptProtected;
	}

	@Override
	public boolean isScriptProtected() {
		return scriptProtected==ScriptProtected.YES ;
	}

	/**
	 * @return the raw
	 */
	public URLItem[] getRaw() {
		return raw;
	}

	public void addRaw(ApplicationContext ac,URLItem[] raw) {
		URLItem[] nr=new URLItem[this.raw.length+raw.length];
		for(int i=0;i<this.raw.length;i++) {
			nr[i]=this.raw[i];
		}
		for(int i=0;i<raw.length;i++) {
			nr[this.raw.length+i]=raw[i];
		}
		this.raw=nr;
		
		if(!isInitalized()) return;
        fillDecodedEL(this.raw,encoding,isScriptProtected(),ac.getSameFieldAsArray(SCOPE_FORM));
        setFieldNames();
	}

	private class Item implements FormItem {
		Resource resource;
		String contentType;
		String name;
		private String fieldName;
		
		public Item(Resource resource, String contentType,String name, String fieldName) {
			this.fieldName = fieldName;
			this.name = name;
			this.resource = resource;
			this.contentType = contentType;
		}
		/**
		 * @return the resource
		 */
		public Resource getResource() {
			return resource;
		}
		/**
		 * @return the contentType
		 */
		public String getContentType() {
			return contentType;
		}
		/**
		 * @return the name
		 */
		public String getName() {
			return name;
		}
		/**
		 * @return the fieldName
		 */
		public String getFieldName() {
			return fieldName;
		}
	}

	/**
	 * @return return content as a http header input stream
	 */
	public ServletInputStream getInputStream() {
		if(headerType==HEADER_APP_URL_ENC) {
			return new ServletInputStreamDummy(toBarr(raw,AMP));
		}
		else if(headerType==HEADER_TEXT_PLAIN) {
			return new ServletInputStreamDummy(toBarr(raw,NL));
		}
		/*else if(headerType==HEADER_MULTIPART_FORM_DATA) {
			return new FormImplInputStream(this);
			// TODO
		}*/
		return new ServletInputStreamDummy(new byte[]{});
	}

	private byte[] toBarr(URLItem[] items, byte del) {
		
		ByteNameValuePair[] raw=new ByteNameValuePair[items.length];
		for(int i=0;i<raw.length;i++) {
			try {
				raw[i]=new ByteNameValuePair(items[i].getName().getBytes("iso-8859-1"),items[i].getValue().getBytes("iso-8859-1"),items[i].isUrlEncoded());
			} catch (UnsupportedEncodingException e) {}
		}
		
		int size=0;
		if(!ArrayUtil.isEmpty(raw)){
			for(int i=0;i<raw.length;i++) {
				size+=raw[i].getName().length;
				size+=raw[i].getValue().length;
				size+=2;
			}
			size--;
		}
		byte[] barr = new byte[size],bname,bvalue;
		int count=0;
		
		for(int i=0;i<raw.length;i++) {
			bname=raw[i].getName();
			bvalue=raw[i].getValue();
			// name
			for(int y=0;y<bname.length;y++) {
				barr[count++]=bname[y];
			}
			barr[count++]=EQL;
			// value
			for(int y=0;y<bvalue.length;y++) {
				barr[count++]=bvalue[y];
			}
			if(i+1<raw.length)barr[count++]=del;
		}
		return barr;
	}

}