/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

====================================================================== */

package org.bodington.servlet.facilities;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.bodington.server.BuildingServer;
import org.bodington.server.BuildingServerException;
import org.bodington.server.realm.Acl;
import org.bodington.server.realm.AclEntry;
import org.bodington.server.realm.Group;
import org.bodington.server.realm.Permission;
import org.bodington.server.realm.User;
import org.bodington.server.resources.Resource;

/**
 * Class for working out the acls for a resource.
 * @author buckett
 */
public class AclResource
{
    
    private Resource resource;
    private boolean includeAdmin;
    private Map users = new HashMap();
    private Map groups = new HashMap();

    public AclResource(Resource resource) throws BuildingServerException
    {
        this(resource, false);
    }

    /**
     * Create a new AclResource that will calculate the permissions available
     * to various principals at the supplied resource.
     * @param resource The resource to check permissions on.
     * @param includeAdmin If true then automatically inherited admin
     * permissions are included.
     */
    public AclResource(Resource resource, boolean includeAdmin) throws BuildingServerException
    {
        this.resource = resource;
        this.includeAdmin = includeAdmin;
        addCurrentUser();
        calculate(resource);
        // Need to update all the visible users with the collapsed groups.
        updateOverlapping();
    }
    
    /**
     * Gets a set containing @{link AclResourcePermission} objects keyed on @{link User} objects.
     */
    public Set getResults()
    {
        TreeSet results =new TreeSet();
        results.addAll(users.values());
        results.addAll(groups.values());
        return results;
    }
   
    /**
     * Get a @{link java.util.Set} containing just the @{link AclResourcePermission} for the
     * user. 
     * @param user The user to get the permission for.
     * @return A Set containing one entry.
     */
    public Set getResults(User user)
    {
        TreeSet results = new TreeSet();
        results.add(users.get(user));
        return results;
    }

    /**
     * Added the current user to the list of user permissions.
     * @throws BuildingServerException
     */
    private void addCurrentUser() throws BuildingServerException
    {
        // Make sure the current use is always in the list
        AclUserResourcePermission permission = new AclUserResourcePermission();
        User user = (User)BuildingServer.getInstance().getContext().getUser();
        if (user != null)
        {
            permission.setUser(user);
            users.put(user, permission);
        }
    }
    
    /**
     * Calculates the permissions summary.
     * @param resource The resource to check.
     * @throws BuildingServerException
     */
    private void calculate (Resource resource) throws BuildingServerException 
    {
        
        Acl acl = resource.getAcl();
        AclEntry aclEntry;
        Enumeration aclEntries = acl.entries();
        while(aclEntries.hasMoreElements())
        {
            aclEntry = (AclEntry)aclEntries.nextElement();
            Group group = aclEntry.getGroup();
            Enumeration members = (group.isSpecialGroup() || group.size() == 0 || group.size() > 10)? null :  group.membersChecked();
            if (members == null)
            {
                AclGroupResourcePermission permission = getGroupPermission(group);
                permission.setGroup(group);
                permission.addPermissions(aclEntry);
            }
            else 
            {
                while(members.hasMoreElements())
                {
                    User member = (User)members.nextElement();
                    AclUserResourcePermission permission = getUserPermission(member);
                    permission.addPermissions(aclEntry);
                    permission.addGroup(group);
                }
            }
            
        }
        if (resource.getParent() != null)
        {
            if (resource.getUseParentAcl() )
                calculate(resource.getParent());
            else if (includeAdmin)
                calculateAdmin(resource.getParent());
        }

    }
    
    private void calculateAdmin (Resource resource) throws BuildingServerException 
    {
        
        Acl acl = resource.getAcl();
        AclEntry aclEntry;
        Enumeration aclEntries = acl.entries();
        while(aclEntries.hasMoreElements())
        {
            aclEntry = (AclEntry)aclEntries.nextElement();
            Group group = aclEntry.getGroup();
            if (!(aclEntry.checkPermission(Permission.ADMINISTER) || aclEntry
                .checkPermission(Permission.SYSADMIN)))
                continue;
            Enumeration members = (group.isSpecialGroup() || group.size() == 0 || group.size() > 10)? null :  group.membersChecked();
            if (members == null)
            {
                AclGroupResourcePermission permission = getGroupPermission(group);
                permission.setGroup(group);
                permission.addAdminPermissions(aclEntry);

            }
            else 
            {
                while(members.hasMoreElements())
                {
                    User member = (User)members.nextElement();
                    AclUserResourcePermission permission = getUserPermission(member);
                    permission.addGroup(group);
                    permission.addAdminPermissions(aclEntry);
                }
            }
            
        }
        if (resource.getParent() != null)
        {
            calculateAdmin(resource.getParent());
        }

    }
    
    /**
     * Update users who are in colapsed groups.
     * If we didn't display the contents of a group because it was too big or the 
     * user didn't have permission we need to update the users which can be seen 
     * with the permissions of that group and add to the users list.
     */
    private void updateOverlapping()
    {
        Iterator usersIterator = users.keySet().iterator();
        while(usersIterator.hasNext())
        {
            User user = (User)usersIterator.next();
            AclUserResourcePermission userAcl = (AclUserResourcePermission)users.get(user);
            Iterator groupsIterator = groups.keySet().iterator();
            while(groupsIterator.hasNext())
            { 
                Group group = (Group)groupsIterator.next();
                AclGroupResourcePermission groupAcl = (AclGroupResourcePermission)groups.get(group);
                if (user.isMember(group)) {
                    userAcl.addGroup(group);
                    userAcl.addPermissions(groupAcl);
                }
            }
        }
        
    }
    
    private AclGroupResourcePermission getGroupPermission(Group group)
    {
        AclGroupResourcePermission permission = (AclGroupResourcePermission) groups
            .get(group);
        if (permission == null)
        {
            permission = new AclGroupResourcePermission();
            groups.put(group, permission);
        }
        return permission;
    }

    private AclUserResourcePermission getUserPermission(User user)
    {
        AclUserResourcePermission permission = (AclUserResourcePermission)users.get(user);
        if (permission == null)
        {
            permission = new AclUserResourcePermission();
            permission.setUser(user);
            users.put(user, permission);
        }
        return permission;
    }
    

}
