/*
 * Copyright (C) 2003-2011 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.exoplatform.social.service.rest;

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

import org.exoplatform.container.PortalContainer;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.social.core.activity.model.ActivityStream;
import org.exoplatform.social.core.activity.model.ExoSocialActivity;
import org.exoplatform.social.core.identity.model.Identity;
import org.exoplatform.social.core.identity.provider.OrganizationIdentityProvider;
import org.exoplatform.social.core.identity.provider.SpaceIdentityProvider;
import org.exoplatform.social.core.manager.IdentityManager;
import org.exoplatform.social.core.manager.RelationshipManager;
import org.exoplatform.social.core.relationship.model.Relationship;
import org.exoplatform.social.core.space.model.Space;
import org.exoplatform.social.core.space.spi.SpaceService;

/**
 * The security manager helper class for Social Rest APIs.
 *
 * @author <a href="http://hoatle.net">hoatle (hoatlevan at gmail dot com)</a>
 * @author <a href="http://phuonglm.net">phuonglm</a>
 * @since 1.2.0-GA
 * @since Jun 17, 2011
 */
public class SecurityManager {

  /**
   * The logger
   */
  private static Log LOG = ExoLogger.getLogger(SecurityManager.class);

  /**
   * <p>Checks if an authenticated remoteId of user can access an existing activity.</p>
   *
   * If the authenticated identity is the one who posted that existing activity, return true.<br />
   * If the existing activity belongs to that authenticated identity's activity stream, return true.<br />
   * If the existing activity belongs to that authenticated identity's connections' activity stream, return true.<br />
   * If the existing activity belongs to a space stream that the authenticated is a space member, return true.<br />
   * Otherwise, return false.
   *
   * @param portalContainer the specified portal container
   * @param userIdentityId the authenticated identity to check
   * @param existingActivity the existing activity to check
   * @return true or false
   */
  public static boolean canAccessActivity(PortalContainer portalContainer, String userIdentityId,
                                          ExoSocialActivity existingActivity) {

    IdentityManager identityManager = (IdentityManager) portalContainer.getComponentInstanceOfType(IdentityManager.class);
    
    Identity authenticateIdentity = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, userIdentityId, false);
    
    if(authenticateIdentity == null || existingActivity == null){
      return false;
    }
    else {
      return canAccessActivity(portalContainer, authenticateIdentity, existingActivity);
    }
    
  }

  /**
   * <p>Checks if an authenticated identity can access an existing activity.</p>
   *
   * If the authenticated identity is the one who posted that existing activity, return true.<br />
   * If the existing activity belongs to that authenticated identity's activity stream, return true.<br />
   * If the existing activity belongs to that authenticated identity's connections' activity stream, return true.<br />
   * If the existing activity belongs to a space stream that the authenticated is a space member, return true.<br />
   * Otherwise, return false.
   *
   * @param portalContainer the specified portal container
   * @param authenticatedIdentity the authenticated identity to check
   * @param existingActivity the existing activity to check
   * @return true or false
   */
  public static boolean canAccessActivity(PortalContainer portalContainer, Identity authenticatedIdentity,
                                          ExoSocialActivity existingActivity) {

    SpaceService spaceService = (SpaceService) portalContainer.getComponentInstanceOfType(SpaceService.class);
    RelationshipManager relationshipManager = (RelationshipManager) portalContainer.getComponentInstanceOfType(RelationshipManager.class);

    if(authenticatedIdentity == null || existingActivity == null){
      return false;
    }

    // My activity
    if (authenticatedIdentity.getId().equals(existingActivity.getUserId())) {
      return true;
    }

    switch (existingActivity.getActivityStream().getType()) {

      case SPACE:

        // member or manager
        String spaceName = existingActivity.getActivityStream().getPrettyId();
        Space space = spaceService.getSpaceByPrettyName(spaceName);
        List<String> allIds = new ArrayList<String>();
        allIds.addAll(Arrays.asList(space.getMembers()));
        allIds.addAll(Arrays.asList(space.getManagers()));
        if (allIds.contains(authenticatedIdentity.getRemoteId())) {
          return true;
        }

        break;

      case USER:

        // My stream
        if (authenticatedIdentity.getId().equals(existingActivity.getActivityStream().getId())) {
          return true;
        }

        // My netword stream
        String contactId = existingActivity.getActivityStream().getId();
        Relationship relationship = relationshipManager.get(authenticatedIdentity, new Identity(contactId));
        if (relationship != null && Relationship.Type.CONFIRMED.equals(relationship.getStatus())) {
          return true;
        }

        break;

    }

    return false;

  }
  
  /**
   * <p>Checks if an poster identity has the permission to post activities on an owner identity stream.</p>
   *
   * If posterIdentity is the same as ownerIdentityStream, return true.<br />
   * If ownerIdentityStream is a user identity, and poster identity is connected to owner identity stream, return true.
   * <br />
   * If ownerIdentityStream is a space identity, and poster identity is a member of that space, return true.<br />
   * Otherwise, return false.
   *
   * @param portalContainer the specified portal container
   * @param authenticatedIdentity  the authenticated identity to check
   * @param ownerIdentityStream the identity of an existing activity stream.
   * @return true or false
   */
  public static boolean canPostActivity(PortalContainer portalContainer, Identity authenticatedIdentity,
                                        Identity ownerIdentityStream) {
    SpaceService spaceService = (SpaceService) portalContainer.getComponentInstanceOfType(SpaceService.class);
    
    RelationshipManager relationshipManager = (RelationshipManager) portalContainer.getComponentInstanceOfType(RelationshipManager.class);    
    String posterID =  authenticatedIdentity.getId();
    String ownerID = ownerIdentityStream.getId();
    
    // if poserIdentity is the same as ownerIdentityStream, return true
    if(ownerID.equals(posterID)){
      return true;
    }
    
    // Check if owner identity stream is a user identity or space identity
    if(ownerIdentityStream.getProviderId().equals(SpaceIdentityProvider.NAME)){
      //if space identity, check if is a member of
      Space space = spaceService.getSpaceByPrettyName(ownerIdentityStream.getRemoteId());
      if(spaceService.isMember(space, authenticatedIdentity.getRemoteId()) ||
            spaceService.isManager(space, authenticatedIdentity.getRemoteId()) ){
        return true;
      }
    } else {
      // if user identity, check if connected
      Relationship relationship = relationshipManager.get(authenticatedIdentity, ownerIdentityStream);
      if(relationship!=null && Relationship.Type.CONFIRMED.equals(relationship.getStatus())){
        return true;
      }
    }
    return false;
  }

  
  /**
   * <p>Checks if an authenticated identity has the permission to delete an existing activity.</p>
   *
   * If the authenticated identity is the identity who creates that existing activity, return true.<br />
   * If the authenticated identity is the stream owner of that existing activity, return true. <br />
   * Otherwise, return false.
   *
   * @param portalContainer the specified portal container
   * @param authenticatedIdentity the identity to check
   * @param existingActivity the existing activity
   * @return true or false
   */
  public static boolean canDeleteActivity(PortalContainer portalContainer, Identity authenticatedIdentity,
                                          ExoSocialActivity existingActivity) {
    SpaceService spaceService = (SpaceService) portalContainer.getComponentInstanceOfType(SpaceService.class);
    IdentityManager identityManager = (IdentityManager) portalContainer.getComponentInstanceOfType(IdentityManager.class);

    String removerUserId =  authenticatedIdentity.getId();
    String ownerId = existingActivity.getUserId();    

    
    if(removerUserId.equals(ownerId)){
      return true;
    }
    
    String streamOwnerRemoteID = existingActivity.getStreamOwner();
    Identity streamOwnerIdentity = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, streamOwnerRemoteID, false);
    String streamOwnerId  = null;
    if(streamOwnerIdentity!=null){
      streamOwnerId = streamOwnerIdentity.getId();
    }
    
    if(removerUserId.equals(streamOwnerId)){
      return true;
    }
    
    Space spaceOfActivity = spaceService.getSpaceByPrettyName(existingActivity.getActivityStream().getPrettyId());
    if(spaceOfActivity != null){
      String[] adminsOfSpaceArray = spaceOfActivity.getManagers();
      
      for (int i = 0; i < adminsOfSpaceArray.length; i++) {
        if(adminsOfSpaceArray[i].equals(authenticatedIdentity.getRemoteId())){
          return true;
        }
      }
    }
    return false;
  }

  /**
   * <p>Checks if an authenticated identity has the permission to comment on an existing activity.</p>
   *
   * If commenterIdentity is the one who creates the existing activity, return true.<br />
   * If commenterIdentity is the one who is connected to existing activity's user identity, return true.<br />
   * If commenterIdentity is the one who is a member of the existing activity's space identity, return true.<br />
   * Otherwise, return false.
   *
   * @param portalContainer the specified portal container
   * @param authenticatedIdentity the authenticated identity to check
   * @param existingActivity the existing activity
   * @return true or false
   */
  public static boolean canCommentToActivity(PortalContainer portalContainer, Identity authenticatedIdentity,
                                       ExoSocialActivity existingActivity) {
    IdentityManager identityManager = (IdentityManager) portalContainer.getComponentInstanceOfType(IdentityManager.class);
    Identity ownerIdentityStream = identityManager.getIdentity(existingActivity.getUserId(), false);
    if(canPostActivity(portalContainer, authenticatedIdentity, ownerIdentityStream)){
      return true;
    }
    return false;
  }
  
  /**
   * <p>Checks if an authenticated identity has the permission to delete an existing comment.</p>
   *
   * If authenticatedIdentity is the one who creates the existing comment, return true.<br />
   * If authenticatedIdentity is the one who create the activity for that existing comment, return true.
   * If authenticatedIdentity is the one who is the stream owner of that comment to an activity, return true.<br />
   * If authenticatedIdentity is the one who is a manager of the existing activity's space identity, return true.<br />
   * Otherwise, return false.
   *
   * @param portalContainer the specified portal container
   * @param authenticatedIdentity the authenticated identity to check
   * @param existingComment the existing comment
   * @return true or false
   */
  public static boolean canDeleteComment(PortalContainer portalContainer, Identity authenticatedIdentity,
                                         ExoSocialActivity existingComment) {
    //TODO if the author of Activity which comment belong to want to delete comment return true
    
    if(canDeleteActivity(portalContainer, authenticatedIdentity, existingComment)){
      return true;
    }
    return false;
  }
  /**
   * <p>Gets the current logged in Identity, if not logged in return null</p>
   * @return logged in Identity or null
   * @since 1.2.2
   * @deprecated use {@link Util#getAuthenticatedUserIdentity(String)} instead.
   */
  public static Identity getAuthenticatedUserIdentity() {
    if(ConversationState.getCurrent()!=null && ConversationState.getCurrent().getIdentity() != null &&
              ConversationState.getCurrent().getIdentity().getUserId() != null){
      IdentityManager identityManager =  Util.getIdentityManager();
      String authenticatedUserRemoteId = ConversationState.getCurrent().getIdentity().getUserId(); 
      return identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, authenticatedUserRemoteId, false);
    } else {
      return null;
    }
  }

   /**
   * Checks if an authenticated identity could access the activity stream of an owner stream identity.
   * If the owner stream is a user identity, return true.
   * If the owner stream is a space identity, return true only if the authenticated identity is the space member.
   *
   * Note that: this can work only with access permission of user - user, user - space.
   * If there is other identity type, this will return true.
   *
   * @param portalContainer       the portal container
   * @param authenticatedIdentity the authenticated identity
   * @param ownerStream           the stream owner identity
   * @return true or false to indicate access permission
   * @author <a href="http://hoatle.net">hoatle (hoatlevan at gmail dot com)</a>
   * @since  1.2.3
   */
  public static boolean canAccessActivityStream(PortalContainer portalContainer, Identity authenticatedIdentity,
                                                Identity ownerStream) {
    if (ownerStream.getProviderId().equals(SpaceIdentityProvider.NAME)) {
      SpaceService spaceService = (SpaceService) portalContainer.getComponentInstanceOfType(SpaceService.class);
      Space targetSpace = spaceService.getSpaceByPrettyName(ownerStream.getRemoteId());
      if (targetSpace == null) {
        LOG.warn("targetSpace is null with prettyName: " + ownerStream.getRemoteId());
        return true;
      }
      return spaceService.isMember(targetSpace, authenticatedIdentity.getRemoteId()) ||
             spaceService.isManager(targetSpace, authenticatedIdentity.getRemoteId());
    }
    return true;
  }
}
