/**
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2006 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: PolicyDecision.java,v 1.3 2008/06/25 05:43:44 qcheng Exp $
 *
 * Portions Copyrighted 2011-2014 ForgeRock AS.
 */
package com.sun.identity.policy;

import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.Iterator;
import java.util.Collections;
import com.sun.identity.shared.xml.XMLUtils;
import com.sun.identity.sm.AttributeSchema;
import org.w3c.dom.*;


/**
 * The <code>PolicyDecision</code> class represents the result of a policy
 * evaluation. 
 *
 * @supported.api
 * @deprecated since 12.0.0
 */
@Deprecated
public class PolicyDecision {

    static final String POLICY_DECISION = "PolicyDecision";
    static final String RESPONSE_DECISIONS = "ResponseDecisions";
    
    //added in 7.0 for attributes provided by policy response providers
    static final String RESPONSE_ATTRIBUTES = "ResponseAttributes"; 

    Map actionDecisions = new HashMap();
    private Map responseDecisions;
    private Map responseAttributes;
    private long timeToLive = Long.MAX_VALUE;
    private boolean advicesAreSet = false;

    /**
     * Default constructor.
     * @supported.api
     */
    public PolicyDecision() {
    }

    /**
     * Gets the  <code>Map</code> of action decisions associated 
     * with this policy decision. The 
     * action name is the key to the Map. The value for each key is an 
     * <code>ActionDecision</code>.
     *
     * @return  the  <code>Map</code> of action decisions associated 
     *         with this policy decision. The 
     *         action name is the key to the Map. The value for each key is an 
     *         <code>ActionDecision</code>.
     * @supported.api
     */
    public Map getActionDecisions() {
        return actionDecisions;
    }

    /**
     * Add an <code>ActionDecision</code> to the <code>PolicyDecision</code>
     * These are the rules followed to add action decision:
     * If the action schema has boolean syntax, boolean false value
     * overrides boolean true value. The time to live of boolean false 
     * value overrides the time to live of boolean true value.
     * Otherwise, action values are simply  aggregated. Time to live
     * is set to the minimum of time to live(s) of all values of the
     * action.
     *
     * @param newActionDecision an <code>ActionDecision</code> to be added.
     * @param resourceType <code>ServiceType</code> representing the
     * service which provides the schema for the action.
     * 
     */

    void addActionDecision(ActionDecision newActionDecision, ServiceType
            resourceType) {
        String action = newActionDecision.getActionName();
        ActionDecision oldActionDecision 
                = (ActionDecision) actionDecisions.get(action);
        if ( oldActionDecision == null ) {
            addActionDecision(newActionDecision);
        } else {
            ActionSchema actionSchema = null;
            AttributeSchema.Syntax actionSyntax = null;
            try {
              actionSchema = resourceType.getActionSchema(action);
              actionSyntax = actionSchema.getSyntax();
            } catch(InvalidNameException e) {
              PolicyManager.debug.error(
                    "can not find action schmea for action = " 
                     + action, e );
            }
            if (!AttributeSchema.Syntax.BOOLEAN.equals(
                    actionSyntax)) {
              addActionDecision(newActionDecision);
            } else { //boolean valued action
              String falseValue = actionSchema.getFalseValue();
              String trueValue = actionSchema.getTrueValue();
              addActionDecision(newActionDecision, trueValue, falseValue);
            }
        }
    }

    /**
     * Add an <code>ActionDecision</code> to the <code>PolicyDecision</code>
     * using the provided <code>trueValue</code> and <code>falseValue</code>
     * These are the rules followed to add action decision:
     * Boolean false value overrides boolean true value. The time to live 
     * of boolean false value overrides the time to live of boolean true value.
     * Otherwise, action values are simply  aggregated. Time to live
     * is set to the minimum of time to live(s) of all values of the
     * action.
     *
     * @param newActionDecision an <code>ActionDecision</code> to be added.
     * @param trueValue <code>String</code> representing the </code>true</code>
     * value in the action schema.
     * @param falseValue <code>String</code> representing the 
     * </code>false</code> value in the action schema.
     * 
     */
    public void addActionDecision(ActionDecision newActionDecision, 
            String trueValue, String falseValue) {
        String action = newActionDecision.getActionName();
        ActionDecision oldActionDecision 
                = (ActionDecision) actionDecisions.get(action);
        if ( (oldActionDecision == null) 
                || (trueValue == null) || (falseValue == null)) {
            addActionDecision(newActionDecision);
        } else { //boolean valued action
            long newTtl = newActionDecision.getTimeToLive();
            long oldTtl = oldActionDecision.getTimeToLive();
            Set oldActionValues = oldActionDecision.getValues();
            Set newActionValues = newActionDecision.getValues();
            Map advices = null;
            Map oldAdvices = oldActionDecision.getAdvices();
            Map newAdvices = newActionDecision.getAdvices();
            advices = PolicyUtils.addMapToMap(oldAdvices, newAdvices);
            if ( (oldActionValues != null) 
                    && (oldActionValues.contains(falseValue)) ) {
              if ( (newActionValues != null) 
                        && newActionValues.contains(falseValue) ) {
                  //both old and new values are false
                  //get the ttl to max of newTtl and oldTtl
                  oldActionDecision.setTimeToLive(Math.max(newTtl, oldTtl));
              }

              /* else block not required here since
                 oldActionDecision does not need to change as it is false
                 and newActionDecision is null or true
              */
            } else if ( (oldActionValues != null)
                    && oldActionValues.contains(trueValue) ) {
              if ( (newActionValues != null)
                        && newActionValues.contains(falseValue) ) {
                  actionDecisions.put(action, newActionDecision);
              } else if ( newActionDecision.getValues().contains(trueValue) ) {
                  //get the ttl to max of newTtl and oldTtl
                  oldActionDecision.setTimeToLive(Math.max(newTtl, oldTtl));
              }
            } else {
                  actionDecisions.put(action, newActionDecision);
            }
            ActionDecision ad = (ActionDecision) actionDecisions.get(action);
            ad.setAdvices(advices);
            setTimeToLive();
        }
    }

    /**
     * Adds an action decision to this object
     * if there is already an existing actionDecision associated with the
     * action name in the param <code>actionDecision</code>, merges
     * the values of the new decision with the existing one,
     * changing the time to live for the decision appropriately.
     *
     * @param actionDecision action decision to be added
     * @supported.api
     */
    public void addActionDecision(ActionDecision actionDecision) {
        ActionDecision oldDecision =
                (ActionDecision) actionDecisions.get(
                actionDecision.getActionName());
        if ( oldDecision == null ) {
            actionDecisions.put(actionDecision.getActionName(), 
                    actionDecision);
        } else {
            Set oldValues = oldDecision.getValues();
            if ( (oldValues == Collections.EMPTY_SET)
                    || ( oldValues == null) ) {
                    oldDecision.setValues(actionDecision.getValues());
            } else {
                oldValues.addAll(actionDecision.getValues());
            }
            if ( actionDecision.getTimeToLive() 
                    < oldDecision.getTimeToLive() ) {
                oldDecision.setTimeToLive(actionDecision.getTimeToLive());
            }
            PolicyUtils.appendMapToMap(actionDecision.getAdvices(),
                    oldDecision.getAdvices());
        }
        setTimeToLive();
    }

    /**
     * Gets a String representation of this <code>PolicyDecision</code>
     * @return a String representation of this <code>PolicyDecision</code>
     *
     * @supported.api
     */
     public String toString() {
        StringBuilder sb = new StringBuilder();
        if ((responseAttributes != null) && 
            (responseAttributes != Collections.EMPTY_MAP)) {
             Iterator attrNames = responseAttributes.keySet().iterator();
             while ( attrNames.hasNext() ) {
                 String attrName = (String) attrNames.next();
                 Set attrValues = (Set) responseAttributes.get(attrName);
                 sb.append(attrName).append("=").append(attrValues).append("\n");
             }
        }
        Iterator actionNames = actionDecisions.keySet().iterator();
        while ( actionNames.hasNext() ) {
            String actionName = (String) actionNames.next();
            ActionDecision actionDecision = 
                (ActionDecision) actionDecisions.get(actionName);
            Set actionValues = (Set) actionDecision.getValues();
            sb.append(actionName).append("=").append(actionValues).append("\n");
        }
        return sb.toString();
     }


    /**
     * Gets an XML representation of this object
     * @return an XML representation of this object
     *
     * @supported.api
     */
     public String toXML() {
        StringBuilder sb  = new StringBuilder(300);
        sb.append("<").append(POLICY_DECISION)
               /*
                .append(" ").append("timeToLive")
                .append("=\"").append(timeToLive).append("\"") 
                .append(" ").append("hasAdvices")
                .append("=\"").append(hasAdvices()).append("\"")  
                */
                .append(">").append(PolicyUtils.CRLF);
        if ((responseAttributes != null) && 
            (responseAttributes != Collections.EMPTY_MAP)) {
            sb.append("<").append(RESPONSE_ATTRIBUTES);
            sb.append(">").append(PolicyUtils.CRLF);
            sb.append(PolicyUtils.mapToXMLString(responseAttributes));
            sb.append("<").append("/").append(RESPONSE_ATTRIBUTES);
            sb.append(">").append(PolicyUtils.CRLF);
        }
        Iterator actionNames = actionDecisions.keySet().iterator();
        while ( actionNames.hasNext() ) {
            String actionName = (String) actionNames.next();
            ActionDecision actionDecision = (ActionDecision)
            actionDecisions.get(actionName);
            sb.append(actionDecision.toXML());
        }
        if (responseDecisions != null) {
            sb.append("<").append(RESPONSE_DECISIONS).append(">")
                    .append(PolicyUtils.CRLF);
            sb.append(PolicyUtils.mapToXMLString(responseDecisions));
            sb.append("</").append(RESPONSE_DECISIONS).append(">")
                    .append(PolicyUtils.CRLF);
        }
        sb.append("</").append(POLICY_DECISION).append(">");
        sb.append(PolicyUtils.CRLF);
        return sb.toString();
     }

    /**
     * Gets a PolicyDecision given corresponding XML node
     * @param policyDecisionNode XML node for the policy decision
     * @return policy decision based on the XML node
     */
     public static PolicyDecision parsePolicyDecision(Node policyDecisionNode) 
        throws PolicyException 
    {
        PolicyDecision policyDecision = new PolicyDecision();
        Set nodeSet = XMLUtils.getChildNodes(policyDecisionNode, 
                ActionDecision.ACTION_DECISION);
        if (nodeSet == null) {
            PolicyManager.debug.error("parsePolicyDecision: Required element "
                + "not found in policy decision node:"
                + ActionDecision.ACTION_DECISION);
            Object [] args = { ActionDecision.ACTION_DECISION };
            throw new PolicyException(ResBundleUtils.rbName,
                "missing_element", args, null);
        } else {
            Iterator nodes = nodeSet.iterator();
            while (nodes.hasNext()) {
                Node node = (Node)nodes.next();
                ActionDecision actionDecision = 
                    ActionDecision.parseActionDecision(node);
                policyDecision.addActionDecision(actionDecision);
            }
        }
        Set resposeAttrsSet = XMLUtils.getChildNodes(policyDecisionNode, 
                RESPONSE_ATTRIBUTES);
        if ( (resposeAttrsSet != null) && !resposeAttrsSet.isEmpty() ) {
            Node node = (Node) resposeAttrsSet.iterator().next();
            Map responseAttrsMap = PolicyUtils.parseAttributeValuePairs(node);
            policyDecision.setResponseAttributes(responseAttrsMap);
        }
        Set responseNodeSet = XMLUtils.getChildNodes(policyDecisionNode, 
                RESPONSE_DECISIONS);
        if ( (responseNodeSet != null) && !responseNodeSet.isEmpty() ) {
            Node node = (Node) responseNodeSet.iterator().next();
            Map responseMap = PolicyUtils.parseAttributeValuePairs(node);
            policyDecision.setResponseDecisions(responseMap);
        }
        return policyDecision;
     }

    /**
     * 
     * Gets response decisions associated with this policy decision
     * @return <code>Map</code> representing the response decisions associated 
     * with this policy decision
     *
     */
    public Map getResponseDecisions() {
        return responseDecisions;
    }

    /**
     * 
     * Sets response decisions associated with this policy decision
     * @param responseDecisions A <code>Map</code> representing response 
     * decisions associated with this policy decision
     */
    public void setResponseDecisions(Map responseDecisions) {
        this.responseDecisions = responseDecisions;
    }


    /**
     * 
     * Gets response attributes associated with this policy decision.
     * Response attributes are computed as an aggregation of the return
     * <code>Map</code>(s) of the <code>ResponseProvider</code> objects 
     * associated with the policy obtained via the getResponseDecision() call.
     * @return the <code>Map</code> of response attributes associated with 
     * this policy decision.
     *
     */
    public Map getResponseAttributes() {
        return responseAttributes;
    }

    /**
     * 
     * Sets response attributes associated with this policy decision
     * @param responseAttributes <code>Map</code> of attribute value pairs 
     * associated with this policy decision.
     */
    public void setResponseAttributes(Map responseAttributes) {
        this.responseAttributes = responseAttributes;
    }
    /**
     * Makes a copy of this object
     *
     * @return a copied instance
     */
    public Object clone() {
        PolicyDecision clone = new PolicyDecision();
        clone.actionDecisions = new HashMap(actionDecisions.size());
        Iterator actionDecisionIter = actionDecisions.keySet().iterator();
        while (actionDecisionIter.hasNext()) {
            String key = (String) actionDecisionIter.next();
            ActionDecision ad = (ActionDecision) actionDecisions.get(key);
            clone.addActionDecision((ActionDecision)ad.clone());
        }

        if (responseDecisions != null) {
            clone.responseDecisions = new HashMap(responseDecisions.size());
            Iterator responseDecisionsIter =
                responseDecisions.keySet().iterator();
            while (responseDecisionsIter.hasNext()) {
                String key = (String) responseDecisionsIter.next();
                clone.responseDecisions.put(key, responseDecisions.get(key));
            }
        }
        if (responseAttributes != null) {
            clone.responseAttributes = new HashMap(responseAttributes.size());
            Iterator responseAttributesIter =
                responseAttributes.keySet().iterator();
            while (responseAttributesIter.hasNext()) {
                String key = (String) responseAttributesIter.next();
                clone.responseAttributes.put(key, responseAttributes.get(key));
            }
        }
        return clone;
    }


    /**
     * Gets the GMT time in milliseconds since epoch when this object is to
     * be treated as expired. That is the policy decision would likely 
     * be different after that time.
     * This is computed as a result of <code>SimpleTimeCondition</code>s 
     * specified in the <code>Policy</code> definition. 
     *
     * @return time to live
     */
    public long getTimeToLive() {
        return timeToLive;
    }

    /**
     * Sets the <code>timeToLive</code> value of the policy decision to the
     * smallest of <code>timeToLive(s)<code> of contained
     * <code>ActionDecision(s)<code>. Also sets value of
     * <code>advicesAreSet</code>. This is set to <code>true</code>
     * if any of the contained action decision(s) has advice defined.
     */
    private void setTimeToLive() {
        timeToLive = Long.MAX_VALUE;
        advicesAreSet = false;
        Iterator actionDecisionIter = actionDecisions.keySet().iterator();
        while (actionDecisionIter.hasNext()) {
            String key = (String) actionDecisionIter.next();
            ActionDecision ad = (ActionDecision) actionDecisions.get(key);
            long actionTtl = ad.getTimeToLive();
            if ( actionTtl < timeToLive) {
                timeToLive = actionTtl;
            }
            advicesAreSet = advicesAreSet || 
                 ((ad.getAdvices()) != null) && (!(ad.getAdvices().isEmpty()));
        }
    }

    /**
     * Sets the timeToLive value of the policy decision.
     *
     * @param ttl timeToLive value to be set
     */
    void setTimeToLive(long ttl) {
        timeToLive = ttl;
    }

    /**
     * Checks wether advices are set in this object
     * @return <code>true</code>, if advices are set, else <code>false</code>
     */
    public boolean hasAdvices() {
        return advicesAreSet;
    }
}
