/*
* Copyright 2014 - LAit SpA
*				 Realizzato da Icona Management srl su commessa LAit SpA
* Copyright 2015- LAit SpA
*                                                  Aggiornato da LAit SpA
*
* Concesso in licenza a norma dell'EUPL, versione 1.1 o
* successive dell'EUPL (la "Licenza")– non appena saranno
* approvate dalla Commissione europea;
* Non è possibile utilizzare l'opera salvo nel rispetto della Licenza.
* È possibile ottenere una copia della Licenza al seguente indirizzo:
*
* http://ec.europa.eu/idabc/eupl
*
* Salvo diversamente indicato dalla legge applicabile o
* concordato per iscritto, il software distribuito secondo
* i termini della Licenza è distribuito "TAL QUALE",
* SENZA GARANZIE O CONDIZIONI DI ALCUN TIPO,
* esplicite o implicite.
* Si veda la Licenza per la lingua specifica che disciplina
* le autorizzazioni e le limitazioni secondo i termini della
* Licenza.
*/
package it.laitspa.cpf.util.mail;

import it.laitspa.cpf.util.log.LogUtils;
import it.laitspa.cpf.util.misc.JndiPropertiesLoader;
import it.laitspa.cpf.util.misc.JndiServiceLocator;
import it.laitspa.cpf.util.misc.PropertiesLoader;

import java.io.StringWriter;
import java.util.ArrayList;

import javax.activation.DataHandler;
import javax.mail.BodyPart;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.log4j.Logger;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.tools.generic.DateTool;
import org.apache.velocity.tools.generic.EscapeTool;
import org.apache.velocity.tools.generic.MathTool;
import org.apache.velocity.tools.generic.NumberTool;
import org.bouncycastle.mail.smime.SMIMESigned;
import org.w3c.dom.Document;

import com.sun.mail.util.BASE64DecoderStream;

public class MailUtils
{
  protected Logger          logger = Logger.getLogger(this.getClass());

  protected String            subject;
  protected String            template;
  protected VelocityContext   context;
  protected String            from;
  protected ArrayList<String> to;
  protected ArrayList<String> cc;
  protected ArrayList<String> bcc;

  protected Session           session;

  public MailUtils()
  {
    to = new ArrayList<String>();
    cc = new ArrayList<String>();
    bcc = new ArrayList<String>();

    context = new VelocityContext();

    JndiPropertiesLoader pl = new JndiPropertiesLoader("/it/laitspa/cpf/util/mail/mail.properties");

    // Load properties from JNDI
    //
    pl.loadJndiProperty("mail.debug");

    // Added to manage different SMTP Behavior
    //
    pl.loadJndiProperty("x.mail.smtp.user");
    pl.loadJndiProperty("x.mail.smtp.password");
    pl.loadJndiProperty("mail.smtp.domain");
    pl.loadJndiProperty("mail.smtp.host");

    if(pl.getString("mail.smtp.domain") != null)
    {
      context.put("from", "fatturazione@" + pl.getString("mail.smtp.domain"));
    }
    /*
     * PropertiesLoader pl = new PropertiesLoader(
     * "/it/laitspa/cpf/util/mail/mail.properties");
     */

    // Check for Authentication.
    //
    AuthenticatorSmtp auth = null;
    if(pl.getString("x.mail.smtp.user") != null)
    {
      auth =
          new AuthenticatorSmtp(pl.getString("x.mail.smtp.user"),
              pl.getString("x.mail.smtp.password"));
      pl.addProperty("mail.smtp.auth", "true");

    }
    
    try
    {
      Context initCtx = new InitialContext();
      session = (Session) initCtx.lookup("java:comp/env/mail/Session");
    }
    catch(NamingException e)
    {
      LogUtils.error(getClass(), "JavaMail session not available.", e);
    }
  }

  public Session getSession()
  {
    return session;
  }

  public void setSession(Session session)
  {
    this.session = session;
  }

  public String getSubject()
  {
    return subject;
  }

  public void setSubject(String subject)
  {
    this.subject = subject;
  }

  public String getTemplate()
  {
    return template;
  }

  public void setTemplate(String template)
  {
    this.template = template;
  }

  public VelocityContext getContext()
  {
    return context;
  }

  public void setContext(VelocityContext context)
  {
    this.context = context;
  }

  public String getFrom()
  {
    return from;
  }

  public void setFrom(String from)
  {
    this.from = from;
  }

  public ArrayList<String> getTo()
  {
    return to;
  }

  public ArrayList<String> getCc()
  {
    return cc;
  }

  public ArrayList<String> getBcc()
  {
    return bcc;
  }

  public void addTo(String recipient)
  {
    to.add(recipient);
  }

  public void addCc(String recipient)
  {
    cc.add(recipient);
  }

  public void addBcc(String recipient)
  {
    bcc.add(recipient);
  }

  public Message buildMessage()
  {
    // Create velocity engine.
    //
    VelocityEngine ve = getEngine();

    try
    {
      // Create message skeleton.
      //
      Message message = new MimeMessage(session);

      // Load property file from classpath
      // and initialize velocity.
      //
      PropertiesLoader pl = new PropertiesLoader("/velocity.properties");
      ve.init(pl.getProperties());

      // Register tools with velocity context.
      //
      context.put("date", new DateTool());
      context.put("math", new MathTool());
      context.put("number", new NumberTool());
      context.put("esc", new EscapeTool());

      // Get plain text template. At least the plain template must be present.
      //
      String template_name = "/templates/mail/" + template + ".plain.vm";
      if(!ve.resourceExists(template_name))
        return null;
      Template plain = ve.getTemplate(template_name);

      // Get html template. This could also not be present.
      //
      Template html = null;
      template_name = "/templates/mail/" + template + ".html.vm";
      if(ve.resourceExists(template_name))
        html = ve.getTemplate(template_name);

      // Create outer multipart.
      //
      MimeMultipart root = new MimeMultipart("mixed");

      // Create multipart MIME encoding (text and html).
      //
      MimeMultipart content = new MimeMultipart("alternative");

      MimeBodyPart mbp = new MimeBodyPart();
      mbp.setContent(content);
      root.addBodyPart(mbp);

      // Merge plain template and set MIME part.
      //
      StringWriter sw = new StringWriter();
      plain.merge(context, sw);
      BodyPart bp1 = new MimeBodyPart();
      bp1.setContent(sw.toString(), "text/plain");
      content.addBodyPart(bp1);

      // Merge html template and set MIME part.
      //
      if(html != null)
      {
        sw = new StringWriter();
        html.merge(context, sw);
        BodyPart bp2 = new MimeBodyPart();
        bp2.setContent(sw.toString(), "text/html");
        content.addBodyPart(bp2);
      }

      // Set message content.
      //
      message.setContent(root);

      // Set "from" address. Local member variable overrides template
      // settings.
      //
      String s_from = session.getProperty("mail.smtp.from");
      InternetAddress addressFrom = null;
      if(from != null && from.trim().length() > 0)
        addressFrom = new InternetAddress(from);
      else if (s_from != null)
        addressFrom = new InternetAddress(s_from);
      else
        addressFrom = new InternetAddress((String)context.get("from"));
      message.setFrom(addressFrom);
      

      // Set "to" addresses. Local member variable is appended to
      // template settings.
      //
      ArrayList<InternetAddress> list = new ArrayList<InternetAddress>();
      String[] tmp_array = getContextObjectAsArrayOfString(context, "to");
      if(tmp_array != null)
      {
        for(int i = 0; i < tmp_array.length; i++)
          list.add(new InternetAddress(tmp_array[i]));
      }
      if(to != null)
      {
        for(int i = 0; i < to.size(); i++)
          list.add(new InternetAddress((String)to.get(i)));
      }
      InternetAddress[] addressTo = new InternetAddress[list.size()];
      list.toArray(addressTo);
      message.setRecipients(Message.RecipientType.TO, addressTo);

      // Set "cc" addresses. Local member variable is appended to
      // template settings.
      //
      list = new ArrayList<InternetAddress>();
      tmp_array = getContextObjectAsArrayOfString(context, "cc");
      if(tmp_array != null)
      {
        for(int i = 0; i < tmp_array.length; i++)
          list.add(new InternetAddress(tmp_array[i]));
      }
      if(cc != null)
      {
        for(int i = 0; i < cc.size(); i++)
          list.add(new InternetAddress((String)cc.get(i)));
      }
      InternetAddress[] addressCc = new InternetAddress[list.size()];
      list.toArray(addressCc);
      message.setRecipients(Message.RecipientType.CC, addressCc);

      // Set "bcc" addresses. Local member variable is appended to
      // template settings.
      //
      list = new ArrayList<InternetAddress>();
      tmp_array = getContextObjectAsArrayOfString(context, "bcc");
      if(tmp_array != null)
      {
        for(int i = 0; i < tmp_array.length; i++)
          list.add(new InternetAddress(tmp_array[i]));
      }
      if(bcc != null)
      {
        for(int i = 0; i < bcc.size(); i++)
          list.add(new InternetAddress((String)bcc.get(i)));
      }
      InternetAddress[] addressBcc = new InternetAddress[list.size()];
      list.toArray(addressBcc);
      message.setRecipients(Message.RecipientType.BCC, addressBcc);

      // Set message "subject". Local member variable if set overrides
      // template settings.
      //
      if(subject != null)
        message.setSubject(subject);
      else
        message.setSubject((String)context.get("subject"));

      return message;
    }
    catch(Exception e)
    {
      logger.error("Si è verificata un'eccezione in fase di composizione del messaggio.", e);
      return null;
    }
  }
  
  public Message buildMessageWithAttachment(byte[] data, String attachment)
  {
    Message message = buildMessage();
    
    if(data == null)
      return message;
    
    // create the second message part
    //
    try
    {
      MimeBodyPart mbp2 = makeBodyPart(data, "application/zip", attachment);
      // attach the file to the message
      //
      MimeMultipart content = (MimeMultipart) message.getContent();
      content.addBodyPart(mbp2);
    }
    catch (Exception e)
    {
      logger.error("Unexpected error caught while building attachment for message "+ message, e);
    }
    return message;
  }

  protected String[] getContextObjectAsArrayOfString(VelocityContext vc, String key)
  {
    Object o = context.get(key);

    if(o instanceof String)
    {
      String[] ret = new String[1];
      ret[0] = (String)o;
      return ret;
    }
    else if(o instanceof String[])
    {
      return (String[])o;
    }
    else if(o instanceof ArrayList)
    {
      @SuppressWarnings("unchecked")
      ArrayList<String> list = (ArrayList<String>)o;
      String[] ret = new String[list.size()];
      list.toArray(ret);

      return ret;
    }

    return null;
  }

  public MimeBodyPart makeBodyPart(byte[] binary, String mimeType, String filename)
    throws MessagingException
  {
    MimeBodyPart bp = new MimeBodyPart();
    ByteArrayDataSource ds = new ByteArrayDataSource();
    ds.setContentType(mimeType);
    ds.setData(binary);
    bp.setDataHandler(new DataHandler(ds));
    bp.setFileName(filename);
    bp.setDisposition(Part.ATTACHMENT);

    return bp;
  }
  
  public String sendMessage(Message message)
    throws MessagingException
  {
    String user = this.getSession().getProperty("x.mail.smtps.user");
    String password = this.getSession().getProperty("x.mail.smtps.password");
    Transport t = this.getSession().getTransport("smtps");
    String id = null;
    try
    {
      t.connect(user, password);
      t.sendMessage(message, message.getAllRecipients());

      if(message instanceof MimeMessage)
        id = ((MimeMessage)message).getMessageID();
      
      t.close();
    }
    finally
    {
      if (t != null)
        t.close();
    }
    return id;
  }

  public Folder openInboxFolder()
  {
    try
    {
      String user = this.getSession().getProperty("x.mail.pop3s.user");
      String password = this.getSession().getProperty("x.mail.pop3s.password");

      Store store = session.getStore("pop3s");
      store.connect(user, password);

      Folder inbox = store.getDefaultFolder().getFolder("INBOX");
      inbox.open(Folder.READ_WRITE);

      return inbox;
    }
    catch(MessagingException e)
    {
      return null;
    }
  }

  public Folder openImapInboxFolder(String prefix)
  {
    try
    {
      String user = this.getSession().getProperty("x.mail.imaps." + prefix + ".user");
      String password = this.getSession().getProperty("x.mail.imaps." + prefix + ".password");

      Store store = session.getStore("imaps");
      store.connect(user, password);

      Folder def = store.getDefaultFolder();
      Folder inbox = def.getFolder("INBOX");
      inbox.open(Folder.READ_WRITE);

      return inbox;
    }
    catch(Exception e)
    {
      logger.error(e);
      return null;
    }
  }

  public Folder openImapMiscFolder(String prefix)
  {
    return openImapUserFolder(prefix, "Varie");
  }

  public Folder openImapProcessedFolder(String prefix)
  {
    return openImapUserFolder(prefix, "Caricati");
  }

  public Folder openImapInvalidFolder(String prefix)
  {
    return openImapUserFolder(prefix, "Non validi");
  }

  protected Folder openImapUserFolder(String prefix, String folder_name)
  {
    try
    {
      String user = this.getSession().getProperty("x.mail.imaps." + prefix + ".user");
      String password = this.getSession().getProperty("x.mail.imaps." + prefix + ".password");

      Store store = session.getStore("imaps");
      store.connect(user, password);

      Folder def = store.getDefaultFolder();
      Folder inbox = def.getFolder("INBOX");
      Folder folder = inbox.getFolder(folder_name);

      // Check if the folder exists. If not, create it.
      //
      if(!folder.exists())
        folder.create(Folder.HOLDS_MESSAGES);

      // Check if the folder is subscribed. If not, subscribe it.
      //
      if(!folder.isSubscribed())
        folder.setSubscribed(true);

      // Open the folder and return it.
      //
      folder.open(Folder.READ_WRITE);
      return folder;
    }
    catch(Exception e)
    {
      logger.error(e);
      return null;
    }
  }

  public Document getDatiCert(Message message)
  {
    try
    {
      if(!message.isMimeType("multipart/signed"))
        return null;

      SMIMESigned s = new SMIMESigned((MimeMultipart)message.getContent());
      MimeBodyPart content = s.getContent();
      Object cont = content.getContent();

      if(!(cont instanceof Multipart))
        return null;

      Multipart mp = (Multipart)cont;
      int count = mp.getCount();
      for(int i = 0; i < count; i++)
      {
        BodyPart m = mp.getBodyPart(i);
        Object part = m.getContent();

        if(part instanceof BASE64DecoderStream && m.getFileName().equalsIgnoreCase("daticert.xml"))
        {
          BASE64DecoderStream b64 = (BASE64DecoderStream)part;

          DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
          DocumentBuilder db = dbf.newDocumentBuilder();
          Document xml = db.parse(b64);

          return xml;
        }
      }

      return null;
    }
    catch(Exception e)
    {
      logger.error(e);
      return null;
    }
  }

  public void send(Message message)
    throws MessagingException
  {
    String user = this.getSession().getProperty("x.mail.smtp.user");
    String password = this.getSession().getProperty("x.mail.smtp.password");

    if(user != null && password != null)
    {
      Transport t = this.getSession().getTransport("smtp");
      t.connect(user, password);
      t.sendMessage(message, message.getAllRecipients());
      t.close();
    }
    else
      Transport.send(message);

  }
  
  public Template getHtmlTemplate(VelocityEngine ve)
  {
    // Get html template. This could also not be present.
    //
    Template html = null;
    String template_name = "/templates/mail/" + template + ".html.vm";
    if(ve.resourceExists(template_name))
      html = ve.getTemplate(template_name);
    
    return html;
  }
  
  public Template getPlainTemplate(VelocityEngine ve)
  {
    // Get plain text template. At least the plain template must be present.
    //
    String template_name = "/templates/mail/" + template + ".plain.vm";
    if(!ve.resourceExists(template_name))
      return null;
    Template plain = ve.getTemplate(template_name);
    return plain;
  }
  
  public VelocityEngine getEngine()
  {
    return new VelocityEngine();
  }

}