/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included with this distribution in
 * the docs/licenses/apache-1.1.txt file.
 */
package org.jboss.axis.attachments;

import org.jboss.axis.AxisFault;
import org.jboss.axis.AxisProperties;
import org.jboss.axis.Part;
import org.jboss.axis.transport.http.HTTPConstants;
import org.jboss.axis.utils.Messages;
import org.jboss.axis.utils.SessionUtils;
import org.jboss.logging.Logger;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.internet.ContentType;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Properties;


/**
 * This class is defines utilities for mime.
 */
public class MimeUtils
{

   /**
    * Field log
    */
   private static Logger log = Logger.getLogger(MimeUtils.class.getName());

   /**
    * Determine as efficiently as possible the content length for attachments in a mail Multipart.
    *
    * @param mp is the multipart to be serarched.
    * @return the actual length.
    * @throws  MessagingException
    */
   public static long getContentLength(Multipart mp)
           throws MessagingException
   {

      int totalParts = mp.getCount();
      long totalContentLength = 0;

      for (int i = 0; i < totalParts; ++i)
      {
         MimeBodyPart bp = (MimeBodyPart)mp.getBodyPart(i);
         totalContentLength += getContentLength(bp);
      }

      String ctype = mp.getContentType();
      ContentType ct = new ContentType(ctype);
      String boundaryStr = ct.getParameter("boundary");
      // must add two for -- prefix and another two for crlf
      int boundaryStrLen = boundaryStr.length() + 4;

      // there is one more boundary than parts
      // each parts data must have crlf after it.
      // last boundary has an additional --crlf
      return totalContentLength + boundaryStrLen * (totalParts + 1) + 2 * totalParts + +4;
   }

   /**
    * Determine the length for the individual part.
    *
    * @param bp is the part to be searched.
    * @return the length in bytes.
    */
   protected static long getContentLength(MimeBodyPart bp)
   {

      long headerLength = -1L;
      long dataSize = -1L;

      try
      {
         headerLength = getHeaderLength(bp);

         DataHandler dh = bp.getDataHandler();
         DataSource ds = dh.getDataSource();

         // Do files our selfs since this is costly to read in. Ask the file system.
         // This is 90% of the use of attachments.
         if (ds instanceof FileDataSource)
         {
            FileDataSource fdh = (FileDataSource)ds;
            File df = fdh.getFile();

            if (!df.exists())
            {
               throw new RuntimeException(Messages.getMessage("noFile", df.getAbsolutePath()));
            }

            dataSize = df.length();
         }
         else
         {
            dataSize = bp.getSize();

            if (-1 == dataSize)
            {    // Data size is not known so read it the hard way...
               dataSize = 0;

               InputStream in = ds.getInputStream();
               byte[] readbuf = new byte[64 * 1024];
               int bytesread;

               do
               {
                  bytesread = in.read(readbuf);

                  if (bytesread > 0)
                  {
                     dataSize += bytesread;
                  }
               }
               while (bytesread > -1);

               in.close();
            }
         }
      }
      catch (Exception e)
      {
         log.error(Messages.getMessage("exception00"), e);
      }

      return dataSize + headerLength;
   }

   /**
    * Gets the header length for any part.
    *
    * @param bp the part to determine the header length for.
    * @return the length in bytes.
    * @throws  MessagingException
    * @throws  IOException
    */
   private static long getHeaderLength(MimeBodyPart bp)
           throws MessagingException, IOException
   {

      MimeBodyPart headersOnly = new MimeBodyPart(new InternetHeaders(), new byte[0]);

      for (Enumeration en = bp.getAllHeaders(); en.hasMoreElements();)
      {
         Header header = (Header)en.nextElement();
         headersOnly.addHeader(header.getName(), header.getValue());
      }

      ByteArrayOutputStream bas = new ByteArrayOutputStream(1024 * 16);

      headersOnly.writeTo(bas);
      bas.close();

      return (long)bas.size();    // This has header length plus the crlf part that seperates the data
   }

   /**
    * Field filter
    */
   public static String[] filter = new String[]{"Message-ID", "Mime-Version",
                                                "Content-Type"};

   /**
    * This routine will the multi part type and write it out to a stream.
    * <p/>
    * <p>Note that is does *NOT* pass <code>AxisProperties</code>
    * to <code> Session.getInstance</code>, but instead
    * the System properties.
    * </p>
    *
    * @param os is the output stream to write to.
    * @param mp the multipart that needs to be written to the stream.
    */
   public static void writeToMultiPartStream(OutputStream os, MimeMultipart mp)
   {

      try
      {
         Properties props = AxisProperties.getProperties();

         // this is a bogus since we will never mail it.
         props.setProperty("mail.smtp.host", "localhost");

         Session session = Session.getInstance(props, null);
         MimeMessage message = new MimeMessage(session);

         message.setContent(mp);
         message.saveChanges();
         message.writeTo(os, filter);
      }
      catch (MessagingException e)
      {
         log.error(Messages.getMessage("javaxMailMessagingException00"), e);
      }
      catch (IOException e)
      {
         log.error(Messages.getMessage("javaIOException00"), e);
      }
   }

   /**
    * This routine will get the content type.
    *
    * @param mp
    * @return
    */
   public static String getContentType(MimeMultipart mp)
   {
      StringBuffer contentType = new StringBuffer(mp.getContentType());
      // TODO (dims): Commons HttpClient croaks if we don't do this.
      //              Need to get Commons HttpClient fixed.
      for (int i = 0; i < contentType.length();)
      {
         char ch = contentType.charAt(i);
         if (ch == '\r' || ch == '\n')
            contentType.deleteCharAt(i);
         else
            i++;
      }
      return contentType.toString();
   }

   /**
    * This routine will create a multipart object from the parts and the SOAP content.
    *
    * @param env   should be the text for the main root part.
    * @param parts contain a collection of the message parts.
    * @return a new MimeMultipart object
    * @throws  AxisFault
    */
   public static MimeMultipart createMP(String env, Collection parts)
           throws AxisFault
   {

      MimeMultipart multipart = null;

      try
      {
         String rootCID = SessionUtils.generateSessionId();

         multipart = new MimeMultipart("related; type=\"text/xml\"; start=\"<" + rootCID + ">\"");

         MimeBodyPart messageBodyPart = new MimeBodyPart();

         messageBodyPart.setText(env, "UTF-8");
         messageBodyPart.setHeader("Content-Type", "text/xml; charset=UTF-8");
         messageBodyPart.setHeader("Content-Id", "<" + rootCID + ">");
         messageBodyPart.setHeader(HTTPConstants.HEADER_CONTENT_TRANSFER_ENCODING, "binary");
         multipart.addBodyPart(messageBodyPart);

         for (Iterator it = parts.iterator(); it.hasNext();)
         {
            Part part = (Part)it.next();
            DataHandler dh = AttachmentUtils.getActivationDataHandler(part);
            String contentID = part.getContentId();

            messageBodyPart = new MimeBodyPart();
            messageBodyPart.setDataHandler(dh);

            String contentType = part.getContentType();
            if (contentType == null)
               contentType = dh.getContentType();

            if (contentType == null || contentType.trim().length() == 0)
               contentType = "application/octet-stream";

            messageBodyPart.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, contentType);
            messageBodyPart.setHeader(HTTPConstants.HEADER_CONTENT_ID, "<" + contentID + ">");
            // Safe and fastest for anything other than mail;
            messageBodyPart.setHeader(HTTPConstants.HEADER_CONTENT_TRANSFER_ENCODING, "binary");

            for (Iterator i = part.getNonMatchingMimeHeaders(new String[]{
               HTTPConstants.HEADER_CONTENT_TYPE,
               HTTPConstants.HEADER_CONTENT_ID,
               HTTPConstants.HEADER_CONTENT_TRANSFER_ENCODING}); i.hasNext();)
            {
               javax.xml.soap.MimeHeader header = (javax.xml.soap.MimeHeader)i.next();
               messageBodyPart.setHeader(header.getName(), header.getValue());
            }

            multipart.addBodyPart(messageBodyPart);
         }
      }
      catch (MessagingException e)
      {
         log.error(Messages.getMessage("javaxMailMessagingException00"), e);
      }

      return multipart;
   }
}
