/*
 * ModeShape (http://www.modeshape.org)
 *
 * 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.modeshape.jcr.security.acl;

import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.Privilege;

/**
 * Implementation for the Access Control entry record. An AccessControlEntry represents the association of one or more
 * <code>Privilege</code> objects with a specific <code>Principal</code>.
 * 
 * @author kulikov
 */
public class AccessControlEntryImpl implements AccessControlEntry {

    private Principal principal;
    private ArrayList<Privilege> privileges = new ArrayList<Privilege>();

    /**
     * Creates new ACL entry.
     * 
     * @param principal principal associated with this entry.
     * @param privileges one or more privilege in association with given principal.
     * @throws AccessControlException if one or more privileges are invalid.
     */
    public AccessControlEntryImpl( Principal principal,
                                   Privilege[] privileges ) throws AccessControlException {
        for (Privilege p : privileges) {
            if (p.getName() == null) throw new AccessControlException("Invalid privilege");
        }
        this.principal = principal;
        this.privileges.clear();
        for (Privilege privilege : privileges) {
            this.privileges.add(privilege);
        }
    }

    @Override
    public Principal getPrincipal() {
        return principal;
    }

    @Override
    public Privilege[] getPrivileges() {
        return privileges.toArray(new Privilege[privileges.size()]);
    }

    /**
     * Tests given privileges.
     * 
     * @param privileges privileges for testing.
     * @return true if this entry contains all given privileges
     */
    protected boolean hasPrivileges( Privilege[] privileges ) {
        for (Privilege p : privileges) {
            if (!contains(this.privileges, p)) {
                return false;
            }
        }
        return true;
    }

    private boolean contains( List<Privilege> privileges,
                              Privilege p ) {
        for (int i = 0; i < privileges.size(); i++) {
            if (((PrivilegeImpl)privileges.get(i)).contains(p)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Adds specified privileges to this entry.
     * 
     * @param privileges privileges to add.
     * @return true if at least one of privileges was added.
     */
    protected boolean addIfNotPresent( Privilege[] privileges ) {
        ArrayList<Privilege> list = new ArrayList<Privilege>();
        for (Privilege privilege : privileges) {
            list.add(privilege);
        }

        boolean res = combineRecursively(list, privileges);

        this.privileges.addAll(list);
        return res;
    }

    /**
     * Adds specified privileges to the given list.
     * 
     * @param list the result list of combined privileges.
     * @param privileges privileges to add.
     * @return true if at least one of privileges was added.
     */
    protected boolean combineRecursively( List<Privilege> list,
                                          Privilege[] privileges ) {
        boolean res = false;

        for (Privilege p : privileges) {
            if (p.isAggregate()) {
                res = combineRecursively(list, p.getAggregatePrivileges());
            } else if (!contains(list, p)) {
                list.add(p);
                res = true;
            }
        }
        return res;
    }

    @Override
    public boolean equals( Object other ) {
        if (other == null) {
            return false;
        }

        if (!(other instanceof AccessControlEntryImpl)) {
            return false;
        }

        AccessControlEntryImpl entry = (AccessControlEntryImpl)other;

        if (!entry.principal.equals(principal)) {
            return false;
        }

        if (this.privileges.size() != entry.privileges.size()) {
            return false;
        }

        for (int i = 0; i < privileges.size(); i++) {
            if (!contains(entry.privileges, privileges.get(i))) {
                return false;
            }
        }

        return true;
    }

    @Override
    public int hashCode() {
        return this.principal.hashCode();
    }
}
