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

import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.core.nodetype.NodeDefinitionData;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeData;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager;
import org.exoplatform.services.jcr.core.nodetype.PropertyDefinitionData;
import org.exoplatform.services.jcr.dataflow.ItemDataConsumer;
import org.exoplatform.services.jcr.dataflow.ItemState;
import org.exoplatform.services.jcr.dataflow.PlainChangesLog;
import org.exoplatform.services.jcr.dataflow.PlainChangesLogImpl;
import org.exoplatform.services.jcr.datamodel.InternalQName;
import org.exoplatform.services.jcr.datamodel.ItemData;
import org.exoplatform.services.jcr.datamodel.ItemType;
import org.exoplatform.services.jcr.datamodel.NodeData;
import org.exoplatform.services.jcr.datamodel.QPathEntry;
import org.exoplatform.services.jcr.datamodel.ValueData;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.core.value.BaseValue;
import org.exoplatform.services.jcr.impl.dataflow.TransientNodeData;
import org.exoplatform.services.jcr.impl.dataflow.TransientPropertyData;
import org.exoplatform.services.jcr.impl.dataflow.TransientValueData;
import org.exoplatform.services.jcr.impl.dataflow.version.VersionHistoryDataHelper;
import org.exoplatform.services.jcr.util.IdGenerator;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.ValueFactory;

/**
 * @author <a href="mailto:Sergey.Kabashnyuk@exoplatform.org">Sergey Kabashnyuk</a>
 * @version $Id: exo-jboss-codetemplates.xml 34360 2009-07-22 23:58:59Z ksm $
 *
 */
public class ItemAutocreator
{

   private static final Log LOG = ExoLogger.getLogger("exo.jcr.component.core.ItemAutocreator");

   private final NodeTypeDataManager nodeTypeDataManager;

   private final ItemDataConsumer dataConsumer;

   private final ValueFactory valueFactory;

   private final boolean avoidCheckExistedChildItems;

   /**
    * @param nodeTypeDataManager
    */
   public ItemAutocreator(NodeTypeDataManager nodeTypeDataManager, ValueFactory valueFactory,
      ItemDataConsumer dataConsumer, boolean avoidCheckExistedChildItems)
   {
      super();
      this.nodeTypeDataManager = nodeTypeDataManager;
      this.valueFactory = valueFactory;
      this.dataConsumer = dataConsumer;
      this.avoidCheckExistedChildItems = avoidCheckExistedChildItems;
   }

   public PlainChangesLog makeAutoCreatedItems(final NodeData parent, final InternalQName nodeTypeName,
      final ItemDataConsumer targetDataManager, final String owner) throws RepositoryException
   {
      return makeAutoCreatedItems(parent, nodeTypeName, targetDataManager, owner, false);
   }

   /**
    * Prepares changes log  
    */
   private PlainChangesLog makeAutoCreatedItems(final NodeData parent, final InternalQName nodeTypeName,
      final ItemDataConsumer targetDataManager, final String owner, boolean addedAutoCreatedNodes)
      throws RepositoryException
   {
      final PlainChangesLogImpl changes = new PlainChangesLogImpl();
      final NodeTypeData type = nodeTypeDataManager.getNodeType(nodeTypeName);

      changes.addAll(makeAutoCreatedProperties(parent, nodeTypeName,
         nodeTypeDataManager.getAllPropertyDefinitions(nodeTypeName), targetDataManager, owner).getAllStates());
      // Add autocreated child nodes
      changes.addAll(makeAutoCreatedNodes(parent, nodeTypeName,
         nodeTypeDataManager.getAllChildNodeDefinitions(nodeTypeName), targetDataManager, owner).getAllStates());

      // versionable
      if (nodeTypeDataManager.isNodeType(Constants.MIX_VERSIONABLE, new InternalQName[]{type.getName()}))
      {
         // using VH helper as for one new VH, all changes in changes log
         changes.addAll(makeMixVesionableChanges(parent).getAllStates());
      }
      return changes;
   }

   public PlainChangesLog makeAutoCreatedNodes(final NodeData parent, final InternalQName typeName,
      final NodeDefinitionData[] nodeDefs, final ItemDataConsumer targetDataManager, final String owner)
      throws RepositoryException
   {
      return makeAutoCreatedNodes(parent, typeName, nodeDefs, targetDataManager, owner, false);
   }

   private PlainChangesLog makeAutoCreatedNodes(final NodeData parent, final InternalQName typeName,
      final NodeDefinitionData[] nodeDefs, final ItemDataConsumer targetDataManager, final String owner,
      boolean addedAutoCreatedNodes) throws RepositoryException
   {
      final PlainChangesLogImpl changes = new PlainChangesLogImpl();
      final Set<InternalQName> addedNodes = new HashSet<InternalQName>();
      for (final NodeDefinitionData ndef : nodeDefs)
      {
         if (ndef.isAutoCreated())
         {
            final ItemData pdata =
               avoidCheckExistedChildItems ? null : targetDataManager.getItemData(parent, new QPathEntry(
                  ndef.getName(), 0), ItemType.NODE, false);
            if (pdata == null && !addedNodes.contains(ndef.getName()) || pdata != null && !pdata.isNode())
            {

               final TransientNodeData childNodeData =
                  TransientNodeData.createNodeData(parent, ndef.getName(), ndef.getDefaultPrimaryType(),
                     IdGenerator.generate());

               if (!addedAutoCreatedNodes)
               {
                  addedAutoCreatedNodes = true;
                  changes.add(ItemState.createAddedAutoCreatedNodes(parent));
               }

               changes.add(ItemState.createAddedState(childNodeData, false));
               changes.addAll(makeAutoCreatedItems(childNodeData, childNodeData.getPrimaryTypeName(),
                  targetDataManager, owner, addedAutoCreatedNodes).getAllStates());
               addedNodes.add(ndef.getName());
            }
            else
            {
               if (this.LOG.isDebugEnabled())
               {
                  this.LOG.debug("Skipping existed node " + ndef.getName() + " in " + parent.getQPath().getAsString()
                     + "   during the automatic creation of items for " + typeName.getAsString()
                     + " nodetype or mixin type");
               }
            }
         }
      }
      return changes;
   }

   public PlainChangesLog makeAutoCreatedProperties(final NodeData parent, final InternalQName typeName,
      final PropertyDefinitionData[] propDefs, final ItemDataConsumer targetDataManager, final String owner)
      throws RepositoryException
   {
      final PlainChangesLogImpl changes = new PlainChangesLogImpl();

      final Set<InternalQName> addedProperties = new HashSet<InternalQName>();

      // Add autocreated child properties

      for (final PropertyDefinitionData pdef : propDefs)
      {

         if (pdef.isAutoCreated())
         {

            final ItemData pdata =
               avoidCheckExistedChildItems ? null : targetDataManager.getItemData(parent, new QPathEntry(
                  pdef.getName(), 0), ItemType.PROPERTY, false);
            if (pdata == null && !addedProperties.contains(pdef.getName()) || pdata != null && pdata.isNode())
            {

               final List<ValueData> listAutoCreateValue = autoCreatedValue(parent, typeName, pdef, owner);

               if (listAutoCreateValue != null)
               {
                  final TransientPropertyData propertyData =
                     TransientPropertyData.createPropertyData(parent, pdef.getName(), pdef.getRequiredType(),
                        pdef.isMultiple(), listAutoCreateValue);
                  changes.add(ItemState.createAddedState(propertyData));
                  addedProperties.add(pdef.getName());
               }
            }
            else
            {
               if (this.LOG.isDebugEnabled())
               {
                  this.LOG.debug("Skipping existed property " + pdef.getName() + " in "
                     + parent.getQPath().getAsString() + "   during the automatic creation of items for "
                     + typeName.getAsString() + " nodetype or mixin type");
               }
            }
         }
      }
      return changes;
   }

   /**
    * @param parent
    * @return
    * @throws RepositoryException
    */
   public PlainChangesLog makeMixVesionableChanges(final NodeData parent) throws RepositoryException
   {
      PlainChangesLog changesLog = new PlainChangesLogImpl();
      new VersionHistoryDataHelper(parent, changesLog, dataConsumer, nodeTypeDataManager);
      return changesLog;
   }

   protected List<ValueData> autoCreatedValue(final NodeData parent, final InternalQName typeName,
      final PropertyDefinitionData def, final String owner) throws RepositoryException
   {
      final List<ValueData> vals = new ArrayList<ValueData>();

      if (nodeTypeDataManager.isNodeType(Constants.NT_BASE, new InternalQName[]{typeName})
         && def.getName().equals(Constants.JCR_PRIMARYTYPE))
      {
         vals.add(new TransientValueData(parent.getPrimaryTypeName()));

      }
      else if (nodeTypeDataManager.isNodeType(Constants.MIX_REFERENCEABLE, new InternalQName[]{typeName})
         && def.getName().equals(Constants.JCR_UUID))
      {
         vals.add(new TransientValueData(parent.getIdentifier()));

      }
      else if (nodeTypeDataManager.isNodeType(Constants.NT_HIERARCHYNODE, new InternalQName[]{typeName})
         && def.getName().equals(Constants.JCR_CREATED))
      {
         vals.add(new TransientValueData(Calendar.getInstance()));
      }
      else if (nodeTypeDataManager.isNodeType(Constants.NT_HIERARCHYNODE, new InternalQName[]{typeName})
         && def.getName().equals(new InternalQName(Constants.NS_JCR_URI, "createdBy")))
      {
         vals.add(new TransientValueData(owner));

      }
      else if (nodeTypeDataManager.isNodeType(Constants.EXO_OWNEABLE, new InternalQName[]{typeName})
         && def.getName().equals(Constants.EXO_OWNER))
      {
         // String owner = session.getUserID();
         vals.add(new TransientValueData(owner));
      }
      else if (nodeTypeDataManager.isNodeType(Constants.EXO_PRIVILEGEABLE, new InternalQName[]{typeName})
         && def.getName().equals(Constants.EXO_PERMISSIONS))
      {
         for (final AccessControlEntry ace : parent.getACL().getPermissionEntries())
         {
            vals.add(new TransientValueData(ace));
         }

      }
      else
      {
         final String[] propVal = def.getDefaultValues();
         // there can be null in definition but should not be null value
         if (propVal != null && propVal.length != 0)
         {
            for (final String v : propVal)
            {
               if (v != null)
               {
                  if (def.getRequiredType() == PropertyType.UNDEFINED)
                  {
                     vals.add(((BaseValue)this.valueFactory.createValue(v)).getInternalData());
                  }
                  else
                  {
                     vals.add(((BaseValue)this.valueFactory.createValue(v, def.getRequiredType())).getInternalData());
                  }
               }
               else
               {
                  vals.add(null);
               }
            }
         }
         else
         {
            return null;
         }
      }

      return vals;
   }
}
