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

import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.access.AccessControlList;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager;
import org.exoplatform.services.jcr.dataflow.ItemState;
import org.exoplatform.services.jcr.datamodel.InternalQName;
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.ValueData;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.core.SessionDataManager;
import org.exoplatform.services.jcr.util.IdGenerator;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import javax.jcr.RepositoryException;

/**
 * The class visits each node, all subnodes and all of them properties. It transfer as parameter of
 * a method <code>ItemData.visits()</code>. During visiting the class forms the <b>itemAddStates</b>
 * list of <code>List&lt;ItemState&gt;</code> for copying new nodes and their properties.
 * 
 * @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter Nedonosko</a>
 * @version $Id$
 */

public abstract class DefaultItemDataCopyVisitor extends AbstractItemDataCopyVisitor
{

   /**
    * Destination data manager.
    */
   protected SessionDataManager dstDataManager;

   /**
    * Destination node name
    */
   protected InternalQName destNodeName;

   /**
    * The stack. In the top it contains a parent node.
    */
   protected Stack<NodeData> parents;

   /**
    * The list of added item states
    */
   protected List<ItemState> itemAddStates = new ArrayList<ItemState>();

   /**
    * The variable shows necessity of preservation <code>Identifier</code>, not generate new one, at
    * transformation of <code>Item</code>.
    */
   protected boolean keepIdentifiers;

   /**
    * The NodeTypeManager
    */
   protected NodeTypeDataManager ntManager;

   protected QPath ancestorToSave = null;

   /**
    * Creates an instance of this class.
    * 
    * @param parent
    *          - The parent node
    * @param destNodeName
    *          - Destination node name
    * @param nodeTypeManager
    *          - The NodeTypeManager
    * @param srcDataManager
    *          - Source data manager
    * @param keepIdentifiers
    *          - Is it necessity to keep <code>Identifier</code>
    */

   public DefaultItemDataCopyVisitor(NodeData parent, InternalQName destNodeName, NodeTypeDataManager nodeTypeManager,
      SessionDataManager srcDataManager, SessionDataManager dstDataManager, boolean keepIdentifiers)
   {
      super(srcDataManager);

      this.dstDataManager = dstDataManager;
      this.keepIdentifiers = keepIdentifiers;
      this.ntManager = nodeTypeManager;
      this.destNodeName = destNodeName;

      this.parents = new Stack<NodeData>();
      this.parents.add(parent);
   }

   /**
    * {@inheritDoc}
    */
   @Override
   protected void entering(PropertyData property, int level) throws RepositoryException
   {

      InternalQName qname = property.getQPath().getName();

      List<ValueData> values;

      if (ntManager.isNodeType(Constants.MIX_REFERENCEABLE, curParent().getPrimaryTypeName(), curParent()
         .getMixinTypeNames())
         && qname.equals(Constants.JCR_UUID))
      {
         values = new ArrayList<ValueData>(1);
         values.add(new TransientValueData(curParent().getIdentifier()));
      }
      else
      {
         values = copyValues(property);
      }

      TransientPropertyData newProperty =
         new TransientPropertyData(QPath.makeChildPath(curParent().getQPath(), qname), keepIdentifiers ? property
            .getIdentifier() : IdGenerator.generate(), -1, property.getType(), curParent().getIdentifier(), property
            .isMultiValued(), values);

      itemAddStates.add(new ItemState(newProperty, ItemState.ADDED, true, ancestorToSave, level != 0));
   }

   /**
    * {@inheritDoc}
    */
   @Override
   protected void entering(NodeData node, int level) throws RepositoryException
   {

      if (ancestorToSave == null)
      {
         ancestorToSave = curParent().getQPath();
      }

      NodeData parent = curParent();
      QPath qpath = calculateNewNodePath(node, level);
      // Calc order number if parent supports orderable nodes...
      // If ordering is supported by the node type of the parent node of the new
      // location, then the newly moved node is appended to the end of the child node list.
      int orderNum = 0;
      if (level == 0
         && ntManager.isOrderableChildNodesSupported(parent.getPrimaryTypeName(), parent.getMixinTypeNames()))
      {
         orderNum = calculateNewNodeOrderNumber();
      }
      else
      {
         orderNum = node.getOrderNumber(); // has no matter
      }
      String id = keepIdentifiers ? node.getIdentifier() : IdGenerator.generate();

      AccessControlList acl = parent.getACL();

      boolean isPrivilegeable =
         ntManager.isNodeType(Constants.EXO_PRIVILEGEABLE, node.getPrimaryTypeName(), node.getMixinTypeNames());

      boolean isOwneable =
         ntManager.isNodeType(Constants.EXO_OWNEABLE, node.getPrimaryTypeName(), node.getMixinTypeNames());

      if (isPrivilegeable || isOwneable)
      {
         List<AccessControlEntry> permissionEntries = new ArrayList<AccessControlEntry>();
         permissionEntries.addAll((isPrivilegeable ? node.getACL() : parent.getACL()).getPermissionEntries());

         String owner = isOwneable ? node.getACL().getOwner() : parent.getACL().getOwner();

         acl = new AccessControlList(owner, permissionEntries);
      }

      TransientNodeData newNode =
         new TransientNodeData(qpath, id, -1, node.getPrimaryTypeName(), node.getMixinTypeNames(), orderNum,
            parent.getIdentifier(), acl);

      parents.push(newNode);

      // ancestorToSave is a parent node
      // if level == 0 set internal createt as false for validating on save
      itemAddStates.add(new ItemState(newNode, ItemState.ADDED, true, ancestorToSave, level != 0));
   }

   /**
    * {@inheritDoc}
    */
   @Override
   protected void leaving(PropertyData property, int level) throws RepositoryException
   {

   }

   /**
    * {@inheritDoc}
    */
   @Override
   protected void leaving(NodeData node, int level) throws RepositoryException
   {
      parents.pop();
   }

   /**
    * Returns the current parent node.
    */
   protected NodeData curParent()
   {
      return parents.peek();
   }

   /**
    * Returns the list of item add states.
    */
   public List<ItemState> getItemAddStates()
   {
      return itemAddStates;
   }

   /**
    * Find item states.
    *
    * @param itemPath item path
    * @return List of states
    */
   protected List<ItemState> findItemStates(QPath itemPath)
   {
      List<ItemState> istates = new ArrayList<ItemState>();
      for (ItemState istate : itemAddStates)
      {
         if (istate.getData().getQPath().equals(itemPath))
            istates.add(istate);
      }
      return istates;
   }

   /**
    * Find last ItemState.
    *
    * @param itemPath item path
    * @return ItemState
    */
   protected ItemState findLastItemState(QPath itemPath)
   {
      for (int i = itemAddStates.size() - 1; i >= 0; i--)
      {
         ItemState istate = itemAddStates.get(i);
         if (istate.getData().getQPath().equals(itemPath))
            return istate;
      }
      return null;
   }

   protected int calculateNewNodeOrderNumber() throws RepositoryException
   {
      return dstDataManager.getLastOrderNumber(curParent()) + 1;
   }

   protected QPath calculateNewNodePath(NodeData node, int level) throws RepositoryException
   {
      NodeData parent = curParent();

      InternalQName qname = null;

      List<NodeData> existedChilds = dstDataManager.getChildNodesData(parent);
      int newIndex = 1;
      if (level == 0)
      {
         qname = destNodeName;
         // Calculate SNS index for dest root
         for (NodeData child : existedChilds)
         {
            if (child.getQPath().getName().equals(qname))
            {
               newIndex++; // next sibling index
            }
         }
      }
      else
      {
         qname = node.getQPath().getName();
         newIndex = node.getQPath().getIndex();
      }
      return QPath.makeChildPath(parent.getQPath(), qname, newIndex);
   }
}
