/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (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/MPL/>.
 * 
 * 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 Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2002 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.dynamo.mail;

import java.io.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.log4j.Logger;
import org.apache.velocity.*;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.*;
import org.apache.velocity.runtime.*;
import org.w3c.dom.*;
import com.silverwrist.util.*;
import com.silverwrist.util.xml.*;
import com.silverwrist.dynamo.Namespaces;
import com.silverwrist.dynamo.UserInfoNamespace;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;
import com.silverwrist.dynamo.velocity.VelocityRenderer;
import com.silverwrist.dynamo.velocity.VelocityDynamoResourceLoader;

public class MailSubSystem implements NamedObject, ComponentInitialize, ComponentShutdown, MailMessageProvider
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static final String LOGGING_CATEGORY_NAME = "velocity.mailformat";

  private static Logger vel_logger = Logger.getLogger(LOGGING_CATEGORY_NAME);
  private static Logger logger = Logger.getLogger(MailSubSystem.class);

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private String m_name;                                // subsystem name
  private MailProperties m_properties;                  // mail properties object
  private String m_template_prefix;                     // resource template prefix
  private VelocityEngine m_ve;                          // Velocity engine for formatting
  private ComponentShutdown m_runtime_hook;             // hook where we inserted MailMessageProvider

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  public MailSubSystem()
  {
    // Initialize our own Velocity engine.
    m_ve = new VelocityEngine();
    m_ve.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
		     "org.apache.velocity.runtime.log.SimpleLog4JLogSystem");
    m_ve.setProperty("runtime.log.logsystem.log4j.category",LOGGING_CATEGORY_NAME);
    m_ve.setProperty("resource.loader","dynamo");
    m_ve.setProperty("dynamo.loader.description","Dynamo Resource Loader");
    m_ve.setProperty("dynamo.resource.loader.class",VelocityDynamoResourceLoader.class.getName());
    m_ve.setProperty("velocimacro.permissions.allow.inline","true");
    m_ve.setProperty("velocimacro.permissions.allow.inline.to.replace.global","false");
    m_ve.setProperty("velocimacro.permissions.allow.inline.local.scope","true");

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal operations
   *--------------------------------------------------------------------------------
   */

  private static final String readObject(ObjectProvider userdata, String name, boolean required)
      throws MailException
  {
    try
    { // call through to the ObjectProvider
      return userdata.getObject(UserInfoNamespace.NAMESPACE,name).toString();

    } // end try
    catch (NoSuchObjectException e)
    { // not present - if it's required, we're screwed
      if (required)
      { // this is a problem!
	MailException me = new MailException(MailSubSystem.class,"MailMessages","userInfo.missing",e);
	me.setParameter(0,name);
	throw me;

      } // end if

      return null;

    } // end catch

  } // end readObject

  /*--------------------------------------------------------------------------------
   * Implementations from interface NamedObject
   *--------------------------------------------------------------------------------
   */

  public String getName()
  {
    return m_name;

  } // end getName

  /*--------------------------------------------------------------------------------
   * Implementations from interface ComponentInitialize
   *--------------------------------------------------------------------------------
   */

  public void initialize(Element config_root, ServiceProvider services) throws ConfigException
  {
    logger.info("MailSubSystem initializing");

    XMLLoader loader = XMLLoader.get();
    try
    { // verify the right node name
      loader.verifyNodeName(config_root,"object");

      // get the object's name
      m_name = loader.getAttribute(config_root,"name");

      // get the mail properties
      m_properties = new MailProperties(config_root,services);

      // get the template resource prefix
      DOMElementHelper config_root_h = new DOMElementHelper(config_root);
      String tmp = config_root_h.getSubElementText("template-resource-prefix");
      if (tmp==null)
      { // set up default prefix
	logger.debug("no template-resource-prefix element - defaulting");
	tmp = "/";

      } // end if
      else
      { // make sure the prefix is properly set
	if (!(tmp.startsWith("/")))
	  tmp = "/" + tmp;
	if (!(tmp.endsWith("/")))
	  tmp += "/";

      } // end else

      m_template_prefix = tmp;
      m_ve.setApplicationAttribute(VelocityRenderer.PREFIX_NAME,tmp);

    } // end try
    catch (XMLLoadException e)
    { // error loading XML config data
      throw new ConfigException(e);

    } // end catch

    // Get Dynamo's ResourceProvider and put it where our ResourceLoader can find it.
    ResourceProvider prov = (ResourceProvider)(services.queryService(ResourceProvider.class));
    m_ve.setApplicationAttribute(ResourceProvider.class.getName(),prov);

    // We're all set now, initialize Velocity!
    try
    { // call the init method
      m_ve.init();

    } // end try
    catch (Exception e)
    { // whoops!  the engine failed!
      logger.error("Velocity initialization failed",e);
      ConfigException ce = new ConfigException(MailSubSystem.class,"MailMessages","velocityInit.failure",e);
      ce.setParameter(0,e.getMessage());
      throw ce;

    } // end catch

    // Add the MailMessageProvider service to Dynamo's runtime services.
    HookServiceProviders hooker = (HookServiceProviders)(services.queryService(HookServiceProviders.class));
    SingletonServiceProvider ssp = new SingletonServiceProvider("MailSubSystem",MailMessageProvider.class,
								(MailMessageProvider)this);
    m_runtime_hook = hooker.hookRuntimeServiceProvider(ssp);

    logger.info("MailSubSystem initialization complete");

  } // end initialize

  /*--------------------------------------------------------------------------------
   * Implementations from interface ComponentShutdown
   *--------------------------------------------------------------------------------
   */

  public void shutdown()
  {
    logger.info("MailSubSystem shutting down");

    m_runtime_hook.shutdown();
    m_runtime_hook = null;

    m_properties.shutdown();
    m_properties = null;

    m_ve = null;

  } // end shutdown

  /*--------------------------------------------------------------------------------
   * Implementations from interface MailMessageProvider
   *--------------------------------------------------------------------------------
   */

  public MailMessage createSystemMessage(Request req)
  {
    MimeMessage msg = new MimeMessage(m_properties.getMailSession());
    m_properties.setupMailer(msg,req);

    try
    { // set the "From" address on this message
      msg.setFrom(m_properties.getSystemFromAddress());

    } // end try
    catch (MessagingException me)
    { // this does not make sense
      logger.error("MessagingException thrown in createSystemMessage() where it shouldn't be",me);

    } // end catch
    catch (UnsupportedEncodingException e)
    { // this does not make sense
      logger.error("UnsupportedEncodingException thrown in createSystemMessage() where it shouldn't be",e);

    } // end catch

    return new MailMessageImpl(this,msg,false);

  } // end createSystemMessage

  public MailMessage createUserMessage(Request req, ObjectProvider userdata) throws MailException
  {
    // Read in the data from the ObjectProvider.
    String user_addr = readObject(userdata,UserInfoNamespace.ATTR_EMAIL_ADDRESS,true);
    String user_fullname = readObject(userdata,UserInfoNamespace.ATTR_FULLNAME,false);
    String user_id = readObject(userdata,UserInfoNamespace.ATTR_ID,false);
    String user_name = readObject(userdata,UserInfoNamespace.ATTR_USERNAME,false);

    // Create the sender address.
    InternetAddress sender_addr = null;
    try
    { // parse the address
      sender_addr = new InternetAddress(user_addr,true);
      if (user_fullname!=null)
	sender_addr.setPersonal(user_fullname);

    } // end try
    catch (AddressException e)
    { // the address is not valid
      logger.error("sender address failed validation",e);
      MailException me = new MailException(MailSubSystem.class,"MailMessages","addr.invalid",e);
      me.setParameter(0,user_addr);
      throw me;

    } // end catch
    catch (UnsupportedEncodingException e)
    { // this does not make sense
      logger.error("UnsupportedEncodingException thrown in createUserMessage() where it shouldn't be",e);

    } // end catch

    // Set up the MimeMessage.
    MimeMessage msg = new MimeMessage(m_properties.getMailSession());
    m_properties.setupMailer(msg,req);
    m_properties.setupDisclaimer(msg);

    try
    { // set the "From" address on this message
      msg.setFrom(sender_addr);

      // also set the "sender" address
      msg.setSender(m_properties.getSystemFromAddress());

    } // end try
    catch (MessagingException me)
    { // this does not make sense
      logger.error("MessagingException thrown in createUserMessage() where it shouldn't be",me);

    } // end catch
    catch (UnsupportedEncodingException e)
    { // this does not make sense
      logger.error("UnsupportedEncodingException thrown in createUserMessage() where it shouldn't be",e);

    } // end catch

    // Create the info line.
    m_properties.setupUserInfo(msg,req,user_id,user_name);

    return new MailMessageImpl(this,msg,true);

  } // end createUserMessage

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  String formatStringTemplate(VelocityContext ctxt, String data) throws MailException
  {
    try
    { // format the template!
      StringWriter wr = new StringWriter();
      if (m_ve.evaluate(new VelocityContext(ctxt),wr,"MailSubSystem-dynamic",data))
	return wr.toString();
      throw new MailException(MailSubSystem.class,"MailMessages","velocity.stringFail");

    } // end try
    catch (ResourceNotFoundException e)
    { // turn this into a MailException
      throw new MailException(MailSubSystem.class,"MailMessages","velocity.resource2",e);

    } // end catch
    catch (ParseErrorException e)
    { // turn this into a MailException
      MailException me = new MailException(MailSubSystem.class,"MailMessages","velocity.parseError2",e);
      me.setParameter(0,e.getMessage());
      throw me;

    } // end catch
    catch (MethodInvocationException e)
    { // turn this into a MailException too
      Throwable inner = e.getWrappedThrowable();
      MailException me = new MailException(MailSubSystem.class,"MailMessages","velocity.methodInvoke2",inner);
      me.setParameter(0,inner.getClass().getName());
      me.setParameter(1,e.getMethodName());
      me.setParameter(2,e.getReferenceName());
      throw me;

    } // end catch
    catch (Exception e)
    { // Velocity can throw some weird shit - catch it, too
      logger.error("caught unhandled exception in string template",e);
      MailException me = new MailException(MailSubSystem.class,"MailMessages","velocity.unknownExcept2",e);
      me.setParameter(0,e.getMessage());
      throw me;

    } // end catch

  } // end formatStringTemplate

  String formatResourceTemplate(VelocityContext ctxt, String resource) throws MailException
  {
    try
    { // Load the Velocity template.
      Template templ = m_ve.getTemplate(resource);

      // Format the thing!
      StringWriter wr = new StringWriter();
      templ.merge(new VelocityContext(ctxt),wr);
      return wr.toString();

    } // end try
    catch (ResourceNotFoundException e)
    { // turn this into a MailException
      MailException me = new MailException(MailSubSystem.class,"MailMessages","velocity.resource",e);
      me.setParameter(0,"/" + resource);
      throw me;

    } // end catch
    catch (ParseErrorException e)
    { // turn this into a MailException
      MailException me = new MailException(MailSubSystem.class,"MailMessages","velocity.parseError",e);
      me.setParameter(0,e.getMessage());
      me.setParameter(1,"/" + resource);
      throw me;

    } // end catch
    catch (MethodInvocationException e)
    { // turn this into a MailException too
      Throwable inner = e.getWrappedThrowable();
      MailException me = new MailException(MailSubSystem.class,"MailMessages","velocity.methodInvoke",inner);
      me.setParameter(0,inner.getClass().getName());
      me.setParameter(1,e.getMethodName());
      me.setParameter(2,e.getReferenceName());
      me.setParameter(3,"/" + resource);
      throw me;

    } // end catch
    catch (Exception e)
    { // Velocity can throw some weird shit - catch it, too
      logger.error("caught unhandled exception in template",e);
      MailException me = new MailException(MailSubSystem.class,"MailMessages","velocity.unknownExcept",e);
      me.setParameter(0,e.getMessage());
      me.setParameter(1,"/" + resource);
      throw me;

    } // end catch

  } // end formatResourceTemplate

  String getUserInfoHeader()
  {
    return m_properties.getUserInfoHeader();

  } // end getUserInfoHeader

  String getSignature()
  {
    return m_properties.getSignature();

  } // end getSignature

  MimeMessage copyMessage(MimeMessage msg, boolean user)
  {
    MimeMessage rc = new MimeMessage(m_properties.getMailSession());
    m_properties.setupMailer(rc);
    if (user)
    { // set up user-related stuff for this message
      m_properties.setupDisclaimer(rc);
      m_properties.copyUserInfo(rc,msg);

    } // end if

    try
    { // set the "From" address on this message
      Address[] blort = msg.getFrom();
      rc.setFrom(blort[0]);

      if (user) // also set the "sender" address
	rc.setSender(msg.getSender());

    } // end try
    catch (MessagingException me)
    { // this does not make sense
      logger.error("MessagingException thrown in copyMessage() where it shouldn't be",me);

    } // end catch

    return rc;
    
  } // end copyMessage

} // end class MailSubSystem
