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

import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.Session;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;

import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.component.ComponentPlugin;
import org.exoplatform.ecm.utils.text.Text;
import org.exoplatform.services.cms.link.LinkManager;
import org.exoplatform.services.cms.link.NodeFinder;
import org.exoplatform.services.cms.thumbnail.ThumbnailPlugin;
import org.exoplatform.services.cms.thumbnail.ThumbnailService;
import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.ext.app.SessionProviderService;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.jcr.impl.core.NodeImpl;
import org.exoplatform.services.rest.resource.ResourceContainer;

/**
 * Created by The eXo Platform SARL
 * Author : Dang Van Minh
 *          minh.dang@exoplatform.com
 * Oct 23, 2008 11:09:39 AM
 */
/**
 * Provide the request which will be used to get the response data
 * {repoName} Repository name
 * {workspaceName} Name of workspace
 * {nodePath} The node path
 * Example:
 * <img src="/portal/rest/thumbnailImage/repository/collaboration/test.gif" />
 */
@Path("/thumbnailImage/")
public class ThumbnailRESTService implements ResourceContainer {

  /** The Constant LAST_MODIFIED_PROPERTY. */
  private static final String LAST_MODIFIED_PROPERTY = "Last-Modified";

  /** The Constant IF_MODIFIED_SINCE_DATE_FORMAT. */
  private static final String IF_MODIFIED_SINCE_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";

  private final RepositoryService repositoryService_;
  private final ThumbnailService thumbnailService_;
  private final NodeFinder nodeFinder_;
  private final LinkManager linkManager_;

  public ThumbnailRESTService(RepositoryService repositoryService,
                              ThumbnailService thumbnailService,
                              NodeFinder nodeFinder,
                              LinkManager linkManager) {
    repositoryService_ = repositoryService;
    thumbnailService_ = thumbnailService;
    nodeFinder_ = nodeFinder;
    linkManager_ = linkManager;
  }
/**
 * Get the image with medium size
 * ex: /portal/rest/thumbnailImage/medium/repository/collaboration/test.gif/
 * @param repoName Repository name
 * @param wsName Workspace name
 * @param nodePath Node path
 * @return Response inputstream
 * @throws Exception
 */

  @Path("/medium/{repoName}/{workspaceName}/{nodePath:.*}/")
  @GET
  public Response getThumbnailImage(@PathParam("repoName") String repoName,
                                    @PathParam("workspaceName") String wsName,
                                    @PathParam("nodePath") String nodePath,
                                    @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
    return getThumbnailByType(wsName,
                              nodePath,
                              ThumbnailService.MEDIUM_SIZE,
                              ifModifiedSince);
  }

/**
 * Get the image with big size
 * ex: /portal/rest/thumbnailImage/big/repository/collaboration/test.gif/
 * @param repoName Repository name
 * @param wsName Workspace name
 * @param nodePath Node path
 * @return Response inputstream
 * @throws Exception
 */
  @Path("/big/{repoName}/{workspaceName}/{nodePath:.*}/")
  @GET
  public Response getCoverImage(@PathParam("repoName") String repoName,
                                @PathParam("workspaceName") String wsName,
                                @PathParam("nodePath") String nodePath,
                                @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
    return getThumbnailByType(wsName, nodePath, ThumbnailService.BIG_SIZE, ifModifiedSince);
  }

/**
 * Get the image with large size
 * ex: /portal/rest/thumbnailImage/large/repository/collaboration/test.gif/
 * @param repoName Repository name
 * @param wsName Workspace name
 * @param nodePath Node path
 * @return Response inputstream
 * @throws Exception
 */
  @Path("/large/{repoName}/{workspaceName}/{nodePath:.*}/")
  @GET
  public Response getLargeImage(@PathParam("repoName") String repoName,
                                @PathParam("workspaceName") String wsName,
                                @PathParam("nodePath") String nodePath,
                                @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
    return getThumbnailByType(wsName, nodePath, ThumbnailService.BIG_SIZE, ifModifiedSince);
  }

/**
 * Get the image with small size
 * ex: /portal/rest/thumbnailImage/small/repository/collaboration/test.gif/
 * @param repoName Repository name
 * @param wsName Workspace name
 * @param nodePath Node path
 * @return Response inputstream
 * @throws Exception
 */
  @Path("/small/{repoName}/{workspaceName}/{nodePath:.*}/")
  @GET
  public Response getSmallImage(@PathParam("repoName") String repoName,
                                @PathParam("workspaceName") String wsName,
                                @PathParam("nodePath") String nodePath,
                                @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
    return getThumbnailByType(wsName, nodePath, ThumbnailService.SMALL_SIZE, ifModifiedSince);
  }

/**
 * Get the image with custom size
 * ex: /portal/rest/thumbnailImage/custom/30x40/repository/collaboration/test.gif/
 * @param repoName Repository name
 * @param wsName Workspace name
 * @param nodePath Node path
 * @return Response inputstream
 * @throws Exception
 */
  @Path("/custom/{size}/{repoName}/{workspaceName}/{nodePath:.*}/")
  @GET
  public Response getCustomImage(@PathParam("size") String size,
                                @PathParam("repoName") String repoName,
                                @PathParam("workspaceName") String wsName,
                                @PathParam("nodePath") String nodePath,
                                @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
    return getThumbnailByType(wsName, nodePath, "exo:"+size, ifModifiedSince);
  }

  /**
   * Get the image with origin data
   * ex: /portal/rest/thumbnailImage/origin/repository/collaboration/test.gif/
   * @param repoName Repository name
   * @param wsName Workspace name
   * @param nodePath Node path
   * @return Response data stream
   * @throws Exception
   */
  @Path("/origin/{repoName}/{workspaceName}/{nodePath:.*}/")
  @GET
  public Response getOriginImage(@PathParam("repoName") String repoName,
                                 @PathParam("workspaceName") String wsName,
                                 @PathParam("nodePath") String nodePath,
                                 @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
    DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
    if (!thumbnailService_.isEnableThumbnail())
      return Response.ok().header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
    Node showingNode = getShowingNode(wsName, getNodePath(nodePath));
    Node targetNode = getTargetNode(showingNode);
    if (targetNode.getPrimaryNodeType().getName().equals("nt:file")
        || targetNode.getPrimaryNodeType().getName().equals("nt:resource")) {
      Node content = targetNode;
      if (targetNode.getPrimaryNodeType().getName().equals("nt:file"))
        content = targetNode.getNode("jcr:content");
      if (ifModifiedSince != null) {
        // get last-modified-since from header
        Date ifModifiedSinceDate = dateFormat.parse(ifModifiedSince);

        // get last modified date of node
        Date lastModifiedDate = content.getProperty("jcr:lastModified").getDate().getTime();

        // Check if cached resource has not been modifed, return 304 code
        if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
          return Response.notModified().build();
        }
      }

      String mimeType = content.getProperty("jcr:mimeType").getString();
      for (ComponentPlugin plugin : thumbnailService_.getComponentPlugins()) {
        if (plugin instanceof ThumbnailPlugin) {
          ThumbnailPlugin thumbnailPlugin = (ThumbnailPlugin) plugin;
          if (thumbnailPlugin.getMimeTypes().contains(mimeType)) {
            InputStream inputStream = content.getProperty("jcr:data").getStream();
            return Response.ok(inputStream, "image").header(LAST_MODIFIED_PROPERTY,
                                                            dateFormat.format(new Date())).build();
          }
        }
      }
    }

    return Response.ok().header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
  }

  private Response getThumbnailByType(String wsName, String nodePath,
      String propertyName, String ifModifiedSince) throws Exception {
    DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
    if (!thumbnailService_.isEnableThumbnail())
      return Response.ok().header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
    Node showingNode = getShowingNode(wsName, getNodePath(nodePath));
    Node parentNode = showingNode.getParent();
    String identifier = ((NodeImpl) showingNode).getInternalIdentifier();
    Node targetNode = getTargetNode(showingNode);
    if(targetNode.getPrimaryNodeType().getName().equals("nt:file")) {
      Node content = targetNode.getNode("jcr:content");
      String mimeType = content.getProperty("jcr:mimeType").getString();
      for(ComponentPlugin plugin : thumbnailService_.getComponentPlugins()) {
        if(plugin instanceof ThumbnailPlugin) {
          ThumbnailPlugin thumbnailPlugin = (ThumbnailPlugin) plugin;
          if(thumbnailPlugin.getMimeTypes().contains(mimeType)) {
            Node thumbnailFolder = ThumbnailUtils.getThumbnailFolder(parentNode);

            Node thumbnailNode = ThumbnailUtils.getThumbnailNode(thumbnailFolder, identifier);

            if(!thumbnailNode.hasProperty(propertyName)) {
              BufferedImage image = thumbnailPlugin.getBufferedImage(content, targetNode.getPath());
              thumbnailService_.addThumbnailImage(thumbnailNode, image, propertyName);
            }

            if(ifModifiedSince != null && thumbnailNode.hasProperty(ThumbnailService.THUMBNAIL_LAST_MODIFIED)) {
              // get last-modified-since from header
              Date ifModifiedSinceDate = dateFormat.parse(ifModifiedSince);

              // get last modified date of node
              Date lastModifiedDate = thumbnailNode.getProperty(ThumbnailService.THUMBNAIL_LAST_MODIFIED)
                                                   .getDate()
                                                   .getTime();

              // Check if cached resource has not been modifed, return 304 code
              if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
                return Response.notModified().build();
              }
            }
            InputStream inputStream = null;
            if(thumbnailNode.hasProperty(propertyName)) {
              inputStream = thumbnailNode.getProperty(propertyName).getStream();
            }
            return Response.ok(inputStream, "image").header(LAST_MODIFIED_PROPERTY,
                                                            dateFormat.format(new Date())).build();
          }
        }
      }
    }
    return getThumbnailRes(parentNode, identifier, propertyName, ifModifiedSince);
  }

  private Response getThumbnailRes(Node parentNode,
                                   String identifier,
                                   String propertyName,
                                   String ifModifiedSince) throws Exception {

    DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
    if(parentNode.hasNode(ThumbnailService.EXO_THUMBNAILS_FOLDER)) {
      Node thumbnailFolder = parentNode.getNode(ThumbnailService.EXO_THUMBNAILS_FOLDER);
      if(thumbnailFolder.hasNode(identifier)) {
        Node thumbnailNode = thumbnailFolder.getNode(identifier);
        if (ifModifiedSince != null && thumbnailNode.hasProperty("exo:dateModified")) {
          // get last-modified-since from header
          Date ifModifiedSinceDate = dateFormat.parse(ifModifiedSince);

          // get last modified date of node
          Date lastModifiedDate = thumbnailNode.getProperty("exo:dateModified").getDate().getTime();

          // Check if cached resource has not been modifed, return 304 code
          if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
            return Response.notModified().build();
          }
        }

        if(thumbnailNode.hasProperty(propertyName)) {
          InputStream inputStream = thumbnailNode.getProperty(propertyName).getStream();
          return Response.ok(inputStream, "image").header(LAST_MODIFIED_PROPERTY,
                                                          dateFormat.format(new Date())).build();
        }
      }
    }
    return Response.ok().header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
  }

  private String getNodePath(String nodePath) throws Exception {
    ArrayList<String> encodeNameArr = new ArrayList<String>();
    if(!nodePath.equals("/")) {
      for(String name : nodePath.split("/")) {
        if(name.length() > 0) {
          encodeNameArr.add(Text.escapeIllegalJcrChars(name));
        }
      }
      StringBuilder encodedPath = new StringBuilder();
      for(String encodedName : encodeNameArr) {
        encodedPath.append("/").append(encodedName);
      }
      nodePath = encodedPath.toString();
    }
    return nodePath;
  }

  private Node getTargetNode(Node showingNode) throws Exception {
    Node targetNode = null;
    if (linkManager_.isLink(showingNode)) {
      try {
        targetNode = linkManager_.getTarget(showingNode);
      } catch (ItemNotFoundException e) {
        targetNode = showingNode;
      }
    } else {
      targetNode = showingNode;
    }
    return targetNode;
  }

  private Node getShowingNode(String wsName, String nodePath) throws Exception {
    ManageableRepository repository = repositoryService_.getCurrentRepository();
    Session session = getSystemProvider().getSession(wsName, repository);
    Node showingNode = null;
    Node root = session.getRootNode();
    root.getNodes();
    if(nodePath.equals("/")) showingNode = session.getRootNode();
    else {
      showingNode = (Node) nodeFinder_.getItem(session, nodePath);
    }
    return showingNode;
  }

  private SessionProvider getSystemProvider() {
    ExoContainer container = ExoContainerContext.getCurrentContainer();
    SessionProviderService service = (SessionProviderService) container.
        getComponentInstanceOfType(SessionProviderService.class);
    return service.getSystemSessionProvider(null) ;
  }
}
