/*
 * Copyright (C) 2003-2008 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.wcm.connector.collaboration;

import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.version.VersionHistory;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;

import org.exoplatform.common.http.HTTPStatus;
import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.jcr.ext.app.SessionProviderService;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.rest.resource.ResourceContainer;
import org.exoplatform.services.wcm.core.NodetypeConstant;
import org.exoplatform.services.wcm.core.WCMService;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;

/**
 * Gets the image binary data of a given image node.
 *
 * @LevelAPI Provisional
 * 
 * @anchor RESTImagesRendererService
 */
@Path("/images/")
public class RESTImagesRendererService implements ResourceContainer{

  /** The session provider service. */
  private SessionProviderService sessionProviderService;

  /** The repository service. */
  private RepositoryService repositoryService;

  /** The log. */
  private static final Log LOG = ExoLogger.getLogger(RESTImagesRendererService.class.getName());

  /** 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";

  /** Default mime type **/
  private static String DEFAULT_MIME_TYPE = "image/jpg";

  /** Mime type property **/
  private static String PROPERTY_MIME_TYPE = "jcr:mimeType";

  /**
   * Instantiates a new REST images renderer service.
   *
   * @param repositoryService The repository service.
   * @param sessionProviderService The session provider service.
   */
  public RESTImagesRendererService(RepositoryService repositoryService, SessionProviderService sessionProviderService) {
    this.repositoryService = repositoryService;
    this.sessionProviderService = sessionProviderService;
  }

  /**
   * Gets the image binary data of a given image node.
   * @param repositoryName The repository.
   * @param workspaceName The workspace.
   * @param nodeIdentifier The node identifier.
   * @param param Checks if the document is a file or not. The default value is "file".
   * @param ifModifiedSince Checks the modification date.
   * @return The response
   *
   * @anchor RESTImagesRendererService.serveImage
   */
  @GET
  @Path("/{repositoryName}/{workspaceName}/{nodeIdentifier}")
  public Response serveImage(@PathParam("repositoryName") String repositoryName,
                                     @PathParam("workspaceName") String workspaceName,
                                     @PathParam("nodeIdentifier") String nodeIdentifier,
                                     @QueryParam("param") @DefaultValue("file") String param,
                                     @HeaderParam("If-Modified-Since") String ifModifiedSince) {
    try {
      SessionProvider sessionProvider = sessionProviderService.getSessionProvider(null);
      WCMService wcmService = WCMCoreUtils.getService(WCMService.class);
      Node node = wcmService.getReferencedContent(sessionProvider, workspaceName, nodeIdentifier);
      if (node == null) return Response.status(HTTPStatus.NOT_FOUND).build();

      if ("file".equals(param)) {
        Node dataNode = null;
        if(WCMCoreUtils.isNodeTypeOrFrozenType(node, NodetypeConstant.NT_FILE)) {
          dataNode = node;
        }else if(node.isNodeType("nt:versionedChild")) {
          VersionHistory versionHistory = (VersionHistory)node.getProperty("jcr:childVersionHistory").getNode();
          String versionableUUID = versionHistory.getVersionableUUID();
          dataNode = sessionProvider.getSession(workspaceName,
                                                repositoryService.getCurrentRepository())
                                    .getNodeByUUID(versionableUUID);
        }else {
          return Response.status(HTTPStatus.NOT_FOUND).build();
        }

        if (ifModifiedSince != null && isModified(ifModifiedSince, dataNode) == false) {
          return Response.notModified().build();
        }

        DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);

        Node jcrContentNode = dataNode.getNode("jcr:content");
        String mimeType = DEFAULT_MIME_TYPE;
        if (jcrContentNode.hasProperty(PROPERTY_MIME_TYPE)) {
          mimeType = jcrContentNode.getProperty(PROPERTY_MIME_TYPE).getString();
        }
        InputStream jcrData = jcrContentNode.getProperty("jcr:data").getStream();
        return Response.ok(jcrData, mimeType).header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
      }

      if (ifModifiedSince != null && isModified(ifModifiedSince, node) == false) {
        return Response.notModified().build();
      }

      DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
      InputStream jcrData = node.getProperty(param).getStream();
      return Response.ok(jcrData, DEFAULT_MIME_TYPE).header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();

    } catch (PathNotFoundException e) {
      return Response.status(HTTPStatus.NOT_FOUND).build();
    }catch (ItemNotFoundException e) {
      return Response.status(HTTPStatus.NOT_FOUND).build();
    }catch (Exception e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("Error when serveImage: ", e);
      }
      return Response.serverError().build();
    }
  }

  /**
   * Gets the last modified date of a node.
  + * @param node A specific node.
  + * @return The last modified date.
  + * @throws Exception
  + */
  private Date getLastModifiedDate(Node node) throws Exception {
     Date lastModifiedDate = null;
     if (node.hasNode("jcr:content") && node.getNode("jcr:content").hasProperty("jcr:lastModified")) {
       lastModifiedDate = node.getNode("jcr:content").getProperty("jcr:lastModified").getDate().getTime();
     } else if (node.hasProperty("exo:dateModified")) {
         lastModifiedDate = node.getProperty("exo:dateModified").getDate().getTime();
     } else if (node.hasProperty("jcr:created")){
       lastModifiedDate = node.getProperty("jcr:created").getDate().getTime();
     }
     return lastModifiedDate;
  }

  /**
   * Checks if resources were modified or not.
   * @param ifModifiedSince The date when the node is modified.
   * @param node A specific node.
   * @return
   * @throws Exception
   */
  private boolean isModified(String ifModifiedSince, Node node) throws Exception {
     // get last-modified-since from header
     DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
     if(ifModifiedSince == null || ifModifiedSince.length() == 0)
       return false;
     try {
       Date ifModifiedSinceDate = dateFormat.parse(ifModifiedSince);
       // get last modified date of node
       Date lastModifiedDate = getLastModifiedDate(node);
       // Check if cached resource has not been modifed, return 304 code
       if (lastModifiedDate != null && ifModifiedSinceDate != null &&
           ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
         return false;
       }
       return true;
     } catch(ParseException pe) {
       return false;
     }

  }

}
