/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.email.server.impl.subetha;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.mail.internet.MimeMessage.RecipientType;

import org.alfresco.service.cmr.email.EmailMessage;
import org.alfresco.service.cmr.email.EmailMessageException;
import org.alfresco.service.cmr.email.EmailMessagePart;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/* 
 * TODO There's a lot of metadata extraction going on in this class that 
 * is duplicated by RFC822MetadataExtracter
 */

/**
 * Concrete representation of an email message as implemented for the SubEtha mail server.
 * 
 * @since 2.2
 */

public class SubethaEmailMessage implements EmailMessage
{
    private static final String ERR_FAILED_TO_CREATE_MIME_MESSAGE = "email.server.err.failed_to_create_mime_message";
    private static final String ERR_EXTRACTING_FROM_ADDRESS = "email.server.err.extracting_from_address";
    private static final String ERR_NO_FROM_ADDRESS = "email.server.err.no_from_address";
    private static final String ERR_EXTRACTING_TO_ADDRESS = "email.server.err.extracting_to_address";
    private static final String ERR_NO_TO_ADDRESS = "email.server.err.no_to_address";
    private static final String ERR_EXTRACTING_SUBJECT = "email.server.err.extracting_subject";
    private static final String ERR_EXTRACTING_SENT_DATE = "email.server.err.extracting_sent_date";
    private static final String ERR_PARSE_MESSAGE = "email.server.err.parse_message";
    
    private static final long serialVersionUID = -3735187524926395261L;

    private static final Log log = LogFactory.getLog(SubethaEmailMessage.class);

    private static final String MIME_PLAIN_TEXT = "text/plain";
    private static final String MIME_HTML_TEXT = "text/html";
    private static final String MIME_XML_TEXT = "text/xml";
    private static final String MIME_APPLICATION = "application/*";
    private static final String MIME_IMAGE = "image/*";
    private static final String MIME_MULTIPART = "multipart/*";
    private static final String MIME_RFC822 = "message/rfc822";
    private static final String FILENAME_ATTACHMENT_PREFIX = "Attachment";

    private String from;
    private String to;
    private String subject;
    private List<String> cc;
    private Date sentDate;
    private EmailMessagePart body;
    private EmailMessagePart[] attachments;
    transient private int bodyNumber = 0;
    transient private int attachmentNumber = 0;
    transient private List<EmailMessagePart> attachmentList = new LinkedList<EmailMessagePart>();

    protected SubethaEmailMessage()
    {
        super();
    }

    public SubethaEmailMessage(MimeMessage mimeMessage)
    {
        processMimeMessage(mimeMessage);
    }

    public SubethaEmailMessage(InputStream dataInputStream)
    {
        MimeMessage mimeMessage = null;
        try
        {
            mimeMessage = new MimeMessage(Session.getDefaultInstance(System.getProperties()), dataInputStream);
        }
        catch (MessagingException e)
        {
            throw new EmailMessageException(ERR_FAILED_TO_CREATE_MIME_MESSAGE, e.getMessage());
        }

        processMimeMessage(mimeMessage);
    }

    private void processMimeMessage(MimeMessage mimeMessage)
    {
        if (from == null)
        {
            Address[] addresses = null;
            try
            {
                addresses = mimeMessage.getFrom();
            }
            catch (MessagingException e)
            {
                throw new EmailMessageException(ERR_EXTRACTING_FROM_ADDRESS, e.getMessage());
            }
            if (addresses == null || addresses.length == 0)
            {
                //throw new EmailMessageException(ERR_NO_FROM_ADDRESS);
            }
            else
            {
                if(addresses[0] instanceof InternetAddress)
                {
                    from = ((InternetAddress)addresses[0]).getAddress();
                }
                else
                {
                    from = addresses[0].toString();
                }
            }
        }

        if (to == null)
        {
            Address[] addresses = null;
            try
            {
                addresses = mimeMessage.getAllRecipients();
            }
            catch (MessagingException e)
            {
                throw new EmailMessageException(ERR_EXTRACTING_TO_ADDRESS, e.getMessage());
            }
            if (addresses == null || addresses.length == 0)
            {
                //throw new EmailMessageException(ERR_NO_TO_ADDRESS);
            }
            else
            {
                if(addresses[0] instanceof InternetAddress)
                {
                    to = ((InternetAddress)addresses[0]).getAddress();
                }
                else
                {
                    to = addresses[0].toString();
                }
            }
        }
        
        if (cc == null)
        {
            try
            {
                ArrayList<String> list = new ArrayList<String>();
            
                Address[] cca = mimeMessage.getRecipients(RecipientType.CC);
            
                if(cca != null)
                {
                    for(Address a : cca)
                    {
                        list.add(a.toString());
                    }
                }
                cc = list;
            }
            catch (MessagingException e)
            {
                // Do nothing - this is not a show-stopper.
                cc = null;
            }
        }
            
        try
        {
            subject = mimeMessage.getSubject();
            //subject = encodeSubject(mimeMessage.getSubject());
        }
        catch (MessagingException e)
        {
            throw new EmailMessageException(ERR_EXTRACTING_SUBJECT, e.getMessage());
        }
        //if (subject == null)
        //{
        //    subject = ""; // Just anti-null stub :)
        //}

        try
        {
            sentDate = mimeMessage.getSentDate();
        }
        catch (MessagingException e)
        {
            throw new EmailMessageException(ERR_EXTRACTING_SENT_DATE, e.getMessage());
        }
        if (sentDate == null)
        {
            sentDate = new Date(); // Just anti-null stub :)
        }

        parseMessagePart(mimeMessage);
        attachments = new EmailMessagePart[attachmentList.size()];
        attachmentList.toArray(attachments);
        attachmentList = null;
    }

    private void parseMessagePart(Part messagePart)
    {
        try
        {
            if (messagePart.isMimeType(MIME_PLAIN_TEXT) || messagePart.isMimeType(MIME_HTML_TEXT))
            {
                if (log.isDebugEnabled())
                {
                    log.debug("Text or HTML part was found. ContentType: " + messagePart.getContentType());
                }
                addBody(messagePart);
            }
            else if (messagePart.isMimeType(MIME_XML_TEXT))
            {
                if (log.isDebugEnabled())
                {
                    log.debug("XML part was found.");
                }
                addAttachment(messagePart);
            }
            else if (messagePart.isMimeType(MIME_APPLICATION))
            {
                if (log.isDebugEnabled())
                {
                    log.debug("Application part was found.");
                }
                addAttachment(messagePart);
            }
            else if (messagePart.isMimeType(MIME_IMAGE))
            {
                if (log.isDebugEnabled())
                {
                    log.debug("Image part was found.");
                }
                addAttachment(messagePart);
            }
            else if (messagePart.isMimeType(MIME_MULTIPART))
            {
                // if multipart, this method will be called recursively
                // for each of its parts
                Multipart mp = (Multipart) messagePart.getContent();
                int count = mp.getCount();

                if (log.isDebugEnabled())
                {
                    log.debug("MULTIPART with " + count + " part(s) found. Processin each part...");
                }
                for (int i = 0; i < count; i++)
                {
                    BodyPart bp = mp.getBodyPart(i);
                    if (bp.getContent() instanceof MimeMultipart)
                    {
                        // It's multipart.  Recurse.
                        parseMessagePart(bp);
                    }
                    else
                    {
                        // It's the body
                        addBody(bp);
                    }
                }

                if (log.isDebugEnabled())
                {
                    log.debug("MULTIPART processed.");
                }

            }
            else if (messagePart.isMimeType(MIME_RFC822))
            {
                // if rfc822, call this method with its content as the part
                if (log.isDebugEnabled())
                {
                    log.debug("MIME_RFC822 part found. Processing inside part...");
                }

                parseMessagePart((Part) messagePart.getContent());

                if (log.isDebugEnabled())
                {
                    log.debug("MIME_RFC822 processed.");
                }

            }
            else
            {
                // if all else fails, put this in the attachments map.
                // Actually we don't know what it is.
                if (log.isDebugEnabled())
                {
                    log.debug("Unrecognized part was found. Put it into attachments.");
                }
                addAttachment(messagePart);
            }
        }
        catch (IOException e)
        {
            throw new EmailMessageException(ERR_PARSE_MESSAGE, e.getMessage());
        }
        catch (MessagingException e)
        {
            throw new EmailMessageException(ERR_PARSE_MESSAGE, e.getMessage());
        }
    }

    private void addBody(Part messagePart) throws MessagingException
    {
        if (body != null)
        {
            attachmentList.add(new SubethaEmailMessagePart(messagePart, getPartFileName(getSubject() + " (part " + ++bodyNumber + ")", messagePart)));
            if (log.isInfoEnabled())
            {
                log.info(String.format("Attachment \"%s\" has been added.", attachmentList.get(attachmentList.size() - 1).getFileName()));
            }
        }
        else
        {
            body = new SubethaEmailMessagePart(messagePart, getPartFileName(getSubject(), messagePart));
            if (log.isDebugEnabled())
            {
                log.debug("Body has been added.");
            }
        }

    }

    /**
     * Method adds a message part to the attachments list
     * 
     * @param messagePart A part of message
     * @throws EmailMessageException
     * @throws MessagingException
     */
    private void addAttachment(Part messagePart) throws MessagingException
    {
        String fileName = getPartFileName(FILENAME_ATTACHMENT_PREFIX + attachmentNumber, messagePart);
        attachmentList.add(new SubethaEmailMessagePart(messagePart, fileName));
        if (log.isDebugEnabled())
        {
            log.debug("Attachment added: " + fileName);
        }
    }

    /**
     * Method extracts file name from a message part for saving its as aa attachment. If the file name can't be extracted, it will be generated based on defaultPrefix parameter.
     * 
     * @param defaultPrefix This prefix fill be used for generating file name.
     * @param messagePart A part of message
     * @return File name.
     * @throws MessagingException
     */
    private String getPartFileName(String defaultPrefix, Part messagePart) throws MessagingException
    {
        String fileName = messagePart.getFileName();
        if (fileName != null)
        {
            try
            {
                fileName = MimeUtility.decodeText(fileName);
            }
            catch (UnsupportedEncodingException ex)
            {
                // Nothing to do :)
            }
        }
        else
        {
            fileName = defaultPrefix;
            if (messagePart.isMimeType(MIME_PLAIN_TEXT))
                fileName += ".txt";
            else if (messagePart.isMimeType(MIME_HTML_TEXT))
                fileName += ".html";
            else if (messagePart.isMimeType(MIME_XML_TEXT))
                fileName += ".xml";
            else if (messagePart.isMimeType(MIME_IMAGE))
                fileName += ".gif";
        }
        return fileName;
    }

    public void setRmiRegistry(String rmiRegistryHost, int rmiRegistryPort)
    {
        if (body instanceof SubethaEmailMessagePart) 
        {
            ((SubethaEmailMessagePart) body).setRmiRegistry(rmiRegistryHost, rmiRegistryPort);
        }
        
        for (EmailMessagePart attachment : attachments)
        {
            if (attachment instanceof SubethaEmailMessagePart)
            {
                ((SubethaEmailMessagePart) attachment).setRmiRegistry(rmiRegistryHost, rmiRegistryPort);
            }
        }
    }
    
    public List<String> getCC()
    {
        return cc;
    }
    
    
    public String getFrom()
    {
        return from;
    }

    public String getTo()
    {
        return to;
    }

    public Date getSentDate()
    {
        return sentDate;
    }

    public String getSubject()
    {
        return subject;
    }

    public EmailMessagePart getBody()
    {
        return body;
    }

    public EmailMessagePart[] getAttachments()
    {
        return attachments;
    }

}
