/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * https://opensso.dev.java.net/public/CDDLv1.0.html or
 * opensso/legal/CDDLv1.0.txt
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at opensso/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * $Id: PCMappingTable.java,v 1.3 2009/01/28 05:34:50 ww203982 Exp $
 *
 * Portions Copyrighted 2011-2015 ForgeRock AS.
 */

package com.iplanet.ums;

import java.util.Enumeration;

import com.iplanet.services.ldap.Attr;
import com.iplanet.services.ldap.AttrSet;
import org.forgerock.opendj.ldap.DN;

/**
 * Package class to manage the mapping table between user and people container
 * of an organization. Right now, we store the mapping table as an entry right
 * under the organization.
 */
class PCMappingTable implements java.io.Serializable {

    protected PCMappingTable(java.security.Principal principal, 
            Guid mappingGuid) throws UMSException {
        _principal = principal;
        _mappingGuid = mappingGuid;
    }

    /**
     * Gets the PCMappingTable associated with the given organization
     * 
     * @param org
     *            organization to be managed
     * @return PCMappingTable associated with the given organization
     * @exception UMSException
     *                Failure
     */
    public static PCMappingTable getPCMappingTable(Organization org)
            throws UMSException {

        PersistentObject po = null;
        SearchResults results = org.getChildren("ou=" + MAPPINGTABLE_ENTRYNAME,
                null);
        if (results.hasMoreElements()) {
            po = results.next();
        } else {
            // Create an entry to store the mapping table if it is
            // not already existed.
            // Right now, we store the mapping table as an entry right under
            // the organization.
            po = new PersistentObject();

            po.setAttribute(new Attr("objectclass", new String[] { "top",
                    "extensibleobject" }));
            po.setAttribute(new Attr("ou", MAPPINGTABLE_ENTRYNAME));

            // po.save( org.getPrincipal(), "ou", org.getGuid() );
            Guid guid = new Guid("ou=" + MAPPINGTABLE_ENTRYNAME + ","
                    + org.getGuid().getDn());
            po.setGuid(guid);
            org.addChild(po);
        }
        results.abandon();

        PCMappingTable mt = new PCMappingTable(
                                org.getPrincipal(), po.getGuid());
        return mt;
    }

    /**
     * Gets People Container associated with the user
     * 
     * @param user
     *            user object to look up
     * @return guid identifying People Container associated with the user, null
     *         if no match found and default has not been set
     * @exception UMSException
     *                Failure
     */
    public String getPeopleContainer(User user) throws UMSException {
        PersistentObject po = UMSObject.getObject(_principal, _mappingGuid);
        AttrSet attrSet = po.getAttrSet();
        String defaultPC = getDefault(attrSet);

        for (int j = 0; j < ATTRNAMESTOSKIP.length; j++) {
            attrSet.remove(ATTRNAMESTOSKIP[j]);
        }

        Enumeration e1 = attrSet.getAttributes();
        while (e1.hasMoreElements()) {
            Attr attr = (Attr) e1.nextElement();
            String guid = attr.getName();
            String[] filters = attr.getStringValues();

            for (int j = 0; j < filters.length; j++) {
                AttrSet filterAttrSet = getAttrSetFromFilter(filters[j]);

                // loop through filterAttrSet and compare each one to the
                // user's AttrSet
                Enumeration e2 = filterAttrSet.getAttributes();
                while (e2.hasMoreElements()) {
                    Attr filterAttr = (Attr) e2.nextElement();
                    Attr userAttr = user.getAttribute(filterAttr.getName());
                    if (userAttr != null) {
                        String[] filterAttrValues = filterAttr
                                .getStringValues();
                        for (int i = 0; i < filterAttrValues.length; i++) {
                            if (userAttr.contains(filterAttrValues[i])) {
                                return guid;
                            }
                        }
                    }
                }
            }
        }
        return defaultPC;
    }

    /**
     * Adds rule for determining which People Container the user is supposed to
     * be in.
     * 
     * @param filter
     *            filter representation of the rule
     * @param guid
     *            guid of the People Container which the rule is applied to
     * @exception UMSException
     *                Failure
     */
    public void addRule(Guid guid, String filter) throws UMSException {
        DataLayer.getInstance().addAttributeValue(_principal, _mappingGuid, guid.getDn(), filter);
    }

    /**
     * Removes the rule with the given filter string applying to the given
     * People Container guid.
     * 
     * @param filter
     *            filter string of the rule to be removed
     * @param guid
     *            guid of which the rule applies to
     * @exception UMSException
     *                Failure
     */
    public void removeRule(Guid guid, String filter) throws UMSException {
        DataLayer.getInstance().removeAttributeValue(_principal, _mappingGuid, guid.getDn(), filter);
    }

    /**
     * Sets the default People Container.
     * 
     * @param guid
     *            guid of the default People Container
     * @exception UMSException
     *                Failure
     */
    public void setDefault(Guid guid) throws UMSException {
        DataLayer.getInstance().addAttributeValue(_principal, _mappingGuid,
                DEFAULT_PC_ATTRNAME, guid.getDn());
    }

    /**
     * Gets the default People Container from the given attribute set.
     * 
     * @param attrSet
     *            attribute set to get from
     * @return guid identifing the default People Container from the given
     *         attribute set.
     */
    private String getDefault(AttrSet attrSet) {
        Attr attr = attrSet.getAttribute(DEFAULT_PC_ATTRNAME);
        if (attr != null) {
            return attr.getValue();
        }
        return null;
    }

    /**
     * Gets the attribute set correspondent to the filter string. Only accepts
     * filter string with the following format:
     * 
     * <PRE>
     * 
     * &ltfilter&gt ::= &ltand&gt | &ltitem&gt &ltand&gt ::= '(' '&'
     * &ltitemlist&gt ')' &ltitemlist&gt ::= &ltitem&gt | &ltitem&gt
     * &ltitemlist&gt &ltitem&gt ::= '(' &ltattr&gt '=' &ltvalue&gt ')'
     * 
     * </PRE>
     * 
     * @param filter
     *            filter string to parse
     * @return the attribute set correspondent to the filter string
     */
    private AttrSet getAttrSetFromFilter(String filter) {
        AttrSet attrSet = new AttrSet();
        String f = filter;
        f.trim();
        if (f.startsWith("(") && f.endsWith(")")) {
            f = f.substring(1, f.length() - 1);
        }

        if (f.startsWith("|") || f.startsWith("!")) {
            // TODO: should throw an exception: invalid pc filter
            return null;
        }
        if (f.startsWith("&")) {
            int level = 0;
            int start = 0;
            int end = 0;

            for (int i = 0; i < f.length(); i++) {
                if (f.charAt(i) == '(') {
                    if (level == 0) {
                        start = i;
                    }
                    level++;
                }
                if (f.charAt(i) == ')') {
                    level--;
                    if (level == 0) {
                        end = i;
                        String subf = f.substring(start, end + 1);
                        if (subf.startsWith("(") && subf.endsWith(")")) {
                            subf = subf.substring(1, subf.length() - 1);
                        }

                        int idx = subf.indexOf('=');
                        if (idx == -1) {
                            return null;
                        }
                        String type = subf.substring(0, idx).trim();
                        String value = subf.substring(idx + 1).trim();
                        attrSet.add(new Attr(type, value));
                    }
                }
            }

        } else {
            int idx = f.indexOf('=');
            if (idx == -1) {
                return null;
            }
            String type = f.substring(0, idx).trim();
            String value = f.substring(idx + 1).trim();
            attrSet.add(new Attr(type, value));
        }

        return attrSet;
    }

    private java.security.Principal _principal;

    private Guid _mappingGuid;

    private static final String[] ATTRNAMESTOSKIP = { "ou", "objectclass",
            "default" };

    private static final String MAPPINGTABLE_ENTRYNAME = "pcmappingtable";

    private static final String DEFAULT_PC_ATTRNAME = "default";
}
