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

import org.exoplatform.services.jcr.access.AccessManager;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager;
import org.exoplatform.services.jcr.dataflow.ItemDataConsumer;
import org.exoplatform.services.jcr.dataflow.ItemState;
import org.exoplatform.services.jcr.dataflow.PlainChangesLogImpl;
import org.exoplatform.services.jcr.datamodel.ItemType;
import org.exoplatform.services.jcr.datamodel.NodeData;
import org.exoplatform.services.jcr.datamodel.PropertyData;
import org.exoplatform.services.jcr.datamodel.QPath;
import org.exoplatform.services.jcr.datamodel.QPathEntry;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.core.RepositoryImpl;
import org.exoplatform.services.jcr.impl.core.SessionImpl;
import org.exoplatform.services.jcr.impl.dataflow.ItemDataRemoveVisitor;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.security.ConversationState;

import java.io.IOException;
import java.util.List;

import javax.jcr.RepositoryException;

/**
 * Created by The eXo Platform SAS. Helper class for removing version history.
 * 
 * @author <a href="mailto:Sergey.Kabashnyuk@gmail.com">Sergey Kabashnyuk</a>
 * @version $Id: $
 */
public class VersionHistoryRemover
{
   /**
    * Class logger.
    */
   private final Log log = ExoLogger.getLogger("exo.jcr.component.core.VersionHistoryRemover");

   /**
    * Version history identifier.
    */
   private final String vhID;

   /**
    * ItemDataConsumer.
    */
   private final ItemDataConsumer dataManager;

   /**
    * NodeTypeManager.
    */
   private final NodeTypeDataManager ntManager;

   /**
    * Repository.
    */
   private final RepositoryImpl repository;

   /**
    * Current workspace name.
    */
   private final String currentWorkspaceName;

   /**
    * Containing history path.
    */
   private final QPath containingHistory;

   /**
    * Ancestor to save.
    */
   private final QPath ancestorToSave;

   /**
    * Changes log.
    */
   private final PlainChangesLogImpl transientChangesLog;

   /**
    * Access manager.
    */
   private final AccessManager accessManager;

   /**
    * User state.
    */
   private final ConversationState userState;

   /**
    * @param vhID - version history identifier.
    * @param dataManager - ItemDataConsumer.
    * @param ntManager - NodeTypeManagerImpl.
    * @param repository - RepositoryImpl.
    * @param currentWorkspaceName - current workspace name.
    * @param containingHistory - containingHistory.
    * @param ancestorToSave - ancestor to save.
    * @param transientChangesLog - changes log.
    * @param accessManager - access manager.
    * @param userState - user state.
    */
   public VersionHistoryRemover(String vhID, ItemDataConsumer dataManager, NodeTypeDataManager ntManager,
      RepositoryImpl repository, String currentWorkspaceName, QPath containingHistory, QPath ancestorToSave,
      PlainChangesLogImpl transientChangesLog, AccessManager accessManager, ConversationState userState)
   {
      super();
      this.vhID = vhID;
      this.dataManager = dataManager;
      this.ntManager = ntManager;
      this.repository = repository;
      this.currentWorkspaceName = currentWorkspaceName;
      this.containingHistory = containingHistory;
      this.ancestorToSave = ancestorToSave;
      this.transientChangesLog = transientChangesLog;
      this.accessManager = accessManager;
      this.userState = userState;
   }

   /**
    * Remove history.
    * 
    * @exception RepositoryException if an repository error occurs.
    */
   public void remove() throws RepositoryException
   {
      NodeData vhnode = (NodeData)dataManager.getItemData(vhID);

      if (vhnode == null)
      {
         ItemState vhState = null;
         List<ItemState> allStates = transientChangesLog.getAllStates();
         for (int i = allStates.size() - 1; i >= 0; i--)
         {
            ItemState state = allStates.get(i);
            if (state.getData().getIdentifier().equals(vhID))
               vhState = state;
         }
         if (vhState != null && vhState.isDeleted())
            // [PN] TODO check why we here if VH already isn't exists.
            // usecase: child version remove when child versionable node is located
            // as child
            // of its containing history versionable node.
            // We may check this case in ChildVersionRemoveVisitor.
            return;

         throw new RepositoryException("Version history is not found. UUID: " + vhID
            + ". Context item (ancestor to save) " + ancestorToSave.getAsString());
      }

      // mix:versionable
      // we have to be sure that any versionable node somewhere in repository
      // doesn't refers to a VH of the node being deleted.
      for (String wsName : repository.getWorkspaceNames())
      {
         SessionImpl wsSession = repository.getSystemSession(wsName);
         try
         {
            for (PropertyData sref : wsSession.getTransientNodesManager().getReferencesData(vhID, false))
            {
               // Check if this VH isn't referenced from somewhere in workspace
               // or isn't contained in another one as a child history.
               // Ask ALL references incl. properties from version storage.
               if (sref.getQPath().isDescendantOf(Constants.JCR_VERSION_STORAGE_PATH))
               {
                  if (!sref.getQPath().isDescendantOf(vhnode.getQPath())
                     && (containingHistory != null ? !sref.getQPath().isDescendantOf(containingHistory) : true))
                     // has a reference to the VH in version storage,
                     // it's a REFERENCE property jcr:childVersionHistory of
                     // nt:versionedChild
                     // i.e. this VH is a child history in an another history.
                     // We can't remove this VH now.
                     return;
               }
               else if (!currentWorkspaceName.equals(wsName))
               {
                  // has a reference to the VH in traversed workspace,
                  // it's not a version storage, i.e. it's a property of versionable
                  // node somewhere in ws.
                  // We can't remove this VH now.
                  return;
               } // else -- if we has a references in workspace where the VH is being
               // deleted we can remove VH now.
            }
         }
         finally
         {
            wsSession.logout();
         }
      }

      // remove child versions from VH (if found)
      // ChildVersionRemoveVisitor cvremover = new
      // ChildVersionRemoveVisitor(session,
      // vhnode.getQPath(),
      // ancestorToSave);
      // vhnode.accept(cvremover);

      List<NodeData> childs = dataManager.getChildNodesData(vhnode);
      for (NodeData nodeData : childs)
      {
         if (ntManager.isNodeType(Constants.NT_VERSIONEDCHILD, vhnode.getPrimaryTypeName(), vhnode.getMixinTypeNames()))
         {
            PropertyData property =
               (PropertyData)dataManager.getItemData(nodeData, new QPathEntry(Constants.JCR_CHILDVERSIONHISTORY, 1),
                  ItemType.PROPERTY);

            if (property == null)
               throw new RepositoryException("Property " + Constants.JCR_CHILDVERSIONHISTORY.getAsString()
                  + " for node " + nodeData.getQPath().getAsString() + " not found");

            String childVhID;
            try
            {
               childVhID = new String(property.getValues().get(0).getAsByteArray());
            }
            catch (IOException e)
            {
               throw new RepositoryException("Child version history UUID read error " + e, e);
            }
            VersionHistoryRemover historyRemover =
               new VersionHistoryRemover(childVhID, dataManager, ntManager, repository, currentWorkspaceName,
                  containingHistory, ancestorToSave, transientChangesLog, accessManager, userState);
            historyRemover.remove();

         }

      }
      // remove VH
      ItemDataRemoveVisitor visitor = new ItemDataRemoveVisitor(dataManager, ancestorToSave);
      vhnode.accept(visitor);
      transientChangesLog.addAll(visitor.getRemovedStates());
   };

}
