/*
 * 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-03 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.dynamo.mail;

import java.io.UnsupportedEncodingException;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.log4j.Logger;
import org.w3c.dom.*;
import com.silverwrist.util.*;
import com.silverwrist.util.xml.*;
import com.silverwrist.dynamo.Namespaces;
import com.silverwrist.dynamo.event.*;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;

class MailProperties implements ComponentShutdown, DynamicUpdateListener
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static Logger logger = Logger.getLogger(MailProperties.class);

  public static final String MAILER_ID_REPLACE = "$id";

  private static final int NDX_SMTP_HOST = 1;
  private static final int NDX_SYS_MAIL_ADDR = 2;
  private static final int NDX_SYS_MAIL_NAME = 3;
  private static final int NDX_MAILER_NAME = 4;
  private static final int NDX_USER_DISCLAIMER = 5;
  private static final int NDX_USER_INFO_HEADER = 6;
  private static final int NDX_SIGNATURE = 7;

  private static final Map XML_TO_INDEX;
  private static final Map INDEX_REQUIRED;

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

  private boolean m_initialized = false;                // fully-initialized flag
  private ObjectProvider m_props = null;                // global properties object
  private ObjectProvider m_blocks = null;               // global blocks object
  private String m_namespace = null;                    // namespace containing properties and blocks
  private Map m_prop_to_index;                          // map from property names to internal indices
  private Map m_block_to_index;                         // map from block names to internal indices
  private ComponentShutdown m_event1;                   // event registration
  private ComponentShutdown m_event2;                   // event registration
  private Properties m_mail_props = new Properties();   // JavaMail system properties
  private Session m_mail_session;                       // JavaMail session
  private String m_sys_personal;                        // "personal name" of the system sender
  private String m_mailer_template;                     // template for mailer line
  private String m_mailer = null;                       // the actual mailer line
  private String[] m_user_disclaimer;                   // user disclaimer header lines
  private String m_user_info_header;                    // user info header name
  private String m_signature = null;                    // automatic signature

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

  MailProperties(Element config, ServiceProvider services) throws ConfigException, XMLLoadException
  {
    logger.info("loading MailProperties");
    XMLLoader loader = XMLLoader.get();

    // Initialize mail properties.
    m_mail_props.put("mail.transport.protocol","smtp");

    // Read stuff out of the config element.
    DOMElementHelper config_h = new DOMElementHelper(config);
    Element elt = config_h.getSubElement("global-properties");
    if (elt!=null)
    { // get the object name and load the global properties and blocks providers
      String obj_name = loader.getAttribute(elt,"object");
      ServiceProvider gdm_sp =
          (ServiceProvider)(GetObjectUtils.getDynamoComponent(services,ServiceProvider.class,obj_name));
      m_props = (ObjectProvider)(gdm_sp.queryService(ObjectProvider.class,"properties"));
      m_blocks = (ObjectProvider)(gdm_sp.queryService(ObjectProvider.class,"blocks"));

      // get the namespace we want to use
      m_namespace = loader.getAttribute(elt,"namespace");

      if (logger.isDebugEnabled())
	logger.debug("connected MailProperties to global data provider \"" + obj_name + "\". namespace \""
		     + m_namespace + "\"");
      
    } // end if

    // Start reading in the various configuration properties.
    HashMap tmp_prop_to_index = new HashMap();
    HashMap tmp_block_to_index = new HashMap();
    Iterator it = XML_TO_INDEX.keySet().iterator();
    while (it.hasNext())
    { // get the key values and required flag
      String xml_key = (String)(it.next());
      Integer my_key = (Integer)(XML_TO_INDEX.get(xml_key));
      boolean required = ((Boolean)(INDEX_REQUIRED.get(my_key))).booleanValue();
      if (logger.isDebugEnabled())
	logger.debug("getting XML parameter <" + xml_key + "/>, required = " + required);
      String value = null;
      if (required)
      { // get the configuration element
	elt = loader.getSubElement(config_h,xml_key);
	String prop_name = elt.getAttribute("property");
	if (StringUtils.isNotEmpty(prop_name))
	{ // get the appropriate value and save it so we know it's a property
	  value = getInitValue(prop_name,false,true);
	  tmp_prop_to_index.put(prop_name,my_key);
	  if (logger.isDebugEnabled())
	    logger.debug("value comes from global property \"" + prop_name + "\"");

	} // end if
	else
	{ // it's not a property, but it mmight be a block
	  String block_name = elt.getAttribute("block");
	  if (StringUtils.isNotEmpty(block_name))
	  { // it's a block - get the value and save the name mapping
	    value = getInitValue(block_name,true,true);
	    tmp_block_to_index.put(block_name,my_key);
	    if (logger.isDebugEnabled())
	      logger.debug("value comes from global block \"" + block_name + "\"");

	  } // end if
	  else
	  { // get the raw text value associated with this element
	    value = loader.getText(elt);
	    logger.debug("value comes straight from XML file");

	  } // end else

	} // end else

      } // end if
      else
      { // look for the configuration element (optional)
	elt = config_h.getSubElement(xml_key);
	if (elt!=null)
	{ // we know it exists, but is it a property or a block?
	  String prop_name = elt.getAttribute("property");
	  if (StringUtils.isNotEmpty(prop_name))
	  { // get the appropriate value and save it so we know it's a property
	    value = getInitValue(prop_name,false,false);
	    tmp_prop_to_index.put(prop_name,my_key);
	    if (logger.isDebugEnabled())
	      logger.debug("value comes from global property \"" + prop_name + "\"");

	  } // end if
	  else
	  { // it's not a property, but it might be a block
	    String block_name = elt.getAttribute("block");
	    if (StringUtils.isNotEmpty(block_name))
	    { // it's a block - get the value and save the name mapping
	      value = getInitValue(block_name,true,false);
	      tmp_block_to_index.put(block_name,my_key);
	      if (logger.isDebugEnabled())
		logger.debug("value comes from global block \"" + block_name + "\"");

	    } // end if
	    else
	    { // get the raw text value associated with this element
	      value = new DOMElementHelper(elt).getElementText();
	      if ((value!=null) && (value.length()==0))
		value = null;
	      logger.debug("value comes straight from XML file");

	    } // end else

	  } // end else

	} // end if
	// else don't get a value - just use the default

      } // end else

      setNewValue(my_key.intValue(),value);

    } // end while

    if (logger.isDebugEnabled())
      logger.debug("finished loading " + tmp_prop_to_index.size() + " properties, " + tmp_block_to_index.size()
		   + " blocks");

    // Set up the maps for use when monitoring for changes later.
    if (tmp_prop_to_index.isEmpty())
      m_prop_to_index = Collections.EMPTY_MAP;
    else
      m_prop_to_index = Collections.unmodifiableMap(tmp_prop_to_index);
    if (tmp_block_to_index.isEmpty())
      m_block_to_index = Collections.EMPTY_MAP;
    else
      m_block_to_index = Collections.unmodifiableMap(tmp_block_to_index);

    if (m_props!=null)
    { // Register to receive global property update events.
      EventListenerRegistration reg =
          (EventListenerRegistration)(services.queryService(EventListenerRegistration.class));
      m_event1 = reg.registerDynamicUpdateListener(GlobalPropertyUpdateEvent.class,this);
      m_event2 = reg.registerDynamicUpdateListener(GlobalBlockUpdateEvent.class,this);

    } // end if

    // Get a new mail session.
    logger.debug("About to get mail session");
    m_mail_session = javax.mail.Session.getInstance(m_mail_props,null);
    m_initialized = true;
    logger.info("MailProperties init complete");

  } // end constructor

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

  private final String getInitValue(String prop_name, boolean block, boolean required) throws ConfigException
  {
    try
    { // retrieve the value
      if (block)
	return m_blocks.getObject(m_namespace,prop_name).toString();
      else
	return m_props.getObject(m_namespace,prop_name).toString();

    } // end try
    catch (NoSuchObjectException e)
    { // this may not be an error...
      if (required)
      { // but it is!  translate to ConfigException
	ConfigException ce = new ConfigException(MailProperties.class,"MailMessages",
						 (block ? "block.missing" : "property.missing"),e);
	ce.setParameter(0,prop_name);
	ce.setParameter(1,m_namespace);
	throw ce;
	
      } // end if

      return null;  // null value is OK

    } // end catch

  } // end getInitValue

  private final String getRuntimeValue(String prop_name, boolean block)
  {
    try
    { // get the value from the appropriate place...
      if (block)
	return m_blocks.getObject(m_namespace,prop_name).toString();
      else
	return m_props.getObject(m_namespace,prop_name).toString();

    } // end try
    catch (NoSuchObjectException e)
    { // ignore this error and return null
      logger.warn("getRuntimeValue() caught NoSuchObjectException",e);
      return null;

    } // end catch

  } // end getRuntimeValue

  private final synchronized void setNewValue(int index, String value)
  {
    switch (index)
    {
      case NDX_SMTP_HOST:
	m_mail_props.put("mail.smtp.host",value);
	if (m_initialized)
	  m_mail_session = Session.getInstance(m_mail_props,null);
	break;

      case NDX_SYS_MAIL_ADDR:
	m_mail_props.put("mail.from",value);
	if (m_initialized)
	  m_mail_session = Session.getInstance(m_mail_props,null);
	break;

      case NDX_SYS_MAIL_NAME:
	m_sys_personal = value;
	break;

      case NDX_MAILER_NAME:
	if (value==null)
	  m_mailer_template = MAILER_ID_REPLACE;
	else if (value.indexOf(MAILER_ID_REPLACE)>=0)
	  m_mailer_template = value;
	else
	  m_mailer_template = value + " (" + MAILER_ID_REPLACE + ")";
	m_mailer = null;
	break;
      
      case NDX_USER_DISCLAIMER:
	{ // split the disclaimer block into individual lines
	  String[] disc = StringUtils.split(value.trim(),"\r\n");
	  m_user_disclaimer = new String[disc.length];
	  for (int i=0; i<disc.length; i++)
	    m_user_disclaimer[i] = disc[i].trim();

	} // end case
	break;

      case NDX_USER_INFO_HEADER:
	m_user_info_header = value;
	if (!(m_user_info_header.startsWith("X-") || m_user_info_header.startsWith("x-")))
	  m_user_info_header = "X-" + m_user_info_header;
	break;

      case NDX_SIGNATURE:
        if (value!=null)
	  m_signature = value.trim();
        else
          m_signature = null;
	break;

      default:
	break;

    } // end switch

  } // end setNewValue

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

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

    if (m_props!=null)
    { // shut down our event handler
      m_event1.shutdown();
      m_event1 = null;
      m_event2.shutdown();
      m_event2 = null;

    } // end if

    m_mail_session = null;
    m_props = null;
    m_blocks = null;

  } // end shutdown

  /*--------------------------------------------------------------------------------
   * Implementations from interface DynamicUpdateListener
   *--------------------------------------------------------------------------------
   */

  public void updateReceived(DynamicUpdateEvent evt)
  {
    if (evt instanceof GlobalPropertyUpdateEvent)
    { // a global property was updated - see if it's one of ours
      GlobalPropertyUpdateEvent event = (GlobalPropertyUpdateEvent)evt;
      if (event.getPropertyNamespace().equals(m_namespace))
      { // OK, probe the mapping to see if that's what's changed
	Integer my_key = (Integer)(m_prop_to_index.get(event.getPropertyName()));
	if (my_key!=null)
	{ // This is one of our properties that was updated!  get the new value and set it
	  String value = getRuntimeValue(event.getPropertyName(),false);
	  if (value==null)
	  { // if there's no value and it's required, dont reset it
	    if (((Boolean)(INDEX_REQUIRED.get(my_key))).booleanValue())
	      return;

	  } // end if

	  setNewValue(my_key.intValue(),value);

	} // end if

      } // end if

    } // end if

    if (evt instanceof GlobalBlockUpdateEvent)
    { // a global block was updated - see if it's one of ours
      GlobalBlockUpdateEvent event = (GlobalBlockUpdateEvent)evt;
      if (event.getBlockNamespace().equals(m_namespace))
      { // OK, probe the mapping to see if that's what's changed
	Integer my_key = (Integer)(m_block_to_index.get(event.getBlockName()));
	if (my_key!=null)
	{ // This is one of our blocks that was updated!  get the new value and set it
	  String value = getRuntimeValue(event.getBlockName(),true);
	  if (value==null)
	  { // if there's no value and it's required, dont reset it
	    if (((Boolean)(INDEX_REQUIRED.get(my_key))).booleanValue())
	      return;

	  } // end if

	  setNewValue(my_key.intValue(),value);

	} // end if

      } // end if

    } // end if

  } // end updateReceived

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

  synchronized Session getMailSession()
  {
    return m_mail_session;

  } // end getMailSession

  synchronized InternetAddress getSystemFromAddress() throws UnsupportedEncodingException
  {
    InternetAddress rc = InternetAddress.getLocalAddress(m_mail_session);
    if (m_sys_personal!=null)
      rc.setPersonal(m_sys_personal);
    return rc;

  } // end getSystemFromAddress

  synchronized void setupMailer(MimeMessage msg, Request r)
  {
    if (m_mailer==null)
    { // get the identity from the application namespace and substitute it in
      ObjectProvider prov = (ObjectProvider)(r.queryService(ObjectProvider.class));
      String id = (String)(prov.getObject(Namespaces.DYNAMO_APPLICATION_NAMESPACE,"identity"));
      m_mailer = StringUtils.replace(m_mailer_template,MAILER_ID_REPLACE,id);

    } // end if

    try
    { // set the X-Mailer header
      msg.setHeader("X-Mailer",m_mailer);

    } // end try
    catch (Exception e)
    { // do nothing
    } // end catch

  } // end setupMailer

  synchronized void setupMailer(MimeMessage msg)
  {
    try
    { // set the X-Mailer header
      if (m_mailer!=null)
	msg.setHeader("X-Mailer",m_mailer);

    } // end try
    catch (Exception e)
    { // do nothing
    } // end catch

  } // end setupMailer

  synchronized void setupDisclaimer(MimeMessage msg)
  {
    try
    { // set the X-Disclaimer headers
      for (int i=0; i<m_user_disclaimer.length; i++)
	msg.setHeader("X-Disclaimer-" + String.valueOf(i+1),m_user_disclaimer[i]);

    } // end try
    catch (Exception e)
    { // do nothing
    } // end catch

  } // end setupDisclaimer

  synchronized String getUserInfoHeader()
  {
    return m_user_info_header;

  } // end getUserInfoHeader

  synchronized void setupUserInfo(MimeMessage msg, Request r, String user_id, String user_name)
  {
    ArrayList info = new ArrayList(4);
    if (user_id!=null)
      info.add("id " + user_id);
    if (user_name!=null)
      info.add("username " + user_name);
    info.add("ip [" + r.getSourceAddress() + "]");
    String info_line = StringUtils.join(info.iterator(),"; ");
    try
    { // set the header
      msg.setHeader(m_user_info_header,info_line);

    } // end try
    catch (Exception e)
    { // do nothing
    } // end catch

  } // end setupUserInfo

  synchronized void copyUserInfo(MimeMessage dest, MimeMessage source)
  {
    try
    { // set the header
      dest.setHeader(m_user_info_header,source.getHeader(m_user_info_header," "));

    } // end try
    catch (Exception e)
    { // do nothing
    } // end catch

  } // end copyUserInfo

  synchronized String getSignature()
  {
    if (m_signature!=null)
      return "\r\n--\r\n" + m_signature;
    else
      return null;

  } // end getSignature

  /*--------------------------------------------------------------------------------
   * Static initializer
   *--------------------------------------------------------------------------------
   */

  static
  { // create underlying hash maps
    HashMap xml_to_index = new HashMap();
    HashMap index_required = new HashMap();

    // add parameter for SMTP Host
    Integer tmp = new Integer(NDX_SMTP_HOST);
    xml_to_index.put("smtp-host",tmp);
    index_required.put(tmp,Boolean.TRUE);

    // add parameter for system mail address
    tmp = new Integer(NDX_SYS_MAIL_ADDR);
    xml_to_index.put("system-mail-addr",tmp);
    index_required.put(tmp,Boolean.TRUE);

    // add parameter for system mail name
    tmp = new Integer(NDX_SYS_MAIL_NAME);
    xml_to_index.put("system-mail-name",tmp);
    index_required.put(tmp,Boolean.FALSE);

    // add parameter for mailer name
    tmp = new Integer(NDX_MAILER_NAME);
    xml_to_index.put("mailer",tmp);
    index_required.put(tmp,Boolean.FALSE);

    // add parameter for user disclaimer
    tmp = new Integer(NDX_USER_DISCLAIMER);
    xml_to_index.put("user-disclaimer",tmp);
    index_required.put(tmp,Boolean.TRUE);

    // add parameter for user info header
    tmp = new Integer(NDX_USER_INFO_HEADER);
    xml_to_index.put("user-info-header",tmp);
    index_required.put(tmp,Boolean.TRUE);

    // add parameter for auto signature
    tmp = new Integer(NDX_SIGNATURE);
    xml_to_index.put("signature",tmp);
    index_required.put(tmp,Boolean.FALSE);

    // freeze the maps
    XML_TO_INDEX = Collections.unmodifiableMap(xml_to_index);
    INDEX_REQUIRED = Collections.unmodifiableMap(index_required);

  } // end static initializer

} // end class MailProperties
