package org.jboss.seam.security.permission;

import static org.jboss.seam.ScopeType.APPLICATION;
import static org.jboss.seam.annotations.Install.BUILT_IN;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Startup;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.core.Events;
import org.jboss.seam.core.Init;

/**
 * Maps permission checks to resolver chains
 * 
 * @author Shane Bryzak
 */
@Scope(APPLICATION)
@Name("org.jboss.seam.security.permissionMapper")
@Install(precedence = BUILT_IN)
@BypassInterceptors
@Startup
public class PermissionMapper implements Serializable
{
   public static final String DEFAULT_RESOLVER_CHAIN_CREATED = "org.jboss.seam.security.defaultResolverChainCreated";
   
   private Map<Class,Map<String,String>> resolverChains = new HashMap<Class,Map<String,String>>();
   
   private String defaultResolverChain;
   
   private static final String DEFAULT_RESOLVER_CHAIN = "org.jboss.seam.security.defaultResolverChain";
   
   private ResolverChain getResolverChain(Object target, String action)
   {
      Class targetClass = null;
      
      if (target instanceof Class)
      {
         targetClass = (Class) target;
      }
      else
      {
         // TODO target may be a component name, or an object, or a view name (or arbitrary name) -
         // we need to deal with all of these possibilities
      }
      
      if (targetClass != null)
      {
         Map<String,String> chains = resolverChains.get(target);
         if (chains != null && chains.containsKey(action))
         {
            return (ResolverChain) Component.getInstance(chains.get(action), true);
         }
      }      
      
      if (defaultResolverChain != null && !"".equals(defaultResolverChain))
      {
         return (ResolverChain) Component.getInstance(defaultResolverChain, true);   
      }
      
      return createDefaultResolverChain();
   }   
   
   public boolean resolvePermission(Object target, String action)
   {
      ResolverChain chain = getResolverChain(target, action);
      for (PermissionResolver resolver : chain.getResolvers())
      {
         if (resolver.hasPermission(target, action))
         {
            return true;
         }
      }
      
      return false;
   }   
   
   public void filterByPermission(Collection collection, String action)
   {
      boolean homogenous = true;
      
      Class targetClass = null;
      for (Object target : collection)
      {
         if (targetClass == null) targetClass = target.getClass();
         if (!targetClass.equals(target.getClass()))
         {
            homogenous = false;
            break;
         }
      }
           
      if (homogenous)
      {
         Set<Object> denied = new HashSet<Object>(collection);   
         ResolverChain chain = getResolverChain(targetClass, action);
         for (PermissionResolver resolver : chain.getResolvers())
         {
            resolver.filterSetByAction(denied, action);
         }
         
         for (Object target : denied)
         {
            collection.remove(target);
         }     
      }
      else
      {
         Map<Class,Set<Object>> deniedByClass = new HashMap<Class,Set<Object>>();
         for (Object obj : collection)
         {
            if (!deniedByClass.containsKey(obj.getClass()))
            {
               Set<Object> denied = new HashSet<Object>();
               denied.add(obj);
               deniedByClass.put(obj.getClass(), denied);
            }
            else
            {
               deniedByClass.get(obj.getClass()).add(obj);
            }
         }
         
         for (Class cls : deniedByClass.keySet())
         {
            Set<Object> denied = deniedByClass.get(cls);
            ResolverChain chain = getResolverChain(cls, action);
            for (PermissionResolver resolver : chain.getResolvers())
            {
               resolver.filterSetByAction(denied, action);
            }
            
            for (Object target : denied)
            {
               collection.remove(target);
            }
         }
      }
   }
   
   private ResolverChain createDefaultResolverChain()
   {
      ResolverChain chain = (ResolverChain) Contexts.getSessionContext().get(DEFAULT_RESOLVER_CHAIN);
      
      if (chain == null)
      {
         chain = new ResolverChain();
         
         for (String resolverName : Init.instance().getPermissionResolvers())
         {
            chain.getResolvers().add((PermissionResolver) Component.getInstance(resolverName, true)); 
         }
         
         Contexts.getSessionContext().set(DEFAULT_RESOLVER_CHAIN, chain);
         if (Events.exists()) Events.instance().raiseEvent(DEFAULT_RESOLVER_CHAIN_CREATED, chain);
      }
      
      return chain;
   }

   public static PermissionMapper instance()
   {
      if ( !Contexts.isApplicationContextActive() )
      {
         throw new IllegalStateException("No active application context");
      }
   
      PermissionMapper instance = (PermissionMapper) Component.getInstance(
            PermissionMapper.class, ScopeType.APPLICATION);
   
      if (instance == null)
      {
         throw new IllegalStateException("No PermissionMapper could be created");
      }
   
      return instance;
   }
}
