/*
 * 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.command.proppatch;

import org.exoplatform.common.http.HTTPStatus;
import org.exoplatform.common.util.HierarchicalProperty;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager;
import org.exoplatform.services.jcr.core.nodetype.PropertyDefinitionData;
import org.exoplatform.services.jcr.core.nodetype.PropertyDefinitionDatas;
import org.exoplatform.services.jcr.datamodel.InternalQName;
import org.exoplatform.services.jcr.datamodel.NodeData;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.core.NodeImpl;
import org.exoplatform.services.jcr.impl.core.SessionImpl;
import org.exoplatform.services.jcr.impl.core.WorkspaceImpl;
import org.exoplatform.services.jcr.webdav.WebDavConst;
import org.exoplatform.services.jcr.webdav.util.PropertyConstants;
import org.exoplatform.services.jcr.webdav.xml.PropertyWriteUtil;
import org.exoplatform.services.jcr.webdav.xml.WebDavNamespaceContext;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jcr.AccessDeniedException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Workspace;
import javax.ws.rs.core.StreamingOutput;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

/**
 * Created by The eXo Platform SAS.
 * @author Vitaly Guly - gavrikvetal@gmail.com
 * 
 * @version $Id: $
 */

public class PropPatchResponseEntity implements StreamingOutput
{

   /**
    * Namespace context.
    */
   private final WebDavNamespaceContext nsContext;

   /**
    * Node.
    */
   private Node node;

   /**
    * Uri.
    */
   private final URI uri;

   /**
    * The list of properties to set.
    */
   private final List<HierarchicalProperty> setList;

   /**
    * The list of properties ro remove.
    */
   private final List<HierarchicalProperty> removeList;

   /**
    * The list of properties that can-not be removed.
    */
   protected final static Set<QName> NON_REMOVING_PROPS = new HashSet<QName>();
   static
   {
      NON_REMOVING_PROPS.add(PropertyConstants.CREATIONDATE);
      NON_REMOVING_PROPS.add(PropertyConstants.DISPLAYNAME);
      NON_REMOVING_PROPS.add(PropertyConstants.GETCONTENTLANGUAGE);
      NON_REMOVING_PROPS.add(PropertyConstants.GETCONTENTLENGTH);
      NON_REMOVING_PROPS.add(PropertyConstants.GETCONTENTTYPE);
      NON_REMOVING_PROPS.add(PropertyConstants.GETLASTMODIFIED);

      NON_REMOVING_PROPS.add(PropertyConstants.JCR_DATA);
   };

   /**
    * The list of properties that can-not be changed.
    */
   protected final static Set<QName> READ_ONLY_PROPS = new HashSet<QName>();
   static
   {
      READ_ONLY_PROPS.add(PropertyConstants.JCR_DATA);
   };

   /**
    * Constructor.
    * 
    * @param nsContext namespace context
    * @param node node
    * @param uri iru
    * @param setList list of properties to set
    * @param removeList list of properties to remove
    */
   public PropPatchResponseEntity(WebDavNamespaceContext nsContext, Node node, URI uri,
      List<HierarchicalProperty> setList, List<HierarchicalProperty> removeList)
   {
      this.nsContext = nsContext;
      this.node = node;
      this.uri = uri;
      this.setList = setList;
      this.removeList = removeList;
   }

   /**
    * {@inheritDoc}
    */
   public void write(OutputStream outStream) throws IOException
   {
      try
      {
         XMLStreamWriter xmlStreamWriter =
            XMLOutputFactory.newInstance().createXMLStreamWriter(outStream, Constants.DEFAULT_ENCODING);

         xmlStreamWriter.setNamespaceContext(nsContext);

         xmlStreamWriter.writeStartDocument();
         xmlStreamWriter.writeStartElement("D", "multistatus", "DAV:");
         xmlStreamWriter.writeNamespace("D", "DAV:");

         xmlStreamWriter.writeAttribute("xmlns:b", "urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/");

         xmlStreamWriter.writeStartElement("DAV:", "response");
         xmlStreamWriter.writeStartElement("DAV:", "href");
         xmlStreamWriter.writeCharacters(uri.toASCIIString());
         xmlStreamWriter.writeEndElement();

         Map<String, Set<HierarchicalProperty>> propStats = getPropStat();
         PropertyWriteUtil.writePropStats(xmlStreamWriter, propStats);

         xmlStreamWriter.writeEndElement();

         // D:multistatus
         xmlStreamWriter.writeEndElement();
         xmlStreamWriter.writeEndDocument();

      }
      catch (XMLStreamException exc)
      {
         throw new IOException(exc.getMessage(), exc);
      }
   }

   /**
    * Performs manipulations with properties and returns the list of
    * corresponding statuses.
    * 
    * @return map with the list of properties statuses
    */
   protected Map<String, Set<HierarchicalProperty>> getPropStat()
   {
      Map<String, Set<HierarchicalProperty>> propStats = new HashMap<String, Set<HierarchicalProperty>>();

      for (int i = 0; i < setList.size(); i++)
      {
         HierarchicalProperty setProperty = setList.get(i);

         String statname = WebDavConst.getStatusDescription(HTTPStatus.OK);

         try
         {

            if (setProperty.getStringName().equals(WebDavConst.NodeTypes.JCR_CONTENT))
            {
               for (HierarchicalProperty child : setProperty.getChildren())
               {

                  if (child.getChildren().isEmpty())
                  {

                     if (node.isNodeType(WebDavConst.NodeTypes.MIX_VERSIONABLE) && !node.isCheckedOut())
                     {
                        node.checkout();
                        node.save();
                     }

                     Node content = getContentNode();
                     statname = setProperty(content, child);

                     if (!propStats.containsKey(statname))
                     {
                        propStats.put(statname, new HashSet<HierarchicalProperty>());
                     }

                     Set<HierarchicalProperty> propSet = propStats.get(statname);

                     HierarchicalProperty jcrContentProp = new HierarchicalProperty(PropertyConstants.JCR_CONTENT);
                     jcrContentProp.addChild(new HierarchicalProperty(child.getName()));

                     propSet.add(jcrContentProp);

                  }
                  else
                  {
                  }

               }
            }
            else
            {
               statname = setProperty(node, setProperty);

               if (!propStats.containsKey(statname))
               {
                  propStats.put(statname, new HashSet<HierarchicalProperty>());
               }

               Set<HierarchicalProperty> propSet = propStats.get(statname);
               propSet.add(new HierarchicalProperty(setProperty.getName()));

            }

         }
         catch (RepositoryException e)
         {
            statname = WebDavConst.getStatusDescription(HTTPStatus.CONFLICT);
         }

      }

      for (int i = 0; i < removeList.size(); i++)
      {
         HierarchicalProperty removeProperty = removeList.get(i);

         String statname = WebDavConst.getStatusDescription(HTTPStatus.OK);

         if (NON_REMOVING_PROPS.contains(removeProperty.getName()))
         {
            statname = WebDavConst.getStatusDescription(HTTPStatus.CONFLICT);

            if (!propStats.containsKey(statname))
            {
               propStats.put(statname, new HashSet<HierarchicalProperty>());
            }

            Set<HierarchicalProperty> propSet = propStats.get(statname);
            propSet.add(new HierarchicalProperty(removeProperty.getName()));

            continue;

         }

         if (removeProperty.getStringName().equals(WebDavConst.NodeTypes.JCR_CONTENT))
         {
            for (HierarchicalProperty child : removeProperty.getChildren())
            {
               try
               {
                  Node content = getContentNode();
                  statname = removeProperty(content, child);
                  if (!propStats.containsKey(statname))
                  {
                     propStats.put(statname, new HashSet<HierarchicalProperty>());
                  }

                  Set<HierarchicalProperty> propSet = propStats.get(statname);

                  HierarchicalProperty jcrContentProp =
                     new HierarchicalProperty(new QName(WebDavConst.NodeTypes.JCR_CONTENT));
                  jcrContentProp.addChild(new HierarchicalProperty(child.getName()));
                  propSet.add(jcrContentProp);

               }
               catch (RepositoryException e)
               {
                  statname = WebDavConst.getStatusDescription(HTTPStatus.CONFLICT);
               }
            }
         }
         else
         {
            statname = removeProperty(node, removeProperty);
            if (!propStats.containsKey(statname))
            {
               propStats.put(statname, new HashSet<HierarchicalProperty>());
            }

            Set<HierarchicalProperty> propSet = propStats.get(statname);
            propSet.add(new HierarchicalProperty(removeProperty.getName()));
         }

      }

      return propStats;
   }

   /**
    * Gets the content node.
    * 
    * @return content node
    * @throws RepositoryException Repository exception.
    */
   public Node getContentNode() throws RepositoryException
   {
      return node.getNode("jcr:content");
   }

   /**
    * Sets changes the property value.
    * 
    * @param node node
    * @param property property to set.
    * @return status
    */
   private String setProperty(Node node, HierarchicalProperty property)
   {

      String propertyName = WebDavNamespaceContext.createName(property.getName());

      if (READ_ONLY_PROPS.contains(property.getName()))
      {
         return WebDavConst.getStatusDescription(HTTPStatus.CONFLICT);
      }

      try
      {

         Workspace ws = node.getSession().getWorkspace();
         NodeTypeDataManager nodeTypeHolder = ((WorkspaceImpl)ws).getNodeTypesHolder();
         NodeData data = (NodeData)((NodeImpl)node).getData();
         InternalQName propName =
            ((SessionImpl)node.getSession()).getLocationFactory().parseJCRName(propertyName).getInternalName();
         PropertyDefinitionDatas propdefs =
            nodeTypeHolder.getPropertyDefinitions(propName, data.getPrimaryTypeName(), data.getMixinTypeNames());
         if (propdefs == null)
         {
            throw new RepositoryException();
         }

         PropertyDefinitionData propertyDefinitionData = propdefs.getAnyDefinition();
         if (propertyDefinitionData == null)
         {
            throw new RepositoryException();
         }

         boolean isMultiValued = propertyDefinitionData.isMultiple();

         if (node.isNodeType(WebDavConst.NodeTypes.MIX_VERSIONABLE) && !node.isCheckedOut())
         {
            node.checkout();
            node.save();
         }

         if (!isMultiValued)
         {
            node.setProperty(propertyName, property.getValue());
         }
         else
         {
            String[] value = new String[1];
            value[0] = property.getValue();
            node.setProperty(propertyName, value);
         }

         node.save();
         return WebDavConst.getStatusDescription(HTTPStatus.OK);

      }
      catch (AccessDeniedException e)
      {
         return WebDavConst.getStatusDescription(HTTPStatus.FORBIDDEN);
      }
      catch (ItemNotFoundException e)
      {
         return WebDavConst.getStatusDescription(HTTPStatus.NOT_FOUND);
      }
      catch (PathNotFoundException e)
      {
         return WebDavConst.getStatusDescription(HTTPStatus.NOT_FOUND);
      }
      catch (RepositoryException e)
      {
         return WebDavConst.getStatusDescription(HTTPStatus.CONFLICT);

      }

   }

   /**
    * Removes the property.
    * 
    * @param node node
    * @param property property
    * @return status
    */
   private String removeProperty(Node node, HierarchicalProperty property)
   {

      try
      {
         node.getProperty(property.getStringName()).remove();
         node.save();
         return WebDavConst.getStatusDescription(HTTPStatus.OK);
      }
      catch (AccessDeniedException e)
      {
         return WebDavConst.getStatusDescription(HTTPStatus.FORBIDDEN);
      }
      catch (ItemNotFoundException e)
      {
         return WebDavConst.getStatusDescription(HTTPStatus.NOT_FOUND);
      }
      catch (PathNotFoundException e)
      {
         return WebDavConst.getStatusDescription(HTTPStatus.NOT_FOUND);
      }
      catch (RepositoryException e)
      {
         return WebDavConst.getStatusDescription(HTTPStatus.CONFLICT);
      }

   }

}
