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

import org.exoplatform.services.ext.action.InvocationContext;
import org.exoplatform.services.jcr.config.RepositoryEntry;
import org.exoplatform.services.jcr.config.SimpleParameterEntry;
import org.exoplatform.services.jcr.config.WorkspaceEntry;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.security.Identity;
import org.exoplatform.services.security.IdentityConstants;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.jcr.RepositoryException;

/**
 * Created by The eXo Platform SAS<br>
 *
 * The AccessManager can be queried to determine whether permission is granted
 * to perform a specific action on a specific item.
 * 
 * @author Gennady Azarenkov
 * @version $Id: AccessManager.java 14518 2008-05-20 13:27:19Z ksm $
 * @LevelAPI Experimental
 */

public abstract class AccessManager
{

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

   protected final Map<String, String> parameters;

   private static ThreadLocal<InvocationContext> contextHolder = new ThreadLocal<InvocationContext>();

   protected AccessManager(RepositoryEntry config, WorkspaceEntry wsConfig) throws RepositoryException
   {

      this.parameters = new HashMap<String, String>();
      if (wsConfig != null && wsConfig.getAccessManager() != null)
      {
         Collection<SimpleParameterEntry> paramList = wsConfig.getAccessManager().getParameters();
         for (SimpleParameterEntry param : paramList)
            parameters.put(param.getName(), param.getValue());
      }
   }

   protected final InvocationContext context()
   {
      return contextHolder.get();
   }

   public final void setContext(InvocationContext context)
   {
      contextHolder.set(context);
   }

   /**
    * Has permission.
    * 
    * @param acl
    *          access control list
    * @param permission
    *          permission
    * @param user
    *          user Identity
    * @return boolean
    * @throws RepositoryException
    */
   public final boolean hasPermission(AccessControlList acl, String permission, Identity user)
      throws RepositoryException
   {
      return hasPermission(acl, parseStringPermissions(permission), user);
   }

   /**
    * Has permission.
    * 
    * @param acl
    *          access control list
    * @param permission
    *          permissions array
    * @param user
    *          user Identity
    * @return boolean
    */
   public boolean hasPermission(AccessControlList acl, String[] permission, Identity user)
   {

      String userId = user.getUserId();

      if (userId.equals(IdentityConstants.SYSTEM))
      {
         // SYSTEM has permission everywhere
         return true;
      }
      else if (userId.equals(acl.getOwner()))
      {
         // Current user is owner of node so has all privileges
         return true;
      }
      else if (userId.equals(IdentityConstants.ANONIM))
      {
         List<String> anyPermissions = acl.getPermissions(IdentityConstants.ANY);

         if (anyPermissions.size() < permission.length)
            return false;

         for (int i = 0; i < permission.length; i++)
         {
            if (!anyPermissions.contains(permission[i]))
               return false;
         }

         return true;
      }
      else
      {

         if (acl.getPermissionsSize() > 0 && permission.length > 0)
         {
            // check permission to perform all of the listed actions
            for (int i = 0; i < permission.length; i++)
            {
               // check specific actions
               if (!isPermissionMatch(acl.getPermissionsList(), permission[i], user))
                  return false;
            }
            return true;
         }
         return false;

      }
   }

   private static String[] parseStringPermissions(String str) throws RepositoryException
   {
      List<String> permissions = new ArrayList<String>();
      int fromIndex = 0;
      int index;
      do
      {
         index = str.indexOf(',', fromIndex);
         String token = index == -1 ? str.substring(fromIndex) : str.substring(fromIndex, index);
         token = token.trim();
         fromIndex = index + 1;
         if (token.isEmpty())
            continue;
         if (PermissionType.READ.equals(token) || PermissionType.ADD_NODE.equals(token)
            || PermissionType.REMOVE.equals(token) || PermissionType.SET_PROPERTY.equals(token))
         {
            permissions.add(token);
         }
         else
         {
            throw new RepositoryException("Unknown permission entry " + token);
         }
      }
      while (index != -1);
      return permissions.toArray(new String[permissions.size()]);
   }

   private boolean isPermissionMatch(List<AccessControlEntry> existedPermission, String testPermission, Identity user)
   {
      for (int i = 0, length = existedPermission.size(); i < length; i++)
      {
         AccessControlEntry ace = existedPermission.get(i);
         // match action
         if (ace.getPermission().equals(testPermission))
         {
            // match any
            if (ace.getIdentity().equals(IdentityConstants.ANY))
               return true;
            else if (ace.getIdentity().indexOf(":") == -1)
            {
               // just user
               if (ace.getIdentity().equals(user.getUserId()))
                  return true;

            }
            else if (user.isMemberOf(ace.getMembershipEntry()))
            {
               return true;
            }
         }
      }
      return false;
   }

}
