/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

====================================================================== */

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 " );
        out.print( DateFormatter.formatDate( since, DateFormatter.MEDIUM ) );
        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( "    " );
                    out.print( DateFormatter.formatDate( event.getEventTime(), DateFormatter.MEDIUM ) );
                    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 );
        }
    }
}
