package railo.runtime.tag;

import static railo.runtime.tag.util.FileUtil.NAMECONFLICT_ERROR;
import static railo.runtime.tag.util.FileUtil.NAMECONFLICT_MAKEUNIQUE;
import static railo.runtime.tag.util.FileUtil.NAMECONFLICT_OVERWRITE;
import static railo.runtime.tag.util.FileUtil.NAMECONFLICT_SKIP;
import static railo.runtime.tag.util.FileUtil.NAMECONFLICT_UNDEFINED;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

import railo.commons.io.CharsetUtil;
import railo.commons.io.IOUtil;
import railo.commons.io.ModeUtil;
import railo.commons.io.SystemUtil;
import railo.commons.io.res.Resource;
import railo.commons.io.res.type.s3.S3;
import railo.commons.io.res.type.s3.S3Resource;
import railo.commons.io.res.util.ModeObjectWrap;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.StringUtil;
import railo.commons.lang.mimetype.MimeType;
import railo.runtime.PageContext;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.PageException;
import railo.runtime.ext.tag.BodyTagImpl;
import railo.runtime.functions.list.ListFirst;
import railo.runtime.functions.list.ListLast;
import railo.runtime.functions.s3.StoreSetACL;
import railo.runtime.img.ImageUtil;
import railo.runtime.op.Caster;
import railo.runtime.op.Decision;
import railo.runtime.tag.util.FileUtil;
import railo.runtime.type.Array;
import railo.runtime.type.ArrayImpl;
import railo.runtime.type.Struct;
import railo.runtime.type.StructImpl;
import railo.runtime.type.dt.DateImpl;
import railo.runtime.type.dt.DateTimeImpl;
import railo.runtime.type.scope.Form;
import railo.runtime.type.scope.FormItem;
import railo.runtime.type.util.ArrayUtil;
import railo.runtime.type.util.KeyConstants;
import railo.runtime.type.util.ListUtil;

/**
* Handles all interactions with files. The attributes you use with cffile depend on the value of the action attribute. 
*  For example, if the action = "write", use the attributes associated with writing a text file.
*
*
*
**/
public final class FileTag extends BodyTagImpl {

	private static final int ACTION_UNDEFINED = 0;
	private static final int ACTION_MOVE = 1;
	private static final int ACTION_WRITE = 2;
	private static final int ACTION_APPEND = 3;
	private static final int ACTION_READ = 4;
	private static final int ACTION_UPLOAD = 5;
	private static final int ACTION_UPLOAD_ALL = 6;
	private static final int ACTION_COPY = 7;
	private static final int ACTION_INFO = 8;
	private static final int ACTION_TOUCH = 9;
	private static final int ACTION_DELETE = 10;
	private static final int ACTION_READ_BINARY = 11;
	//private static final Key SET_ACL = KeyImpl.intern("setACL");
    
    //private static final String DEFAULT_ENCODING=Charset.getDefault();

	/** Type of file manipulation that the tag performs. */
	private int action;

	/** Absolute pathname of directory or file on web server. */
	private String strDestination;

	/** Content of the file to be created. */
	private Object output;

	/** Absolute pathname of file on web server. */
	private Resource file;

	/** Applies only to Solaris and HP-UX. Permissions. Octal values of UNIX chmod command. Assigned to owner, group, and other, respectively. */
	private int mode=-1;

	/** Name of variable to contain contents of text file. */
	private String variable;

	/** Name of form field used to select the file. */
	private String filefield;

	/** Character set name for the file contents. */
	private Charset charset=null;

	/** Yes: appends newline character to text written to file */
	private boolean addnewline=true;
	private boolean fixnewline=true;
	/** One attribute (Windows) or a comma-delimited list of attributes (other platforms) to set on the file. 
	** If omitted, the file's attributes are maintained. */
	private String attributes;

	/** Absolute pathname of file on web server. 
	** On Windows, use backward slashes; on UNIX, use forward slashes. */
	private Resource source;

	/** Action to take if filename is the same as that of a file in the directory. */
	private int nameconflict=NAMECONFLICT_UNDEFINED;

	/** Limits the MIME types to accept. Comma-delimited list. For example, to permit JPG and Microsoft Word file uploads:
	** accept = "image/jpg, application/msword"
	** The browser uses file extension to determine file type. */
	private String accept;

	private boolean strict=true;
	private boolean createPath=false;
    
    private String result=null;
	
	private railo.runtime.security.SecurityManager securityManager;

	private String serverPassword=null;
	private Object acl=null;

	@Override
	public void release()	{
		super.release();
		acl=null;
		action=ACTION_UNDEFINED;
		strDestination=null;
		output=null;
		file=null;
		mode=-1;
		variable=null;
		filefield=null;
		charset=null;
		addnewline=true;
		fixnewline=true;
		attributes=null;
		source=null;
		nameconflict=NAMECONFLICT_UNDEFINED;
		accept=null;
		strict=true;
		createPath=false;
		securityManager=null;
        result=null;
        serverPassword=null;
	}

	/** set the value action
	*  Type of file manipulation that the tag performs.
	* @param strAction value to set
	**/	
	public void setAction(String strAction) throws ApplicationException	{
		strAction=strAction.toLowerCase();
		if(strAction.equals("move") || strAction.equals("rename")) action=ACTION_MOVE;
		else if(strAction.equals("copy")) action=ACTION_COPY;
		else if(strAction.equals("delete")) action=ACTION_DELETE;
		else if(strAction.equals("read")) action=ACTION_READ;
		else if(strAction.equals("readbinary")) action=ACTION_READ_BINARY;
		else if(strAction.equals("write")) action=ACTION_WRITE;
		else if(strAction.equals("append")) action=ACTION_APPEND;
		else if(strAction.equals("upload")) action=ACTION_UPLOAD;
		else if(strAction.equals("uploadall")) action=ACTION_UPLOAD_ALL;
        else if(strAction.equals("info")) action=ACTION_INFO;
        else if(strAction.equals("touch")) action=ACTION_TOUCH;
        else 
			throw new ApplicationException("invalid value ["+strAction+"] for attribute action","values for attribute action are:info,move,rename,copy,delete,read,readbinary,write,append,upload,uploadall,touch");
	}

	/** set the value destination
	*  Absolute pathname of directory or file on web server.
	* @param destination value to set
	**/
	public void setDestination(String destination)	{
		this.strDestination=destination;//ResourceUtil.toResourceNotExisting(pageContext ,destination);
	}

	/** set the value output
	*  Content of the file to be created.
	* @param output value to set
	**/
	public void setOutput(Object output)	{
		if(output==null)this.output="";
		else this.output=output;
	}

	/** set the value file
	*  Absolute pathname of file on web server.
	* @param file value to set
	**/
	public void setFile(String file)	{
		this.file=ResourceUtil.toResourceNotExisting(pageContext ,file);
        
	}

	/** set the value mode
	*  Applies only to Solaris and HP-UX. Permissions. Octal values of UNIX chmod command. Assigned to owner, group, and other, respectively.
	* @param mode value to set
	 * @throws PageException 
	**/
	public void setMode(String mode) throws PageException	{
		this.mode=toMode(mode);
	}
	

	public static int toMode(String mode) throws PageException	{
		if(StringUtil.isEmpty(mode,true)) return -1;
		try {
			return ModeUtil.toOctalMode(mode);
		} 
		catch (IOException e) {
			throw Caster.toPageException(e);
		}
	}
	

	/** set the value variable
	*  Name of variable to contain contents of text file.
	* @param variable value to set
	**/
	public void setVariable(String variable)	{
		this.variable=variable;
	}

	/** set the value filefield
	*  Name of form field used to select the file.
	* @param filefield value to set
	**/
	public void setFilefield(String filefield)	{
		this.filefield=filefield;
	}

	/** set the value charset
	*  Character set name for the file contents.
	* @param charset value to set
	**/
	public void setCharset(String charset)	{
		if(StringUtil.isEmpty(charset)) return;
		this.charset=CharsetUtil.toCharset(charset.trim());
	}
	
	/** set the value acl
	*  used only for s3 resources, for all others ignored
	* @param acl value to set
	 * @throws ApplicationException 
	 * @Deprecated only exists for backward compatibility to old ra files.
	**/
	public void setAcl(String acl) throws ApplicationException	{
		this.acl=acl;
	}
	public void setAcl(Object acl)	{
		this.acl=acl;
	}
	public void setStoreacl(Object acl)	{
		this.acl=acl;
	}
	
	
	
	public void setServerpassword(String serverPassword)	{
	    this.serverPassword=serverPassword;
	}

	/** set the value addnewline
	*  Yes: appends newline character to text written to file
	* @param addnewline value to set
	**/
	public void setAddnewline(boolean addnewline)	{
		this.addnewline=addnewline;
	}

	/** set the value attributes
	*  One attribute (Windows) or a comma-delimited list of attributes (other platforms) to set on the file. 
	* If omitted, the file's attributes are maintained.
	* @param attributes value to set
	**/
	public void setAttributes(String attributes)	{
		this.attributes=attributes;
	}

	/** set the value source
	*  Absolute pathname of file on web server. 
	* On Windows, use backward slashes; on UNIX, use forward slashes.
	* @param source value to set
	**/
	public void setSource(String source)	{
		this.source=ResourceUtil.toResourceNotExisting(pageContext ,source);
	}

	/** set the value nameconflict
	* Action to take if filename is the same as that of a file in the directory.
	* @param nameconflict value to set
	* @throws ApplicationException
	**/
	public void setNameconflict(String nameconflict) throws ApplicationException	{

		this.nameconflict = FileUtil.toNameConflict( nameconflict );
	}


	/** set the value accept
	*  Limits the MIME types to accept. Comma-delimited list. For example, to permit JPG and Microsoft Word file uploads:
	* accept = "image/jpg, application/msword"
	* The browser uses file extension to determine file type.
	* @param accept value to set
	**/
	public void setAccept(String accept)	{
		this.accept=accept;
	}

	public void setStrict(boolean strict)	{
		this.strict=strict;
	}
	public void setCreatepath(boolean createPath)	{
		this.createPath=createPath;
	}
    
    /**
     * @param result The result to set.
     */
    public void setResult(String result) {
        this.result = result;
    }


	@Override
	public int doStartTag() throws PageException	{
		
		if(charset==null) charset=CharsetUtil.toCharset(pageContext.getConfig().getResourceCharset()); // TODO 4.2
		securityManager = pageContext.getConfig().getSecurityManager();
		
		switch(action){
		case ACTION_MOVE: actionMove(pageContext, securityManager,source, strDestination, nameconflict,serverPassword,acl, mode, attributes);
		break;
		case ACTION_COPY: actionCopy(pageContext, securityManager,source, strDestination, nameconflict,serverPassword,acl, mode, attributes);
		break;
		case ACTION_DELETE: actionDelete();
		break;
		case ACTION_READ: actionRead();
		break;
		case ACTION_READ_BINARY: actionReadBinary();
		break;
		case ACTION_UPLOAD: actionUpload();
		break;
		case ACTION_UPLOAD_ALL: actionUploadAll();
		break;
		case ACTION_INFO: actionInfo();
		break;
		case ACTION_TOUCH: actionTouch();
		break;
		case ACTION_UNDEFINED: throw new ApplicationException("missing attribute action"); // should never happens
		
		// write and append
		default:
			return EVAL_BODY_BUFFERED;
		}
		return SKIP_BODY;
	}
	
	@Override
	public int doAfterBody() throws ApplicationException	{
		if(action==ACTION_APPEND || action==ACTION_WRITE) {
			String body = bodyContent.getString();
			if(!StringUtil.isEmpty(body)){
				if(!StringUtil.isEmpty(output))
					throw new ApplicationException("if a body is defined for the tag, the attribute [output] is not allowed");
				output=body;
			}
		}
		return SKIP_BODY;
	}

	@Override
	public int doEndTag() throws PageException	{
		switch(action){
		case ACTION_APPEND: actionAppend();
		break;
		case ACTION_WRITE: actionWrite();
		break;
		}
		
		return EVAL_PAGE;
	}
	
	public void hasBody(boolean hasBody) {
		if(output==null && hasBody) output="";
	}

	/**
	 * move source file to destination path or file
	 * @throws PageException
	 */
	public static void actionMove(PageContext pageContext, railo.runtime.security.SecurityManager securityManager,
			Resource source, String strDestination, int nameconflict,String serverPassword,
			Object acl, int mode, String attributes) throws PageException {
		if(nameconflict==NAMECONFLICT_UNDEFINED) nameconflict=NAMECONFLICT_OVERWRITE;
		
		if(source==null)
			throw new ApplicationException("attribute source is not defined for tag file");
		if(StringUtil.isEmpty(strDestination))
			throw new ApplicationException("attribute destination is not defined for tag file");
		
		Resource destination=toDestination(pageContext,strDestination,source);
		
		securityManager.checkFileLocation(pageContext.getConfig(),source,serverPassword);
		securityManager.checkFileLocation(pageContext.getConfig(),destination,serverPassword);
		if(source.equals(destination)) return ;
		
		// source
		if(!source.exists())
			throw new ApplicationException("source file ["+source.toString()+"] doesn't exist");
		else if(!source.isFile())
			throw new ApplicationException("source file ["+source.toString()+"] is not a file");
		else if(!source.isReadable() || !source.isWriteable())
			throw new ApplicationException("no access to source file ["+source.toString()+"]");
		
		// destination
		if(destination.isDirectory()) destination=destination.getRealResource(source.getName());
		if(destination.exists()) {
			// SKIP
			if(nameconflict==NAMECONFLICT_SKIP) return;
			// OVERWRITE
			else if(nameconflict==NAMECONFLICT_OVERWRITE) destination.delete();
			// MAKEUNIQUE
			else if(nameconflict==NAMECONFLICT_MAKEUNIQUE) destination=makeUnique(destination);
			// ERROR
			else throw new ApplicationException("destiniation file ["+destination.toString()+"] already exist");
		}
			
        
		try {
			source.moveTo(destination);
				
		}
		catch(Throwable t) {t.printStackTrace();
			throw new ApplicationException(t.getMessage());
		}
		setACL(destination,acl);
		setMode(destination,mode);
        setAttributes(destination,attributes);
	}

	private static Resource toDestination(PageContext pageContext,String path, Resource source) {
		if(source!=null && path.indexOf(File.separatorChar)==-1 && path.indexOf('/')==-1 && path.indexOf('\\')==-1) {
			Resource p = source.getParentResource();
			if(p!=null)return p.getRealResource(path);
		}
		return ResourceUtil.toResourceNotExisting(pageContext ,path);
	}

	/**
	 * copy source file to destination file or path
	 * @throws PageException
	 */
	public static void actionCopy(PageContext pageContext, railo.runtime.security.SecurityManager securityManager,
			Resource source, String strDestination, int nameconflict,String serverPassword,
			Object acl, int mode, String attributes) throws PageException {
		if(nameconflict==NAMECONFLICT_UNDEFINED) nameconflict=NAMECONFLICT_OVERWRITE;
		
		if(source==null)
			throw new ApplicationException("attribute source is not defined for tag file");
		if(StringUtil.isEmpty(strDestination))
			throw new ApplicationException("attribute destination is not defined for tag file");

		Resource destination=toDestination(pageContext,strDestination,source);
		
		
		securityManager.checkFileLocation(pageContext.getConfig(),source,serverPassword);
		securityManager.checkFileLocation(pageContext.getConfig(),destination,serverPassword);
		
		// source
		if(!source.exists())
			throw new ApplicationException("source file ["+source.toString()+"] doesn't exist");
		else if(!source.isFile())
			throw new ApplicationException("source file ["+source.toString()+"] is not a file");
		else if(!source.canRead())
			throw new ApplicationException("no access to source file ["+source.toString()+"]");
		
		// destination
		if(destination.isDirectory()) destination=destination.getRealResource(source.getName());
		if(destination.exists()) {
			// SKIP
			if(nameconflict==NAMECONFLICT_SKIP) return;
			// SKIP
			else if(nameconflict==NAMECONFLICT_OVERWRITE) destination.delete();
			// MAKEUNIQUE
			else if(nameconflict==NAMECONFLICT_MAKEUNIQUE) destination=makeUnique(destination);
			// ERROR
			else throw new ApplicationException("destiniation file ["+destination.toString()+"] already exist");
		}
		
        try {
            IOUtil.copy(source,destination);			
		}
		catch(IOException e) {
			
            ApplicationException ae = new ApplicationException("can't copy file ["+source+"] to ["+destination+"]",e.getMessage());
            ae.setStackTrace(e.getStackTrace());
            throw ae;
		}
		setACL(destination,acl);
		setMode(destination,mode);
        setAttributes(destination,attributes);
	}

	private static void setACL(Resource res,Object acl) throws PageException {
		String scheme = res.getResourceProvider().getScheme();
		
		if("s3".equalsIgnoreCase(scheme)){
			S3Resource s3r=(S3Resource) res;
			
			if(acl!=null){
				try {
					// old way
					if(Decision.isString(acl)) {
						s3r.setACL(S3.toIntACL(Caster.toString(acl)));
					}
					// new way
					else {
						StoreSetACL.invoke(s3r, acl);
					}
				} catch (IOException e) {
					throw Caster.toPageException(e);
				}
			}
			
		}
		// set acl for s3 resource
		/*if(res instanceof S3Resource) {
			((S3Resource)res).setACL(acl);
		}*/
	}

	private static Resource makeUnique(Resource res) {

		String ext=getFileExtension(res);
		String name=getFileName(res);
		ext=(ext==null)?"":"."+ext;
		int count=0;
		while(res.exists()) {
			res=res.getParentResource().getRealResource(name+(++count)+ext);
		}
		
		return res;
	}

	/**
	 * copy source file to destination file or path
	 * @throws PageException 
	 */
	private void actionDelete() throws PageException {
		checkFile(false,false,false,false);
		setACL(file,acl);
		try {
			if(!file.delete()) throw new ApplicationException("can't delete file ["+file+"]");
		}
		catch(Throwable t) {
			throw new ApplicationException(t.getMessage());
		}
	}

	/**
	 * read source file
	 * @throws PageException
	 */
	private void actionRead() throws PageException {
		if(variable==null)
			throw new ApplicationException("attribute variable is not defined for tag file");
		checkFile(false,false,true,false);
		//print.ln(charset);
		//TextFile tf=new TextFile(file.getAbsolutePath());
			
		try {
		    pageContext.setVariable(variable,IOUtil.toString(file,charset));
		}
        catch (IOException e) {
        	
			throw new ApplicationException("can't read file ["+file+"]",e.getMessage());
		}

	}

	/**
	 * read source file
	 * @throws PageException
	 */
	private void actionReadBinary() throws PageException {
		if(variable==null)
			throw new ApplicationException("attribute variable is not defined for tag file");
		checkFile(false,false,true,false);
		
		//TextFile tf=new TextFile(file.getAbsolutePath());
		
		try {
            pageContext.setVariable(variable,IOUtil.toBytes(file));
		}catch (IOException e) {
			throw new ApplicationException("can't read binary file ["+source.toString()+"]",e.getMessage());
		}
	}
	
    /**
     * write to the source file
     * @throws PageException
     */
    private void actionWrite() throws PageException {
        if(output==null)
            throw new ApplicationException("attribute output is not defined for tag file");
        checkFile(createPath,true,false,true);
        
        try {
        	if(output instanceof InputStream)	{
        		IOUtil.copy(
        				(InputStream)output,
        				file,
        				false);
        	}
        	else if(Decision.isCastableToBinary(output,false))	{
        		IOUtil.copy(
        				new ByteArrayInputStream(Caster.toBinary(output)), 
        				file,
        				true);
        	}
        	else {
        		String content=Caster.toString(output);
        		if(fixnewline)content=doFixNewLine(content);
        		if(addnewline) content+=SystemUtil.getOSSpecificLineSeparator();
        		
                IOUtil.write(file,content,charset,false);
        		
        	}    
        } 
        catch (UnsupportedEncodingException e) {
            throw new ApplicationException("Unsupported Charset Definition ["+charset+"]",e.getMessage());
        }
        catch (IOException e) {
            
            throw new ApplicationException("can't write file "+file.getAbsolutePath(),e.getMessage());
        }
        setACL(file,acl);
        setMode(file,mode);
        setAttributes(file,attributes);
    }
    
    /**
     * write to the source file
     * @throws PageException
     */
    private void actionTouch() throws PageException {
        checkFile(createPath,true,true,true);
        
        try {
            ResourceUtil.touch(file);
        } 
        catch (IOException e) {
            
            throw new ApplicationException("can't touch file "+file.getAbsolutePath(),e.getMessage());
        }
        
        setACL(file,acl);
        setMode(file,mode);
        setAttributes(file,attributes);
    }
    
    

	/**
	 * append data to source file
	 * @throws PageException
	 */
	private void actionAppend() throws PageException {
		if(output==null)
			throw new ApplicationException("attribute output is not defined for tag file");
		checkFile(createPath,true,false,true);
		
        try {

            if(!file.exists()) file.createNewFile();
            String content=Caster.toString(output);
            if(fixnewline)content=doFixNewLine(content);
    		if(addnewline) content+=SystemUtil.getOSSpecificLineSeparator();
            IOUtil.write(file,content,charset,true);
        	
        } 
		catch (UnsupportedEncodingException e) {
            throw new ApplicationException("Unsupported Charset Definition ["+charset+"]",e.getMessage());
        }
        catch (IOException e) {
            throw new ApplicationException("can't write file",e.getMessage());
        }
        setACL(file,acl);
		setMode(file,mode);
        setAttributes(file,attributes);
	}

    private String doFixNewLine(String content) {
		// TODO replace new line with system new line
		return content;
	}

	/**
	 * list all files and directories inside a directory
	 * @throws PageException
	 */
	private void actionInfo() throws PageException {
		
		if(variable==null)
			throw new ApplicationException("attribute variable is not defined for tag file");
		checkFile(false,false,false,false);
		
		Struct sct =new StructImpl();
		pageContext.setVariable(variable,sct);
		
		// fill data to query
		sct.setEL(KeyConstants._name,file.getName());
		sct.setEL(KeyConstants._size,Long.valueOf(file.length()));
		sct.setEL(KeyConstants._type,file.isDirectory()?"Dir":"File");
		sct.setEL("dateLastModified",new DateTimeImpl(pageContext,file.lastModified(),false));
		sct.setEL("attributes",getFileAttribute(file));
		if(SystemUtil.isUnix())sct.setEL(KeyConstants._mode,new ModeObjectWrap(file));
        
		try { 		
			BufferedImage bi = ImageUtil.toBufferedImage(file, null);
            if(bi!=null) {
	            Struct img =new StructImpl();
	            img.setEL(KeyConstants._width,new Double(bi.getWidth()));
	            img.setEL(KeyConstants._height,new Double(bi.getHeight()));
	            sct.setEL(KeyConstants._img,img);
            }
        } 
		catch (Throwable t) {}
	}

	private static String getFileAttribute(Resource file){
		return  file.exists() && !file.canWrite() ? "R".concat(file.isHidden() ? "H" : "") : file.isHidden() ? "H" : "";
	}
	
	/**
	 * read source file
	 * @throws PageException
	 */

	public void actionUpload() throws PageException {
		FormItem item=getFormItem(pageContext,filefield);
		Struct cffile = _actionUpload(pageContext,securityManager,item,strDestination,nameconflict,accept,strict,mode,attributes,acl,serverPassword);
		if(StringUtil.isEmpty(result)) {
            pageContext.undefinedScope().set(KeyConstants._file,cffile);
		    pageContext.undefinedScope().set("cffile",cffile);
        }
        else {
            pageContext.setVariable(result,cffile);
        }
	}
	
	
	public static Struct actionUpload(PageContext pageContext,railo.runtime.security.SecurityManager securityManager,String filefield,
			String strDestination,int nameconflict,String accept,boolean strict,int mode,String attributes,Object acl,String serverPassword) throws PageException {
		FormItem item=getFormItem(pageContext,filefield);
		return _actionUpload(pageContext,securityManager,item,strDestination,nameconflict,accept,strict,mode,attributes,acl,serverPassword);
	}
	
	public void actionUploadAll() throws PageException {
		Array arr=actionUploadAll(pageContext,securityManager,strDestination,nameconflict,accept,strict,mode,attributes,acl,serverPassword);
		if(StringUtil.isEmpty(result)) {
			Struct sct;
			if(arr!=null && arr.size()>0) sct=(Struct) arr.getE(1);
			else sct=new StructImpl();
			
            pageContext.undefinedScope().set(KeyConstants._file,sct);
		    pageContext.undefinedScope().set("cffile",sct);
        }
        else {
            pageContext.setVariable(result,arr);
        }
	}
	

	public static Array actionUploadAll(PageContext pageContext,railo.runtime.security.SecurityManager securityManager,
			String strDestination,int nameconflict,String accept,boolean strict,int mode,String attributes,Object acl,String serverPassword) throws PageException {
		FormItem[] items=getFormItems(pageContext);
		Struct sct=null;
		Array arr=new ArrayImpl();
		for(int i=0;i<items.length;i++){
			sct = _actionUpload(pageContext,securityManager,items[i],strDestination,nameconflict,accept,strict,mode,attributes,acl,serverPassword);
			arr.appendEL(sct);
		}
		return arr;
	}
	
	private static synchronized Struct _actionUpload(PageContext pageContext, railo.runtime.security.SecurityManager securityManager, 
			FormItem formItem,String strDestination,int nameconflict,String accept,boolean strict,int mode,String attributes,Object acl,String serverPassword) throws PageException {
		if(nameconflict==NAMECONFLICT_UNDEFINED) nameconflict=NAMECONFLICT_ERROR;

		boolean fileWasRenamed=false;
		boolean fileWasAppended=false;
		boolean fileExisted=false;
		boolean fileWasOverwritten=false;
		
		
		String contentType=formItem.getContentType();
		
		
		// set cffile struct
		Struct cffile=new StructImpl();
        
    	long length = formItem.getResource().length();
		cffile.set("timecreated",new DateTimeImpl(pageContext.getConfig()));
		cffile.set("timelastmodified",new DateTimeImpl(pageContext.getConfig()));
		cffile.set("datelastaccessed",new DateImpl(pageContext));
		cffile.set("oldfilesize",Long.valueOf(length));
		cffile.set("filesize",Long.valueOf(length));
		cffile.set("contenttype",ListFirst.call(pageContext,contentType,"/"));
		cffile.set("contentsubtype",ListLast.call(pageContext,contentType,"/"));
		
		// client file
		String strClientFile=formItem.getName();
		while(strClientFile.indexOf('\\')!=-1)
			strClientFile=strClientFile.replace('\\','/');
		Resource clientFile=pageContext.getConfig().getResource(strClientFile);
		String clientFileName=clientFile.getName();
		
		// check file type
		checkContentType(contentType,accept,getFileExtension(clientFile),strict);
		
			
			//String dir=clientFile.getParent();
			//dir=correctDirectory(dir);
		
			cffile.set("clientdirectory",getParent(clientFile));
			cffile.set("clientfile",clientFile.getName());
			cffile.set("clientfileext",getFileExtension(clientFile));
			cffile.set("clientfilename",getFileName(clientFile));
		
	    // check destination
	    if(StringUtil.isEmpty(strDestination))
	    	throw new ApplicationException("attribute destination is not defined in tag file");

	    
	    Resource destination=toDestination(pageContext,strDestination,null);
	    
		securityManager.checkFileLocation(pageContext.getConfig(),destination,serverPassword);
		
	    if(destination.isDirectory()) 
	    	destination=destination.getRealResource(clientFileName);
	    else if(!destination.exists() && (strDestination.endsWith("/") || strDestination.endsWith("\\"))) 
	    	destination=destination.getRealResource(clientFileName);
	    else if(!clientFileName.equalsIgnoreCase(destination.getName())) {
	    	if(ResourceUtil.getExtension(destination, null)==null)
	    		destination=destination.getRealResource(clientFileName);
	    	else 
	    		fileWasRenamed=true;
	    }
	    
	    // check parent destination -> directory of the desinatrion
	    Resource parentDestination=destination.getParentResource();
	    
	    if(!parentDestination.exists()) {
	    	Resource pp = parentDestination.getParentResource();
	    	if(pp==null || !pp.exists()) 
	    		throw new ApplicationException("attribute destination has an invalid value ["+destination+"], directory ["+parentDestination+"] doesn't exist");
	    	try {
				parentDestination.createDirectory(true);
			}
			catch (IOException e) {
				throw Caster.toPageException(e);
			} 
	    }
	    else if(!parentDestination.canWrite())
	    	throw new ApplicationException("can't write to destination directory ["+parentDestination+"], no access to write");
	    
	    // set server variables
		cffile.set("serverdirectory",getParent(destination));
		cffile.set("serverfile",destination.getName());
		cffile.set("serverfileext",getFileExtension(destination));
		cffile.set("serverfilename",getFileName(destination));
		cffile.set("attemptedserverfile",destination.getName());
	    
		
	    // check nameconflict
	    if(destination.exists()) {
	    	fileExisted=true;
	    	if(nameconflict==NAMECONFLICT_ERROR) {
	    		throw new ApplicationException("destination file ["+destination+"] already exist");
	    	}
	    	else if(nameconflict==NAMECONFLICT_SKIP) {
				cffile.set("fileexisted",Caster.toBoolean(fileExisted));
				cffile.set("filewasappended",Boolean.FALSE);
				cffile.set("filewasoverwritten",Boolean.FALSE);
				cffile.set("filewasrenamed",Boolean.FALSE);
				cffile.set("filewassaved",Boolean.FALSE);
	    		return cffile;
	    	}
	    	else if(nameconflict==NAMECONFLICT_MAKEUNIQUE) {
	    		destination=makeUnique(destination);
	    		fileWasRenamed=true;
	    		
				//if(fileWasRenamed) {
				cffile.set("serverdirectory",getParent(destination));
				cffile.set("serverfile",destination.getName());
				cffile.set("serverfileext",getFileExtension(destination));
				cffile.set("serverfilename",getFileName(destination));
				cffile.set("attemptedserverfile",destination.getName());	
				//}
	    	}
	    	else if(nameconflict==NAMECONFLICT_OVERWRITE) {
	    		//fileWasAppended=true;	
	    		fileWasOverwritten=true;
	    		if(!destination.delete())
	    			if(destination.exists()) // hier hatte ich concurrent problem das damit ausgeraeumt ist
	    				throw new ApplicationException("can't delete destination file ["+destination+"]");
	    	}
	    	// for "overwrite" no action is neded
	    	
	    }
	    
			try {
				destination.createNewFile();
				IOUtil.copy(formItem.getResource(),destination);
			}
			catch(Throwable t) {
				throw Caster.toPageException(t);
			}
			
			// Set cffile/file struct
			
			cffile.set("fileexisted",Caster.toBoolean(fileExisted));
			cffile.set("filewasappended",Caster.toBoolean(fileWasAppended));
			cffile.set("filewasoverwritten",Caster.toBoolean(fileWasOverwritten));
			cffile.set("filewasrenamed",Caster.toBoolean(fileWasRenamed));
			cffile.set("filewassaved",Boolean.TRUE);
			

			setACL(destination,acl);
			setMode(destination,mode);
	        setAttributes(destination, attributes);
	        return cffile;
	}

	/**
	 * check if the content ii ok
	 * @param contentType 
	 * @throws PageException
	 */
	private static void checkContentType(String contentType,String accept,String ext,boolean strict) throws PageException {
		
		if(!StringUtil.isEmpty(ext,true)){
			ext=ext.trim().toLowerCase();
			if(ext.startsWith("*."))ext=ext.substring(2);
			if(ext.startsWith("."))ext=ext.substring(1);
		}
		else ext=null;
		
		if(StringUtil.isEmpty(accept,true)) return;
		
		
		MimeType mt = MimeType.getInstance(contentType),sub;
		
		Array whishedTypes=ListUtil.listToArrayRemoveEmpty(accept,',');
		int len=whishedTypes.size();
		for(int i=1;i<=len;i++) {
			String whishedType=Caster.toString(whishedTypes.getE(i)).trim().toLowerCase();
			if(whishedType.equals("*")) return;
			// check mimetype
			if(ListUtil.len(whishedType, "/", true)==2){
				sub=MimeType.getInstance(whishedType);
				if(mt.match(sub)) return;
			}
			
			// check extension
			if(ext!=null && !strict){
				if(whishedType.startsWith("*."))whishedType=whishedType.substring(2);
				if(whishedType.startsWith("."))whishedType=whishedType.substring(1);
				if(ext.equals(whishedType)) return;
			}
		}
		throw new ApplicationException("The MIME type of the uploaded file ["+contentType+"] was not accepted by the server.","only this ["+accept+"] mime type are accepted");
	}

	/**
	 * rreturn fileItem matching to filefiled definition or throw a exception
	 * @return FileItem
	 * @throws ApplicationException
	 */
	private static FormItem getFormItem(PageContext pageContext, String filefield) throws PageException {
		// check filefield
		if(StringUtil.isEmpty(filefield)){
			FormItem[] items = getFormItems(pageContext);
			if(ArrayUtil.isEmpty(items))
				throw new ApplicationException("no file send with this form");
			return items[0];
		}
			
		PageException pe = pageContext.formScope().getInitException();
		if(pe!=null) throw pe;
		railo.runtime.type.scope.Form upload = pageContext.formScope();
		FormItem fileItem = upload.getUploadResource(filefield);
		if(fileItem==null) {
			FormItem[] items = upload.getFileItems();
			StringBuilder sb=new StringBuilder();
			for(int i=0;i<items.length;i++){
				if(i!=0) sb.append(", ");
				sb.append(items[i].getFieldName());
			}
			String add=".";
			if(sb.length()>0) add=", valid field names are ["+sb+"].";
			
			
			if(pageContext.formScope().get(filefield,null)==null) 
				throw new ApplicationException("form field ["+filefield+"] is not a file field"+add);
			throw new ApplicationException("form field ["+filefield+"] doesn't exist or has no content"+add);
		}
		
		return fileItem;
	}
	
	private static FormItem[] getFormItems(PageContext pageContext) throws PageException {
		PageException pe = pageContext.formScope().getInitException();
		if(pe!=null) throw pe;
		
		Form scope = pageContext.formScope();
		return scope.getFileItems();
	}
	
	
	/**
	 * get file extension of a file object
	 * @param file file object
	 * @return extnesion
	 */
	private static String getFileExtension(Resource file) {
		String name=file.getName();
		String[] arr;
		try {
			arr = ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(name, '.'));
		} catch (PageException e) {
			arr=null;
		}
		if(arr.length<2) return "";
		
		return arr[arr.length-1];
	}
	
	/**
	 * get file name of a file object without extension
	 * @param file file object
	 * @return name of the file 
	 */
	private static String getFileName(Resource file) {
		String name=file.getName();
		int pos=name.lastIndexOf(".");
		
		if(pos==-1)return name;
		return name.substring(0,pos);
	}
	
	/*private String correctDirectory(Resource resource) {
		if(StringUtil.isEmpty(resource,true)) return "";
		resource=resource.trim();
		if((StringUtil.endsWith(resource, '/') || StringUtil.endsWith(resource, '\\')) && resource.length()>1) {
			return resource.substring(0,resource.length()-1);
		}
		return resource;
	}*/
	
	private static String getParent(Resource res) {
		Resource parent = res.getParentResource();
		//print.out("res:"+res);
		//print.out("parent:"+parent);
		if(parent==null) return "";
		return ResourceUtil.getCanonicalPathEL(parent);
	}
	

	private void checkFile(boolean createParent, boolean create, boolean canRead, boolean canWrite) throws PageException {
		if(file==null)
			throw new ApplicationException("attribute file is not defined for tag file");

		securityManager.checkFileLocation(pageContext.getConfig(),file,serverPassword);
		if(!file.exists()) {
			if(create) {
				Resource parent=file.getParentResource();
				if(parent!=null && !parent.exists()) {
					if(createParent) parent.mkdirs();
					else throw new ApplicationException("parent directory for ["+file+"] doesn't exist");
				}
				try {
					file.createFile(false);
				} catch (IOException e) {
					throw new ApplicationException("invalid file ["+file+"]",e.getMessage());
				}
			}
			else if(!file.isFile()) 
				throw new ApplicationException("source file ["+file.toString()+"] is not a file");
			else 
				throw new ApplicationException("source file ["+file.toString()+"] doesn't exist");
		}
		else if(!file.isFile())
			throw new ApplicationException("source file ["+file.toString()+"] is not a file");
        else if(canRead &&!file.canRead())
            throw new ApplicationException("no read access to source file ["+file.toString()+"]");
        else if(canWrite && !file.canWrite())
            throw new ApplicationException("no write access to source file ["+file.toString()+"]");
	
	}

	/**
	 * set attributes on file
     * @param file
	 * @throws PageException
     */
    private static void setAttributes(Resource file,String attributes) throws PageException {
        if(!SystemUtil.isWindows() || StringUtil.isEmpty(attributes)) return;
        try {
        	ResourceUtil.setAttribute(file, attributes);
        } 
        catch (IOException e) {
            throw new ApplicationException("can't change attributes of file "+file,e.getMessage());
        }
    }

    /**
	 * change mode of given file
     * @param file
	 * @throws ApplicationException
     */
    private static void setMode(Resource file,int mode) throws ApplicationException {
        if(mode==-1 || SystemUtil.isWindows()) return;
        try {
        	file.setMode(mode);
            //FileUtil.setMode(file,mode);
        } catch (IOException e) {
            throw new ApplicationException("can't change mode of file "+file,e.getMessage());
        }
    }

	/**
	 * @param fixnewline the fixnewline to set
	 */
	public void setFixnewline(boolean fixnewline) {
		this.fixnewline = fixnewline;
	}
}