/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.notification;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.executer.MailActionExecuter;
import org.alfresco.repo.model.Repository;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.admin.RepoAdminService;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.notification.NotificationContext;
import org.alfresco.service.cmr.notification.NotificationProvider;
import org.alfresco.service.cmr.notification.NotificationService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.TemplateService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.util.ModelUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.I18NUtil;

/**
 * EMail notification provider implementation
 * 
 * @author Roy Wetherall
 * @since 4.0
 */
public class EMailNotificationProvider implements NotificationProvider
{
    /** I18N */
    private static final String MSG_DEFAULT_SENDER_USED = "default-sender-used";
    private static final String MSG_NO_RECIPIENTS = "no-recipients";
    private static final String MSG_NO_BODY_OR_TEMPLATE = "no-body-or-template";
    
    /** Log */
    private static Log logger = LogFactory.getLog(EMailNotificationProvider.class); 
    
    /** Name of provider */
    public final static String NAME = "email";
    
    /** Notification service */
    private NotificationService notificationService;
    
    /** Node service */
    private NodeService nodeService;
    
    /** Action service */
    private ActionService actionService;
    
    /** Person service */
    private PersonService personService;
    
    /** Repository object */
    private Repository repository;

    /** File folder service */
    private FileFolderService fileFolderService;
    
    /** Repository administration service */
    private RepoAdminService repoAdminService;
    
    /**
     * @param notificationService   notification service
     */
    public void setNotificationService(NotificationService notificationService)
    {
        this.notificationService = notificationService;
    }
    
    /**
     * @param nodeService   node service
     */
    public void setNodeService(NodeService nodeService)
    {
        this.nodeService = nodeService;
    }
    
    /**
     * @param actionService action service
     */
    public void setActionService(ActionService actionService)
    {
        this.actionService = actionService;
    }
    
    /**
     * @param personService person service
     */
    public void setPersonService(PersonService personService)
    {
        this.personService = personService;
    }
    
    /**
     * @param repository    repository object
     */
    public void setRepository(Repository repository)
    {
        this.repository = repository;
    }
    
    /**
     * @param repoAdminService  repository administration serviceS
     */
    public void setRepoAdminService(RepoAdminService repoAdminService)
    {
        this.repoAdminService = repoAdminService;
    }
    
    /**
     * @param fileFolderService file folder service
     */
    public void setFileFolderService(FileFolderService fileFolderService)
    {
        this.fileFolderService = fileFolderService;
    }
    
    /**
     * Init method registers provider with notification service.
     */
    public void init()
    {
        notificationService.register(this);
    }
    
    /**
     * @see org.alfresco.service.cmr.notification.NotificationProvider#getName()
     */
    @Override
    public String getName()
    {
        return NAME;
    }
    
    /**
     * @see org.alfresco.service.cmr.notification.NotificationProvider#sendNotification(org.alfresco.service.cmr.notification.NotificationContext)
     */
    @Override
    public void sendNotification(NotificationContext notificationContext)
    {
        Action mail = actionService.createAction(MailActionExecuter.NAME);
        
        // Set from parameter
        String from = notificationContext.getFrom();
        if (from != null && from.length() != 0)
        {
            String fromEMail = getEMailFromUser(from);
            if (fromEMail != null)
            {
                mail.setParameterValue(MailActionExecuter.PARAM_FROM, fromEMail);
            }
            else
            {
                if (logger.isWarnEnabled() == true)
                {
                    logger.warn(I18NUtil.getMessage(MSG_DEFAULT_SENDER_USED, from));
                }
            }
        }
        
        // Set to parameter
        List<String> to = notificationContext.getTo();
        if (to == null || to.size() == 0)
        {
            errorEncountered(notificationContext, 
                             I18NUtil.getMessage(MSG_NO_RECIPIENTS, notificationContext.getDocument()));
            return;
        }
        else
        {
            mail.setParameterValue(MailActionExecuter.PARAM_TO_MANY, (Serializable)to);
        }        
        
        // Set subject
        String subject = notificationContext.getSubject();
        if (subject != null)
        {
            mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT, subject);
        }

        // Note that the smart logic in MailActionExecuter should recognize any
        // preferred locale for the users in the recipient field and override this value
        // Note that the smart logic in MailActionExecuter may be broken if we send email addresses
        // instead of userIDs
        Locale userLocale = I18NUtil.getLocale();
        if (userLocale != null)
        {
            mail.setParameterValue(MailActionExecuter.PARAM_LOCALE, userLocale);
        }

        // Set body 
        String body = notificationContext.getBody();
        if (body != null && body.length() != 0)
        {
            mail.setParameterValue(MailActionExecuter.PARAM_TEXT, body);
        }
        else
        {
            // Check for template
            String template = notificationContext.getBodyTemplate();
            if (template == null)
            {
                errorEncountered(notificationContext, 
                                 I18NUtil.getMessage(MSG_NO_BODY_OR_TEMPLATE, notificationContext.getDocument()));
                return;
            }
            else
            {
                if (template.indexOf(StoreRef.URI_FILLER) != -1)
                {
                    template = fileFolderService.getLocalizedSibling(new NodeRef(template)).toString();
                }
                mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, template);
                mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, 
                                       (Serializable)buildTemplateModel(notificationContext.getTemplateArgs()));
            }
        }
        
        // Set ignore failure
        mail.setParameterValue(MailActionExecuter.PARAM_IGNORE_SEND_FAILURE, Boolean.valueOf(notificationContext.isIgnoreNotificationFailure()));
        
        // Execute mail action upon document
        actionService.executeAction(mail, notificationContext.getDocument(), false, notificationContext.isAsyncNotification());       
    }
    
    /**
     * Gets the email for the given user name.  Returns null if none set.
     * 
     * @param user  user name
     * @return {@link String}   user email
     */
    private String getEMailFromUser(String user)
    {
        String email =  null;
        
        NodeRef person = personService.getPerson(user);
        if (person != null)
        {
            email = (String)nodeService.getProperty(person, ContentModel.PROP_EMAIL);
        }
        
        return email;
    }
    
    /**
     * Build the model for the body template.
     * 
     * @param templateArgs  template args provided by the notification context
     * @return {@link Map}<{@link String},{@link Serializable}> template model values
     */
    private Map<String, Serializable> buildTemplateModel(Map<String, Serializable> templateArgs)
    {
        // Set the core model parts
        // Note - the user part is skipped, as that's implied via the run-as
        Map<String, Serializable> model = new HashMap<String, Serializable>();
        model.put(TemplateService.KEY_COMPANY_HOME, repository.getCompanyHome());
        NodeRef person = repository.getPerson();
        if (person != null)
        {
            model.put(TemplateService.KEY_PERSON, person);
            model.put(TemplateService.KEY_USER_HOME, repository.getUserHome(person));
        }
        model.put(TemplateService.KEY_PRODUCT_NAME, ModelUtil.getProductName(repoAdminService));
        
        // Put the notification context information in the model?
        // TODO
        
        if (templateArgs != null && templateArgs.size() != 0)
        {
            // Put the provided args in the model
            model.put("args", (Serializable)templateArgs);
        }
        
        // All done
        return model;
    }
    
    /**
     * Deals with an error when it is encountered
     * 
     * @param notificationContext   notification context
     * @param message               error message
     */
    private void errorEncountered(NotificationContext notificationContext, String message)
    {
        if (logger.isWarnEnabled() == true)
        {
            logger.warn(message);
        }
        
        if (notificationContext.isIgnoreNotificationFailure() == false)
        {
            throw new AlfrescoRuntimeException(message);
        } 
    }
}