package railo.runtime.tag;


import javax.mail.internet.InternetAddress;

import railo.commons.io.res.Resource;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.StringUtil;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.ext.tag.BodyTagImpl;
import railo.runtime.net.mail.MailException;
import railo.runtime.net.mail.MailPart;
import railo.runtime.net.smtp.SMTPClient;
import railo.runtime.op.Caster;
import railo.runtime.op.Decision;
// TODO test proxy
/**
 * 
* Sends e-mail messages by an SMTP server.
*
*
*
**/
public final class Mail extends BodyTagImpl {

	/** Specifies the query column to use when you group sets of records together to send as an e-mail 
	** 		message. For example, if you send a set of billing statements to customers, you might group on 
	** 		"Customer_ID." The group attribute, which is case sensitive, eliminates adjacent duplicates when the 
	** 		data is sorted by the specified field. See the Usage section for exceptions. */
	private String group;

	/** Boolean indicating whether to group with regard to case or not. The default value is YES; 
	** 		case is considered while grouping. If the query attribute specifies a query object that was generated 
	** 		by a case-insensitive SQL query, set the groupCaseSensitive attribute to NO to keep the recordset 
	** 		intact. */
	private boolean groupcasesensitive;

	/** The name of the cfquery from which to draw data for message(s) to send. Specify this 
	** 		attribute to send more than one mail message, or to send the results of a query within a message. */
	private String query;

	/** Specifies the maximum number of e-mail messages to send. */
	private double maxrows;

	/** Specifies the row in the query to start from. */
	private double startrow;
    


	//private railo.runtime.mail.Mail mail=new railo.runtime.mail.Mail();
	private SMTPClient smtp=new SMTPClient();
	private railo.runtime.net.mail.MailPart part=null;//new railo.runtime.mail.MailPart("UTF-8");

	private String charset;

	private int priority;

	private boolean remove;
	
	

	@Override
	public void release()	{
		super.release();
//       do not clear because spooler
        //mail=new railo.runtime.mail.Mail();	
		smtp=new SMTPClient();
        part=null;//new railo.runtime.mail.MailPart("UTF-8");
		group=null;
		groupcasesensitive=false;
		query=null;
		maxrows=0d;
		startrow=0d;
		charset=null;
		remove=false;
	}
	
	
	/**
	 * @param remove the remove to set
	 */
	public void setRemove(boolean remove) {
		this.remove = remove;
	}


	/**
     * @param proxyserver The proxyserver to set.
	 * @throws ApplicationException 
     */
    public void setProxyserver(String proxyserver) throws ApplicationException {
		try {
			smtp.getProxyData().setServer(proxyserver);
		} catch (Exception e) {
			throw new ApplicationException("attribute [proxyserver] of the tag [mail] is invalid",e.getMessage());
		}
    }
	
	/** set the value proxyport
	*  The port number on the proxy server from which the object is 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 ApplicationException 
	**/
	public void setProxyport(double proxyport) throws ApplicationException	{
		try {
			smtp.getProxyData().setPort((int)proxyport);
		} catch (Exception e) {
			throw new ApplicationException("attribute [proxyport] of the tag [mail] is invalid",e.getMessage());
		}
	}

	/** set the value username
	*  When required by a proxy server, a valid username.
	* @param proxyuser value to set
	 * @throws ApplicationException 
	**/
	public void setProxyuser(String proxyuser) throws ApplicationException	{
		try {
			smtp.getProxyData().setUsername(proxyuser);
		} catch (Exception e) {
			throw new ApplicationException("attribute [proxyuser] of the tag [mail] is invalid",e.getMessage());
		}
	}

    
	/** set the value password
	*  When required by a proxy server, a valid password.
	* @param proxypassword value to set
	 * @throws ApplicationException 
	**/
	public void setProxypassword(String proxypassword) throws ApplicationException	{
		try {
			smtp.getProxyData().setPassword(proxypassword);
		} catch (Exception e) {
			throw new ApplicationException("attribute [proxypassword] of the tag [mail] is invalid",e.getMessage());
		}
	}

	

	/** set the value from
	*  The sender of the e-mail message.
	* @param strForm value to set
	 * @throws PageException 
	**/
	public void setFrom(Object from) throws PageException	{
		if(StringUtil.isEmpty(from)) return;
		try {
			smtp.setFrom(from);
		} catch (Exception e) {
			throw Caster.toPageException(e);
		}
	}

	/** set the value to
	*  The name of the e-mail message recipient.
	* @param strTo value to set
	 * @throws ApplicationException
	**/
	public void setTo(Object to) throws ApplicationException	{
		if(StringUtil.isEmpty(to)) return;
		try {
			smtp.addTo(to);
		} catch (Exception e) {
			throw new ApplicationException("attribute [to] of the tag [mail] is invalid",e.getMessage());
		}
	}

	/** set the value cc
	*  Indicates addresses to copy the e-mail message to; "cc" stands for "carbon copy."
	* @param strCc value to set
	 * @throws ApplicationException
	**/
	public void setCc(Object cc) throws ApplicationException	{
		if(StringUtil.isEmpty(cc)) return;
		try {
			smtp.addCC(cc);
		} catch (Exception e) {
			throw new ApplicationException("attribute [cc] of the tag [mail] is invalid",e.getMessage());
		}
	}



	/** set the value bcc
	*  Indicates addresses to copy the e-mail message to, without listing them in the message header. 
	* 		"bcc" stands for "blind carbon copy."
	* @param strBcc value to set
	 * @throws ApplicationException
	**/
	public void setBcc(Object bcc) throws ApplicationException	{
		if(StringUtil.isEmpty(bcc)) return;
		try {
			smtp.addBCC(bcc);
		} catch (Exception e) {
			throw new ApplicationException("attribute [bcc] of the tag [mail] is invalid",e.getMessage());
		}
	}	
	
	/**
	 * @param strFailto The failto to set.
	 * @throws ApplicationException
	 */
	public void setFailto(Object failto) throws ApplicationException {
		if(StringUtil.isEmpty(failto)) return;
		try {
			smtp.addFailTo(failto);
		} catch (Exception e) {
			throw new ApplicationException("attribute [failto] of the tag [mail] is invalid",e.getMessage());
		}
	}
	/**
	 * @param strReplyto The replyto to set.
	 * @throws ApplicationException
	 */
	public void setReplyto(Object replyto) throws ApplicationException {
		if(StringUtil.isEmpty(replyto)) return;
		try {
			smtp.addReplyTo(replyto);
		} catch (Exception e) {
			throw new ApplicationException("attribute [replyto] of the tag [mail] is invalid",e.getMessage());
		}
	}
	
	/** set the value type
	*  Specifies extended type attributes for the message.
	* @param type value to set
	 * @throws ApplicationException
	**/
	public void setType(String type) throws ApplicationException	{
		type=type.toLowerCase().trim();
		if(type.equals("text/plain") || type.equals("plain") || type.equals("text"))
		    getPart().isHTML(false);
		    //mail.setType(railo.runtime.mail.Mail.TYPE_TEXT);
		else if(type.equals("text/html") || type.equals("html") || type.equals("htm"))
			getPart().isHTML(true);
		else
			throw new ApplicationException("attribute type of tag mail has an invalid values","valid values are [plain,text,html] but value is now ["+type+"]");
			//throw new ApplicationException(("invalid type "+type);
	}

	/** set the value subject
	*  The subject of the mail message. This field may be driven dynamically on 
	* 		a message-by-message basis
	* @param subject value to set
	**/
	public void setSubject(String subject)	{
		smtp.setSubject(subject);
	}
	/**
	 * @param username The username to set.
	 */
	public void setUsername(String username) {
		smtp.setUsername(username);
	}
	/**
	 * @param password The password to set.
	 */
	public void setPassword(String password) {
		smtp.setPassword(password);
	}
	
	/** set the value mimeattach
	*  Specifies the path of the file to be attached to the e-mail message. An attached file 
	* 		is MIME-encoded.
	* @param strMimeattach value to set
	 * @param type mimetype of the file
	 * @param contentID 
	 * @param disposition 
	 * @throws PageException 
	**/
	public void setMimeattach(String strMimeattach, String type, String disposition, String contentID,boolean removeAfterSend) throws PageException	{
		Resource file=ResourceUtil.toResourceNotExisting(pageContext,strMimeattach);
        pageContext.getConfig().getSecurityManager().checkFileLocation(file);
		if(!file.exists())
			throw new ApplicationException("can't attach file "+strMimeattach+", this file doesn't exist");
		

        smtp.addAttachment(file,type,disposition,contentID,removeAfterSend);
		
	}
	public void setMimeattach(String strMimeattach) throws PageException	{
		setMimeattach(strMimeattach, "", null, null,false);
	}
	
	/**
	 * @param spoolenable The spoolenable to set.
	 */
	public void setSpoolenable(boolean spoolenable) {
		smtp.setSpoolenable(spoolenable);
	}
	
	/** set the value server
	* @param strServer value to set
	 * @throws PageException 
	**/
	public void setServer(String strServer) throws PageException {
		smtp.setHost(strServer);
	}
 
	/** set the value mailerid
	* @param mailerid value to set
	**/
	public void setMailerid(String mailerid)	{
		smtp.setXMailer(mailerid);
	}
	
	/** set the value port
	*  The TCP/IP port on which the SMTP server listens for requests. This is normally 25.
	* @param port value to set
	**/
	public void setPort(double port)	{
		smtp.setPort((int)port);
	}
	
	/**
	 * @param wraptext The wraptext to set.
	 */
	public void setWraptext(double wraptext) {
		getPart().setWraptext((int)wraptext);
	}

	/** set the value timeout
	*  The number of seconds to wait before timing out the connection to the SMTP server.
	* @param timeout value to set
	**/
	public void setTimeout(double timeout)	{
		smtp.setTimeout((int)(timeout*1000));
	}
	
	/**
	 * @param charset The charset to set.
	 */
	public void setCharset(String charset) {
		this.charset=charset;
	}

	/** set the value group
	*  Specifies the query column to use when you group sets of records together to send as an e-mail 
	* 		message. For example, if you send a set of billing statements to customers, you might group on 
	* 		"Customer_ID." The group attribute, which is case sensitive, eliminates adjacent duplicates when the 
	* 		data is sorted by the specified field. See the Usage section for exceptions.
	* @param group value to set
	**/
	public void setGroup(String group)	{
		this.group=group;
	}

	/** set the value groupcasesensitive
	*  Boolean indicating whether to group with regard to case or not. The default value is YES; 
	* 		case is considered while grouping. If the query attribute specifies a query object that was generated 
	* 		by a case-insensitive SQL query, set the groupCaseSensitive attribute to NO to keep the recordset 
	* 		intact.
	* @param groupcasesensitive value to set
	**/
	public void setGroupcasesensitive(boolean groupcasesensitive)	{
		this.groupcasesensitive=groupcasesensitive;
	}

	/** set the value query
	*  The name of the cfquery from which to draw data for message(s) to send. Specify this 
	* 		attribute to send more than one mail message, or to send the results of a query within a message.
	* @param query value to set
	**/
	public void setQuery(String query)	{
		this.query=query;
	}

	/** set the value maxrows
	*  Specifies the maximum number of e-mail messages to send.
	* @param maxrows value to set
	**/
	public void setMaxrows(double maxrows)	{
		this.maxrows=maxrows;
	}
	

	public void setTls(boolean tls)	{
		smtp.setTLS(tls);
	}	
	
	public void setUsetls(boolean tls)	{
		smtp.setTLS(tls);
	}	
	
	public void setStarttls(boolean tls)	{
		smtp.setTLS(tls);
	}

	public void setSsl(boolean ssl)	{
		smtp.setSSL(ssl);
	}

	public void setUsessl(boolean ssl)	{
		smtp.setSSL(ssl);
	}

	public void setSecure(boolean ssl)	{
		smtp.setSSL(ssl);
	}
	public void setPriority(String strPriority) throws ExpressionException	{
		strPriority=strPriority.trim().toLowerCase();
		boolean valid=true;
		if(Decision.isNumeric(strPriority)) {
			int p=Caster.toIntValue(strPriority,-1);
			if(p<1 || p>5)valid=false;
			else this.priority=p;
		}
		else {
			if("highest".equals(strPriority))priority=1;
			else if("urgent".equals(strPriority))priority=1;
			else if("high".equals(strPriority))priority=2;
			else if("normal".equals(strPriority))priority=3;
			else if("low".equals(strPriority))priority=4;
			else if("lowest".equals(strPriority))priority=5;
			else if("non-urgent".equals(strPriority))priority=5;
			else if("none-urgent".equals(strPriority))priority=5;
			else valid=false;
		}
		
		if(!valid)throw new ExpressionException("the value of attribute priority is invalid ["+strPriority+"], " +
				"the value should be an integer between [1-5] or " +
				"one of the following [highest,urgent,high,normal,low,lowest,non-urgent]");
		
	}

	/** set the value startrow
	*  Specifies the row in the query to start from.
	* @param startrow value to set
	**/
	public void setStartrow(double startrow)	{
		this.startrow=startrow;
	}

    /**
     * @param part
     */
    public void addPart(MailPart part) {
    	String type = part.getType();
		if(StringUtil.isEmpty(part.getCharset())) part.setCharset(getCharset());
		if(type!=null && (type.equals("text/plain") || type.equals("plain") || type.equals("text"))){
			part.isHTML(false);
			addClassicBodyPart(part);
		}
		else if(type!=null && (type.equals("text/html") || type.equals("html") || type.equals("htm"))){
			part.isHTML(true);
			addClassicBodyPart(part);
		}   
		else {
			addBodyPart(part);
		}
    }
	
    // this was not supported in prior releases
	private void addBodyPart(MailPart part) {
		smtp.setPart(part);
	}

	/**
     * @param part
     */
    private void addClassicBodyPart(MailPart part) {
        if(part.isHTML()) {
            if(!smtp.hasHTMLText())smtp.setHTMLText(part.getBody(), part.getCharset());
        }
        else {
            if(!smtp.hasPlainText())smtp.setPlainText(part.getBody(), part.getCharset());
        }
    }


    @Override
	public int doStartTag() throws ApplicationException	{
		if(isEmpty(smtp.getTos()) && isEmpty(smtp.getCcs()) && isEmpty(smtp.getBccs())) 
			throw new ApplicationException("One of the following attribtues must be defined [to, cc, bcc]");
			
		return EVAL_BODY_BUFFERED;
	}

	private boolean isEmpty(InternetAddress[] addrs) {
		return addrs==null || addrs.length==0;
	}


	@Override
	public void doInitBody()	{
		
	}

	@Override
	public int doAfterBody()	{
		getPart().setBody(bodyContent.getString());
		smtp.setCharset(getCharset());
		getPart().setCharset(getCharset());
		addClassicBodyPart(getPart());
		return SKIP_BODY;
	}
	
	@Override
	public int doEndTag() throws PageException	{
		smtp.setTimeZone(pageContext.getTimeZone());
		try {
			smtp.send(pageContext.getConfig());
		} 
		catch (MailException e) {
			throw Caster.toPageException(e);
		}
		return EVAL_PAGE;
	}

	/**
	 * sets a mail param
	 * @param type
	 * @param file
	 * @param name
	 * @param value
	 * @param contentID 
	 * @param disposition 
	 * @throws PageException 
	 */
	public void setParam(String type, String file, String name, String value, String disposition, String contentID,Boolean oRemoveAfterSend) throws PageException {
		if(file!=null){
			boolean removeAfterSend=(oRemoveAfterSend==null)?remove:oRemoveAfterSend.booleanValue();
				
			setMimeattach(file,type,disposition,contentID,removeAfterSend);
		}
		else {
			if(name.equalsIgnoreCase("bcc"))			setBcc(value);
			else if(name.equalsIgnoreCase("cc"))		setCc(value);
			else if(name.equalsIgnoreCase("charset"))	setCharset(value);
			else if(name.equalsIgnoreCase("failto"))	setFailto(value);
			else if(name.equalsIgnoreCase("from"))		setFrom(value);
			else if(name.equalsIgnoreCase("mailerid"))	setMailerid(value);
			else if(name.equalsIgnoreCase("mimeattach"))setMimeattach(value);
			else if(name.equalsIgnoreCase("priority"))	setPriority(value);
			else if(name.equalsIgnoreCase("replyto"))	setReplyto(value);
			else if(name.equalsIgnoreCase("subject"))	setSubject(value);
			else if(name.equalsIgnoreCase("to"))		setTo(value);
			
			else smtp.addHeader(name,value);
		}
	}	
	
	private railo.runtime.net.mail.MailPart getPart() {
		if(part==null)part=new railo.runtime.net.mail.MailPart(pageContext.getConfig().getMailDefaultEncoding());
		return part;
	}


	/**
	 * @return the charset
	 */
	public String getCharset() {
		if(charset==null)charset=pageContext.getConfig().getMailDefaultEncoding();
		return charset;
	}
}