/*
 *	The contents of this file are subject to the Netscape Public License 
 *	Version 1.0 (the "License"); you may not use this file except in compliance 
 *	with the License. You may obtain a copy of the License at 
 *	http://www.mozilla.org/NPL/. 
 *	
 *	Software distributed under the License is distributed on an "AS IS" basis, 
 *	WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
 *	for the specific language governing rights and limitations under the License. 
 *	
 *	The Original Code is the Netscape Messaging Access SDK Version 3.5 code, 
 *	released on or about June 15, 1998. 
 *	
 *	The Initial Developer of the Original Code is Netscape Communications 
 *	Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape 
 *	Communications Corporation. All Rights Reserved. 
 *	
 *	Contributor(s): ______________________________________. 
*/
  
/*
* Copyright (c) 1997 and 1998 Netscape Communications Corporation
* (http://home.netscape.com/misc/trademarks.html)
*/


package netscape.messaging.mime;
import java.io.*;
import java.lang.*;
import java.util.*;
import netscape.messaging.mime.MIMEParser;



/**
 * The MIMEMessagePart class implements the MIME MessagePart Content Type.
 * @author Prasad Yendluri
 */
public class MIMEMessagePart extends MIMEBodyPart implements Cloneable
{

//===========================================================================
//
//              Internal members not visible at the API
//
//===========================================================================

	// Encodings
        private static final int BINARY          = MIMEBodyPart.BINARY;
        private static final int E7BIT           = MIMEBodyPart.E7BIT;
        private static final int E8BIT           = MIMEBodyPart.E8BIT;


    	private static final int UNINITIALIZED = -1;

	private static final String[] m_stringDisposition  = { "Attachment", "Inline"};
	private static final String[] m_stringEncoding  = { "base64", "quoted-printable",
                                                            "binary", "7bit", "8bit" };
	private static final String MESSAGE = "Message";

        private int             m_contentTransferEncoding;
        private int             m_parsedPart;

        // The key to the hashTable below is the name of the header field.
        // To make sure we handle case differences in the header-name, we
        // must convert the header name to lower-case always prior to the
        // hash look-up. The entries added to the hash table are objects of
        // class Header.
        private Hashtable       m_ExtBodyHeadersTable;

    	private MIMEMessage	m_theMessage;
        private InputStream     m_partial;


//===========================================================================
//
//                              CONSTRUCTORS
//
//===========================================================================

	/**
         * Constructs a MIMEMessagePart object.
         * Default constructor for the MIMEMessagePart.
         */
	public MIMEMessagePart ()
        {
            m_parsedPart = 0;
            m_UserObject = null;
            m_contentDisposition      =  UNINITIALIZED;
            m_contentTransferEncoding =  UNINITIALIZED;
            m_theMessage = null;
            m_partial = null;
            m_ExtBodyHeadersTable = null;
        }



	/**
	* Constructs a MIMEMessagePart object from the given MIMEMessage Object.
	* @param msg The message that should form the body of this part.
	* @exception MIMEException If msg is null.
	*/
	public MIMEMessagePart (MIMEMessage msg) throws MIMEException
        {
                m_parsedPart = 0;
                m_contentDisposition      =  UNINITIALIZED;
                m_contentTransferEncoding =  UNINITIALIZED;

		if (msg != null)
		{
		     try
		     {
		          m_theMessage = (MIMEMessage) msg.clone();
		     }
		     catch (CloneNotSupportedException e)
		     {
                          throw new MIMEException (e.getMessage());
		     }
                }
		else
                     throw new MIMEException ("MIMEMessagePart(): null message passed." );

        }

//===========================================================================
//
//                        Methods from Interface MIMEBodyPart
//
//===========================================================================

        /**
         * Returns Content-Type of this MIME Part. Always returns the string "Message".
         */
        public String getContentType ()
        {
                return (MESSAGE);
        }


	/**
         * Sets Content-Transfer-Encoding of this MIME Part.
	 * Only BINARY, E7BIT, and E8BIT are applicable to MIMEMessagePart.
	 * Additionally, for message/partial and message/external-body, the MIME
	 * standard requires E7BIT encoding. It is possible to
         * set encoding prior to setting the contentSubType.
         * If a different value is specified, no error occurs; instead,
         * this method will be overridden by the putByteStream() method.
         * @param encoding Value that represents the encoding.
         * @see MIMEBodyPart#BINARY
         * @see MIMEBodyPart#E7BIT
         * @see MIMEBodyPart#E8BIT
	 * @exception MIMEException If the value is not one of BINARY, E7BIT or E8BIT
         */
	public void setContentEncoding (int encoding) throws MIMEException
        {
	    	switch (encoding)
	    	{
		    case BINARY:
		    case E7BIT:
		    case E8BIT:
			m_contentTransferEncoding = encoding;
			break;
		    default:
			 throw new MIMEException ("Invalid/Inapplicable  Content-Transfer-Encoding: " + encoding);
              }
        }

	/**
         * Returns value of Content-Transfer-Encoding of this MIME Part. -1 if none present.
         * @see MIMEBodyPart#BINARY
         * @see MIMEBodyPart#E7BIT
         * @see MIMEBodyPart#E8BIT
         */
	public int getContentEncoding ()
        {
		if (m_contentTransferEncoding ==  UNINITIALIZED)
		    return (-1);

		return (m_contentTransferEncoding);
        }

//===========================================================================
//
//                        Methods Specific to this Class alone.
//
//===========================================================================


        /**
         * Sets the specified message as the body of this part.
         * @param msg Message to be set as body of this part.
         * @exception MIMEException If already set (or) can not be set.
         */
        protected void setMessage (MIMEMessage msg) throws MIMEException
        {
            setMessage (msg, true);
        }


	/**
	* Sets the specified message as the body of this part.
	* @param msg Message to be set as body of this part.
	* @param clone If false stores reference to passed object instead of cloning a copy.
	* @exception MIMEException If already set (or) can not be set.
	*/
        public void setMessage (MIMEMessage msg, boolean clone) throws MIMEException
        {
		if (m_theMessage != null)
                    throw new MIMEException ("setMessage(): already set!");

		if (msg != null)
		{
		     try
		     {
		          m_theMessage = (MIMEMessage) (clone ? msg.clone() : msg);
		     }
		     catch (CloneNotSupportedException e)
		     {
                          throw new MIMEException (e.getMessage());
		     }
                }
		else
                     throw new MIMEException ("setMessage(): null message passed." );
        }

	/**
	* Returns the MIMEMessage that is the Body of this part.
	* @exception MIMEException If no Message exists in body-data.
	*/
	protected MIMEMessage getMessage () throws MIMEException
        {
            return getMessage (true);
        }


        /**
         * Returns the MIMEMessage that is the Body of this part.
         * @param clone If false returns reference to the object instead of a cloned copy.
         * @exception MIMEException If no Message exists in body-data.
         */
        public MIMEMessage getMessage (boolean clone) throws MIMEException
        {
		if (m_theMessage == null)
                    throw new MIMEException ("getMessage(): null Message in body!");

		try
		{
                     MIMEMessage l_msg = (MIMEMessage) (clone ? m_theMessage.clone() : m_theMessage);
		     return (l_msg);
		}
		catch (CloneNotSupportedException e)
		{
                     throw new MIMEException (e.getMessage());
		}
        }

	/**
	* Deletes the MIMEMessage that is the Body of this part.
	* Noop if no body was ever set.
	*/
        public void deleteMessage ()
        {
		m_theMessage = null;
        }

	/**
	* Sets the headers associated with External Body, for external-body content-subtypes
	* only. If already set, these headers are
	* ignored for other subtypes (rfc822 and message-partial).
	* @param name Name of the header field. Should not include ':'
	* @param value Value of the header field to be added.
	* @exception MIMEException if either of name or value is null
	*/
        public void setExtBodyHeader (String name, String value) throws MIMEException
        {
	     if ((name == null) || (value == null))
                 throw new MIMEException ("Invalid  NULL Header name or value");

             if ( name.indexOf (':') > -1)
                 throw new MIMEException ("Invalid  ':' in Header name");

	     if (m_ExtBodyHeadersTable == null)
                 m_ExtBodyHeadersTable = new Hashtable();

             String l_hashKey = name.toLowerCase();  // Keys are always lower case
             Header l_hdr = new Header (name, value);
             m_ExtBodyHeadersTable.put (l_hashKey, l_hdr);
        }

	/**
	* Returns the value of the specified header associated with External Body.
	* Applicable to external-body subtypes only.
	* Returns NULL if the header is not present.
	* @param name Name of the header field.
	* @exception MIMEException If name passed is a NULL.
	*/
        public String getExtBodyHeader (String name) throws MIMEException
        {
	     if (name == null)
                 throw new MIMEException ("Invalid  NULL Header name");

	     if (m_ExtBodyHeadersTable == null)
                  return (null);

             String l_hashKey = name.toLowerCase();  // Keys are always lower case
             Header l_hdr = (Header) m_ExtBodyHeadersTable.get (l_hashKey);

             if (l_hdr != null)
                return (l_hdr.getValue()); // getValue() returns a new string.
             else
                  return (null);
        }


	/**
         * Returns all the headers in this Part as an array of Header objects.
	 * @exception MIMEException If no headers exist.
         */
	public Header[] getAllHeaders () throws MIMEException
        {
		int l_numElements = 0, i = 0;

		if (m_ExtBodyHeadersTable != null)
			l_numElements = m_ExtBodyHeadersTable.size();

		if (l_numElements <= 0)
		   return (null);

		Header[] hdrs = new Header [l_numElements];
		Enumeration he = m_ExtBodyHeadersTable.elements();

		while (he.hasMoreElements())
		{
			hdrs[i++] = (Header) he.nextElement();
		}

                return (hdrs);
        }


	/**
	* Sets the message partial. Stores reference to the supplied inputStream.
	* Does not copy data. The data in partial is copied as is by putByteStream().
        * To be used with message/partial subtypes only. It is an error to setPartial()
	* for other subtypes (rfc822 and external-body).
        * @param instream inputStream that supplies the data for this partial.
        * @exception MIMEException if content-subtype is not partial or if already set.
        * @see deletePartial
        */
        protected void setPartial (InputStream instream) throws MIMEException
        {
		if (m_partial != null)
                 	throw new MIMEException ("Partial already set.");

		if ((m_contentSubType != null) &&
		    (!m_contentSubType.equalsIgnoreCase("partial")))
                 	throw new MIMEException ("Content-Subtype partial expected.");

		m_partial = instream;

	}

	/**
	* Deletes the message partial data of this part. Ignores if partial was not set.
        * @see setPartial
        */
        protected void deletePartial ()
        {
		m_partial = null;
	}

        /**
         * Outputs a byte-stream for this part with its MIME part headers and encoded
         * body data.
         * @param os OutputStream to write to.
         * @exception IOException If an IO error occurs.
         * @exception MIMEException If encoding error is detected.
         */
	public void putByteStream (OutputStream os) throws IOException, MIMEException
        {
		boolean l_external_body  = false;
		boolean l_message_partial = false;
	        String l_contentSubType = null;
		if (m_contentSubType != null)
 			l_contentSubType = m_contentSubType.trim();

		if ((l_contentSubType != null) &&
			 (l_contentSubType.equalsIgnoreCase("external-body")))
		{
		        l_external_body = true;

			if (m_ExtBodyHeadersTable == null);
			    //throw new MIMEException ("External body header Content-Id required for external-body");
		}

		if ((l_contentSubType != null) &&
			 (l_contentSubType.equalsIgnoreCase("partial")))
		{
		        l_message_partial = true;

			if (m_partial == null)
			    throw new MIMEException ("Message partial not set");

			if (m_contentTransferEncoding != E7BIT && m_contentTransferEncoding != UNINITIALIZED)
                     		throw new MIMEException ("Bad content-encoding for message/partial");

		}

		if ((m_theMessage == null) && (!l_external_body) && (!l_message_partial))
		{
                     throw new MIMEException ("putByteStream(): null Message body!");
		}

		// Write out the headers first
             	StringBuffer l_hdrbuf = new StringBuffer (HDRBUFSZ);
             	byte[]       l_bytebuf;

		// content-type
		l_hdrbuf.append ("Content-Type: message/");

		if (m_contentSubType != null)
		     l_hdrbuf.append (m_contentSubType);
		else
		{
		     if (m_theMessage != null)
			  l_hdrbuf.append ("rfc822"); // default
		     else
		          throw new MIMEException ("putByteStream(): No content-subtype." );
		}

		if (m_contentTypeParams != null && m_contentTypeParams.length() > 0)
		{
                     l_hdrbuf.append ("; ");
                     l_hdrbuf.append (m_contentTypeParams);
		}

        	l_hdrbuf.append ("\r\n");

		// write the header out to os
		try
		{
		     //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1");
		     l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString());
		     os.write (l_bytebuf);
		}
		catch (Exception e)
		{
		     throw new MIMEException ("Content-Type: " + e.getMessage());
		}

		// contentID
		if (m_contentID != null)
		{
		     l_hdrbuf.setLength (0);
		     l_hdrbuf.append ("Content-ID: " + m_contentID);
		     l_hdrbuf.append ("\r\n");

		     try
		     {
		         // l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1");
		          l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString());
		          os.write (l_bytebuf);
		     }
		     catch (Exception e)
		     {
		          throw new MIMEException ("Content-ID: " + e.getMessage());
		     }
		}

		// contentDisposition
		if (m_contentDisposition != UNINITIALIZED)
		{
		     l_hdrbuf.setLength (0);
		     l_hdrbuf.append ("Content-Disposition: " +
                                      m_stringDisposition [m_contentDisposition]);

		     if (m_contentDispParams != null)
		     {
		          l_hdrbuf.append ("; ");
		          l_hdrbuf.append (m_contentDispParams);
		     }

		     l_hdrbuf.append ("\r\n");

		     try
		     {
		          //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1");
		          l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString());
		          os.write (l_bytebuf);
		     }
		     catch (Exception e)
		     {
		          throw new MIMEException ("Content-Disposition: " + e.getMessage());
		     }
		}

		// contentDescription
		if (m_contentDescription != null)
		{
		     l_hdrbuf.setLength (0);
		     l_hdrbuf.append ("Content-Description: " + m_contentDescription);
		     l_hdrbuf.append ("\r\n");

		     try
		     {
		          //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1");
		          l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString());
		          os.write (l_bytebuf);
		     }
		     catch (Exception e)
		     {
		          throw new MIMEException ("Content-Description: " + e.getMessage());
		     }
		}

		// Content-transfer-encoding
		//if (m_contentTransferEncoding == UNINITIALIZED)
		//{
                //   m_contentTransferEncoding = E7BIT; // default
                //}

		if (m_contentSubType != null)
		{
 		    if 	((l_message_partial || l_external_body) && m_contentTransferEncoding != UNINITIALIZED)
                          m_contentTransferEncoding = E7BIT; // As mandated by MIME standard
                }

		//if (!l_external_body) // for external-body we write it later
		{
		    if (m_contentTransferEncoding != UNINITIALIZED)
		    {
			if (l_external_body)
			       m_contentTransferEncoding = E7BIT;

			l_hdrbuf.setLength (0);
			l_hdrbuf.append ("Content-Transfer-Encoding: " +
                                 m_stringEncoding [m_contentTransferEncoding]);
			l_hdrbuf.append ("\r\n");

			try
			{
                     		// l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1");
                     		l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString());
                     		os.write (l_bytebuf);
			}
			catch (Exception e)
			{
				throw new MIMEException ("Content-Transfer-Encoding: " + e.getMessage());
			}
		    }
		}


		// blank-line after headers
		//l_bytebuf = new String("\r\n").getBytes("iso-8859-1");
		os.write (CRLF);

		// write out the message part if it is not external-body
		if ((m_theMessage != null) && (!l_external_body) && (!l_message_partial))
		     m_theMessage.putByteStream(os);
		else if (l_external_body)
		{
		    //os.write (CRLF); // Prior to the headers for the external-body

		    if (m_ExtBodyHeadersTable != null)
		    {
			Enumeration he = m_ExtBodyHeadersTable.elements();

			while (he.hasMoreElements())
			{
			     Header l_tmphdr = (Header) he.nextElement();

			     try
                    	     {
                                   l_bytebuf = MIMEHelper.unicodeToASCII (l_tmphdr.getLine());
                                   os.write (l_bytebuf);
                              }
                              catch (Exception e)
                              {
                                  throw new MIMEException (l_tmphdr.getLine() + e.getMessage());
                              }

			} // while

		    //if (m_contentTransferEncoding != UNINITIALIZED)
		    //{
		    //	// write out content-encoding
		    //	l_hdrbuf.setLength (0);
		    //	l_hdrbuf.append ("Content-Transfer-Encoding: " +
                    //              m_stringEncoding [m_contentTransferEncoding]);
		   //	l_hdrbuf.append ("\r\n");
   		   //
		   //	try
		   //	{
                   //  		// l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1");
                   //  		l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString());
                   // 		os.write (l_bytebuf);
		   //	}
		   //	catch (Exception e)
		   //	{
		   //		throw new MIMEException ("Content-Transfer-Encoding: " + e.getMessage());
		   //	}
                   //   }

		        os.write (CRLF); // After the headers for the external-body

		    } // if m_ExtBodyHeadersTable

		} // if l_external_body
		else if (l_message_partial)
		{
		    os.write (CRLF);
		    byte l_buf [] = new byte [DATABUFSZ];
		    int l_len;

		    while ( (l_len = m_partial.read (l_buf, 0, DATABUFSZ)) > 0)
		    {
			os.write (l_buf, 0, l_len);
		    }

		} // if l_external_body

		os.write (CRLF); // terminate the part

        } // end putByteStream()

        /**
         * Clones an instance of this MIMEMessagePart object.
         * @exception CloneNotSupportedException If thrown by a constituent components.
         */
        public Object clone () throws CloneNotSupportedException
        {
    		MIMEMessagePart l_theClone = (MIMEMessagePart) super.clone();

    		if (m_theMessage != null)
    		{
    			l_theClone.m_theMessage = (MIMEMessage) m_theMessage.clone();
    		}

    		if (m_contentSubType != null)
                         l_theClone.m_contentSubType = m_contentSubType;

    		if (m_contentTypeParams != null)
                         l_theClone.m_contentTypeParams = m_contentTypeParams;

    		if (m_contentID != null)
                         l_theClone.m_contentID = m_contentID;

    		if (m_contentDispParams != null)
                         l_theClone.m_contentDispParams = m_contentDispParams;

    		if (m_contentDescription != null)
                         l_theClone.m_contentDescription = m_contentDescription;

            	l_theClone.m_UserObject = m_UserObject;

		return (l_theClone);
	}
}
