/*
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.services.jcr.webdav.resource;

import org.exoplatform.common.util.HierarchicalProperty;
import org.exoplatform.services.jcr.impl.core.NodeImpl;
import org.exoplatform.services.jcr.webdav.WebDavConst;
import org.exoplatform.services.jcr.webdav.command.acl.ACLProperties;
import org.exoplatform.services.jcr.webdav.xml.WebDavNamespaceContext;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

import java.io.InputStream;
import java.net.URI;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Set;

import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.xml.namespace.QName;

/**
 * Created by The eXo Platform SARL .<br/>
 * Resource containing JCR's nt:file/jcr:content underneath. Identified by
 * nt:file's URI jcr:content's jcr:data property contains file's payload
 * 
 * @author Gennady Azarenkov
 * @version $Id$
 */
public class FileResource extends GenericResource
{

   /**
    * logger.
    */
   private static final Log LOG = ExoLogger.getLogger("exo.jcr.component.webdav.FileResource");

   /**
    * The list of properties which are skipped for nt:file.
    */
   protected final static Set<String> FILE_SKIP = new HashSet<String>();
   static
   {
      // FILE_SKIP.add("jcr:primaryType");
      FILE_SKIP.add("jcr:mixinTypes");
      FILE_SKIP.add("jcr:created");
      FILE_SKIP.add("jcr:lockOwner");
      FILE_SKIP.add("jcr:lockIsDeep");
   };

   /**
    * The list of properties which are skipped for jcr:content.
    */
   protected final static Set<String> CONTENT_SKIP = new HashSet<String>();
   static
   {
      CONTENT_SKIP.add("jcr:data");
      CONTENT_SKIP.add("jcr:lastModified");
      CONTENT_SKIP.add("jcr:mimeType");
      CONTENT_SKIP.add("jcr:uuid");
      CONTENT_SKIP.add("jcr:lockOwner");
      CONTENT_SKIP.add("jcr:lockIsDeep");

      CONTENT_SKIP.add("dc:date");
      CONTENT_SKIP.add("exo:internalUse");
   };

   /**
    * Node.
    */
   protected final Node node;

   /**
    * Content Node (jcr:content).
    */
   private Node contentNode;

   /**
    * Data Property (jcr:data).
    */
   private Property contentData;

   /**
    * @param identifier resource identifier
    * @param node node
    * @param namespaceContext namespace context
    * @throws IllegalResourceTypeException {@link IllegalResourceTypeException}
    * @throws RepositoryException {@link RepositoryException}
    */
   public FileResource(final URI identifier, Node node, final WebDavNamespaceContext namespaceContext)
      throws IllegalResourceTypeException, RepositoryException
   {
      this(FILE, identifier, node, namespaceContext);
   }

   /**
      * 
      * @param type resource type
      * @param identifier resource identifier
      * @param node node
      * @param namespaceContext namespace context
      * @throws IllegalResourceTypeException {@link IllegalResourceTypeException
      * @throws RepositoryException {@link RepositoryException}
      */
   protected FileResource(final int type, final URI identifier, Node node, final WebDavNamespaceContext namespaceContext)
      throws IllegalResourceTypeException, RepositoryException
   {
      super(type, identifier, namespaceContext);
      this.node = node;
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public Set<HierarchicalProperty> getProperties(boolean namesOnly) throws PathNotFoundException,
      AccessDeniedException, RepositoryException
   {
      Set<HierarchicalProperty> props = super.getProperties(namesOnly);

      props.add(namesOnly ? new HierarchicalProperty(GETLASTMODIFIED) : getProperty(GETLASTMODIFIED));
      props.add(namesOnly ? new HierarchicalProperty(GETCONTENTLENGTH) : getProperty(GETCONTENTLENGTH));
      props.add(namesOnly ? new HierarchicalProperty(GETCONTENTTYPE) : getProperty(GETCONTENTTYPE));

      QName nodeTypeName = namespaceContext.createQName("jcr:nodeType");
      HierarchicalProperty noneTypeProp =
         new HierarchicalProperty(nodeTypeName, contentNode().getPrimaryNodeType().getName());
      props.add(noneTypeProp);

      Set<QName> presents = new HashSet<QName>();

      PropertyIterator jcrProps = node.getProperties();
      while (jcrProps.hasNext())
      {
         Property property = jcrProps.nextProperty();
         if (!FILE_SKIP.contains(property.getName()))
         {
            QName name = namespaceContext.createQName(property.getName());
            presents.add(name);
            props.add((namesOnly) ? new HierarchicalProperty(name) : getProperty(name));
            if (name.getLocalPart().equals("isCheckedOut"))
            {
               QName qname = namespaceContext.createQName("D:checked-in");
               presents.add(qname);
               props.add(new HierarchicalProperty(qname));
            }
         }
      }

      jcrProps = contentNode().getProperties();
      HierarchicalProperty jcrContentProp =
         new HierarchicalProperty(namespaceContext.createQName(WebDavConst.NodeTypes.JCR_CONTENT));

      while (jcrProps.hasNext())
      {
         Property property = jcrProps.nextProperty();
         if (!CONTENT_SKIP.contains(property.getName()))
         {
            QName name = namespaceContext.createQName(property.getName());

            if (presents.contains(name))
            {
               continue;
            }

            jcrContentProp.addChild((namesOnly) ? new HierarchicalProperty(name) : getProperty(name));
         }
      }

      if (!jcrContentProp.getChildren().isEmpty())
      {
         props.add(jcrContentProp);
      }
      return props;
   }

   /**
    * {@inheritDoc}
    */
   public HierarchicalProperty getProperty(QName name) throws PathNotFoundException, AccessDeniedException,
      RepositoryException
   {
      if (name.equals(DISPLAYNAME))
      {
         return new HierarchicalProperty(name, node.getName() + (node.getIndex() > 1 ? "[" + node.getIndex() + "]" : ""));
      }
      else if (name.equals(CREATIONDATE))
      {
         Calendar created = node.getProperty("jcr:created").getDate();

         HierarchicalProperty creationDate = new HierarchicalProperty(name, created, CREATION_PATTERN);
         creationDate.setAttribute("b:dt", "dateTime.tz");
         return creationDate;
      }
      else if (name.equals(CHILDCOUNT))
      {
         return new HierarchicalProperty(name, "0");
      }
      else if (name.equals(GETCONTENTLENGTH))
      {
         return new HierarchicalProperty(name, String.valueOf(dataProperty().getLength()));
      }
      else if (name.equals(GETCONTENTTYPE))
      {
         Node contentNode = contentNode();
         String mimeType = contentNode.getProperty("jcr:mimeType").getString();
         if (contentNode.hasProperty("jcr:encoding"))
         {
            String encoding = contentNode.getProperty("jcr:encoding").getString();
            if (!encoding.isEmpty())
            {
               return new HierarchicalProperty(name, mimeType + "; charset=" + encoding);
            }
         }
         return new HierarchicalProperty(name, mimeType);

      }
      else if (name.equals(GETLASTMODIFIED))
      {
         Calendar modified;
         try
         {
            modified = contentNode().getProperty("jcr:lastModified").getDate();
         }
         catch (PathNotFoundException e)
         {
            modified = node.getProperty("jcr:created").getDate();
         }
         HierarchicalProperty lastModified = new HierarchicalProperty(name, modified, MODIFICATION_PATTERN);
         lastModified.setAttribute("b:dt", "dateTime.rfc1123");
         return lastModified;
      }
      else if (name.equals(HASCHILDREN))
      {
         return new HierarchicalProperty(name, "0");
      }
      else if (name.equals(ISCOLLECTION))
      {
         return new HierarchicalProperty(name, "0");
      }
      else if (name.equals(ISFOLDER))
      {
         return new HierarchicalProperty(name, "0");
      }
      else if (name.equals(ISROOT))
      {
         return new HierarchicalProperty(name, "0");
      }
      else if (name.equals(PARENTNAME))
      {
         return new HierarchicalProperty(name, node.getParent().getName());
      }
      else if (name.equals(RESOURCETYPE))
      {
         return new HierarchicalProperty(name);
      }
      else if (name.equals(SUPPORTEDLOCK))
      {
         if (!node.canAddMixin("mix:lockable"))
         {
            throw new PathNotFoundException();
         }
         return supportedLock();
      }
      else if (name.equals(LOCKDISCOVERY))
      {
         if (node.isLocked())
         {
            String token = node.getLock().getLockToken();
            String owner = node.getLock().getLockOwner();
            return lockDiscovery(token, owner, "86400");
         }
         else
         {
            throw new PathNotFoundException();
         }
      }
      else if (name.equals(ISVERSIONED))
      {
         return new HierarchicalProperty(name, "0");
      }
      else if (name.equals(SUPPORTEDMETHODSET))
      {
         return supportedMethodSet();
      }
      else if (name.equals(ACLProperties.ACL))
      {
         return ACLProperties.getACL((NodeImpl)node);
      }
      else if (name.equals(OWNER))
      {
         return ACLProperties.getOwner((NodeImpl)node);
      }
      else
      {
         try
         {
            return getProperty(node, name);
         }
         catch (PathNotFoundException e)
         {
            return getProperty(contentNode(), name);
         }
      }
   }

   /**
    * Returns node's property wrapped in {@link HierarchicalProperty}.
    */
   private HierarchicalProperty getProperty(Node node, QName name) throws PathNotFoundException, RepositoryException
   {
      Property property = node.getProperty(WebDavNamespaceContext.createName(name));
      String propertyValue;
      if (property.getDefinition().isMultiple())
      {
         if (property.getValues().length >= 1)
         {
            propertyValue = property.getValues()[0].getString();
         }
         else
         {
            // this means that we return empty value, because according to WebDAV spec:
            // this is a property whose semantics and syntax are not enforced by the server
            // the server only records the value of a dead property;
            // the client is responsible for maintaining the consistency of the syntax and semantics of a dead property. 
            propertyValue = "";
         }
      }
      else
      {
         propertyValue = property.getString();
      }
      return new HierarchicalProperty(name, propertyValue);
   }


   /**
    * {@inheritDoc}
    */
   public boolean isCollection()
   {
      return false;
   }

   /**
    * Returns the content of node as text.
    * 
    * @return content as text
    * @throws RepositoryException {@link RepositoryException}
    */
   public String getContentAsText() throws RepositoryException
   {
      return dataProperty().getString();
   }

   /**
    * Returns the content of node as stream.
    * 
    * @return content as stream
    * @throws RepositoryException {@link RepositoryException}
    */
   public InputStream getContentAsStream() throws RepositoryException
   {
      return dataProperty().getStream();
   }

   /**
    * if the content of node is text.
    * 
    * @return true if the content of node is text false if not
    */
   public boolean isTextContent()
   {
      try
      {
         return dataProperty().getType() != PropertyType.BINARY;
      }
      catch (RepositoryException exc)
      {
         LOG.error(exc.getMessage(), exc);
         return false;
      }
   }

   /**
    * Returns the content node.
    * 
    * @return Node, the content node
    * @throws RepositoryException {@link RepositoryException}
    */
   protected Node contentNode() throws RepositoryException
   {
      if (contentNode == null)
      {
         return contentNode = node.getNode("jcr:content");
      }
      else
      {
         return contentNode;
      }
   }

   /**
    * Returns the data property.
    * 
    * @return Property, the content data property
    * @throws RepositoryException {@link RepositoryException}
    */
   protected Property dataProperty() throws RepositoryException
   {
      if (contentData == null)
      {
         return contentData = contentNode().getProperty("jcr:data");
      }
      else
      {
         return contentData;
      }
   }

}
