package railo.runtime.tag;

import java.net.URL;

import railo.commons.io.res.Resource;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.StringUtil;
import railo.commons.net.HTTPUtil;
import railo.commons.security.Credentials;
import railo.commons.security.CredentialsImpl;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.DatabaseException;
import railo.runtime.exp.PageException;
import railo.runtime.ext.tag.TagImpl;
import railo.runtime.net.proxy.ProxyData;
import railo.runtime.net.proxy.ProxyDataImpl;
import railo.runtime.op.Caster;
import railo.runtime.op.date.DateCaster;
import railo.runtime.schedule.ScheduleTask;
import railo.runtime.schedule.ScheduleTaskImpl;
import railo.runtime.schedule.Scheduler;
import railo.runtime.schedule.SchedulerImpl;
import railo.runtime.type.QueryImpl;
import railo.runtime.type.dt.Date;
import railo.runtime.type.dt.DateImpl;
import railo.runtime.type.dt.Time;
import railo.runtime.type.util.KeyConstants;
/**
* Provides a programmatic interface to the Railo scheduling engine. You can run a specified 
*   page at scheduled intervals with the option to write out static HTML pages. This lets you offer users 
*   access to pages that publish data, such as reports, without forcing users to wait while a database transaction 
*   is performed in order to populate the data on the page. 
**/
public final class Schedule extends TagImpl {

    private static final short ACTION_RUN=1;
    private static final short ACTION_UPDATE=2;
    private static final short ACTION_DELETE=3;
    private static final short ACTION_LIST=4;
    private static final short ACTION_PAUSE=5;
    private static final short ACTION_RESUME=6;
    

	/** Password if URL is protected. */
	private String password="";

	/** Required when action ='update'. The date when scheduling of the task should start. */
	private Date startdate;

	/** Specifies whether to resolve links in the result page to absolute references. */
	private boolean resolveurl;

	/**  */
	private short action;

	/** Host name or IP address of a proxy server. */
	private String proxyserver;

	/** The date when the scheduled task ends. */
	private Date enddate;

	/** Required with publish ='Yes' A valid filename for the published file. */
	private String strFile;

	/** Required when creating tasks with action = 'update'. Enter a value in seconds. The time when 
	** 	scheduling of the task starts. */
	private Time starttime;

	/** The port number on the proxy server from which the task is being requested. Default is 80. When 
	** 		used with resolveURL, the URLs of retrieved documents that specify a port number are automatically 
	** 		resolved to preserve links in the retrieved document. */
	private int proxyport=80;

	/** The port number on the server from which the task is being scheduled. Default is 80. When used 
	** 		with resolveURL, the URLs of retrieved documents that specify a port number are automatically resolved 
	** 		to preserve links in the retrieved document. */
	private int port=-1;

	/** The time when the scheduled task ends. Enter a value in seconds. */
	private Time endtime;


	/** Required when creating tasks with action = 'update'. Interval at which task should be scheduled. 
	** 		Can be set in seconds or as Once, Daily, Weekly, and Monthly. The default interval is one hour. The 
	** 		minimum interval is one minute. */
	private String interval;

	/** Specifies whether the result should be saved to a file. */
	private boolean publish;

	/** Customizes the requestTimeOut for the task operation. Can be used to extend the default timeout 
	** 		for operations that require more time to execute. */
	private long requesttimeout=-1;

	/** Username if URL is protected. */
	private String username;

	/** Required when action = 'update'. The URL to be executed. */
	private String url;

	/** Required with publish ='Yes' The path location for the published file. */
	private String strPath;

	/** The name of the task to delete, update, or run. */
	private String task;
    private Scheduler scheduler;

    private String proxyuser;
    private String proxypassword="";
    
    private String returnvariable="cfschedule";
	private boolean hidden;
	private boolean readonly;
	private String serverPassword=null;
	private boolean paused;
	private boolean autoDelete;

	public void setAutodelete(boolean autoDelete) {
		this.autoDelete=autoDelete;
	}
	
	/**
	 * @param readonly the readonly to set
	 */
	public void setReadonly(boolean readonly) {
		this.readonly = readonly;
	}

	/**
	 * @param hidden the hidden to set
	 */
	public void setHidden(boolean hidden) {
		this.hidden = hidden;
	}
	
    /**
     * @param returnvariable The returnvariable to set.
     */
    public void setReturnvariable(String returnvariable) {
        this.returnvariable = returnvariable;
    }

    /**
     * @param proxypassword The proxypassword to set.
     */
    public void setProxypassword(String proxypassword) {
        this.proxypassword = proxypassword;
    }
    /**
     * @param proxyuser The proxyuser to set.
     */
    public void setProxyuser(String proxyuser) {
        this.proxyuser = proxyuser;
    }
    
    public void setPaused(boolean paused) {
        this.paused = paused;
    }

	/** set the value password
	*  Password if URL is protected.
	* @param password value to set
	**/
	public void setPassword(String password)	{
		this.password=password;
	}

	/** set the value startdate
	*  Required when action ='update'. The date when scheduling of the task should start.
	* @param objStartDate value to set
	 * @throws PageException
	**/
	public void setStartdate(Object objStartDate) throws PageException	{
		if(StringUtil.isEmpty(objStartDate)) return;
		this.startdate=new DateImpl(DateCaster.toDateAdvanced(objStartDate,pageContext.getTimeZone()));
	}

	/** set the value resolveurl
	*  Specifies whether to resolve links in the result page to absolute references.
	* @param resolveurl value to set
	**/
	public void setResolveurl(boolean resolveurl)	{
		this.resolveurl=resolveurl;
	}
	
	public void setServerpassword(String serverPassword)	{
	    this.serverPassword=serverPassword;
	}

	/** set the value action
	*  
	* @param action value to set
	 * @throws ApplicationException
	**/
	public void setAction(String action) throws ApplicationException	{
		if(StringUtil.isEmpty(action)) return;
		action=action.toLowerCase().trim();
	    if(action.equals("run"))this.action=ACTION_RUN;
	    else if(action.equals("delete"))this.action=ACTION_DELETE;
	    else if(action.equals("update"))this.action=ACTION_UPDATE;
	    else if(action.equals("list"))this.action=ACTION_LIST;
	    else if(action.equals("lists"))this.action=ACTION_LIST;
	    else if(action.equals("pause"))this.action=ACTION_PAUSE;
	    else if(action.equals("resume"))this.action=ACTION_RESUME;
	    else throw new ApplicationException("attribute action with value ["+action+"] of tag schedule is invalid","valid attributes are [delete,run,update,list,resume,pause]");
	}

	/** set the value proxyserver
	*  Host name or IP address of a proxy server.
	* @param proxyserver value to set
	**/
	public void setProxyserver(String proxyserver)	{
		this.proxyserver=proxyserver;
	}

	/** set the value enddate
	*  The date when the scheduled task ends.
	* @param enddate value to set
	 * @throws PageException
	**/
	public void setEnddate(Object enddate) throws PageException	{
	    if(StringUtil.isEmpty(enddate)) return;
		this.enddate=new DateImpl(DateCaster.toDateAdvanced(enddate,pageContext.getTimeZone()));
	}

	/** set the value file
	*  Required with publish ='Yes' A valid filename for the published file.
	* @param file value to set
	**/
	public void setFile(String file)	{
		this.strFile=file;
	}

	/** set the value starttime
	*  Required when creating tasks with action = 'update'. Enter a value in seconds. The time when 
	* 	scheduling of the task starts.
	* @param starttime value to set
	 * @throws PageException
	**/
	public void setStarttime(Object starttime) throws PageException	{
		if(StringUtil.isEmpty(starttime)) return;
		this.starttime=DateCaster.toTime(pageContext.getTimeZone(),starttime);
	}

	/** set the value proxyport
	*  The port number on the proxy server from which the task is being requested. Default is 80. When 
	* 		used with resolveURL, the URLs of retrieved documents that specify a port number are automatically 
	* 		resolved to preserve links in the retrieved document.
	* @param proxyport value to set
	 * @throws PageException 
	**/
	public void setProxyport(Object oProxyport) throws PageException	{
		if(StringUtil.isEmpty(oProxyport)) return;
		this.proxyport=Caster.toIntValue(oProxyport);
	}

	/** set the value port
	*  The port number on the server from which the task is being scheduled. Default is 80. When used 
	* 		with resolveURL, the URLs of retrieved documents that specify a port number are automatically resolved 
	* 		to preserve links in the retrieved document.
	* @param port value to set
	 * @throws PageException 
	**/
	public void setPort(Object oPort) throws PageException	{
		if(StringUtil.isEmpty(oPort)) return;
		this.port=Caster.toIntValue(oPort);
	}

	/** set the value endtime
	*  The time when the scheduled task ends. Enter a value in seconds.
	* @param endtime value to set
	 * @throws PageException
	**/
	public void setEndtime(Object endtime) throws PageException	{
		if(StringUtil.isEmpty(endtime)) return;
		this.endtime=DateCaster.toTime(pageContext.getTimeZone(),endtime);
	}

	/** set the value operation
	*  The type of operation the scheduler performs when executing this task.
	* @param operation value to set
	 * @throws ApplicationException
	**/
	public void setOperation(String operation) throws ApplicationException	{
		if(StringUtil.isEmpty(operation)) return;
		operation=operation.toLowerCase().trim();
		if(!operation.equals("httprequest"))
		    throw new ApplicationException("attribute operation must have the value [HTTPRequest]");
	}

	/** set the value interval
	*  Required when creating tasks with action = 'update'. Interval at which task should be scheduled. 
	* 		Can be set in seconds or as Once, Daily, Weekly, and Monthly. The default interval is one hour. The 
	* 		minimum interval is one minute.
	* @param interval value to set
	**/
	public void setInterval(String interval)	{
		if(StringUtil.isEmpty(interval)) return;
		interval=interval.trim().toLowerCase();
		if(interval.equals("week")) this.interval="weekly";
		else if(interval.equals("day")) this.interval="daily";
		else if(interval.equals("month")) this.interval="monthly";
		else if(interval.equals("year")) this.interval="yearly";
	    this.interval=interval;
	}

	/** set the value publish
	*  Specifies whether the result should be saved to a file.
	* @param publish value to set
	**/
	public void setPublish(boolean publish)	{
		this.publish=publish;
	}

	/** set the value requesttimeout
	*  Customizes the requestTimeOut for the task operation. Can be used to extend the default timeout 
	* 		for operations that require more time to execute.
	* @param requesttimeout value to set
	**/
	public void setRequesttimeout(Object oRequesttimeout) throws PageException	{
		if(StringUtil.isEmpty(oRequesttimeout)) return;
		this.requesttimeout=Caster.toLongValue(oRequesttimeout)*1000L;
	}

	/** set the value username
	*  Username if URL is protected.
	* @param username value to set
	**/
	public void setUsername(String username)	{
		this.username=username;
	}

	/** set the value url
	*  Required when action = 'update'. The URL to be executed.
	* @param url value to set
	**/
	public void setUrl(String url)	{
		this.url=url;
	}

	/** set the value path
	*  Required with publish ='Yes' The path location for the published file.
	* @param path value to set
	**/
	public void setPath(String path)	{
		this.strPath=path;
	}

	/** set the value task
	*  The name of the task to delete, update, or run.
	* @param task value to set
	**/
	public void setTask(String task)	{
		this.task=task;
	}


	@Override
	public int doStartTag() throws PageException	{
	    scheduler=pageContext.getConfig().getScheduler();
	    
	    if(action!=ACTION_LIST && task==null) {
	        throw new ApplicationException("attribute task is required for tag schedule when action is not list");
	    }
	    
	    
	    switch(action) {
    		case ACTION_DELETE: doDelete();	break;
    		case ACTION_RUN: doRun();	break;
    		case ACTION_UPDATE: doUpdate();	break;
    		case ACTION_LIST: doList();	break;
    		case ACTION_PAUSE: doPause(true);	break;
    		case ACTION_RESUME: doPause(false);	break;
	    }
		return SKIP_BODY;
	}

	/**
	 * @throws PageException
     */
    private void doUpdate() throws PageException {
        String message="missing attribute for tag schedule with action update";
        String detail="required attributes are [startDate, startTime, URL, interval, operation]";
        
        Resource file=null;
        //if(publish) {
        if(!StringUtil.isEmpty(strFile) && !StringUtil.isEmpty(strPath)) {
        	file=ResourceUtil.toResourceNotExisting(pageContext, strPath);
        	file=file.getRealResource(strFile);
        }
        else if(!StringUtil.isEmpty(strFile)) {
            file=ResourceUtil.toResourceNotExisting(pageContext, strFile);
        }
        else if(!StringUtil.isEmpty(strPath)) {
            file=ResourceUtil.toResourceNotExisting(pageContext, strPath);
        }
        if(file!=null)pageContext.getConfig().getSecurityManager().checkFileLocation(pageContext.getConfig(),file,serverPassword);
        
        // missing attributes
        if(startdate==null || starttime==null || url==null || interval==null) 
            throw new ApplicationException(message,detail);
        
        // timeout
        if(requesttimeout<0)requesttimeout=pageContext.getRequestTimeout();
        
        // username/password
        Credentials cr=null;
        if(username!=null) cr=CredentialsImpl.toCredentials(username,password);
        
       try {
           
           ScheduleTask st=new ScheduleTaskImpl(
                   task,
                   file,
                   startdate,
                   starttime,
                   enddate, 
                   endtime,
                   url,
                   port,
                   interval,
                   requesttimeout,
                   cr,
                   ProxyDataImpl.getInstance(proxyserver,proxyport,proxyuser,proxypassword),
                   resolveurl,
                   publish,
                   hidden,
                   readonly,
                   paused,
                   autoDelete);
        scheduler.addScheduleTask(st,true);
    } catch (Exception e) {
        throw Caster.toPageException(e);
    } 
        
        
        
        
        
        //
    }

    /**
     * @throws PageException
     */
    private void doRun() throws PageException {
        try {
            scheduler.runScheduleTask(task,true);
        } catch (Exception e) {
            throw Caster.toPageException(e);
        }
    }

    /**
     * @throws PageException
     */
    private void doDelete() throws PageException {
        try {
            scheduler.removeScheduleTask(task,false);
        } catch (Exception e) {
            throw Caster.toPageException(e);
        }
    }

    /**
     * @throws PageException
     * 
     */
    private void doList() throws PageException  {
    	//if(tr ue) throw new PageRuntimeException("qqq");
        ScheduleTask[] tasks = scheduler.getAllScheduleTasks(); 
        final String v="VARCHAR";
        String[] cols = new String[]{"task","path","file","startdate","starttime","enddate","endtime",
                "url","port","interval","timeout","username","password","proxyserver",
                "proxyport","proxyuser","proxypassword","resolveurl","publish","valid","paused","autoDelete"};
        String[] types = new String[]{v,v,v,"DATE","OTHER","DATE","OTHER",
                v,v,v,v,v,v,v,
                v,v,v,v,"BOOLEAN",v,"BOOLEAN","BOOLEAN"};
        railo.runtime.type.Query query=new QueryImpl(cols,types,tasks.length,"query"
        );
        try {
	        for(int i=0;i<tasks.length;i++) {
	            int row=i+1;
	            ScheduleTask task=tasks[i];
	            query.setAt(KeyConstants._task,row,task.getTask());
	            if(task.getResource()!=null) {
	                query.setAt(KeyConstants._path,row,task.getResource().getParent());
	                query.setAt(KeyConstants._file,row,task.getResource().getName());
	            }
	            query.setAt("publish",row,Caster.toBoolean(task.isPublish()));
	            query.setAt("startdate",row,task.getStartDate());
	            query.setAt("starttime",row,task.getStartTime());
	            query.setAt("enddate",row,task.getEndDate());
	            query.setAt("endtime",row,task.getEndTime());
	            query.setAt(KeyConstants._url,row,printUrl(task.getUrl()));
	            query.setAt(KeyConstants._port,row,Caster.toString(HTTPUtil.getPort(task.getUrl())));
	            query.setAt("interval",row,task.getStringInterval());
	            query.setAt("timeout",row,Caster.toString(task.getTimeout()/1000));
	            query.setAt("valid",row,Caster.toString(task.isValid()));
	            if(task.hasCredentials()) {
	                query.setAt("username",row,task.getCredentials().getUsername());
	                query.setAt("password",row,task.getCredentials().getPassword());
	            }
	            ProxyData pd = task.getProxyData();
	            if(ProxyDataImpl.isValid(pd)){
		            query.setAt("proxyserver",row,pd.getServer());
		            if(pd.getPort()>0)query.setAt("proxyport",row,Caster.toString(pd.getPort()));
		            if(ProxyDataImpl.hasCredentials(pd)) {
		                query.setAt("proxyuser",row,pd.getUsername());
		                query.setAt("proxypassword",row,pd.getPassword());
		            }
	            }
	            query.setAt("resolveurl",row,Caster.toString(task.isResolveURL()));

	            query.setAt("paused",row,Caster.toBoolean(task.isPaused()));
	            query.setAt("autoDelete",row,Caster.toBoolean(((ScheduleTaskImpl)task).isAutoDelete()));
	            
	            
	            
	        }
	        pageContext.setVariable(returnvariable,query);
        } 
        catch (DatabaseException e) {}
        
        
    }
    
	private void doPause(boolean pause) throws PageException {
		try {
			((SchedulerImpl)scheduler).pauseScheduleTask(task,pause,true);
        } catch (Exception e) {
            throw Caster.toPageException(e);
        }
		
	}

    private String printUrl(URL url) {
        
        String qs=url.getQuery();
        if(qs==null) qs="";
        else if(qs.length()>0)qs="?"+qs;
        
        String str=url.getProtocol()+"://"+url.getHost()+url.getPath()+qs;
        return str;
    }
    @Override
	public int doEndTag()	{
		return EVAL_PAGE;
	}

	@Override
	public void release()	{
		super.release();
		readonly=false;
		strPath=null;
		strFile=null;
		starttime=null;
		startdate=null;
		endtime=null;
		enddate=null;
		url=null;
		port=-1;
		interval=null;
		requesttimeout=-1;
		username=null;
		password="";
		proxyserver=null;
		proxyport=80;
		proxyuser=null;
		proxypassword="";
		resolveurl=false;
		publish=false;
		returnvariable="cfschedule";
		task=null;
		hidden=false;
		serverPassword=null;
		paused=false;
		autoDelete=false;
	}

}