/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache", "Jakarta", "JAMES" and "Apache Software Foundation"
 *    must not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * Portions of this software are based upon public domain software
 * originally written at the National Center for Supercomputing Applications,
 * University of Illinois, Urbana-Champaign.
 */

package org.apache.james.transport.mailets;

import org.apache.james.util.RFC2822Headers;
import org.apache.james.util.RFC822DateFormat;
import org.apache.mailet.GenericMailet;
import org.apache.mailet.Mail;
import org.apache.mailet.MailAddress;
import org.apache.mailet.MailetException;

import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.Collection;
import java.util.ArrayList;

/**
 * <P>Generates a response to the Return-Path address, or the
 * address of the message's sender if the Return-Path is not
 * available.  Note that this is different than a mail-client's
 * reply, which would use the Reply-To or From header.</P>
 * <P>Bounced messages are attached in their entirety (headers and
 * content) and the resulting MIME part type is "message/rfc822".</P>
 * <P>The Return-Path header of the response is set to "empty" ("<>"),
 * meaning that no reply should be sent.</P>
 * <P>The layout of the bounce message is the same as the NotifySender.</P>
 * <P>You can optionally specify a sender of the error
 * message.  If you do not specify one, it will use the postmaster's address.
 * <P>Supports passThrough (default is false).</P>
 *
 * <P>Sample configuration:</P>
 * <PRE><CODE>
 * &lt;mailet match="All" class="NotifySender">
 *   &lt;sendingAddress&gt;<I>an address or postmaster</I>&lt;/sendingAddress&gt;
 *   &lt;attachStackTrace&gt;<I>true or false, default=false</I>&lt;/attachStackTrace&gt;
 *   &lt;notice&gt;<I>notice attached to the message (optional)</I>&lt;/notice&gt;
 *   &lt;passThrough&gt;<I>true or false, default=false</I>&lt;/passThrough&gt;
 * &lt;/mailet&gt;
 * </CODE></PRE>
 *
 * <P>The behaviour of this mailet is equivalent to using Redirect with the following
 * configuration:</P>
 * <PRE><CODE>
 * &lt;mailet match="All" class="Redirect">
 *   &lt;sender&gt;<I>an address or postmaster</I>&lt;/sender&gt;
 *   &lt;attachError&gt;<I>true or false, default=false</I>&lt;/attachError&gt;
 *   &lt;message&gt;<I><B>dynamically built</B></I>&lt;/message&gt;
 *   &lt;passThrough&gt;<I>true or false, default=false)</I>&lt;/passThrough&gt;
 *   &lt;recipients&gt;<B>sender</B>&lt;/recipients&gt;
 *   &lt;returnPath&gt;empty&lt;/returnPath&gt;
 *   &lt;inline&gt;none&lt;/inline&gt;
 *   &lt;attachment&gt;message&lt;/attachment&gt;
 *   &lt;isReply&gt;true&lt;/isReply&gt;
 *   &lt;static&gt;true&lt;/static&gt;
 * &lt;/mailet&gt;
 * </CODE></PRE>
 *
 */
public class Bounce extends Redirect {
    
    /**
     * @return true, meaning static (all getX() methods are static)
     */
    protected boolean isStatic() {
        return true;
    }
    
    /**
     * @return <CODE>NONE</CODE>
     */
    protected int getInLineType() {
        return NONE;
    }
    
    /**
     * @return <CODE>MESSAGE</CODE>
     */
    protected int getAttachmentType() {
        return MESSAGE;
    }
    
    /**
     * @return the <CODE>notice</CODE> init parameter
     */
    protected String getMessage() {
        if(getInitParameter("notice") == null) {
            return "We were unable to deliver the attached message because of an error in the mail server.";
        } else {
            return getInitParameter("notice");
        }
    }
    
    /**
     * @return the full message to append, built from the Mail object
     */
    protected String getMessage(Mail originalMail) throws MessagingException {
        MimeMessage message = originalMail.getMessage();
        StringWriter sout = new StringWriter();
        PrintWriter out = new PrintWriter(sout, true);
        
        // First add the "local" notice
        // (either from conf or generic error message)
        out.println(getMessage());
        // And then the message from other mailets
        if (originalMail.getErrorMessage() != null) {
            out.println();
            out.println("Error message below:");
            out.println(originalMail.getErrorMessage());
        }
        out.println();
        out.println("Message details:");
        
        if (message.getSubject() != null) {
            out.println("  Subject: " + message.getSubject());
        }
        if (message.getSentDate() != null) {
            out.println("  Sent date: " + message.getSentDate());
        }
        String[] rcpts = null;
        rcpts = message.getHeader(RFC2822Headers.TO);
        if (rcpts != null) {
            out.print("  To: ");
            for (int i = 0; i < rcpts.length; i++) {
                out.print(rcpts[i] + " ");
            }
            out.println();
        }
        rcpts = message.getHeader(RFC2822Headers.CC);
        if (rcpts != null) {
            out.print("  CC: ");
            for (int i = 0; i < rcpts.length; i++) {
                out.print(rcpts[i] + " ");
            }
            out.println();
        }
        out.println("  Size (in bytes): " + message.getSize());
        if (message.getLineCount() >= 0) {
            out.println("  Number of lines: " + message.getLineCount());
        }
        
        return sout.toString();
    }
    
    /**
     * @return null, because it's all dynamic
     */
    protected Collection getRecipients() {
        return null;
    }
    
    /**
     * @return the original return path, or the sender if missing
     */
    protected Collection getRecipients(Mail originalMail) throws MessagingException {
        // TODO: use magic RETURN_PATH
        Collection recipients = new ArrayList(1);
        recipients.add(getExistingReturnPath(originalMail));
        if (recipients != null && recipients.size() == 1) {
            if (recipients.contains(SpecialAddress.SENDER)) {
                recipients = new ArrayList();
                recipients.add(originalMail.getSender());
            } else if (recipients.contains(SpecialAddress.NULL)) {
                // should never get here
                throw new MessagingException("SpecialAddress.NULL found getting recipients");
            }
        }
        return recipients;
    }
    
    /**
     * @return null, because it's all dynamic
     */
    protected InternetAddress[] getTo() {
        return null;
    }
    
    /**
     * @return null, indicating to reply to the sender of this reply
     */
    protected MailAddress getReplyTo() {
        return null;
    }
    
    /**
     * @return EMPTY (the meaning of bounce)
     */
    protected MailAddress getReturnPath() {
        return SpecialAddress.NULL;
    }
    
    /**
     * @return the value of the <CODE>sendingAddress</CODE> init parameter if not null,
     * the postmaster address otherwise
     */
    protected MailAddress getSender() throws MessagingException {
        if (getInitParameter("sendingAddress") == null) {
            return getMailetContext().getPostmaster();
        } else {
            return new MailAddress(getInitParameter("sendingAddress"));
        }
    }
    
    /**
     * @return "" (no prefix)
     */
    protected String getSubjectPrefix() {
        return "";
    }
    
    /**
     * @return the <CODE>attachStackTrace</CODE> init parameter
     */
    protected boolean attachError() {
        boolean attachStackTrace = false;
        try {
            attachStackTrace = new Boolean(getInitParameter("attachStackTrace")).booleanValue();
        } catch (Exception e) {
            // Ignore exception, default to false
        }
        return attachStackTrace;
    }
    
    /**
     * @return true
     */
    protected boolean isReply() {
        return true;
    }

    /**
     * Return a string describing this mailet.
     *
     * @return a string describing this mailet
     */
    public String getMailetInfo() {
        return "NotifySender Mailet";
    }

    /**
     * Service does the hard work,and redirects the originalMail in the form specified.
     * Checks that the original return path is not empty,
     * and then calls super.service(originalMail), otherwise just returns.
     *
     * @param originalMail the mail to process and redirect
     * @throws MessagingException if a problem arises formulating the redirected mail
     */
    public void service(Mail originalMail) throws MessagingException {
        MailAddress returnAddress = getExistingReturnPath(originalMail);
        if (returnAddress == SpecialAddress.NULL) {
            if (isDebug)
                log("Processing a bounce request for a message with an empty return path.  No bounce will be sent.");
            return;
        } else if (returnAddress == SpecialAddress.SENDER) {
            log("WARNING: Mail to be bounced does not contain a Return-Path header.");
        } else {
            if (isDebug)
                log("Processing a bounce request for a message with a return path header.  The bounce will be sent to " + returnAddress);
        }
        super.service(originalMail);
    }
    
    /**
     * Gets the MailAddress corresponding to the existing "Return-Path" header of
     * <I>mail</I>.
     * If empty returns EMPTY, if missind return SENDER.
     */
    private MailAddress getExistingReturnPath(Mail mail) throws MessagingException {
        MailAddress mailAddress = SpecialAddress.SENDER;
        String[] returnPathHeaders = mail.getMessage().getHeader(RFC2822Headers.RETURN_PATH);
        String returnPathHeader = null;
        if (returnPathHeaders != null) {
            returnPathHeader = returnPathHeaders[0];
            if (returnPathHeader != null) {
                returnPathHeader = returnPathHeader.trim();
                if (returnPathHeader.equals("<>")) {
                    mailAddress = SpecialAddress.NULL;
                } else {
                    mailAddress = new MailAddress(new InternetAddress(returnPathHeader));
                }
            }
        }
        return mailAddress;
    }
}

