/*
 * Copyright (C) 2000 - 2024 Silverpeas
 *
 * 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.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception. You should have received a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "http://www.silverpeas.org/docs/core/legal/floss_exception.html"
 *
 * 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 <https://www.gnu.org/licenses/>.
 */
package org.silverpeas.components.gallery.control;

import org.silverpeas.components.gallery.GalleryComponentSettings;
import org.silverpeas.components.gallery.Watermark;
import org.silverpeas.components.gallery.constant.MediaResolution;
import org.silverpeas.components.gallery.constant.MediaType;
import org.silverpeas.components.gallery.delegate.GalleryPasteDelegate;
import org.silverpeas.components.gallery.delegate.MediaDataCreateDelegate;
import org.silverpeas.components.gallery.delegate.MediaDataUpdateDelegate;
import org.silverpeas.components.gallery.model.AlbumDetail;
import org.silverpeas.components.gallery.model.GalleryRuntimeException;
import org.silverpeas.components.gallery.model.InternalMedia;
import org.silverpeas.components.gallery.model.Media;
import org.silverpeas.components.gallery.model.MediaPK;
import org.silverpeas.components.gallery.model.MediaSelection;
import org.silverpeas.components.gallery.model.MetaData;
import org.silverpeas.components.gallery.model.Order;
import org.silverpeas.components.gallery.model.OrderRow;
import org.silverpeas.components.gallery.service.GalleryService;
import org.silverpeas.components.gallery.service.MediaServiceProvider;
import org.silverpeas.components.gallery.web.ExportOptionValue;
import org.silverpeas.components.gallery.web.MediaSort;
import org.silverpeas.core.admin.service.OrganizationController;
import org.silverpeas.core.admin.user.model.SilverpeasRole;
import org.silverpeas.core.admin.user.model.UserDetail;
import org.silverpeas.kernel.cache.model.SimpleCache;
import org.silverpeas.core.clipboard.ClipboardException;
import org.silverpeas.core.clipboard.ClipboardSelection;
import org.silverpeas.core.comment.model.Comment;
import org.silverpeas.core.comment.service.CommentService;
import org.silverpeas.core.comment.service.CommentServiceProvider;
import org.silverpeas.core.contribution.content.form.DataRecord;
import org.silverpeas.core.contribution.publication.model.Location;
import org.silverpeas.core.contribution.template.publication.PublicationTemplate;
import org.silverpeas.core.contribution.template.publication.PublicationTemplateException;
import org.silverpeas.core.contribution.template.publication.PublicationTemplateManager;
import org.silverpeas.core.importexport.ExportDescriptor;
import org.silverpeas.core.importexport.ExportException;
import org.silverpeas.core.importexport.ImportExportDescriptor;
import org.silverpeas.core.importexport.report.ExportReport;
import org.silverpeas.core.index.indexing.model.FieldDescription;
import org.silverpeas.core.index.search.model.QueryDescription;
import org.silverpeas.core.node.model.NodeDetail;
import org.silverpeas.core.node.model.NodePK;
import org.silverpeas.core.node.model.NodeSelection;
import org.silverpeas.core.notification.NotificationException;
import org.silverpeas.core.notification.message.MessageNotifier;
import org.silverpeas.core.notification.user.client.NotificationMetaData;
import org.silverpeas.core.notification.user.client.NotificationParameters;
import org.silverpeas.core.notification.user.client.NotificationSender;
import org.silverpeas.core.notification.user.client.UserRecipient;
import org.silverpeas.core.pdc.pdc.model.SearchContext;
import org.silverpeas.core.subscription.service.ComponentSubscriptionResource;
import org.silverpeas.core.subscription.service.NodeSubscriptionResource;
import org.silverpeas.core.ui.DisplayI18NHelper;
import org.silverpeas.core.util.Link;
import org.silverpeas.kernel.bundle.LocalizationBundle;
import org.silverpeas.kernel.bundle.ResourceLocator;
import org.silverpeas.kernel.bundle.SettingBundle;
import org.silverpeas.kernel.util.StringUtil;
import org.silverpeas.core.util.URLUtil;
import org.silverpeas.core.util.WebEncodeHelper;
import org.silverpeas.core.util.file.FileRepositoryManager;
import org.silverpeas.core.util.file.FileServerUtils;
import org.silverpeas.kernel.logging.SilverLogger;
import org.silverpeas.core.web.mvc.controller.AbstractComponentSessionController;
import org.silverpeas.core.web.mvc.controller.ComponentContext;
import org.silverpeas.core.web.mvc.controller.MainSessionController;
import org.silverpeas.core.web.subscription.SubscriptionContext;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.silverpeas.core.cache.service.CacheAccessorProvider.getSessionCacheAccessor;

public final class GallerySessionController extends AbstractComponentSessionController {

  private static final String SUBJECT_NOTIF = "gallery.orderNotifAskSubject";
  private String currentAlbumId = "0";
  private AlbumDetail currentAlbum = getAlbum(currentAlbumId);
  private int rang = 0;
  private MediaResolution displayedMediaResolution = MediaResolution.SMALL;
  private MediaSort sort = MediaSort.CreationDateDesc;
  private String currentOrderId = "0";
  private List<String> listSelected = new ArrayList<>();
  private boolean isViewNotVisible = false;
  // manage search keyword
  private String searchKeyWord = "";
  private List<Media> searchResultListMedia = new ArrayList<>();
  private List<Media> restrictedListMedia = new ArrayList<>();
  private boolean isSearchResult = false;
  private QueryDescription query = new QueryDescription();
  private SearchContext pdcSearchContext;
  private DataRecord xmlSearchContext;
  // select/deselect all
  private boolean select = false;
  private transient LocalizationBundle metadataResources = null;
  private transient CommentService commentService = null;
  // pagination de la liste des résultats (PDC via DomainsBar)
  private int indexOfCurrentPage = 0;
  private int nbMediasPerPage = GalleryComponentSettings.getNbMediaDisplayedPerPage();
  // manage basket case (contains list of media identifier)
  private final List<String> basket = new ArrayList<>();
  private static final SettingBundle DEFAULT_SETTINGS =
      ResourceLocator.getSettingBundle("org.silverpeas.gallery.settings.metadataSettings");
  private static final String MULTILANG_GALLERY_BUNDLE =
      "org.silverpeas.gallery.multilang.galleryBundle";

  /**
   * Standard Session Controller Constructeur
   *
   * @param mainSessionCtrl  The user's profile
   * @param componentContext The component's profile
   */
  public GallerySessionController(MainSessionController mainSessionCtrl,
      ComponentContext componentContext) {
    super(mainSessionCtrl, componentContext, MULTILANG_GALLERY_BUNDLE,
        "org.silverpeas.gallery.settings.galleryIcons",
        "org.silverpeas.gallery.settings.gallerySettings");
    String albumResolution = getComponentParameterValue("albumResolution");
    MediaResolution mediaResolution = StringUtil.isDefined(albumResolution) ?
        MediaResolution.fromNameOrLabel(albumResolution) :
        MediaResolution.SMALL;
    setDisplayedMediaResolution(mediaResolution);
  }

  /**
   * Gets the business service of operations on comments
   *
   * @return a DefaultCommentService instance.
   */
  private CommentService getCommentService() {
    if (commentService == null) {
      commentService = CommentServiceProvider.getCommentService();
    }
    return commentService;
  }

  public List<Media> getLastRegisteredMedia() {
    try {
      return getMediaService().getLastRegisteredMedia(getComponentId());
    } catch (Exception e) {
      throw new GalleryRuntimeException(e);
    }
  }

  public List<Comment> getAllComments(Media media) {
    return getCommentService().getAllCommentsOnResource(media.getContributionType(),
        media.getIdentifier().toReference());
  }

  public int getSilverObjectId(String objectId) {
    int silverObjectId = -1;
    try {
      silverObjectId = getMediaService().getSilverObjectId(
          new MediaPK(objectId, getSpaceId(), getComponentId()));
    } catch (Exception e) {
      SilverLogger.getLogger(this).error(e);
    }
    return silverObjectId;
  }

  public AlbumDetail goToAlbum() {
    return currentAlbum;
  }

  public AlbumDetail goToAlbum(String albumId) {
    setCurrentAlbumId(albumId);
    return currentAlbum;
  }

  public AlbumDetail loadCurrentAlbum() {
    return goToAlbum(currentAlbumId);
  }

  public AlbumDetail getAlbum(String albumId) {
    NodePK nodePK = new NodePK(albumId, getComponentId());
    AlbumDetail album;
    try {
      album = getMediaService().getAlbum(nodePK);
      sort(album.getMedia());
    } catch (Exception e) {
      throw new GalleryRuntimeException(e);
    }
    return album;
  }

  public AlbumDetail getAlbumLight(String albumId) {
    NodePK nodePK = new NodePK(albumId, getComponentId());
    AlbumDetail album;
    try {
      album = getMediaService().getAlbum(nodePK);
    } catch (Exception e) {
      throw new GalleryRuntimeException(e);
    }
    return album;
  }

  public Collection<Media> getNotVisible() {
    Collection<Media> media;
    try {
      media = getMediaService().getNotVisible(getComponentId());
      setRestrictedListMedia(media);
      return media;
    } catch (Exception e) {
      throw new GalleryRuntimeException(e);
    }
  }

  public Collection<AlbumDetail> getAllAlbums() {
    try {
      return getMediaService().getAllAlbums(getComponentId());
    } catch (Exception e) {
      throw new GalleryRuntimeException(e);
    }
  }

  public void setMediaToAlbums(String mediaId, String[] albums) {
    if (albums != null) {
      Media media = getMediaService().getMedia(new MediaPK(mediaId, getComponentId()));
      if (media != null) {
        media.setToAlbums(albums);
        MessageNotifier.addSuccess(getString("gallery.media.path.choose.success"));
      }
    }
  }

  public void addMediaToAlbums(String mediaId, String[] albums) {
    if (albums != null) {
      Media media = getMediaService().getMedia(new MediaPK(mediaId, getComponentId()));
      if (media != null) {
        media.addToAlbums(albums);
      }
    }
  }

  public void createAlbum(AlbumDetail album) {
    album.setCreationDate(new Date());
    album.setCreatorId(getUserId());
    album.getNodePK().setComponentName(getComponentId());

    getMediaService().createAlbum(album, new NodePK(currentAlbumId, getComponentId()));

    // Reloading the album.
    loadCurrentAlbum();
  }

  public void updateAlbum(AlbumDetail album) {
    getMediaService().updateAlbum(album);

    // Reloading the album.
    loadCurrentAlbum();
  }

  public void deleteAlbum(String albumId) {
    try {
      final String parentId = getAlbumLight(albumId).getFatherPK().getId();
      getMediaService().deleteAlbum(getUserDetail(), getComponentId(),
          getAlbum(albumId).getNodePK());
      goToAlbum(parentId);
    } catch (Exception e) {
      throw new GalleryRuntimeException(e);
    }
  }

  public LocalizationBundle getMetadataResources() {
    if (metadataResources == null) {
      metadataResources =
          ResourceLocator.getLocalizationBundle("org.silverpeas.gallery.multilang.metadataBundle",
              getLanguage());
    }
    return metadataResources;
  }

  public Media getMedia(String mediaId) {
    Media media = getMediaById(mediaId);

    // Getting current album identifiers with which the media is linked.
    Collection<String> albumIds = getMediaService().getAlbumIdsOf(media);


    // regarder si l'album courant est dans la liste des albums
    boolean inAlbum = albumIds.contains(currentAlbumId);
    String firstAlbumId = albumIds.isEmpty() ? "" : albumIds.iterator().next();
    if (!inAlbum) {
      setCurrentAlbumId(firstAlbumId);
    }


    // Updating the rank of the media.
    List<Media> mediaOfCurrentAlbum = currentAlbum.getMedia();
    rang = mediaOfCurrentAlbum.indexOf(media);

    return media;
  }

  public Media getPrevious() {
    return getPreviousOrNext(true);
  }

  public Media getNext() {
    return getPreviousOrNext(false);
  }

  private Media getPreviousOrNext(boolean isPreviousRequired) {
    int offset = isPreviousRequired ? -1 : 1;
    Media media;
    try {

      // Finding the rank of the media
      int newRang = rang + offset;
      media = currentAlbum.getMedia().get(newRang);

      // Updating the rank of the current media.
      rang = newRang;

    } catch (Exception e) {
      throw new GalleryRuntimeException(e);
    }
    return media;
  }

  /**
   * Creating one media (just only one)
   *
   * @param delegate
   * @return
   */
  public synchronized String createMedia(final MediaDataCreateDelegate delegate) {
    try {
      // Persisting data
      Media createdMedia = getMediaService().createMedia(getUserDetail(), getComponentId(),
          GalleryComponentSettings.getWatermark(getComponentId()), delegate);

      // recharger l'album courant pour prendre en comptes le nouveau média
      goToAlbum(currentAlbumId);

      return createdMedia.getId();
    } catch (Exception e) {
      throw new GalleryRuntimeException(e);
    }
  }

  /**
   * Updating one media (just only one)
   *
   * @param mediaId
   * @param delegate
   * @throws Exception
   */
  public void updateMediaByUser(final String mediaId, final MediaDataUpdateDelegate delegate) {
    // Persisting data
    getMediaService().updateMedia(getUserDetail(), getComponentId(), getMedia(mediaId),
        getWatermark(), delegate);
  }

  /**
   * Updating several media (no file have to be handled)
   *
   * @param mediaIds
   * @param delegate
   * @throws Exception
   */
  public void updateMediaByUser(final Collection<String> mediaIds,
      final MediaDataUpdateDelegate delegate) {
    if (mediaIds != null) {
      // Persisting data
      getMediaService().updateMedia(getUserDetail(), getComponentId(), mediaIds,
          getCurrentAlbumId(), delegate);
    }
  }

  public void updateMedia(final Media media) {
    try {
      getMediaService().updateMedia(getUserDetail(), getComponentId(), media, getWatermark(), null);

      // Reloading the current album.
      loadCurrentAlbum();

    } catch (Exception e) {
      throw new GalleryRuntimeException(e);
    }
  }

  public void deleteMedia(String mediaId) {
    deleteMedia(Collections.singletonList(mediaId));
  }

  public void deleteMedia(final Collection<String> mediaIds) {
    try {
      // Deleting the media.
      getMediaService().deleteMedia(getUserDetail(), getComponentId(), mediaIds);

    } catch (Exception e) {
      throw new GalleryRuntimeException(e);
    }

    // Reloading the album to take into account the deletion.
    loadCurrentAlbum();
  }

  private static GalleryService getMediaService() {
    return MediaServiceProvider.getMediaService();
  }

  public List<NodeDetail> getPath(NodePK nodePK) {
    List<NodeDetail> path = (List<NodeDetail>) getMediaService().getPath(nodePK);
    Collections.reverse(path);
    return path;
  }

  public Collection<NodeDetail> getPath() {
    return getPath(new NodePK(getCurrentAlbumId(), getComponentId()));
  }

  public synchronized Collection<String> getAlbumIdsOf(String mediaId) {
    return getMediaService().getAlbumIdsOf(getMediaById(mediaId));
  }

  public int getNbMediaPerPage() {
    return nbMediasPerPage;
  }

  public void setNbMediasPerPage(int nb) {
    nbMediasPerPage = nb;
  }

  public MediaResolution getDisplayedMediaResolution() {
    return displayedMediaResolution;
  }

  public void setDisplayedMediaResolution(MediaResolution displayedMediaResolution) {
    this.displayedMediaResolution = displayedMediaResolution;
  }

  public MediaSort getSort() {
    return sort;
  }

  public void setSort(MediaSort sort) {
    this.sort = sort;
    sortMedia();
  }

  public void setSortSearch(MediaSort sort) {
    this.sort = sort;
    sortMediaSearch();
  }

  private void sortMedia() {
    sort(currentAlbum.getMedia());
  }

  private void sortMediaSearch() {
    sort(getSearchResultListMedia());
    sort(getRestrictedListMedia());
  }

  private void sort(final List<Media> media) {
    if (sort != null) {
      sort.perform(media);
    }
  }

  public void setIndexOfCurrentPage(String index) {
    this.indexOfCurrentPage = Integer.parseInt(index);
  }

  public int getIndexOfCurrentPage() {
    return indexOfCurrentPage;
  }

  public String getCurrentAlbumId() {
    return currentAlbumId;
  }

  public void setCurrentAlbumId(String currentAlbumId) {
    this.currentAlbumId = currentAlbumId;
    this.currentAlbum = getAlbum(this.currentAlbumId);
  }

  public int getRang() {
    return rang;
  }

  public Boolean isDragAndDropEnabled() {
    return GalleryComponentSettings.isDragAndDropEnabled(getComponentId());
  }

  public Boolean isUsePdc() {
    return GalleryComponentSettings.isPdcEnabled(getComponentId());
  }

  public Boolean isViewMetadata() {
    return GalleryComponentSettings.isViewMetadataEnabled(getComponentId());
  }

  public Watermark getWatermark() {
    return GalleryComponentSettings.getWatermark(getComponentId());
  }

  public Boolean isMakeWatermark() {
    return GalleryComponentSettings.isMakeWatermarkEnabled(getComponentId());
  }

  public Boolean isViewList() {
    return "yes".equalsIgnoreCase(getComponentParameterValue("viewList"));
  }

  public Boolean areCommentsEnabled() {
    return !"no".equalsIgnoreCase(getComponentParameterValue("comments"));
  }

  public Integer getSlideshowWait() {
    String wait = getComponentParameterValue("slideshow");
    final int defaultWait = 5;
    if (wait == null || "null".equalsIgnoreCase(wait) || wait.length() == 0) {
      wait = "5";
    }
    Integer iWait = Integer.parseInt(wait);
    if (iWait <= 0) {
      iWait = defaultWait;
    }
    return iWait;
  }

  public String getXMLFormName() {
    String formName = getComponentParameterValue("XMLFormName");
    // contrôle du formulaire et retour du nom si convenable
    if (StringUtil.isDefined(formName)) {
      try {
        String xmlFormShortName =
            formName.substring(formName.indexOf('/') + 1, formName.indexOf('.'));
        getPublicationTemplateManager().getPublicationTemplate(
            getComponentId() + ":" + xmlFormShortName, formName);
      } catch (PublicationTemplateException e) {
        SilverLogger.getLogger(this).silent(e);
        formName = null;
      }
    }
    return formName;
  }

  public void initIndex() {
    indexOfCurrentPage = 0;
  }

  public Collection<Media> search(QueryDescription query) {
    query.setSearchingUser(getUserId());
    query.addComponent(getComponentId());

    Collection<Media> result = new ArrayList<>();
    try {
      result = getMediaService().search(query);
      // mise à jour de la liste
      isSearchResult = true;
    } catch (Exception e) {
      SilverLogger.getLogger(this).warn(e);
    }
    // sauvegarde de la recherche
    setQuery(query);
    setSearchResultListMedia(result);

    // mise à jour du compteur de paginiation
    setIndexOfCurrentPage("0");
    return result;
  }

  public void sendAskMedia(String asking) {
    // envoyer une notification au gestionnaire de la médiathèque concernant la
    // demande de média
    // 1. création du message
    OrganizationController orga = getOrganisationController();
    UserDetail[] admins = orga.getUsers(getComponentId(), "admin");
    String user = getUserDetail().getDisplayedName();
    String url = URLUtil.getURL(null, getComponentId()) + "Main";

    LocalizationBundle message = ResourceLocator.getLocalizationBundle(MULTILANG_GALLERY_BUNDLE,
        DisplayI18NHelper.getDefaultLanguage());

    String subject = message.getString("gallery.notifAskSubject");
    StringBuilder messageBody = new StringBuilder();
    messageBody =
        messageBody.append(user).append(" ").append(message.getString("gallery.notifBodyAsk"));

    NotificationMetaData notifMetaData =
        new NotificationMetaData(NotificationParameters.PRIORITY_NORMAL, subject,
            messageBody.toString());

    for (String language : DisplayI18NHelper.getLanguages()) {
      message = ResourceLocator.getLocalizationBundle(MULTILANG_GALLERY_BUNDLE, language);
      subject = message.getString("gallery.notifAskSubject");
      messageBody = new StringBuilder();
      messageBody =
          messageBody.append(user).append(" ").append(message.getString("gallery.notifBodyAsk"));
      notifMetaData.addLanguage(language, subject, messageBody.toString());
      notifMetaData.addExtraMessage(asking, language);

      Link link = new Link(url, message.getString("gallery.notifApplicationLinkLabel"));
      notifMetaData.setLink(link, language);
    }

    for (UserDetail admin : admins) {
      notifMetaData.addUserRecipient(new UserRecipient(admin));
    }

    // 2. envoie de la notification aux admin
    notifyUsers(notifMetaData);
  }

  public Collection<String> getListSelected() {
    // restitution de la collection des médias selectionnés

    return listSelected;
  }

  public void clearListSelected() {
    listSelected.clear();
  }

  public List<Media> getRestrictedListMedia() {
    return restrictedListMedia;
  }

  public void setRestrictedListMedia(Collection<Media> restrictedListMedia) {
    this.restrictedListMedia =
        (restrictedListMedia == null ? null : new ArrayList<Media>(restrictedListMedia));
  }

  public List<Media> getSearchResultListMedia() {
    return searchResultListMedia;
  }

  public void setSearchResultListMedia(Collection<Media> searchResultListMedia) {
    this.searchResultListMedia =
        (searchResultListMedia == null ? null : new ArrayList<Media>(searchResultListMedia));
  }

  public String getSearchKeyWord() {
    return searchKeyWord;
  }

  public void setSearchKeyWord(String searchKeyWord) {
    this.searchKeyWord = searchKeyWord;
  }

  public boolean getSelect() {
    return select;
  }

  public void setSelect(boolean select) {
    this.select = select;
  }

  public Boolean isPrivateSearch() {
    // retourne true si on utilise le moteur de recherche dédié
    return "yes".equalsIgnoreCase(getComponentParameterValue("privateSearch"));
  }

  public boolean isAlbumAdmin(SilverpeasRole userRole, String albumId, String userId) {
    if (albumId == null) {
      return isAdminOrPublisher(userRole);
    }
    // Retrieve album creator
    AlbumDetail album = getAlbum(albumId);
    return SilverpeasRole.ADMIN == userRole ||
        (SilverpeasRole.PUBLISHER == userRole && album.getCreatorId().equals(userId));
  }

  public boolean isMediaAdmin(SilverpeasRole userRole, String mediaId, String userId) {
    if (mediaId == null) {
      return isAdminOrPublisher(userRole) || userRole == SilverpeasRole.WRITER;
    }
    // retrieve media creator
    Media media = getMedia(mediaId);
    return isAdminOrPublisher(userRole) ||
        (userRole == SilverpeasRole.WRITER && media.getCreatorId().equals(userId));
  }

  public void copySelectedMedia(Collection<String> mediaIds) throws ClipboardException {
    for (String mediaId : mediaIds) {
      copyMedia(mediaId);
    }
  }

  public void cutSelectedMedia(Collection<String> mediaIds) throws ClipboardException {
    for (String mediaId : mediaIds) {
      cutMedia(mediaId);
    }
  }

  public void copyMedia(String mediaId) throws ClipboardException {
    Media media = getMedia(mediaId);
    MediaSelection mediaSelect = new MediaSelection(media);

    addClipboardSelection(mediaSelect);
  }

  public void cutMedia(String mediaId) throws ClipboardException {
    Media media = getMedia(mediaId);
    MediaSelection mediaSelect = new MediaSelection(media);
    mediaSelect.setCut(true);


    addClipboardSelection(mediaSelect);
  }

  public void copyAlbum(String albumId) throws ClipboardException {
    AlbumDetail album = getAlbum(albumId);
    NodeSelection nodeSelect = new NodeSelection(album);


    addClipboardSelection(nodeSelect);
  }

  public void cutAlbum(String albumId) throws ClipboardException {
    NodeSelection nodeSelect = new NodeSelection(getAlbum(albumId));
    nodeSelect.setCut(true);


    addClipboardSelection(nodeSelect);
  }

  public void paste() throws ClipboardException {
    try {
      GalleryPasteDelegate delegate = new GalleryPasteDelegate(currentAlbum);


      Collection<ClipboardSelection> clipObjects = getClipboardSelectedObjects();
      Map<Object, ClipboardSelection> clipObjectPerformed = new HashMap<>();
      for (ClipboardSelection clipObject : clipObjects) {
        if (clipObject.isDataFlavorSupported(MediaSelection.MediaFlavor)) {
          Media media = (Media) clipObject.getTransferData(MediaSelection.MediaFlavor);

          delegate.addMedia(media, clipObject.isCut());
          clipObjectPerformed.put(media.getMediaPK(), clipObject);
        }
        if (clipObject.isDataFlavorSupported(NodeSelection.NodeDetailFlavor)) {
          AlbumDetail album =
              (AlbumDetail) clipObject.getTransferData(NodeSelection.NodeDetailFlavor);


          delegate.addAlbum(album, clipObject.isCut());
          clipObjectPerformed.put(album.getNodePK(), clipObject);
        }
      }

      // Persisting the paste operation
      getMediaService().paste(getUserDetail(), getComponentId(), delegate);

    } catch (Exception e) {
      throw new GalleryRuntimeException(e);
    }
    clipboardPasteDone();
  }

  public long countAllMediaOf(AlbumDetail album) {
    try {
      return getMediaService().countAllMedia(album.getNodePK());
    } catch (Exception e) {
      SilverLogger.getLogger(this).silent(e);
      return 0;
    }
  }

  public void sendAskOrder(String orderId) {
    // envoyer une notification au gestionnaire pour le prévenir de la demande
    // de l'utilisateur
    // 1. création du message

    OrganizationController orga = getOrganisationController();
    UserDetail[] admins = orga.getUsers(getComponentId(), "admin");
    String user = getUserDetail().getDisplayedName();
    String url = URLUtil.getURL(null, getComponentId()) + "OrderView?OrderId=" + orderId;

    LocalizationBundle message = ResourceLocator.getLocalizationBundle(MULTILANG_GALLERY_BUNDLE,
        DisplayI18NHelper.getDefaultLanguage());

    String subject = message.getString(SUBJECT_NOTIF);
    StringBuilder messageBody = new StringBuilder();
    messageBody = messageBody.append(user)
        .append(" ")
        .append(message.getString("gallery.orderNotifBodyAsk"))
        .append("\n");

    NotificationMetaData notifMetaData =
        new NotificationMetaData(NotificationParameters.PRIORITY_NORMAL, subject,
            messageBody.toString());

    for (String language : DisplayI18NHelper.getLanguages()) {
      message = ResourceLocator.getLocalizationBundle(MULTILANG_GALLERY_BUNDLE, language);
      subject = message.getString(SUBJECT_NOTIF);
      messageBody = new StringBuilder();
      messageBody = messageBody.append(user)
          .append(" ")
          .append(message.getString("gallery.orderNotifBodyAsk"))
          .append("\n");
      notifMetaData.addLanguage(language, subject, messageBody.toString());

      Link link = new Link(url, message.getString("gallery.notifOrderLinkLabel"));
      notifMetaData.setLink(link, language);
    }

    for (UserDetail admin : admins) {
      notifMetaData.addUserRecipient(new UserRecipient(admin));
    }

    // 2. envoie de la notification aux admin
    notifyUsers(notifMetaData);
  }

  /**
   * Send notification to reader to alert him that his order has been processed
   *
   * @param orderId the order identifier
   */
  public void sendAskOrderUser(String orderId) {
    Order order = getOrder(orderId);
    String user = getOrganisationController().getUserDetail(order.
        getProcessUserId()).getDisplayedName();
    String url = URLUtil.getURL(null, getComponentId()) + "OrderView?OrderId=" + orderId;

    LocalizationBundle message = ResourceLocator.getLocalizationBundle(MULTILANG_GALLERY_BUNDLE,
        DisplayI18NHelper.getDefaultLanguage());

    String subject = message.getString(SUBJECT_NOTIF);
    StringBuilder messageBody = new StringBuilder();
    messageBody = messageBody.append(user)
        .append(" ")
        .append(message.getString("gallery.orderNotifBodyAskOk"))
        .append("\n");

    NotificationMetaData notifMetaData =
        new NotificationMetaData(NotificationParameters.PRIORITY_NORMAL, subject,
            messageBody.toString());

    for (String language : DisplayI18NHelper.getLanguages()) {
      message = ResourceLocator.getLocalizationBundle(MULTILANG_GALLERY_BUNDLE, language);
      subject = message.getString(SUBJECT_NOTIF);
      messageBody = new StringBuilder();
      messageBody = messageBody.append(user)
          .append(" ")
          .append(message.getString("gallery.orderNotifBodyAskOk"))
          .append("\n");
      notifMetaData.addLanguage(language, subject, messageBody.toString());

      Link link = new Link(url, message.getString("gallery.notifOrderLinkLabel"));
      notifMetaData.setLink(link, language);
    }

    notifMetaData.addUserRecipient(new UserRecipient(String.valueOf(order.getUserId())));

    notifyUsers(notifMetaData);
  }

  /**
   * Add only photo media inside basket
   *
   * @return true if selection contains only photo, false else if
   */
  public boolean addToBasket() {
    boolean isOnlyPhotoSelection = true;
    for (String mediaId : listSelected) {
      if (!basket.contains(mediaId)) {
        Media media = getMediaById(mediaId);
        if (MediaType.Photo.equals(media.getType())) {
          // add only selected photo inside basket (to do : must change List to Set)
          basket.add(mediaId);
        } else {
          isOnlyPhotoSelection = false;
        }
      }
    }
    // clear list of selected media
    clearListSelected();
    return isOnlyPhotoSelection;
  }

  /**
   * Add media identifier given in parameter in basket
   *
   * @param mediaId the media identifier to add
   */
  public void addMediaToBasket(String mediaId) {
    if (!basket.contains(mediaId)) {
      basket.add(mediaId);
    }
  }

  /**
   * Only remove selected photo from basket
   */
  public void deleteSelectedPhotosFromBasket() {
    for (String mediaId : listSelected) {
      basket.remove(mediaId);
    }
    clearListSelected();
  }

  public void deletePhotoFromBasket(String mediaId) {
    basket.remove(mediaId);
  }

  /**
   * Clear basket
   */
  public void clearBasket() {
    basket.clear();
  }

  public List<String> getBasketMediaIdList() {
    return basket;
  }

  public List<Media> getBasketMedias() {
    List<Media> medias = new ArrayList<>();
    if (!basket.isEmpty()) {
      for (String mediaId : basket) {
        medias.add(getMediaById(mediaId));
      }
    }
    return medias;
  }

  public Order getOrder(String orderId) {
    return getMediaService().getOrder(orderId, getComponentId());
  }

  public String getCurrentOrderId() {
    return currentOrderId;
  }

  public void setCurrentOrderId(String orderId) {
    currentOrderId = orderId;
  }

  public String addOrder() {
    String orderId = getMediaService().createOrder(basket, getUserId(), getComponentId());
    basket.clear();
    return orderId;
  }

  public void updateOrderRow(String orderId, String mediaId) {
    Order order = getOrder(orderId);
    List<OrderRow> rows = order.getRows();
    for (OrderRow row : rows) {
      if (row.getMediaId().equals(mediaId)) {
        // on est sur la bonne ligne, mettre à jour
        row.setDownloadDecision("T");
        getMediaService().updateOrderRow(row);
      }
    }
  }

  public String getUrl(String orderId, String mediaId) {
    Order order = getOrder(orderId);
    for (OrderRow row : order.getRows()) {
      if (row.getMediaId().equals(mediaId)) {
        return getMediaPhotoUrl(row);
      }
    }
    return StringUtil.EMPTY;
  }

  /**
   * @param row the order row
   * @return
   */
  private String getMediaPhotoUrl(OrderRow row) {
    // on est sur la bonne ligne
    InternalMedia media = getInternalMediaById(row.getMediaId());
    String download = row.getDownloadDecision();

    if (!"T".equals(download)) {
      // le média n'a pas déjà été téléchargé par defaut média sans watermark
      String title = WebEncodeHelper.javaStringToHtmlString(media.getFileName());
      String nomRep = media.getWorkspaceSubFolderName();
      if ("DW".equals(download)) {
        // demande avec Watermark
        title = row.getMediaId() + "_watermark.jpg";
        // regarder si le média existe, sinon prendre celui sans watermark
        String pathFile =
            FileRepositoryManager.getAbsolutePath(getComponentId()) + nomRep + File.separator;
        String watermarkFile = pathFile + title;
        File file = new File(watermarkFile);
        if (!file.exists()) {
          title = WebEncodeHelper.javaStringToHtmlString(media.getFileName());
        }
      }
      return FileServerUtils.getUrl(getComponentId(), getUrlEncodedParameter(title),
          media.getFileMimeType().getMimeType(), nomRep);
    }
    return StringUtil.EMPTY;
  }

  public boolean isAccessAuthorized(String orderId) {
    return getOrder(orderId).getUserId().equals(getUserId());
  }

  public List<MetaData> getMetaDataKeys() {
    List<MetaData> metaDatas = new ArrayList<>();
    SettingBundle settings = ResourceLocator.getSettingBundle(
        "org.silverpeas.gallery.settings.metadataSettings_" + getComponentId());
    if (!settings.exists()) {
      settings = DEFAULT_SETTINGS;
    }
    String display = settings.getString("display");
    Iterable<String> propertyNames = StringUtil.splitString(display, ',');

    for (String value : propertyNames) {
      if (value.startsWith("IPTC_")) {
        String property = settings.getString(value + "_TAG");
        String label = settings.getString(value + "_LABEL");
        label = getMetadataResources().getString(label);
        boolean isSearch = StringUtil.getBooleanValue(settings.getString(value + "_SEARCH"));
        if (isSearch) {
          setMetadata(metaDatas, settings, value, property, label);
        }
      }
    }
    return metaDatas;
  }

  private void setMetadata(final List<MetaData> metaDatas, final SettingBundle settings,
      final String value, final String property, final String label) {
    boolean isDate = StringUtil.getBooleanValue(settings.getString(value + "_DATE"));
    MetaData metaData = new MetaData();
    metaData.setProperty(property);
    metaData.setDate(isDate);
    metaData.setLabel(label);
    // rechercher sa valeur dans la query (résultat de la recherche)
    List<FieldDescription> metadataValue = query.getMultiFieldQuery();
    if (metadataValue != null) {
      for (FieldDescription field : metadataValue) {
        if (field.getFieldName().equals("IPTC_" + metaData.getProperty())) {
          metaData.setValue(field.getContent());
        }

      }
    }
    // ajout de cette metadata à la liste
    metaDatas.add(metaData);
  }

  public void updateOrder(Order order) {
    getMediaService().updateOrder(order);
  }

  public List<Order> getAllOrders() {
    return getMediaService().getAllOrders("-1", getComponentId());
  }

  public List<Order> getOrdersByUser() {
    return getMediaService().getAllOrders(getUserId(), getComponentId());
  }

  public String getOrderForm() {
    return getComponentParameterValue("XMLOrderFormName");
  }

  public String getCharteUrl() {
    String url = getComponentParameterValue("UrlCharte");
    if (!StringUtil.isDefined(url)) {
      url = "";
    }
    return url;
  }

  public Boolean isBasket() {
    return isOrder();
  }

  public Boolean isGuest() {
    return getUserDetail().isAccessGuest();
  }

  public Boolean isAnonymous() {
    return getUserDetail().isAnonymous();
  }

  public Boolean isOrder() {
    return !isGuest() && !isAnonymous() && StringUtil.getBooleanValue(getComponentParameterValue("order"));
  }

  public boolean isViewNotVisible() {
    return isViewNotVisible;
  }

  public boolean isExportEnable() {
    String exportParam = getComponentParameterValue("exportImages");
    return ExportOptionValue.YES_ALL.name().equalsIgnoreCase(exportParam) ||
        (ExportOptionValue.YES_PUBLISHER.name().equalsIgnoreCase(exportParam) &&
            getHighestSilverpeasUserRole().isGreaterThanOrEquals(SilverpeasRole.PUBLISHER));
  }

  public void setViewNotVisible(boolean isViewNotVisible) {
    this.isViewNotVisible = isViewNotVisible;
  }

  public boolean isSearchResult() {
    return isSearchResult;
  }

  public void setSearchResult(boolean isSearchResult) {
    this.isSearchResult = isSearchResult;
  }

  public QueryDescription getQuery() {
    return query;
  }

  public void setQuery(QueryDescription query) {
    this.query = query;
  }

  public void setPDCSearchContext(SearchContext context) {
    pdcSearchContext = context;
  }

  public SearchContext getPDCSearchContext() {
    return pdcSearchContext;
  }

  public void setXMLSearchContext(DataRecord data) {
    xmlSearchContext = data;
  }

  public DataRecord getXMLSearchContext() {
    return xmlSearchContext;
  }

  public void clearSearchContext() {
    setQuery(new QueryDescription());
    setXMLSearchContext(null);
    setPDCSearchContext(null);
  }

  protected boolean isAdminOrPublisher(SilverpeasRole userRole) {
    return userRole.isGreaterThanOrEquals(SilverpeasRole.PUBLISHER);
  }

  /**
   * Gets an instance of PublicationTemplateManager.
   *
   * @return an instance of PublicationTemplateManager.
   */
  private PublicationTemplateManager getPublicationTemplateManager() {
    return PublicationTemplateManager.getInstance();
  }

  public static void sortAlbums(List<NodePK> albumPKs) {
    try {
      getMediaService().sortAlbums(albumPKs);
    } catch (Exception e) {
      throw new GalleryRuntimeException(e);
    }
  }

  public Collection<AlbumDetail> addNbMedia(Collection<AlbumDetail> albums) {
    // retourne la liste des albums avec leurs nombre de médias
    for (AlbumDetail album : albums) {
      // pour chaque sous album, rechercher le nombre de médias
      long nbMedia = countAllMediaOf(album);
      // parcourir ses sous albums pour comptabiliser aussi ses médias
      AlbumDetail thisAlbum = getAlbumLight(album.getId());
      Collection<AlbumDetail> subAlbums = addNbMedia(thisAlbum.getChildrenAlbumsDetails());
      for (AlbumDetail oneSubAlbum : subAlbums) {
        nbMedia += oneSubAlbum.getNbMedia();
      }
      album.setNbMedia(nbMedia);
    }
    return albums;
  }

  public void notifyUsers(NotificationMetaData notifMetaData) {
    try {
      notifMetaData.setSender(getUserId());
      notifMetaData.setComponentId(getComponentId());
      NotificationSender notifSender = new NotificationSender(getComponentId());
      notifSender.notifyUser(notifMetaData);
    } catch (NotificationException e) {
      throw new GalleryRuntimeException(e);
    }
  }

  /**
   * Gets a media from the specified identifier.
   *
   * @param mediaId
   * @return the instance of the media behinf the specified identifier, null if it does not exist.
   */
  private Media getMediaById(String mediaId) {
    Media media = getMediaService().getMedia(new MediaPK(mediaId, getComponentId()));
    if (media == null) {
      throw new WebApplicationException(Response.Status.NOT_FOUND);
    }
    return media;
  }

  /**
   * Gets a, internal media from the specified identifier.
   *
   * @param mediaId
   * @return the instance of the media behinf the specified identifier, null if it does not exist.
   */
  private InternalMedia getInternalMediaById(String mediaId) {
    Media media = getMediaService().getMedia(new MediaPK(mediaId, getComponentId()));
    if (media instanceof InternalMedia) {
      return (InternalMedia) media;
    }
    throw new WebApplicationException(Response.Status.NOT_FOUND);
  }

  /**
   * Export all picture from an album with the given resolution
   *
   * @param albumId
   * @param mediaResolution
   */
  public ExportReport exportAlbum(String albumId, MediaResolution mediaResolution)
      throws ExportException {
    ExportReport exportReport = new ExportReport();
    if (isExportEnable() && StringUtil.isDefined(albumId)) {
      // Create export folder then apply each picture from this folder
      ImportExportDescriptor exportDesc =
          new ExportDescriptor().withParameter(GalleryExporter.EXPORT_FOR_USER, getUserDetail())
              .
                  withParameter(GalleryExporter.EXPORT_ALBUM, getAlbum(albumId))
              .withParameter(GalleryExporter.EXPORT_RESOLUTION, mediaResolution);
      aGalleryExporter().exportAlbum(exportDesc, exportReport);
    }
    return exportReport;
  }

  /**
   * Gets a new exporter of Kmelia publications.
   *
   * @return a KmeliaPublicationExporter instance.
   */
  public static GalleryExporter aGalleryExporter() {
    return new GalleryExporter();
  }

  /**
   * Export all selected images from basket with the given resolution
   *
   * @param mediaResolution
   */
  public ExportReport exportSelection(MediaResolution mediaResolution) throws ExportException {
    ExportReport exportReport = new ExportReport();
    if (isExportEnable() && !basket.isEmpty()) {
      // Create export folder then apply each picture from this folder
      ImportExportDescriptor exportDesc =
          new ExportDescriptor().withParameter(GalleryExporter.EXPORT_FOR_USER, getUserDetail())
              .withParameter(GalleryExporter.EXPORT_RESOLUTION, mediaResolution);
      List<Media> medias = new ArrayList<>();
      for (String photoId : basket) {
        medias.add(getMediaById(photoId));
      }
      aGalleryExporter().exportPhysicalMedias(exportDesc, medias, exportReport);
    }
    return exportReport;
  }

  /**
   * Get the resolution preview of images
   *
   * @return the image resolution on preview
   */
  public MediaResolution getImagePreviewSize() {
    String previewSize = getComponentParameterValue("previewSize");
    if ("600x400".equals(previewSize)) {
      return MediaResolution.LARGE;
    } else if ("266x150".equals(previewSize)) {
      return MediaResolution.MEDIUM;
    } else if ("133x100".equals(previewSize)) {
      return MediaResolution.SMALL;
    }
    return MediaResolution.LARGE;
  }

  /**
   * Get the resolution preview of the images
   *
   * @param media
   * @return the media resolution on preview
   */
  public MediaResolution getImageResolutionPreview(Media media) {
    SilverpeasRole highestUserRole = this.getHighestSilverpeasUserRole();
    if (highestUserRole.isGreaterThanOrEquals(SilverpeasRole.PUBLISHER) ||
        (highestUserRole == SilverpeasRole.WRITER &&
            media.getCreatorId().equals(this.getUserId()))) {
      return MediaResolution.PREVIEW;
    } else {
      return getImagePreviewSize();
    }
  }

  public PublicationTemplate getTemplate() throws PublicationTemplateException {
    final String formName = getXMLFormName();
    return getTemplate(formName == null ? "" : formName);
  }

  public PublicationTemplate getOrderTemplate() throws PublicationTemplateException {
    return getTemplate(getOrderForm());
  }

  private PublicationTemplate getTemplate(String fileName) throws PublicationTemplateException {
    PublicationTemplate pub = null;
    if (StringUtil.isDefined(fileName)) {
      String xmlFormShortName =
          fileName.substring(fileName.indexOf('/') + 1, fileName.indexOf('.'));
      String registerId = getComponentId() + ":" + xmlFormShortName;
      final String sessionCacheKey = this.getClass().getName() + registerId;
      SimpleCache sessionCache = getSessionCacheAccessor().getCache();
      if (sessionCache.get(sessionCacheKey) == null) {
        getPublicationTemplateManager().addDynamicPublicationTemplate(registerId, fileName);
        sessionCache.put(sessionCacheKey, registerId);
      }
      pub = getPublicationTemplateManager().getPublicationTemplate(registerId);
    }
    return pub;
  }

  public String manageComponentSubscriptions() {
    final SubscriptionContext subscriptionContext = getSubscriptionContext();
    subscriptionContext.initialize(ComponentSubscriptionResource.from(getComponentId()));
    return subscriptionContext.getDestinationUrl();
  }

  public String manageAlbumSubscriptions() {
    final SubscriptionContext subscriptionContext = getSubscriptionContext();
    final NodePK albumPK = currentAlbum.getNodePK();
    final List<NodeDetail> albumPath = getPath(albumPK);
    albumPath.remove(0);
    subscriptionContext.initialize(NodeSubscriptionResource.from(albumPK))
        .atLocation(new Location(albumPK.getId(), albumPK.getInstanceId()))
        .withNodePath(albumPath);
    return subscriptionContext.getDestinationUrl();
  }
}
