/* ======================================================================
The Bodington System Software License, Version 1.0
 
Copyright (c) 2001 The University of Leeds.  All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
 
1.  Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
 
2.  Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
 
3.  The end-user documentation included with the redistribution, if any,
must include the following acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.
 
4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.
 
5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.
 
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
EVENT SHALL THE UNIVERSITY OF LEEDS OR ITS CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
=========================================================
 
This software was originally created by the University of Leeds and may contain voluntary
contributions from others.  For more information on the Bodington Open Source Project, please
see http://bodington.org/
 
====================================================================== */

package org.bodington.server.events;

import org.apache.log4j.Logger;

import org.bodington.server.*;
import org.bodington.server.realm.*;
import org.bodington.server.resources.*;
import org.bodington.text.*;
import org.bodington.util.BodingtonURL;
import org.bodington.util.DateFormatter;

import java.io.*;
import java.util.Calendar;
import java.util.Properties;
import java.util.Enumeration;
import java.util.Vector;
import javax.mail.*;
import javax.mail.internet.*;
import java.sql.Timestamp;

/**
 * This class sends out emails of events in Bodington and is run from the JobScheduler.
 * This class requires several Bodington properties to be set.
 * <table>
 * <tr>
 * <th>Property</th>
 * <th>Description</th>
 * <th>Default</th>
 * <th>Required</th>
 * </tr>
 * <tr>
 * <td>notification.return_email</td>
 * <td>The email address used for the from address.</td>
 * <td></td>
 * <td>Yes</td>
 * </tr>
 * <tr>
 * <td>notification.smtp</td>
 * <td>The SMTP host for all outbound emails.</td>
 * <td></td>
 * <td>Yes</td>
 * </tr>
 * <tr>
 * <td>notification.subject</td>
 * <td>The subject of the emails sent to users.</td>
 * <td>Bodington System Events</td>
 * <td>No</td>
 * </tr>
 * <tr>
 * <td>notification.preamble</td>
 * <td>The introduction at the start of the email send to users.</td>
 * <td> This Email is to notify you of events that have taken place in a
 * Bodington System web site </td>
 * <td>No</td>
 * </tr>
 * <tr>
 * <td>buildingservlet.server</td>
 * <td>The server through which the Bodington website is accessed.</td>
 * <td></td>
 * <td>Yes</td>
 * </tr>
 * <tr>
 * <td>buildingservlet.http.port</td>
 * <td>The port on which the http protocol is listening.</td>
 * <td>80</td>
 * <td>No</td>
 * </tr>
 * <tr>
 * <td>buildingservlet.servlet</td>
 * <td>The path to the Bodington Site servlet.</td>
 * <td>/bodington/site</td>
 * <td>No</td>
 * </tr>
 * </table>
 */

public class EventMailer implements org.bodington.server.JobSession
{
    private static Logger log = Logger.getLogger(EventMailer.class);
    private Session mail_session;
    private boolean warnedInvalidConfig = false;
    
    private BodingtonURL bodingtonURL;
    private String subject;
    private String preamble;
    
    /**
     * Create an EventMailer for sending out emails to users of events in Bodington.
     */
    public EventMailer()
    {
        Properties props = new Properties();
        
        String returns = BuildingContext.getProperty( "notification.return_email" );
        String host = BuildingContext.getProperty( "notification.smtp" );
        subject = BuildingContext.getProperty( "notification.subject", "Bodington System Events" );
        preamble = BuildingContext.getProperty( "notification.preamble", "This Email is to notify you of events that have taken place in a Bodington System web site." );
        
        if ( returns == null || returns.length()==0 || host==null || host.length()==0 )
            mail_session = null;
        else
        {
            
            String servlet =  BuildingContext.getProperty("buildingservlet.servlet");
            if (servlet == null || servlet.trim().length() == 0)
            {
                log.warn("Property buildingservlet.servlet isn't set. Defaulting to /bodington/site");
                servlet = "/bodington/site";
            }
            int port = BuildingContext.getProperty("buildingservlet.port.http", 80, true);
            
            String server = BuildingContext.getProperty("buildingservlet.server");
            if(server == null || server.trim().length() == 0)
            {
                log.error("Property buildingservlet.server isn't set. Can't send event emails");
            }
            else
            {
                bodingtonURL = new BodingtonURL("http", server, port, servlet);
                
                props.put( "mail.smtp.from", returns );
                props.put( "mail.smtp.user", returns );
                props.put( "mail.smtp.host", host );
                mail_session = Session.getDefaultInstance(props, null);
            }
        }
    }
    
    
    public void sendEventMail( String parameter )
    throws BuildingServerException
    {
        log.debug( "EMAIL DEBUG: Sending Email notification..." );
        
        if ( mail_session == null )
        {
            if (!warnedInvalidConfig)
            {
                log.error("Bodington is not configured for outgoing email. None will be sent");
                warnedInvalidConfig = true;
            }
            return;
        }
        
        User user = (User)BuildingContext.getContext().getUser();
        if ( user==null )
            throw new BuildingServerException( "Unable to determine who to send Email to." );
        
        log.debug( "EMAIL DEBUG: user_id = " + user.getUserId() );
        
        CharArrayWriter buffer = new CharArrayWriter( 2048 );
        
        Calendar calendar = Calendar.getInstance();
        calendar.setTime( new java.util.Date() );
        calendar.add( Calendar.DATE, -1 );
        java.sql.Timestamp since=new java.sql.Timestamp( calendar.getTime().getTime() );
        calendar.setTime( new java.util.Date() );
        calendar.add( Calendar.DATE, -7 );
        java.sql.Timestamp weeksince=new java.sql.Timestamp( calendar.getTime().getTime() );
        
        UserEventSetup ues;
        UserDetail detail;
        String address;
        JobResult job_result = BuildingContext.getContext().getJobResult();
        BigString output_message=null;
        if ( job_result != null )
            output_message = job_result.getBigString();
        
        try
        {
            ues = UserEventSetup.findUserEventSetup( "user_id = " + user.getUserId() );
            detail = UserDetail.findUserDetail( "user_id = " + user.getUserId() );
            if ( detail == null || ues==null )
                return;
            
            address = detail.getEMailAddress();
            if ( address == null )
                return;
            
            if ( output_message!=null )
            {
                output_message.setString( "Composing notification Email." );
                output_message.save();
            }
            
            buffer = composeEMail( ues );
            if ( buffer !=null )
            {
                log.debug( "EMAIL DEBUG: Email Text Complete " + buffer.size() + " chars." );
                if ( output_message!=null )
                {
                    output_message.setString( output_message.getString() + "\nSending notification Email." );
                    output_message.save();
                }
                sendemail( address, buffer );
                if ( output_message!=null )
                {
                    output_message.setString( output_message.getString() + "\nNotification Email sent." );
                    output_message.save();
                }
                log.debug( "EMAIL DEBUG: Email sent." );
            }
            else
            {
                output_message.setString( output_message.getString() + "\nNothing to notify about, no Email sent." );
                output_message.save();
                log.debug( "EMAIL DEBUG: No Email sent." );
            }
            
            ues.setEMailCutoffTime( new java.sql.Timestamp( System.currentTimeMillis() ) );
            ues.save();
        }
        catch ( IOException ioex )
        {
            log.error(ioex.getMessage(), ioex );
            throw new BuildingServerException( "Problem composing Email." );
        }
        
    }
    
    private synchronized CharArrayWriter composeEMail( UserEventSetup ues )
    throws IOException, BuildingServerException
    {
        int r, e, n;
        CharArrayWriter buffer = new CharArrayWriter();
        PrintWriter out = new PrintWriter( buffer );
        
        int interval = ues.getEMailFrequency();
        //long s = System.currentTimeMillis() - (interval*24L*60L*60L*1000L);
        Timestamp since = ues.getEMailCutoffTime();
        
        // if lcutoff date is more the 1 and an 8th before the interval
        // bring it forward.  (In case emails haven't been sent for a long
        // time - avoid sending a batch of very long Emails.
        long allowance = (long)ues.getEMailFrequency()*24L*60L*60L*1000L;
        allowance += allowance/8;
        allowance = System.currentTimeMillis() - allowance;
        if ( since.getTime() < allowance )
            since = new java.sql.Timestamp( allowance );
        
        Resource resource;
        Event event;
        Enumeration resources = ues.findResources();
        Enumeration events;
        boolean sampled;
        Vector all_events=new Vector();
        
        out.println( preamble );
        out.print( "This Email was requested by and is intended for " );
        out.print( ues.getUser().getName() );
        out.println( "." );
        out.println( "The following summarises events that have occurred since " );
        DateFormatter.outputDate( out, since, 2 );
        out.println( ". Only events for locations on your personal list are included." );
        out.println( "(You add locations to your personal list by navigating to them " );
        out.println( "and clicking on the 'Notify is off' link when you get there.)" );
        out.println( "If there are a large number of events for a particular resource " );
        out.println( "a sample of fifty will be listed in this message. A full list " );
        out.println( " can be seen by visiting the notification tool on the web site." );
        out.println( "" );
        
        n=0;
        for ( r=0; resources.hasMoreElements(); r++ )
        {
            resource = (Resource) resources.nextElement();
            log.debug( "EMAIL DEBUG: Starting another resource. resource_id = " + resource.getResourceId() + " Message = " + buffer.size() + " chars." );
            
            events = ues.findEventsForResource( resource, since );
            all_events.clear();
            for ( e=0; events.hasMoreElements(); )
            {
                event = (Event)events.nextElement();
                if ( !event.checkPermission() )
                    continue;
                all_events.addElement( event );
            }

            // send no more than fifty events
            sampled=false;
            if ( all_events.size() > 50 )
            {
                sampled = true;
                // start by weeding out more detailed events
                for ( e=0; e<all_events.size() && all_events.size()>50;  )
                {
                    event = (Event)all_events.elementAt( e );
                    if ( event.getImportance() < Event.IMPORTANCE_USER_MIN )
                        all_events.remove( e ); // remove and stay at this index
                    else
                        e++;                    // move on to next index
                }
                // continue by deleting from top
                while ( all_events.size()>50  )
                {
                    all_events.remove( 0 );
                }
            }
            
            if ( all_events.size() > 0 )
            {
                for ( e=0; e<all_events.size(); e++  )
                {
                    event = (Event)all_events.elementAt( e );
                    if ( e==0 )
                    {
                        out.println();
                        out.println( resource.getTitle() );
                        out.print( bodingtonURL.getResourceUrl(resource) );
                        out.println();
                        
                        if ( sampled )
                        {
                        out.println( "(Sample of 50 events.)" );
                        out.println();
                        }
                    }
                    out.print( "    " );
                    DateFormatter.outputDate( out, event.getEventTime(), 2 );
                    out.print( " " );
                    event.printMessage( out, true );
                    out.println();
                    e++;
                    n++;
                }
                if ( e>0 )
                    out.println();
            }
        }
        
        if ( n==0 )
            return null;
        
        return buffer;
    }
    
    
    private void sendemail( String recipient, CharArrayWriter buffer )
    throws BuildingServerException
    {
        try
        {
            InternetAddress[] address =
            { new InternetAddress(recipient) };
            
            // create a message
            Message msg = new MimeMessage( mail_session );
            
            msg.setRecipients( Message.RecipientType.TO, address );
            msg.setSubject( subject );
            msg.setContent( buffer.toString(), "text/plain" );
            msg.setHeader("Precedence",  "bulk");
            
            Transport.send( msg );
        }
        catch ( Throwable gex )
        {
            log.error( gex.getMessage(), gex );
            throw new BuildingServerException( "Problem sending Email to " + recipient + ": " + gex );
        }
    }
}

