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

import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.access.AccessControlList;
import org.exoplatform.services.jcr.access.AccessControlPolicy;
import org.exoplatform.services.jcr.access.PermissionType;
import org.exoplatform.services.jcr.config.RepositoryConfigurationException;
import org.exoplatform.services.jcr.config.RepositoryEntry;
import org.exoplatform.services.jcr.config.WorkspaceEntry;
import org.exoplatform.services.jcr.core.ExtendedPropertyType;
import org.exoplatform.services.jcr.dataflow.DataManager;
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.dataflow.TransactionChangesLog;
import org.exoplatform.services.jcr.datamodel.InternalQName;
import org.exoplatform.services.jcr.datamodel.NodeData;
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.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.persistent.CacheableWorkspaceDataManager;
import org.exoplatform.services.jcr.util.IdGenerator;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.security.IdentityConstants;

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

import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;

/**
 * Created by The eXo Platform SAS. <br> Default workspace intializer. <br>
 * Can be configured with root-nodetype and root-permissions parameters. If
 * root-nodetype and root-permissions are empty then <br> root-nodetype =
 * nt:unstructured <br> root-permissions = ACL default <br> values will be
 * applied.
 * 
 * @author Gennady Azarenkov
 * @version $Id: ScratchWorkspaceInitializer.java 13986 2008-05-08 10:48:43Z
 *          pnedonosko $
 */

public class ScratchWorkspaceInitializer implements WorkspaceInitializer
{

   protected static final Log log = ExoLogger.getLogger("exo.jcr.component.core.ScratchWorkspaceInitializer");

   private final String systemWorkspaceName;

   private final String workspaceName;

   private final DataManager dataManager;

   private final String accessControlType;

   // private final NamespaceDataPersister nsPersister;

   private final String rootPermissions;

   private final InternalQName rootNodeType;

   public ScratchWorkspaceInitializer(WorkspaceEntry config, RepositoryEntry repConfig,
      CacheableWorkspaceDataManager dataManager, LocationFactory locationFactory)
      throws RepositoryConfigurationException, PathNotFoundException, RepositoryException
   {

      this.systemWorkspaceName = repConfig.getSystemWorkspaceName();
      this.accessControlType = repConfig.getAccessControl();
      this.workspaceName = config.getName();

      // workspace root params
      String rootPermissions = null;
      String rootNodeType = null;
      if (config.getInitializer() != null)
      {
         // use user configuration for initializer
         rootPermissions =
            config.getInitializer().getParameterValue(WorkspaceInitializer.ROOT_PERMISSIONS_PARAMETER, null);
         rootNodeType = config.getInitializer().getParameterValue(WorkspaceInitializer.ROOT_NODETYPE_PARAMETER, null);
      }

      // default behaviour root-nodetype=nt:unstructured, root-permissions will be
      // managed by
      // AccessControlList class
      this.rootPermissions = rootPermissions;
      this.rootNodeType =
         rootNodeType != null ? locationFactory.parseJCRName(rootNodeType).getInternalName()
            : Constants.NT_UNSTRUCTURED;

      this.dataManager = dataManager;
   }

   public NodeData initWorkspace() throws RepositoryException
   {

      if (isWorkspaceInitialized())
      {
         return (NodeData)dataManager.getItemData(Constants.ROOT_UUID);
      }

      NodeData root = initRootNode(rootNodeType);

      if (log.isDebugEnabled())
         log.debug("Root node for " + workspaceName + " initialized. NodeType: " + rootNodeType + " system workspace: "
            + systemWorkspaceName);

      // Init system workspace
      if (workspaceName.equals(systemWorkspaceName))
      {
         // initialize /jcr:system
         NodeData sys = initJcrSystemNode(root);
      }

      return root;
   }

   /**
    * Workspace jobs. Will start after the repository initialization.
    */
   public void start()
   {
   }

   public boolean isWorkspaceInitialized() throws RepositoryException
   {
      try
      {
         return dataManager.getItemData(Constants.ROOT_UUID) != null;
      }
      catch (RepositoryException e)
      {
         throw new RepositoryException("Cannot check if the workspace '" + workspaceName
            + "' has already been initialized", e);
      }
   }

   private NodeData initRootNode(InternalQName rootNodeType) throws RepositoryException
   {

      boolean addACL = !accessControlType.equals(AccessControlPolicy.DISABLE);

      PlainChangesLog changesLog = new PlainChangesLogImpl();
      TransientNodeData rootNode;

      if (addACL)
      {
         AccessControlList acl = new AccessControlList();

         if (rootPermissions != null)
         {
            acl.removePermissions(IdentityConstants.ANY);
            acl.addPermissions(rootPermissions);
         }

         InternalQName[] mixins = new InternalQName[]{Constants.EXO_OWNEABLE, Constants.EXO_PRIVILEGEABLE};

         rootNode =
            new TransientNodeData(Constants.ROOT_PATH, Constants.ROOT_UUID, -1, rootNodeType, mixins, 0, null, acl);
         changesLog.add(new ItemState(rootNode, ItemState.ADDED, false, null));

         TransientPropertyData primaryType =
            new TransientPropertyData(QPath.makeChildPath(rootNode.getQPath(), Constants.JCR_PRIMARYTYPE), IdGenerator
               .generate(), -1, PropertyType.NAME, rootNode.getIdentifier(), false,
               new TransientValueData(rootNodeType));

         changesLog.add(new ItemState(primaryType, ItemState.ADDED, false, null)); // 

         // jcr:mixinTypes
         List<ValueData> mixValues = new ArrayList<ValueData>();
         for (InternalQName mixin : mixins)
         {
            mixValues.add(new TransientValueData(mixin));
         }
         TransientPropertyData exoMixinTypes =
            TransientPropertyData.createPropertyData(rootNode, Constants.JCR_MIXINTYPES, PropertyType.NAME, true,
               mixValues);

         TransientPropertyData exoOwner =
            TransientPropertyData.createPropertyData(rootNode, Constants.EXO_OWNER, PropertyType.STRING, false,
               new TransientValueData(acl.getOwner()));

         List<ValueData> permsValues = new ArrayList<ValueData>();
         for (int i = 0; i < acl.getPermissionEntries().size(); i++)
         {
            AccessControlEntry entry = acl.getPermissionEntries().get(i);
            permsValues.add(new TransientValueData(entry));
         }
         TransientPropertyData exoPerms =
            TransientPropertyData.createPropertyData(rootNode, Constants.EXO_PERMISSIONS,
               ExtendedPropertyType.PERMISSION, true, permsValues);

         changesLog.add(ItemState.createAddedState(exoMixinTypes)).add(ItemState.createAddedState(exoOwner)).add(
            ItemState.createAddedState(exoPerms));
         changesLog.add(new ItemState(rootNode, ItemState.MIXIN_CHANGED, false, null));
      }
      else
      {
         rootNode =
            new TransientNodeData(Constants.ROOT_PATH, Constants.ROOT_UUID, -1, rootNodeType, new InternalQName[0], 0,
               null, new AccessControlList());
         changesLog.add(new ItemState(rootNode, ItemState.ADDED, false, null));

         TransientPropertyData primaryType =
            new TransientPropertyData(QPath.makeChildPath(rootNode.getQPath(), Constants.JCR_PRIMARYTYPE), IdGenerator
               .generate(), -1, PropertyType.NAME, rootNode.getIdentifier(), false,
               new TransientValueData(rootNodeType));
         changesLog.add(new ItemState(primaryType, ItemState.ADDED, false, null)); // 
      }

      dataManager.save(new TransactionChangesLog(changesLog));
      return rootNode;
   }

   private NodeData initJcrSystemNode(NodeData root) throws RepositoryException
   {
      boolean addACL = !accessControlType.equals(AccessControlPolicy.DISABLE);

      PlainChangesLog changesLog = new PlainChangesLogImpl();
      TransientNodeData jcrSystem;

      if (addACL)
      {
         InternalQName[] mixins = new InternalQName[]{Constants.EXO_OWNEABLE, Constants.EXO_PRIVILEGEABLE};

         jcrSystem =
            TransientNodeData.createNodeData(root, Constants.JCR_SYSTEM, Constants.NT_UNSTRUCTURED, mixins,
               Constants.SYSTEM_UUID);

         AccessControlList acl = jcrSystem.getACL();

         TransientPropertyData primaryType =
            TransientPropertyData.createPropertyData(jcrSystem, Constants.JCR_PRIMARYTYPE, PropertyType.NAME, false,
               new TransientValueData(jcrSystem.getPrimaryTypeName()));

         changesLog.add(ItemState.createAddedState(jcrSystem)).add(ItemState.createAddedState(primaryType));

         // jcr:mixinTypes
         List<ValueData> mixValues = new ArrayList<ValueData>();
         for (InternalQName mixin : mixins)
         {
            mixValues.add(new TransientValueData(mixin));
         }
         TransientPropertyData exoMixinTypes =
            TransientPropertyData.createPropertyData(jcrSystem, Constants.JCR_MIXINTYPES, PropertyType.NAME, true,
               mixValues);

         TransientPropertyData exoOwner =
            TransientPropertyData.createPropertyData(jcrSystem, Constants.EXO_OWNER, PropertyType.STRING, false,
               new TransientValueData(acl.getOwner()));

         List<ValueData> permsValues = new ArrayList<ValueData>();
         for (int i = 0; i < acl.getPermissionEntries().size(); i++)
         {
            AccessControlEntry entry = acl.getPermissionEntries().get(i);
            permsValues.add(new TransientValueData(entry));
         }
         TransientPropertyData exoPerms =
            TransientPropertyData.createPropertyData(jcrSystem, Constants.EXO_PERMISSIONS,
               ExtendedPropertyType.PERMISSION, true, permsValues);

         changesLog.add(ItemState.createAddedState(exoMixinTypes)).add(ItemState.createAddedState(exoOwner)).add(
            ItemState.createAddedState(exoPerms));
         changesLog.add(new ItemState(jcrSystem, ItemState.MIXIN_CHANGED, false, null));
      }
      else
      {
         jcrSystem =
            TransientNodeData.createNodeData(root, Constants.JCR_SYSTEM, Constants.NT_UNSTRUCTURED,
               Constants.SYSTEM_UUID);

         TransientPropertyData primaryType =
            TransientPropertyData.createPropertyData(jcrSystem, Constants.JCR_PRIMARYTYPE, PropertyType.NAME, false,
               new TransientValueData(jcrSystem.getPrimaryTypeName()));

         changesLog.add(ItemState.createAddedState(jcrSystem)).add(ItemState.createAddedState(primaryType));
      }

      // init version storage
      AccessControlList acl = new AccessControlList();
      acl.removePermissions(IdentityConstants.ANY);
      acl.addPermissions(IdentityConstants.ANY, new String[]{PermissionType.READ});

      for (AccessControlEntry entry : jcrSystem.getACL().getPermissionEntries())
      {
         String identity = entry.getIdentity();
         String permission = entry.getPermission();

         if (!identity.equals(IdentityConstants.ANY) || !permission.equals(PermissionType.READ))
         {
            acl.addPermissions(identity, new String[]{permission});
         }
      }

      TransientNodeData versionStorageNodeData =
         TransientNodeData.createNodeData(jcrSystem, Constants.JCR_VERSIONSTORAGE, Constants.EXO_VERSIONSTORAGE,
            Constants.VERSIONSTORAGE_UUID, acl);

      TransientPropertyData vsPrimaryType =
         TransientPropertyData.createPropertyData(versionStorageNodeData, Constants.JCR_PRIMARYTYPE, PropertyType.NAME,
            false, new TransientValueData(versionStorageNodeData.getPrimaryTypeName()));

      TransientPropertyData exoMixinTypes =
         TransientPropertyData.createPropertyData(versionStorageNodeData, Constants.JCR_MIXINTYPES, PropertyType.NAME,
            true, new TransientValueData(Constants.EXO_PRIVILEGEABLE));

      List<ValueData> permsValues = new ArrayList<ValueData>();
      for (int i = 0; i < acl.getPermissionEntries().size(); i++)
      {
         AccessControlEntry entry = acl.getPermissionEntries().get(i);
         permsValues.add(new TransientValueData(entry));
      }
      TransientPropertyData exoPerms =
         TransientPropertyData.createPropertyData(versionStorageNodeData, Constants.EXO_PERMISSIONS,
            ExtendedPropertyType.PERMISSION, true, permsValues);

      changesLog.add(ItemState.createAddedState(versionStorageNodeData));
      changesLog.add(ItemState.createAddedState(vsPrimaryType));
      changesLog.add(ItemState.createAddedState(exoMixinTypes));
      changesLog.add(ItemState.createAddedState(exoPerms));
      changesLog.add(new ItemState(versionStorageNodeData, ItemState.MIXIN_CHANGED, false, null));

      dataManager.save(new TransactionChangesLog(changesLog));

      return jcrSystem;
   }

   public void stop()
   {
   }
}
