/*
 * 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.social.core.manager;

import org.apache.commons.lang3.StringUtils;

import org.exoplatform.commons.utils.ListAccess;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.social.core.identity.ConnectionFilterListAccess;
import org.exoplatform.social.core.identity.ConnectionListAccess;
import org.exoplatform.social.core.identity.model.Identity;
import org.exoplatform.social.core.profile.ProfileFilter;
import org.exoplatform.social.core.relationship.RelationshipLifeCycle;
import org.exoplatform.social.core.relationship.RelationshipListener;
import org.exoplatform.social.core.relationship.RelationshipListenerPlugin;
import org.exoplatform.social.core.relationship.model.Relationship;
import org.exoplatform.social.core.relationship.model.Relationship.Type;
import org.exoplatform.social.core.search.Sorting.SortBy;
import org.exoplatform.social.core.storage.api.IdentityStorage;
import org.exoplatform.social.core.storage.api.RelationshipStorage;
import org.exoplatform.social.core.storage.RelationshipStorageException;

import java.util.*;

/**
 * The Class RelationshipManager implements RelationshipManager without caching.
 * 
 * @since Nov 24, 2010
 * @version 1.2.0-GA
 */
public class RelationshipManagerImpl implements RelationshipManager {
  /** The activityStorage. */
  protected RelationshipStorage   storage;

  protected IdentityStorage identityStorage;

  /**
   * lifecycle of a relationship.
   */
  protected RelationshipLifeCycle lifeCycle = new RelationshipLifeCycle();

  private IdentityManager identityManager;
  
  /**
   * The default offset when get list identities with connection list access.
   * 
   * @since 1.2.0-Beta3
   */
  protected static final int OFFSET = 0;
  
  /**
   * The default limit when get list identities with connection list access.
   * 
   * @since 1.2.0-Beta3
   */
  protected static final int LIMIT = 200;

  /**
   * Instantiates a new relationship manager.
   * 
   * @param relationshipStorage
   */
  public RelationshipManagerImpl(IdentityStorage identityStorage, RelationshipStorage relationshipStorage) {
    this.storage = relationshipStorage;
    this.identityStorage = identityStorage;
  }

  /**
   * {@inheritDoc}
   */
  public Relationship get(String relationshipId) {
    Relationship relationship = null;
    try {
      relationship = storage.getRelationship(relationshipId);
    } catch (Exception e) {
      return null;
    }
    return relationship;
  }

  /**
   * {@inheritDoc}
   */
  public Relationship invite(Identity sender, Identity receiver) throws RelationshipStorageException {
    return this.inviteToConnect(sender, receiver);
  }

  /**
   * {@inheritDoc}
   */
  public void save(Relationship relationship) throws RelationshipStorageException {
    this.update(relationship);
  }

  /**
   * {@inheritDoc}
   */
  public void confirm(Relationship relationship) throws RelationshipStorageException {
    this.confirm(relationship.getReceiver(), relationship.getSender());
  }

  /**
   * {@inheritDoc}
   */
  public void deny(Relationship relationship) throws RelationshipStorageException {
    this.deny(relationship.getReceiver(), relationship.getSender());
  }

  /**
   * {@inheritDoc}
   */
  public void remove(Relationship relationship) throws RelationshipStorageException {
    this.delete(relationship);
  }

  /**
   * {@inheritDoc}
   */
  public void ignore(Relationship relationship) throws RelationshipStorageException {
    this.ignore(relationship.getSender(), relationship.getReceiver());
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getPending(Identity sender) throws RelationshipStorageException {
    return getSender(sender, Relationship.Type.PENDING, null);
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getPending(Identity sender, List<Identity> identities) throws RelationshipStorageException {
    return getSender(sender, Relationship.Type.PENDING, identities);
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getIncoming(Identity receiver) throws RelationshipStorageException {
    return getReceiver(receiver, Relationship.Type.PENDING, null);
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getIncoming(Identity receiver, List<Identity> identities) throws RelationshipStorageException {
    return getReceiver(receiver, Relationship.Type.PENDING, identities);
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getConfirmed(Identity identity) throws RelationshipStorageException {
    return getRelationships(identity, Relationship.Type.CONFIRMED, null);
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getConfirmed(Identity identity, List<Identity> identities) throws RelationshipStorageException {
    return getRelationships(identity, Relationship.Type.CONFIRMED, identities);
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getAll(Identity identity) throws RelationshipStorageException {
    return getRelationships(identity, Type.ALL, null);
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getAll(Identity identity, List<Identity> identities) throws RelationshipStorageException {
    return getRelationships(identity, Type.ALL, identities);
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getAll(Identity identity, Relationship.Type type,
                                                List<Identity> identities) throws RelationshipStorageException {
    return getRelationships(identity, type, identities);
  }

  /**
   * {@inheritDoc}
   */
  public Relationship get(Identity identity1, Identity identity2) throws RelationshipStorageException {
    return storage.getRelationship(identity1, identity2);
  }

  /**
   * {@inheritDoc}
   */
  public Relationship.Type getStatus(Identity identity1, Identity identity2) throws RelationshipStorageException {
    Relationship relationship = get(identity1, identity2);
    return relationship == null ? null : relationship.getStatus();
  }

  /**
   * Registers a RelationshipLister.
   *
   * @param listener
   */
  public void registerListener(RelationshipListener listener) {
    lifeCycle.addListener(listener);
  }

  /**
   * Unregisters a RelationshipLister.
   *
   * @param listener
   */
  public void unregisterListener(RelationshipListener listener) {
    lifeCycle.removeListener(listener);
  }

  /**
   * Adds a RelationshipListenerPlugin.
   *
   * @param plugin
   */
  public void addListenerPlugin(RelationshipListenerPlugin plugin) {
    registerListener(plugin);
  }

  /**
   * Gets the relationship activityStorage.
   *
   * @return storage
   */
  protected RelationshipStorage getStorage() {
    return storage;
  }

  /**
   * Sets the relationship activityStorage.
   *
   * @param storage
   */
  protected void setStorage(RelationshipStorage storage) {
    this.storage = storage;
  }

  /**
   * Gets the relationship life cycle.
   *
   * @return lifeCycle
   */
  protected RelationshipLifeCycle getLifeCycle() {
    return lifeCycle;
  }

  /**
   * Sets thre relationship life cycle.
   *
   * @param lifeCycle
   */
  protected void setLifeCycle(RelationshipLifeCycle lifeCycle) {
    this.lifeCycle = lifeCycle;
  }

  /**
   * Get relationships by identity and status from cache or activityStorage
   * 
   * @param identity
   * @param identities
   * @return list of relationship
   * @throws RelationshipStorageException
   */
  protected List<Relationship> getRelationships(Identity identity, Relationship.Type type,
                                                List<Identity> identities) throws RelationshipStorageException {
    return storage.getRelationships(identity, type, identities);
  }

  /**
   * Get relationships by identity and status and identities from activityStorage
   * 
   * @param sender
   * @return list of relationship
   * @throws RelationshipStorageException
   */
  protected List<Relationship> getSender(Identity sender, Relationship.Type type,
                                         List<Identity> identities) throws RelationshipStorageException {
    return storage.getSenderRelationships(sender, type, identities);
  }

  /**
   * Get relationships by identity and status from cache or activityStorage
   * 
   * @param receiver
   * @param identities
   * @return list of relationship
   * @throws RelationshipStorageException
   */
  protected List<Relationship> getReceiver(Identity receiver, Relationship.Type type,
                                           List<Identity> identities) throws RelationshipStorageException {
    return storage.getReceiverRelationships(receiver, type, identities);
  }

  /**
   * {@inheritDoc}
   */
  public Relationship create(Identity sender, Identity receiver) {
    return new Relationship(sender, receiver);
  }

  /**
   * {@inheritDoc}
   */
  public List<Identity> findRelationships(Identity ownerIdentity, Type relationshipType) throws RelationshipStorageException {
    List<Relationship> allRelationships = getAll(ownerIdentity, relationshipType, null);
    List<Identity> identities = new ArrayList<Identity>();
    if (allRelationships == null || allRelationships.size() == 0) {
      return identities;
    }
    for(Relationship relationship : allRelationships) {
      identities.add(relationship.getPartner(ownerIdentity));
    }
    return identities;
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> findRoute(Identity sender, Identity receiver) throws RelationshipStorageException {
    List<Relationship> route = new ArrayList<Relationship>();
    route.add(get(sender,receiver));
    return route;
  }

  /**
   * {@inheritDoc}
   */
  public Type getConnectionStatus(Identity fromIdentity, Identity toIdentity) throws Exception {
    return getStatus(fromIdentity, toIdentity);
  }
  
  /**
   * {@inheritDoc}
   */
  public ListAccess<Identity> getConnections(Identity identity) {
    return (new ConnectionListAccess(storage, identity, ConnectionListAccess.Type.CONNECTION));
  }
  
  /**
   * {@inheritDoc}
   */
  public List<Relationship> getContacts(Identity currIdentity, List<Identity> identities) throws RelationshipStorageException {
    return getAll(currIdentity, Type.CONFIRMED, identities);
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getContacts(Identity identity) throws RelationshipStorageException {
    return getAll(identity, Type.CONFIRMED, null);
  }

  /**
   * {@inheritDoc}
   */
  public List<Identity> getIdentities(Identity id) throws Exception {
    return Arrays.asList(this.getConnections(id).load(OFFSET, LIMIT));
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getPendingRelationships(Identity identity) throws RelationshipStorageException {
    return getPending(identity);
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getPendingRelationships(Identity identity, boolean toConfirm) throws RelationshipStorageException {
    return getAll(identity, Type.PENDING, null);
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getPendingRelationships(Identity currIdentity, List<Identity> identities,
                                                    boolean toConfirm) throws RelationshipStorageException {
    return getAll(currIdentity, Type.PENDING, identities);
  }

  /**
   * {@inheritDoc}
   */
  public Relationship getRelationship(Identity sender, Identity receiver) throws RelationshipStorageException {
    return get(sender, receiver);
  }

  /**
   * {@inheritDoc}
   */
  public Relationship getRelationshipById(String id) throws RelationshipStorageException {
    return get(id);
  }

  /**
   * {@inheritDoc}
   */
  public Type getRelationshipStatus(Relationship rel, Identity id) {
    return rel.getStatus();
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getRelationshipsByIdentityId(String id) throws RelationshipStorageException {
    return getAll(getIdentityManager().getIdentity(id));
  }

  private IdentityManager getIdentityManager() {
    if (identityManager == null) {
      identityManager = (IdentityManager) PortalContainer.getInstance().getComponentInstanceOfType(IdentityManager.class);
    }
    return identityManager;
  }

  /**
   * {@inheritDoc}
   */
  public void saveRelationship(Relationship relationship) throws RelationshipStorageException {
    save(relationship);
  }

  /**
   * {@inheritDoc}
   */
  public List<Relationship> getAllRelationships(Identity identity) throws RelationshipStorageException {
    return getAll(identity);
  }

  /**
   * {@inheritDoc}
   */
  public void confirm(Identity invitedIdentity, Identity invitingIdentity) {
    Relationship relationship = get(invitedIdentity, invitingIdentity);
    if (relationship != null && relationship.getStatus() == Relationship.Type.PENDING) {
      relationship.setStatus(Relationship.Type.CONFIRMED);
      this.update(relationship);
      lifeCycle.relationshipConfirmed(this, relationship);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void delete(Relationship existingRelationship) {
    storage.removeRelationship(existingRelationship);
    lifeCycle.relationshipRemoved(this, existingRelationship);
  }

  /**
   * {@inheritDoc}
   */
  public void deny(Identity invitedIdentity, Identity invitingIdentity) {
    Relationship relationship = this.get(invitedIdentity, invitingIdentity);
    if (relationship != null) {
      //    relationship.setStatus(Relationship.Type.IGNORED);
      //  save(relationship);
      // TODO: now just remove, implement later
      storage.removeRelationship(relationship);
      lifeCycle.relationshipDenied(this, relationship);
    }
  }

  /**
   * {@inheritDoc}
   */
  public ListAccess<Identity> getAllWithListAccess(Identity existingIdentity) {
    return new ConnectionListAccess(this.storage, existingIdentity, ConnectionListAccess.Type.ALL);
  }

  /**
   * {@inheritDoc}
   */
  public ListAccess<Identity> getIncomingWithListAccess(Identity existingIdentity) {
    return new ConnectionListAccess(this.storage, existingIdentity, ConnectionListAccess.Type.INCOMING);
  }

  /**
   * {@inheritDoc}
   */
  public ListAccess<Identity> getOutgoing(Identity existingIdentity) {
    return new ConnectionListAccess(this.storage, existingIdentity, ConnectionListAccess.Type.OUTGOING);
  }

  /**
   * {@inheritDoc}
   */
  public void ignore(Identity sender, Identity receiver) {
    Relationship relationship = this.get(sender, receiver);
    if (relationship == null || !Type.IGNORED.equals(relationship.getStatus())) {
      relationship = new Relationship(sender, receiver);
      relationship.setStatus(Type.IGNORED);
      save(relationship);
      lifeCycle.relationshipIgnored(this, relationship);
    }
  }

  /**
   * {@inheritDoc}
   */
  public Relationship inviteToConnect(Identity invitingIdentity, Identity invitedIdentity) {
    Relationship relationship = get(invitingIdentity, invitedIdentity);
    if (relationship == null || Type.IGNORED.equals(relationship.getStatus())) {
      relationship = new Relationship(invitingIdentity, invitedIdentity);
      relationship.setStatus(Type.PENDING);
      this.update(relationship);
      lifeCycle.relationshipRequested(this, relationship);
    }
    return relationship;
  }

  /**
   * {@inheritDoc}
   */
  public void update(Relationship existingRelationship) {
    String senderId = existingRelationship.getSender().getId();
    String receiverId = existingRelationship.getReceiver().getId();
    if (senderId.equals(receiverId)) {
      throw new RelationshipStorageException(RelationshipStorageException.Type.FAILED_TO_SAVE_RELATIONSHIP,
                                             "the two identity are the same");
    }
    storage.saveRelationship(existingRelationship);
  }

  public ListAccess<Identity> getConnectionsByFilter(Identity existingIdentity, ProfileFilter profileFilter) {
    profileFilter = checkSortingOnProfileFilter(profileFilter);
    return new ConnectionFilterListAccess(identityStorage, this.storage, existingIdentity, profileFilter,
                                          ConnectionFilterListAccess.Type.PROFILE_FILTER_CONNECTION);
  }

  public ListAccess<Identity> getIncomingByFilter(Identity existingIdentity, ProfileFilter profileFilter) {
    profileFilter = checkSortingOnProfileFilter(profileFilter);
    return new ConnectionFilterListAccess(identityStorage, this.storage, existingIdentity, profileFilter,
                                          ConnectionFilterListAccess.Type.PROFILE_FILTER_INCOMMING);
  }

  public ListAccess<Identity> getOutgoingByFilter(Identity existingIdentity, ProfileFilter profileFilter) {
    profileFilter = checkSortingOnProfileFilter(profileFilter);
    return new ConnectionFilterListAccess(identityStorage, this.storage, existingIdentity, profileFilter,
                                          ConnectionFilterListAccess.Type.PROFILE_FILTER_OUTGOING);
  }

  public Map<Identity, Integer> getSuggestions(Identity identity, int maxConnections, 
                                                int maxConnectionsToLoad, int maxSuggestions) {
    return storage.getSuggestions(identity, maxConnections, maxConnectionsToLoad, 
                                                                maxSuggestions);
  }

  public Map<Identity, Integer> getSuggestions(Identity identity, int offset, int limit) {
    Map<Identity, Integer> result = storage.getSuggestions(identity, -1, -1, offset + limit);
    if (result != null && !result.isEmpty()) {
      if (offset > 0) {
        result = new LinkedHashMap<Identity, Integer>(result);
        int o = 0;
        for (Iterator<Map.Entry<Identity, Integer>> it = result.entrySet().iterator(); it.hasNext() && o < offset; o++) {
          it.next();
          it.remove();
        }
      }
      if (result.size() > limit) {
        result = new LinkedHashMap<Identity, Integer>(result);
        int i = 0;
        for (Iterator<Map.Entry<Identity, Integer>> it = result.entrySet().iterator(); it.hasNext(); i++) {
          it.next();
          if (i >= limit)
            it.remove();
        }
      }
    }
    return result;
  }

  @Override
  public List<Identity> getLastConnections(Identity identity, int limit) {
    return storage.getLastConnections(identity, limit);
  }
  
  /**
   * {@inheritDoc}
   */
  public List<Relationship> getRelationshipsByStatus(Identity identity, Relationship.Type type, int offset, int limit) {
    return storage.getRelationshipsByStatus(identity, type, offset, limit);
  }
  
  /**
   * {@inheritDoc}
   */
  public int getRelationshipsCountByStatus(Identity identity, Relationship.Type type) {
    return storage.getRelationshipsCountByStatus(identity, type);
  }

  private ProfileFilter checkSortingOnProfileFilter(ProfileFilter profileFilter) {
    if (profileFilter == null) {
      profileFilter = new ProfileFilter();
    }
    if (profileFilter.isSortingEmpty()) {
      profileFilter.setSorting(getIdentityManager().getDefaultSorting());
    }
    if (StringUtils.isBlank(profileFilter.getFirstCharFieldName())) {
      profileFilter.setFirstCharFieldName(getIdentityManager().getFirstCharacterFiltering());
    }
    return profileFilter;
  }

  @Override
  public int getConnectionsInCommonCount(Identity currentUserIdentity, Identity identity) {
    return storage.getConnectionsInCommonCount(currentUserIdentity, identity);
  }
}
