/*
 * Copyright (C) 2003-2007 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.services.cms.watch.impl;

import groovy.text.GStringTemplateEngine;
import groovy.text.TemplateEngine;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import javax.portlet.PortletRequest;

import org.exoplatform.commons.api.notification.service.NotificationCompletionService;
import org.exoplatform.commons.notification.impl.NotificationSessionManager;
import org.exoplatform.commons.utils.CommonsUtils;
import org.exoplatform.portal.application.PortalRequestContext;
import org.exoplatform.portal.mop.SiteType;
import org.exoplatform.portal.mop.user.UserNavigation;
import org.exoplatform.portal.mop.user.UserNode;
import org.exoplatform.portal.mop.user.UserPortal;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.services.cms.drives.DriveData;
import org.exoplatform.services.cms.drives.ManageDriveService;
import org.exoplatform.services.cms.watch.WatchDocumentService;
import org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator;
import org.exoplatform.services.jcr.impl.core.observation.EventImpl;
import org.exoplatform.services.jcr.impl.util.EntityCollection;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.mail.MailService;
import org.exoplatform.services.mail.Message;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.services.organization.Query;
import org.exoplatform.services.organization.User;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.services.security.Identity;
import org.exoplatform.services.security.IdentityRegistry;
import org.exoplatform.services.security.MembershipEntry;
import org.exoplatform.services.wcm.core.NodeLocation;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;
import org.exoplatform.web.url.navigation.NodeURL;
import org.exoplatform.webui.application.WebuiRequestContext;
import org.exoplatform.webui.application.portlet.PortletRequestContext;

/**
 * Created by The eXo Platform SAS
 * Author : Xuan Hoa Pham
 *          hoapham@exoplatform.com
 *          phamvuxuanhoa@gmail.com
 * Dec 6, 2006
 */
public class EmailNotifyListener implements EventListener {

  private NodeLocation observedNode_ ;

  private NotificationCompletionService notificationCompletionService;

  final public static String  EMAIL_WATCHERS_PROP = "exo:emailWatcher";

  private static final String SITE_EXPLORER       = "siteExplorer";

  private static final String PATH_PARAM          = "path";
  private static final String USER_ID             = "${userId}";

  private static final Log    LOG                 = ExoLogger.getLogger(EmailNotifyListener.class.getName());

  /** list of modified properties when editing a document*/
  private static final String EXO_LANGUAGE_PROPERTY   = "exo:language";
  private static final String JCR_DATA_PROPERTY       = "jcr:content/jcr:data";
  private static final String DC_SOURCE_PROPERTY      = "jcr:content/dc:source";
  private static final String DC_DESCRIPTION_PROPERTY = "jcr:content/dc:description";
  private static final String DC_TITLE_PROPERTY   = "jcr:content/dc:title";
  private static final String DC_CREATOR_PROPERTY   = "jcr:content/dc:creator";


  public EmailNotifyListener(Node oNode) {
    observedNode_ = NodeLocation.getNodeLocationByNode(oNode);
    notificationCompletionService = CommonsUtils.getService(NotificationCompletionService.class);

  }

  /**
   * This method is used for listening to all changes of property of a node, when there is a change,
   * message is sent to list of email
   */
  public void onEvent(EventIterator arg0) {
    List<EventImpl> entities = ((EntityCollection) arg0).getList();
    MailService mailService = WCMCoreUtils.getService(MailService.class);
    WatchDocumentServiceImpl watchService = (WatchDocumentServiceImpl)WCMCoreUtils.getService(WatchDocumentService.class);
    MessageConfig messageConfig = watchService.getMessageConfig();
    Callable<Boolean> task = null;
    //check the modified properties when an action is done
    for(EventImpl entity : entities) {
      if ((entity.getPath().contains(EXO_LANGUAGE_PROPERTY)) || (entity.getPath().contains(JCR_DATA_PROPERTY)) || (entity.getPath().contains(DC_SOURCE_PROPERTY)) ||
              (entity.getPath().contains(DC_DESCRIPTION_PROPERTY)) || (entity.getPath().contains(DC_TITLE_PROPERTY)) || (entity.getPath().contains(DC_CREATOR_PROPERTY))) {
        List<String> emailList = getEmailList(NodeLocation.getNodeByLocation(observedNode_));
        for (String receiver : emailList) {
          try {
            Message message = createMessage(receiver, messageConfig);
            task = new Callable<Boolean>() {
              @Override
              public Boolean call() throws Exception {
                boolean created = NotificationSessionManager.createSystemProvider();
                try {
                  mailService.sendMessage(message);
                } catch (Exception e) {
                  LOG.error("Failed to send a message", e);
                  return false;
                } finally {
                  NotificationSessionManager.closeSessionProvider(created);
                }
                return true;
              }
            };
            notificationCompletionService.addTask(task);
          } catch (Exception e) {
            LOG.error("Unexpected error while sending notification email to " + receiver);
          }
        }
      }
    }
  }

  /**
   * Create message when there is any changes of property of a node.
   * @param receiver
   * @param messageConfig
   * @return
   * @throws Exception
   */
  private Message createMessage(String receiver, MessageConfig messageConfig) throws Exception {
    Message message = new Message();
    message.setFrom(messageConfig.getSender());
    message.setTo(receiver);
    message.setSubject(messageConfig.getSubject());
    TemplateEngine engine = new GStringTemplateEngine();
    Map<String, String> binding = new HashMap<String, String>();
    Query query = new Query();
    query.setEmail(receiver);
    binding.put("user_name", WCMCoreUtils.getService(OrganizationService.class)
                                         .getUserHandler()
                                         .findUsersByQuery(query)
                                         .load(0, 1)[0].getFullName());

    Node node = NodeLocation.getNodeByLocation(observedNode_);
    binding.put("doc_title", org.exoplatform.services.cms.impl.Utils.getTitle(node));
    binding.put("doc_name", node.getName());
    binding.put("doc_url", getViewableLink());
    message.setBody(engine.createTemplate(messageConfig.getContent()).make(binding).toString());
    message.setMimeType(messageConfig.getMimeType());
    return message;
  }

  /**
   *
   * @return
   * @throws Exception
   */
  private String getViewableLink() throws Exception {
    PortalRequestContext pContext = Util.getPortalRequestContext();
    NodeURL nodeURL = pContext.createURL(NodeURL.TYPE);
    String nodePath = NodeLocation.getNodeByLocation(observedNode_).getPath();

    ManageDriveService manageDriveService = WCMCoreUtils.getService(ManageDriveService.class);
    List<DriveData> driveList = manageDriveService.getDriveByUserRoles(pContext.getRemoteUser(),
                                                                       getMemberships());
    DriveData drive = getDrive(driveList,
                               WCMCoreUtils.getRepository()
                                           .getConfiguration()
                                           .getDefaultWorkspaceName(),
                               nodePath);

    String driverName = drive.getName();
    String nodePathInDrive = "/".equals(drive.getHomePath()) ? nodePath
                                                            : nodePath.substring(drive.getHomePath()
                                                                                      .length());
    UserNode siteExNode = getUserNodeByURI(SITE_EXPLORER);
    nodeURL.setNode(siteExNode);

    nodeURL.setQueryParameterValue(PATH_PARAM, "/" + driverName + nodePathInDrive);

    PortletRequestContext portletRequestContext = WebuiRequestContext.getCurrentInstance();
    PortletRequest portletRequest = portletRequestContext.getRequest();
    String baseURI = portletRequest.getScheme() + "://" + portletRequest.getServerName() + ":"
        + String.format("%s", portletRequest.getServerPort());
    return baseURI + nodeURL.toString();
  }

  /**
   *
   * @param lstDrive
   * @param workspace
   * @param nodePath
   * @return
   * @throws RepositoryException
   */
  private DriveData getDrive(List<DriveData> lstDrive, String workspace, String nodePath) throws RepositoryException {
    NodeHierarchyCreator nhc = WCMCoreUtils.getService(NodeHierarchyCreator.class);
    String userName = ConversationState.getCurrent().getIdentity().getUserId();
    int idx;
    String userNodePath = null;
    try {
      userNodePath = nhc.getUserNode(WCMCoreUtils.getSystemSessionProvider(), userName).getPath();
    }catch (Exception e) {
      //Exception while finding the user home node
      userNodePath = null;
    }
    DriveData driveData = null;
    for (DriveData drive : lstDrive) {
      String driveHomePath = drive.getHomePath();
      idx = driveHomePath.indexOf(USER_ID) ;
      if (idx >=0 && userNodePath!=null) {
        driveHomePath = userNodePath + driveHomePath.substring(idx + USER_ID.length());
      }
      if (workspace.equals(drive.getWorkspace()) && nodePath.startsWith(driveHomePath)) {
        driveData = drive;
        break;
      }
    }
    return driveData;
  }

  /**
   *
   * @param uri
   * @return
   */
  private UserNode getUserNodeByURI(String uri) {
    UserPortal userPortal = Util.getPortalRequestContext().getUserPortalConfig().getUserPortal();
    List<UserNavigation> allNavs = userPortal.getNavigations();

    for (UserNavigation nav : allNavs) {
      if (nav.getKey().getType().equals(SiteType.GROUP)) {
        UserNode userNode = userPortal.resolvePath(nav, null, uri);
        if (userNode != null) {
          return userNode;
        }
      }
    }
    return null;
  }

  /**
   *
   * @return
   */
  public List<String> getMemberships() {
    String userId = Util.getPortalRequestContext().getRemoteUser();
    List<String> userMemberships = new ArrayList<String>();
    userMemberships.add(userId);
    // here we must retrieve memberships of the user using the
    // IdentityRegistry Service instead of Organization Service to
    // allow JAAS based authorization
    Collection<MembershipEntry> memberships = getUserMembershipsFromIdentityRegistry(userId);
    if (memberships != null) {
      for (MembershipEntry membership : memberships) {
        String role = membership.getMembershipType() + ":" + membership.getGroup();
        userMemberships.add(role);
      }
    }
    return userMemberships;
  }

  /**
   *
   * @param authenticatedUser
   * @return
   */
  private Collection<MembershipEntry> getUserMembershipsFromIdentityRegistry(String authenticatedUser) {
    IdentityRegistry identityRegistry = WCMCoreUtils.getService(IdentityRegistry.class);
    Identity currentUserIdentity = identityRegistry.getIdentity(authenticatedUser);
    return currentUserIdentity.getMemberships();
  }

  /**
   * This Method will get email of watchers when they watch a document
   * @param observedNode
   * @return
   */
  private List<String> getEmailList(Node observedNode) {
    List<String> emailList = new ArrayList<String>() ;
    OrganizationService orgService = WCMCoreUtils.getService(OrganizationService.class);
    try{
      if(observedNode.hasProperty(EMAIL_WATCHERS_PROP)) {
        Value[] watcherNames = observedNode.getProperty(EMAIL_WATCHERS_PROP).getValues() ;
        for(Value value: watcherNames) {
          String userName = value.getString() ;
          User user = orgService.getUserHandler().findUserByName(userName) ;
          if(user != null) {
            emailList.add(user.getEmail()) ;
          }
        }
      }
    } catch (Exception e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("Unexpected error", e);
      }
    }
    return emailList ;
  }
}

