/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.jetspeed.security.spi.impl;

import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.jetspeed.security.JetspeedPrincipal;
import org.apache.jetspeed.security.JetspeedPrincipalAssociationType;
import org.apache.jetspeed.security.JetspeedPrincipalType;
import org.apache.jetspeed.security.User;
import org.apache.jetspeed.security.spi.JetspeedPrincipalAccessManager;
import org.apache.jetspeed.security.spi.UserSubjectPrincipalsProvider;
import org.apache.jetspeed.security.spi.UserSubjectPrincipalsResolver;

/**
 * @version $Id: UserSubjectPrincipalsResolverImpl.java 698741 2008-09-24 21:22:18Z ate $
 *
 */
public class UserSubjectPrincipalsResolverImpl implements UserSubjectPrincipalsResolver
{
    private UserSubjectPrincipalsProvider spp;
    private JetspeedPrincipalType principalType;
    private JetspeedPrincipalAssociationType uat; 
    private List<JetspeedPrincipalAssociationType> iatList; 
    private boolean fromUser;
    private boolean initialized;
    private JetspeedPrincipalAccessManager accessManager;
    
    private static List<Object> wrapInList(JetspeedPrincipalAssociationType type)
    {
        if (type != null)
        {
            List<Object> list = new ArrayList<Object>(1);
            list.add(type);
            return list;
        }
        return Collections.emptyList();
    }

    public UserSubjectPrincipalsResolverImpl(UserSubjectPrincipalsProvider spp, JetspeedPrincipalType principalType,
                                         JetspeedPrincipalAssociationType userAssociationType)
    {
        this(spp, principalType, userAssociationType, (JetspeedPrincipalAssociationType)null);
    }
    
    public UserSubjectPrincipalsResolverImpl(UserSubjectPrincipalsProvider spp, JetspeedPrincipalType principalType,
            JetspeedPrincipalAssociationType userAssociationType,
            JetspeedPrincipalAssociationType indirectAssociationType)
    {
        this(spp, principalType, userAssociationType, wrapInList(indirectAssociationType));
    }
    
    public UserSubjectPrincipalsResolverImpl(UserSubjectPrincipalsProvider spp, JetspeedPrincipalType principalType,
                                         JetspeedPrincipalAssociationType userAssociationType,
                                         List<Object> indirectAssociationTypes)
    {
        if (!spp.getPrincipalType().getName().equals(JetspeedPrincipalType.USER))
        {
            throw new IllegalArgumentException("The provided SubjectPrincipalsProvider should be for the user principalType");
        }
        this.spp = spp;

        if (principalType.getName().equals(JetspeedPrincipalType.USER))
        {
            throw new IllegalArgumentException("This resolver principalType cannot be that of the user principalType");
        }
        this.principalType = principalType;        
        
        if (userAssociationType.getFromPrincipalType().getName().equals(JetspeedPrincipalType.USER))
        {
            if (userAssociationType.getToPrincipalType() != principalType)
            {
                throw new IllegalArgumentException("Provided userAssociationType doesn't target this resolver principalType");
            }
            fromUser = true;
        }
        else if (userAssociationType.getToPrincipalType().getName().equals(JetspeedPrincipalType.USER))
        {
            if (userAssociationType.getFromPrincipalType() != principalType)
            {
                throw new IllegalArgumentException("Provided userAssociationType doesn't target this resolver principalType");
            }
            fromUser = false;
        }
        else
        {
            throw new IllegalArgumentException("Provided userAssociationType is not for a user association");
        }
        this.uat = userAssociationType;
        
        if (indirectAssociationTypes != null && !indirectAssociationTypes.isEmpty())
        {
            iatList = new ArrayList<JetspeedPrincipalAssociationType>(indirectAssociationTypes.size());
            
            for (Object o: indirectAssociationTypes)
            {
                JetspeedPrincipalAssociationType iat = (JetspeedPrincipalAssociationType)o;
                if (iat.getFromPrincipalType() != principalType && iat.getToPrincipalType() != principalType)
                {
                    throw new IllegalArgumentException("Provided indirectAssociationType "+iat.getAssociationName()+" should match this resolvers principalType");
                }
                else
                {
                   iatList.add(iat); 
                }
            }
        }
        
        spp.addSubjectPrincipalsResolver(this);
    }
    
    protected void checkInitialized()
    {
        if (!initialized)
        {
            accessManager = spp.getPrincipalAccessManager();
            initialized = true;
        }
    }

    public JetspeedPrincipalType getPrincipalType()
    {
        return principalType;
    }
    
    protected void processFound(List<JetspeedPrincipal> found, User user, Set<Long> resolvedIds, Set<Principal> principals, Map<String, UserSubjectPrincipalsResolver> resolvers)
    {
        for (int i = found.size() -1; i > -1; i--)
        {
            JetspeedPrincipal p = found.get(i);
            if (!p.isEnabled() || !resolvers.containsKey(p.getType().getName()) || !resolvedIds.add(p.getId()))
            {
                found.remove(i);
            }
        }
        for (JetspeedPrincipal p : found)
        {
            resolvers.get(p.getType().getName()).processPrincipal(p, user, resolvedIds, principals, resolvers);
        }
    }

    public void resolve(User user, Set<Long> resolvedIds, Set<Principal> principals, Map<String, UserSubjectPrincipalsResolver> resolvers)
    {
        checkInitialized();
        if (user.getId() == null || !user.isEnabled())
        {
            // sanity check
            return;
        }
        List<JetspeedPrincipal> found = null;
        if (fromUser)
        {
            found = accessManager.getAssociatedFrom(user.getId(), uat.getFromPrincipalType(), uat.getToPrincipalType(), uat.getAssociationName());
        }
        else
        {
            found = accessManager.getAssociatedTo(user.getId(), uat.getFromPrincipalType(), uat.getToPrincipalType(), uat.getAssociationName());
        }
        processFound(found, user, resolvedIds, principals, resolvers);
    }
    
    public void processPrincipal(JetspeedPrincipal principal, User user, Set<Long> resolvedIds, Set<Principal> principals, Map<String, UserSubjectPrincipalsResolver> resolvers)
    {
        checkInitialized();
        if (user.getId() == null || !user.isEnabled() || principal.getId() == null || !principal.isEnabled() || principal.getType() != principalType)
        {
            // sanity check
            return;
        }
        principals.add(principal);
        if (iatList != null)
        {
            for (JetspeedPrincipalAssociationType iat : iatList)
            {
                List <JetspeedPrincipal> found = null;
                
                if ((!iat.isMixedTypes() && !iat.isSingular()) || !iat.getFromPrincipalType().getName().equals(getPrincipalType().getName()))
                {
                    found = accessManager.getAssociatedTo(principal.getId(), iat.getFromPrincipalType(), iat.getToPrincipalType(), iat.getAssociationName());
                }
                else
                {
                    found = accessManager.getAssociatedFrom(principal.getId(), iat.getFromPrincipalType(), iat.getToPrincipalType(), iat.getAssociationName());
                }
                processFound(found, user, resolvedIds, principals, resolvers);
            }
        }
    }
}