/*
 * Copyright (C) 2003-2009 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.wcm.newsletter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;

import org.exoplatform.commons.utils.PageList;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.container.component.ComponentRequestLifecycle;
import org.exoplatform.portal.config.DataStorage;
import org.exoplatform.portal.config.model.Page;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.access.PermissionType;
import org.exoplatform.services.jcr.core.ExtendedNode;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.organization.Group;
import org.exoplatform.services.organization.MembershipHandler;
import org.exoplatform.services.organization.MembershipType;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.services.organization.User;
import org.exoplatform.services.organization.UserHandler;
import org.exoplatform.services.wcm.core.WCMConfigurationService;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;

/**
 * Created by The eXo Platform SAS
 * Author : eXoPlatform
 * chuong.phan@exoplatform.com, phan.le.thanh.chuong@gmail.com
 * May 21, 2009
 */
@SuppressWarnings("deprecation")
public class NewsletterConstant {

  private static Log log = ExoLogger.getLogger("wcm.NewsletterConstant");
  
  // Categories property
  /** The Constant CATEGORIES_PROPERTY_ADDMINISTRATOR. */
  public static final String  CATEGORIES_PROPERTY_ADDMINISTRATOR = "exo:newsletteraddministrator";

  /** The Constant CATEGORY_NODETYPE. */
  public static final String CATEGORY_NODETYPE                   = "exo:newsletterCategory";

  /** The Constant CATEGORY_PROPERTY_TITLE. */
  public static final String CATEGORY_PROPERTY_TITLE             = "exo:newsletterCategoryTitle";

  /** The Constant CATEGORY_PROPERTY_DESCRIPTION. */
  public static final String CATEGORY_PROPERTY_DESCRIPTION       = "exo:newsletterCategoryDescription";

  // Subscription nodetype
  /** The Constant SUBSCRIPTION_NODETYPE. */
  public static final String SUBSCRIPTION_NODETYPE               = "exo:newsletterSubscription";

  /** The Constant SUBSCRIPTION_PROPERTY_USER. */
  public static final String SUBSCRIPTION_PROPERTY_USER          = "exo:newsletterSubscribedUser";

  /** The Constant SUBSCRIPTION_PROPERTY_TITLE. */
  public static final String SUBSCRIPTION_PROPERTY_TITLE         = "exo:newsletterSubscriptionTitle";

  /** The Constant SUBSCRIPTION_PROPERTY_DECRIPTION. */
  public static final String SUBSCRIPTION_PROPERTY_DECRIPTION    = "exo:newsletterSubscriptionDecription";

  /** The Constant SUBSCRIPTION_PROPERTY_CATEGORY_NAME. */
  public static final String SUBSCRIPTION_PROPERTY_CATEGORY_NAME = "exo:newsletterSubscriptionCategoryName";

  // Entry nodetype
  /** The Constant ENTRY_NODETYPE. */
  public static final String ENTRY_NODETYPE                      = "exo:newsletterEntry";

  /** The Constant ENTRY_PROPERTY_TYPE. */
  public static final String ENTRY_PROPERTY_TYPE                 = "exo:newsletterEntryType";

  /** The Constant ENTRY_PROPERTY_DATE. */
  public static final String ENTRY_PROPERTY_DATE                 = "exo:newsletterEntryDate";

  /** The Constant ENTRY_PROPERTY_STATUS. */
  public static final String ENTRY_PROPERTY_STATUS               = "exo:newsletterEntryStatus";

  /** The Constant ENTRY_PROPERTY_SUBSCRIPTION_NAME. */
  public static final String ENTRY_PROPERTY_SUBSCRIPTION_NAME    = "exo:newsletterEntrySubscriptionName";

  /** The Constant ENTRY_PROPERTY_CATEGORY_NAME. */
  public static final String ENTRY_PROPERTY_CATEGORY_NAME        = "exo:newsletterEntryCategoryName";

  /** The Constant ENTRY_PROPERTY_CONTENT_MAIN. */
  public static final String ENTRY_PROPERTY_CONTENT_MAIN         = "exo:newsletterEntryContentMain";

  /** The Constant ENTRY_PROPERTY_NAME. */
  public static final String ENTRY_PROPERTY_NAME                 = "exo:newsletterEntryName";
  
  /** The Constant ENTRY_PROPERTY_TITLE. */
  public static final String ENTRY_PROPERTY_TITLE                = "exo:newsletterEntryTitle";

  // User nodetype
  /** The Constant USER_NODETYPE. */
  public static final String USER_NODETYPE                       = "exo:newsletterUser";
  
  /** The Constant USER_PROPERTY_MAIL. */
  public static final String USER_PROPERTY_MAIL                  = "exo:newsletterUserMail";
  
  /** The Constant USER_PROPERTY_BANNED. */
  public static final String USER_PROPERTY_BANNED                = "exo:newsletterUserBanned";
  
  /** The Constant USER_PROPERTY_VALIDATION_CODE. */
  public static final String USER_PROPERTY_VALIDATION_CODE       = "exo:newsletterUserValidationCode";
  
  public static final String USER_PROPERTY_IS_CONFIRM            = "exo:newsletterUserConfirm";
  
  // Entry status
  /** The Constant STATUS_DRAFT. */
  public static final String STATUS_DRAFT                        = "draft";

  /** The Constant STATUS_AWAITING. */
  public static final String STATUS_AWAITING                     = "awaiting";

  /** The Constant STATUS_SENT. */
  public static final String STATUS_SENT                         = "sent";

  // Newsletter application configuration
  /** The Constant PORTAL_NAME. */
  public static final String PORTAL_NAME                         = "portalName";
  
  /** The TEMPLATE base path. */
  public static String       TEMPLATE_BASE_PATH                  = "/sites content/live/" + PORTAL_NAME + "/ApplicationData/NewsletterApplication/DefaultTemplates";
  
  /** The CATEGORY base path. */
  public static String       CATEGORY_BASE_PATH                  = "/sites content/live/" + PORTAL_NAME + "/ApplicationData/NewsletterApplication/Categories";

  /** The USER base path. */
  public static String       USER_BASE_PATH                      = "/sites content/live/" + PORTAL_NAME + "/ApplicationData/NewsletterApplication/Users";
  
  private static PortalContainer manager;
  
  /**
   * Generate default template path.
   * 
   * @param portalName the portal name
   * 
   * @return the string
   */
  public static String generateDefaultTemplatePath(String portalName) {
    return TEMPLATE_BASE_PATH.replaceAll(PORTAL_NAME, portalName);
  }
  
  /**
   * Generate category template base path.
   * 
   * @param portalName the portal name
   * @param categoryName the category name
   * 
   * @return the string
   */
  public static String generateCategoryTemplateBasePath(String portalName, String categoryName) {
    return generateCategoryPath(portalName).concat("/" + categoryName).concat("/Templates");
  }
  
  /**
   * Generate category path.
   * 
   * @param portalName the portal name
   * 
   * @return the string
   */
  public static String generateCategoryPath(String portalName) {
    return CATEGORY_BASE_PATH.replaceAll(PORTAL_NAME, portalName);
  }
  
  /**
   * Generate user path.
   * 
   * @param portalName the portal name
   * 
   * @return the string
   */
  public static String generateUserPath(String portalName) {
    return USER_BASE_PATH.replaceAll(PORTAL_NAME, portalName);
  }

  /**
   * Generate subscription path.
   * 
   * @param portalName the portal name
   * @param categoryName the category name
   * @param subscriptionName the subscription name
   * 
   * @return the string
   */
  public static String generateSubscriptionPath(String portalName, String categoryName, String subscriptionName) {
    return generateCategoryPath(portalName).concat("/" + categoryName).concat("/" + subscriptionName);
  }
  
  /**
   * Generate newsletter path.
   * 
   * @param portalName the portal name
   * @param categoryName the category name
   * @param subscriptionName the subscription name
   * @param newsletterName the newsletter name
   * 
   * @return the string
   */
  public static String generateNewsletterPath(String portalName, String categoryName, String subscriptionName, String newsletterName) {
    return generateSubscriptionPath(portalName, categoryName, subscriptionName).concat("/" + newsletterName);
  }
  
  /**
   * Get all permission of node, only get users, groups or membership who have all permissions per this node
   * @param node        Node which you want to get
   * @return            List of permission include: users, groups or membership
   * @throws Exception  The exception
   */
  @SuppressWarnings("unchecked")
  public static List<String> getAllPermissionOfNode(Node node) throws Exception{
    ExtendedNode webContent = (ExtendedNode)node;
    Iterator permissionIterator = webContent.getACL().getPermissionEntries().iterator();
    String currentIdentity;
    AccessControlEntry accessControlEntry;
    Map<String, Integer> mapPermission = new HashMap<String, Integer>();
    while (permissionIterator.hasNext()) {
      accessControlEntry = (AccessControlEntry) permissionIterator.next();
      currentIdentity = accessControlEntry.getIdentity();
      if(mapPermission.containsKey(currentIdentity)){
        mapPermission.put(currentIdentity, mapPermission.get(currentIdentity) + 1);
      } else {
        mapPermission.put(currentIdentity, 1);
      }
    }
    int size = PermissionType.ALL.length;
    List<String> listPermission = new ArrayList<String>();
    for(String key : mapPermission.keySet().toArray(new String[]{})){
      if(mapPermission.get(key) == size)
        listPermission.add(key);
    }
    return listPermission;
  }
  
  /**
   * Check permission by comparing two lists permission. If in <code>list1</code> have any element of <code>list2</code>
   * will return <code>true</code> and return false if don't have any element which contained in <code>list1</code> but 
   * don't contained in <code>list2</code>
   * @param list1   List of permission
   * @param list2   List of Permission
   * @return        return <code>true</code> and return false if don't have any element which contained in <code>list1</code> but 
   *                don't contained in <code>list2</code>
   */
  public static boolean havePermission(List<String> list1, List<String> list2){
    for(String str : list1){
      if(list2.contains(str)) return true;
    }
    return false;
  }
  
  /**
   * Add all permissions of category into subscriptions of this category. All moderators after added into
   * category will be updated into subscription. All users will have permissions which you wanted in <code>permissions</code>
   * @param categoryNode  Category Node which have just updated
   * @param userIds       Arrays of users
   * @param permissions   Arrays of permission
   * @throws Exception    The exception
   */
  public static void addPermissionsFromCateToSubs(Node categoryNode, String[] userIds, String[] permissions) throws Exception{
    Node subscriptionNode;
    ExtendedNode extendSubscriptionNode;
    for(NodeIterator ni = categoryNode.getNodes(); ni.hasNext();){
      subscriptionNode = ni.nextNode();
      if(subscriptionNode.isNodeType(NewsletterConstant.SUBSCRIPTION_NODETYPE)){
        extendSubscriptionNode = ExtendedNode.class.cast(subscriptionNode);
        if (extendSubscriptionNode.canAddMixin("exo:privilegeable") || extendSubscriptionNode.isNodeType("exo:privilegeable")) {
          if(extendSubscriptionNode.canAddMixin("exo:privilegeable"))
            extendSubscriptionNode.addMixin("exo:privilegeable");
          for(String userId : userIds){
            extendSubscriptionNode.setPermission(userId, permissions);
          }
        }
      }
    }
  }
  
  /**
   * Update access  permissions 
   * @param accessPermissions   list of user will be set access permission
   * @param component           UIComponent
   * @throws Exception          The exception
   */
  public static void updateAccessPermission(String[] accessPermissions) throws Exception{
    OrganizationService organizationService = WCMCoreUtils.getService(OrganizationService.class) ;
    ((ComponentRequestLifecycle) organizationService).startRequest(manager);

    WCMConfigurationService wcmConfigurationService = WCMCoreUtils.getService(WCMConfigurationService.class);
    String membership = wcmConfigurationService.getRuntimeContextParam(WCMConfigurationService.NEWSLETTER_MANAGE_MEMBERSHIP);
    MembershipType membershipType = organizationService.getMembershipTypeHandler().findMembershipType(membership.split(":")[0]);
    Group group = organizationService.getGroupHandler().findGroupById(membership.split(":")[1]);
    List<User> users = new ArrayList<User>();
    
    DataStorage dataStorage = WCMCoreUtils.getService(DataStorage.class);
    Page page = dataStorage.getPage(Util.getUIPortal().getSelectedNode().getPageReference());
    List<String> pageAccessPermissions = new ArrayList<String>(Arrays.asList(page.getAccessPermissions()));
    
    UserHandler userHandler = organizationService.getUserHandler();
    MembershipHandler membershipHandler = organizationService.getMembershipHandler();
    for(String newAccessPermission : accessPermissions){
      if(pageAccessPermissions.contains(newAccessPermission)) continue;
      User currentUser = userHandler.findUserByName(newAccessPermission);
      if (currentUser == null) {
        PageList<User> listUsers = userHandler.findUsersByGroup(newAccessPermission.split(":")[1]);
        List<User> usersTmp = listUsers.getAll();
        for (User userTmp: usersTmp) {
        	users.add(userTmp);
        }
      } else
        users.add(currentUser);
      for(User user : users){
        membershipHandler.linkMembership(userHandler.findUserByName(user.getUserName()), group, membershipType, true);
      }
      pageAccessPermissions.add(newAccessPermission);
    }
    ((ComponentRequestLifecycle) organizationService).endRequest(manager);
    page.setAccessPermissions(pageAccessPermissions.toArray(new String[]{}));
    dataStorage.save(page);
  }
  
  /**
   * Remove access  permissions 
   * @param accessPermissions   list of user will be set access permission
   * @throws Exception          The exception
   */
  public static void removeAccessPermission(String[] removedPermissions) throws Exception {
    OrganizationService organizationService = WCMCoreUtils.getService(OrganizationService.class) ;
    ((ComponentRequestLifecycle) organizationService).startRequest(manager);
    MembershipHandler memberShipHandler = organizationService.getMembershipHandler();
    
    DataStorage dataStorage = WCMCoreUtils.getService(DataStorage.class);
    Page page = dataStorage.getPage(Util.getUIPortal().getSelectedNode().getPageReference());
    List<String> pageAccessPermissions = new ArrayList<String>(Arrays.asList(page.getAccessPermissions()));
    for(String removedPermission : removedPermissions){
      if(pageAccessPermissions.contains(removedPermission)) {
        pageAccessPermissions.remove(removedPermission);
        memberShipHandler.removeMembershipByUser(removedPermission, true);
      }
    }
    ((ComponentRequestLifecycle) organizationService).endRequest(manager);
    page.setAccessPermissions(pageAccessPermissions.toArray(new String[]{}));
    dataStorage.save(page);
  }
  
  public static NodeIterator getAllCategories(Session session) throws Exception {
    QueryManager queryManager = session.getWorkspace().getQueryManager();
    String sqlQuery = "Select * from " + NewsletterConstant.CATEGORY_NODETYPE;
    Query query = queryManager.createQuery(sqlQuery, Query.SQL);
    QueryResult queryResult = query.execute();
    return queryResult.getNodes();
  }
  
  /**
   * Get all redactors in all subscriptions of newsletter
   * @param portalName        name of portal
   * @param sessionProvider   The SessionProvider
   * @return                  List of redactor
   * @throws Exception        The exception
   */
  public static List<String> getAllRedactor(String portalName, Session session)throws Exception{
    List<String> list = new ArrayList<String>();
    String path = NewsletterConstant.generateCategoryPath(portalName);
    QueryManager queryManager = session.getWorkspace().getQueryManager();
    String sqlQuery = "select * from " + NewsletterConstant.SUBSCRIPTION_NODETYPE + 
                      " where jcr:path LIKE '" + path + "[%]/%'";
    Query query = queryManager.createQuery(sqlQuery, Query.SQL);
    QueryResult queryResult = query.execute();
    Node subNode;
    for(NodeIterator nodeIterator = queryResult.getNodes(); nodeIterator.hasNext();){
      subNode = nodeIterator.nextNode();
      for(String str : NewsletterConstant.getAllPermissionOfNode(subNode)){
        if(list.contains(str)) continue;
        list.add(str);
      }
    }
    return list;
  }
  
  public static List<String> removePermission(Node subscriptionNode,
                                        Node categoryNode,
                                        List<String> candidateRemove,
                                        boolean isAddNew,
                                        String portalName,
                                        Session session) {
    if(candidateRemove == null) candidateRemove = new ArrayList<String>();
    try {
     NodeIterator categoriesIterator = NewsletterConstant.getAllCategories(session);
     List<String> allRedactor = NewsletterConstant.getAllRedactor(portalName, session);
     List<String> allpermissions = null;
     while(categoriesIterator.hasNext()) {
       allpermissions = NewsletterConstant.getAllPermissionOfNode(categoriesIterator.nextNode());
       for(String permission : allpermissions) {
         if(candidateRemove.isEmpty()) break;
         if(candidateRemove.contains(permission)) {
           candidateRemove.remove(permission);
         } 
       }
     }
     for(String redactor : allRedactor) {
       if(candidateRemove.isEmpty()) break;
       if(candidateRemove.contains(redactor)) {
         candidateRemove.remove(redactor);
       }
     }
    } catch (Exception e) {
      return new ArrayList<String>();
    }
    return candidateRemove;
  }
  
  public static List<String> convertValuesToArray(Value[] values){
    List<String> listString = new ArrayList<String>();
    for(Value value : values){
      try {
        listString.add(value.getString());
      }catch (Exception e) {
        return null;
      }
    }
    return listString;
  }
  
  
  
  
  
  
  /**
   * Check current user has permission to access a node or not 
   * -  List all node's permissions
   * -    For each node's permissions, get AccessControlEntries
   * -      For each AccessControlEntries, compare with user's permissions
   * -        If AccessControlEntry has membership type is "*", just check the user's group id only
   * -        If AccessControlEntry has other membership types, then check the user's membership type and user's group id
   * -          If user have full access (READ, ADD_NODE, SET_PROPERTY, REMOVE) return true
   * -          Otherwise return false
   * - Other case, return false
   *  
   * @param userId the current user's name
   * @param node the current node
   * 
   * @return the list of newsletter category object 
   */
  public static boolean hasPermission(String userId, Node node) {
    try {
      ExtendedNode categoryNode = (ExtendedNode)node;
      AccessControlEntry accessControlEntry = null;
      Iterator<?> permissionIterator = categoryNode.getACL().getPermissionEntries().iterator();
      List<String> permissions = new ArrayList<String>();
      while (permissionIterator.hasNext()) {
        accessControlEntry = (AccessControlEntry) permissionIterator.next();
        permissions.add(accessControlEntry.getIdentity()); 
      }
      return WCMCoreUtils.hasPermission(userId, permissions, true);
    } catch (Exception e) {
      log.error("Exception when call NewsletterConstant.hasPermission()", e);
    }
    return false;
  }
  
}
