/*
 * 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.comments.impl;

import java.util.*;

import javax.jcr.*;

import org.exoplatform.services.cache.CacheService;
import org.exoplatform.services.cache.ExoCache;
import org.exoplatform.services.cms.comments.CommentsService;
import org.exoplatform.services.cms.i18n.MultiLanguageService;
import org.exoplatform.services.cms.jcrext.activity.ActivityCommonService;
import org.exoplatform.services.listener.ListenerService;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.services.organization.User;
import org.exoplatform.services.wcm.core.NodetypeConstant;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;

public class CommentsServiceImpl implements CommentsService {

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

  private static final String          CACHE_NAME         = "ecms.CommentsService";

  private final static String          COMMENTS           = "comments";

  private final static String          COMMENTABLE        = "mix:commentable";

  private final static String          EXO_COMMENTS       = "exo:comments";

  private final static String          NT_UNSTRUCTURE     = "nt:unstructured";

  private final static String          MESSAGE            = "exo:commentContent";

  private final static String          COMMENTOR          = "exo:commentor";

  private final static String          COMMENTOR_FULLNAME = "exo:commentorFullName";

  private final static String          COMMENTOR_EMAIL    = "exo:commentorEmail";

  private final static String          COMMENTOR_SITE     = "exo:commentorSite";

  private final static String          CREATED_DATE       = "exo:commentDate";

  private static final String          LANGUAGES          = "languages";

  private static final String          ANONYMOUS          = "anonymous";

  private ExoCache<String, List<Node>> commentsCache_;

  private MultiLanguageService         multiLangService_;

  private ListenerService              listenerService_;

  private ActivityCommonService        activityService;

  /**
   * Constructor Method
   * 
   * @param cacheService CacheService Object
   * @param multiLangService MultiLanguageService Object
   */
  public CommentsServiceImpl(CacheService cacheService,
                             MultiLanguageService multiLangService,
                             ListenerService listenerService)
      throws Exception {
    commentsCache_ = cacheService.getCacheInstance(CACHE_NAME);
    multiLangService_ = multiLangService;
    listenerService_ = listenerService;
    activityService = WCMCoreUtils.getService(ActivityCommonService.class);
  }

  /**
   * {@inheritDoc}
   */
  public void addComment(Node node, String commentor, String email, String site, String comment, String language)
                                                                                                                  throws Exception {
    Session session = node.getSession();
    try {
      Node document = (Node) session.getItem(node.getPath());
      if (!document.isNodeType(COMMENTABLE)) {
        if (document.canAddMixin(COMMENTABLE))
          document.addMixin(COMMENTABLE);
        else
          throw new Exception("This node does not support comments.");
      }
      Node multiLanguages = null, languageNode = null, commentNode = null;

      if (!document.hasNode(LANGUAGES) || language.equals(multiLangService_.getDefault(document))) {
        if (document.hasNode(COMMENTS))
          commentNode = document.getNode(COMMENTS);
        else {
          commentNode = document.addNode(COMMENTS, NT_UNSTRUCTURE);
          commentNode.addMixin("exo:hiddenable");
        }
      } else {
        multiLanguages = document.getNode(LANGUAGES);
        if (multiLanguages.hasNode(language)) {
          languageNode = multiLanguages.getNode(language);
        } else {
          languageNode = multiLanguages.addNode(language);
        }
        if (languageNode.hasNode(COMMENTS)) {
          commentNode = languageNode.getNode(COMMENTS);
        } else {
          commentNode = languageNode.addNode(COMMENTS, NT_UNSTRUCTURE);
          commentNode.addMixin("exo:hiddenable");
        }
      }

      if (commentor == null || commentor.length() == 0) {
        commentor = ANONYMOUS;
      }

      Calendar commentDate = new GregorianCalendar();
      String name = Long.toString(commentDate.getTimeInMillis());
      Node newComment = commentNode.addNode(name, EXO_COMMENTS);
      newComment.setProperty(COMMENTOR, commentor);

      OrganizationService organizationService = WCMCoreUtils.getService(OrganizationService.class);
      User user = organizationService.getUserHandler().findUserByName(commentor);

      if (user == null)
        newComment.setProperty(COMMENTOR_FULLNAME, "ANONYMOUS");
      else {
        String fullName = user.getDisplayName();
        if (fullName == null)
          fullName = user.getUserName();
        newComment.setProperty(COMMENTOR_FULLNAME, fullName);
      }
      newComment.setProperty(CREATED_DATE, commentDate);
      newComment.setProperty(MESSAGE, comment);
      if (email != null && email.length() > 0) {
        newComment.setProperty(COMMENTOR_EMAIL, email);
      }
      if (site != null && site.length() > 0) {
        newComment.setProperty(COMMENTOR_SITE, site);
      }
      document.save();
      if (listenerService_ != null) {
        try {
          if (activityService.isAcceptedNode(document)
              || (document.getPrimaryNodeType().getName().equals(NodetypeConstant.NT_FILE)
                  && activityService.isBroadcastNTFileEvents(document))) {
            listenerService_.broadcast(ActivityCommonService.COMMENT_ADDED_ACTIVITY, document, newComment);
          }
        } catch (Exception e) {
          if (LOG.isErrorEnabled()) {
            LOG.error("Can not notify CommentAddedActivity because of: " + e.getMessage());
          }
        }
      }
      commentsCache_.remove(commentNode.getPath());
    } catch (Exception e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("Unexpected problem happen when try to add comment", e);
      }
    }

  }

  /**
   * {@inheritDoc}
   */
  public void updateComment(Node commentNode, String newComment) throws Exception {
    Calendar commentDate = new GregorianCalendar();
    commentNode.setProperty(CREATED_DATE, commentDate);
    commentNode.setProperty(MESSAGE, newComment);
    commentNode.save();
    Node documentNode = commentNode.getParent().getParent();
    if (listenerService_ != null && activityService != null) {
      try {
        if (activityService.isAcceptedNode(documentNode) ||
            (documentNode.getPrimaryNodeType().getName().equals(NodetypeConstant.NT_FILE) &&
                activityService.isBroadcastNTFileEvents(documentNode))) {
          listenerService_.broadcast(ActivityCommonService.COMMENT_UPDATED_ACTIVITY, documentNode, commentNode);
        }
      } catch (Exception e) {
        if (LOG.isErrorEnabled()) {
          LOG.error("Can not notify CommentModifiedActivity because of: " + e.getMessage());
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public void deleteComment(Node commentNode) throws Exception {
    Node document = commentNode.getParent();
    commentNode.remove();
    document.save();
    if (listenerService_ != null && activityService != null) {
      Node parentNode = document.getParent();
      try {
        if (activityService.isAcceptedNode(parentNode) ||
            (parentNode.getPrimaryNodeType().getName().equals(NodetypeConstant.NT_FILE) &&
                activityService.isBroadcastNTFileEvents(parentNode))) {
          listenerService_.broadcast(ActivityCommonService.COMMENT_REMOVED_ACTIVITY, commentNode, null);
        }
      } catch (Exception e) {
        if (LOG.isErrorEnabled()) {
          LOG.error("Can not notify CommentRemovedActivity because of: " + e.getMessage());
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unchecked")
  public List<Node> getComments(Node document, String language) throws Exception {
    Node commentsNode = null;
    Node languagesNode = null;
    Node languageNode = null;
    if (!isSupportedLocalize(document, language)) {
      if (document.hasProperty("exo:language"))
        language = document.getProperty("exo:language").getString();
    }
    if (document.hasNode(LANGUAGES)) {
      languagesNode = document.getNode(LANGUAGES);
      if (languagesNode.hasNode(language)) {
        languageNode = languagesNode.getNode(language);
        if (languageNode.hasNode(COMMENTS))
          commentsNode = languageNode.getNode(COMMENTS);
      } else if (language.equals(multiLangService_.getDefault(document))) {
        languageNode = document;
      }
    } else {
      languageNode = document;
    }
    if (!languageNode.hasNode(COMMENTS))
      return new ArrayList<Node>();
    Session session = document.getSession();
    // TODO check if really need delegate to system session
    Session systemSession = WCMCoreUtils.getSystemSessionProvider()
                                        .getSession(session.getWorkspace().getName(),
                                                    WCMCoreUtils.getRepository());
    List<Node> list = new ArrayList<Node>();
    try {
      commentsNode = (Node) systemSession.getItem(languageNode.getPath() + "/" + COMMENTS);
      String cacheKey = document.getPath().concat(commentsNode.getPath());
      Object comments = commentsCache_.get(cacheKey);
      if (comments != null)
        return (List<Node>) comments;
      for (NodeIterator iter = commentsNode.getNodes(); iter.hasNext();) {
        list.add(iter.nextNode());
      }
      Collections.sort(list, new DateComparator());
      commentsCache_.put(commentsNode.getPath(), list);
    } catch (Exception e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("Unexpected problem happen when try to get comments", e);
      }
    }
    return list;
  }

  /**
   * This Class implements Comparator<Node> to compare the created date of
   * nodes.
   */
  private class DateComparator implements Comparator<Node> {

    /**
     * Compare the created date of nodes
     * 
     * @param node1 node is used to compare
     * @param node2 node is used to compare
     */
    public int compare(Node node1, Node node2) {
      try {
        Date date1 = node1.getProperty(CREATED_DATE).getDate().getTime();
        Date date2 = node2.getProperty(CREATED_DATE).getDate().getTime();
        return date2.compareTo(date1);
      } catch (Exception e) {
        return 0;
      }
    }
  }

  /**
   * Check language of comment is supported in a document
   * 
   * @param document The document node is commented
   * @param language The language of comment node
   * @throws Exception
   */
  private boolean isSupportedLocalize(Node document, String language) throws Exception {
    List<String> locales = multiLangService_.getSupportedLanguages(document);
    if (Collections.frequency(locales, language) > 0)
      return true;
    return false;
  }

}
