/**
 * 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.cxf.ws.rm.policy;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.xml.namespace.QName;

import org.apache.cxf.message.Message;
import org.apache.cxf.ws.policy.AssertionInfo;
import org.apache.cxf.ws.policy.AssertionInfoMap;
import org.apache.cxf.ws.policy.builder.jaxb.JaxbAssertion;
import org.apache.cxf.ws.rm.RM11Constants;
import org.apache.cxf.ws.rm.RMConfiguration;
import org.apache.cxf.ws.rm.RMConfiguration.DeliveryAssurance;
import org.apache.cxf.ws.rm.RMUtils;
import org.apache.cxf.ws.rmp.v200502.RMAssertion;
import org.apache.neethi.builders.PrimitiveAssertion;

/**
 * Utilities for working with policies and configurations.
 */
public final class RMPolicyUtilities {
    
    private static final List<QName> ASSERTION_NAMES;
    static {
        ASSERTION_NAMES = new ArrayList<QName>();
        ASSERTION_NAMES.addAll(RM10AssertionBuilder.KNOWN_ELEMENTS);
        for (QName qn : RM12AssertionBuilder.KNOWN_ELEMENTS) {
            ASSERTION_NAMES.add(qn);
        }
        ASSERTION_NAMES.add(RSPAssertionBuilder.CONFORMANT_QNAME);
    }
    
    private RMPolicyUtilities() {
    }
    
    /**
     * Returns an RMAssertion that is compatible with the default value and all RMAssertions pertaining to the
     * message (can never be null).
     * 
     * @param rma the default value (non-<code>null</code>)
     * @param message the message
     * @return the compatible RMAssertion
     */
    public static RMConfiguration getRMConfiguration(RMConfiguration defaultValue, Message message) {
        RMConfiguration compatible = defaultValue;
        Collection<AssertionInfo> ais = collectRMAssertions(message.get(AssertionInfoMap.class));
        for (AssertionInfo ai : ais) {
            if (ai.getAssertion() instanceof JaxbAssertion<?>) {
                RMAssertion rma = (RMAssertion)((JaxbAssertion<?>)ai.getAssertion()).getData();
                compatible = intersect(rma, compatible);
            } else if (ai.getAssertion() instanceof PrimitiveAssertion) {
                PrimitiveAssertion assertion = (PrimitiveAssertion)ai.getAssertion();
                if (RM11Constants.WSRMP_NAMESPACE_URI.equals(assertion.getName().getNamespaceURI())) {
                    compatible = intersect(assertion, compatible);
                }
            }
        }
        return compatible;
    }

    /**
     * Collect RMAssertions from map. This checks both namespaces defined for WS-RM policy assertions, along with the
     * WS-I RSP namespace.
     * 
     * @param aim map, may be <code>null</code>
     * @return merged collection, never <code>null</code>
     */
    public static Collection<AssertionInfo> collectRMAssertions(AssertionInfoMap aim) {
        Collection<AssertionInfo> mergedAsserts = new ArrayList<AssertionInfo>();
        if (aim != null) {
            for (QName qn : ASSERTION_NAMES) {
                Collection<AssertionInfo> ais = aim.get(qn);
                if (ais != null) {
                    mergedAsserts.addAll(ais);
                }
            }
        }
        return mergedAsserts;
    }

    public static boolean equals(RMAssertion a, RMAssertion b) {
        if (a == b) {
            return true;
        }
        
        Long aval = null;
        if (null != a.getInactivityTimeout()) {
            aval = a.getInactivityTimeout().getMilliseconds();
        }
        Long bval = null;
        if (null != b.getInactivityTimeout()) {
            bval = b.getInactivityTimeout().getMilliseconds();            
        }
        if (!RMUtils.equalLongs(aval, bval)) {
            return false;
        }
            
        aval = null;
        if (null != a.getBaseRetransmissionInterval()) {
            aval = a.getBaseRetransmissionInterval().getMilliseconds();
        }
        bval = null;
        if (null != b.getBaseRetransmissionInterval()) {
            bval = b.getBaseRetransmissionInterval().getMilliseconds();            
        }
        if (!RMUtils.equalLongs(aval, bval)) {
            return false;
        }
        
        aval = null;
        if (null != a.getAcknowledgementInterval()) {
            aval = a.getAcknowledgementInterval().getMilliseconds();
        }
        bval = null;
        if (null != b.getAcknowledgementInterval()) {
            bval = b.getAcknowledgementInterval().getMilliseconds(); 
        }
        if (!RMUtils.equalLongs(aval, bval)) {
            return false;
        }
        
        return null == a.getExponentialBackoff()
            ? null == b.getExponentialBackoff() 
            : null != b.getExponentialBackoff();         
    }
    
    public static boolean equals(RMConfiguration a, RMConfiguration b) {
        if (a == b) {
            return true;
        }
        if (a == null) {
            return b == null;
        } else if (b == null) {
            return false;
        }
        if (a.getDeliveryAssurance() == null) {
            if (b.getDeliveryAssurance() != null) {
                return false;
            }
        } else if (b.getDeliveryAssurance() == null) {
            return false;
        } else if (a.getDeliveryAssurance() != b.getDeliveryAssurance()) {
            return false;
        }
        if (a.getRM10AddressingNamespace() == null) {
            if (b.getRM10AddressingNamespace() != null) {
                return false;
            }
        } else if (b.getRM10AddressingNamespace() == null) {
            return false;
        } else if (!RMUtils.equalStrings(a.getRM10AddressingNamespace(), b.getRM10AddressingNamespace())) {
            return false;
        }
        if (!RMUtils.equalStrings(a.getRMNamespace(), b.getRMNamespace())) {
            return false;
        }
        return a.isInOrder() == b.isInOrder()
               && a.isExponentialBackoff() == b.isExponentialBackoff()
               && a.isSequenceSTRRequired() == b.isSequenceSTRRequired()
               && a.isSequenceTransportSecurityRequired() == b.isSequenceTransportSecurityRequired()
               && RMUtils.equalLongs(a.getAcknowledgementInterval(), b.getAcknowledgementInterval())
               && RMUtils.equalLongs(a.getBaseRetransmissionInterval(), b.getBaseRetransmissionInterval())
               && RMUtils.equalLongs(a.getInactivityTimeout(), b.getInactivityTimeout());
    }
    
    /**
     * Intersect a policy with a supplied configuration.
     * 
     * @param rma
     * @param cfg
     * @return result configuration
     */
    public static RMConfiguration intersect(RMAssertion rma, RMConfiguration cfg) {
        if (isCompatible(rma, cfg)) {
            return cfg;
        }
        
        RMConfiguration compatible = new RMConfiguration(cfg);
        
        // if supplied, policy value overrides default inactivity timeout
        Long aval = cfg.getInactivityTimeout();
        Long bval = null;
        if (null != rma.getInactivityTimeout()) {
            bval = rma.getInactivityTimeout().getMilliseconds();            
        }
        if (null != aval || null != bval) {
            Long use;
            if (bval != null) {
                use = bval;
            } else {
                use = aval;
            }
            compatible.setInactivityTimeout(use);
        }
        
        // if supplied, policy value overrides base retransmission interval
        aval = cfg.getBaseRetransmissionInterval();
        bval = null;
        if (null != rma.getBaseRetransmissionInterval()) {
            bval = rma.getBaseRetransmissionInterval().getMilliseconds();            
        }
        if (null != aval || null != bval) {
            Long use;
            if (bval != null) {
                use = bval;
            } else {
                use = aval;
            }
            compatible.setBaseRetransmissionInterval(use);
        }
        
        // if supplied, policy value overrides acknowledgement interval
        aval = cfg.getAcknowledgementInterval();
        bval = null;
        if (null != rma.getAcknowledgementInterval()) {
            bval = rma.getAcknowledgementInterval().getMilliseconds(); 
        }
        if (null != aval || null != bval) {
            Long use;
            if (bval != null) {
                use = bval;
            } else {
                use = aval;
            }
            compatible.setAcknowledgementInterval(use);
        }
    
        // backoff parameter
        if (cfg.isExponentialBackoff() || null != rma.getExponentialBackoff()) {
            compatible.setExponentialBackoff(true);
        }
        return compatible;
    }
    
    /**
     * Check if a policy is compatible with a supplied configuration.
     * 
     * @param asser
     * @param cfg
     * @return <code>true</code> if compatible, <code>false</code> if not
     */
    public static boolean isCompatible(RMAssertion asser, RMConfiguration cfg) {
        Long aval = null;
        if (null != asser.getInactivityTimeout()) {
            aval = asser.getInactivityTimeout().getMilliseconds();            
        }
        if (!RMUtils.equalLongs(cfg.getInactivityTimeout(), aval)) {
            return false;
        }
            
        aval = null;
        if (null != asser.getBaseRetransmissionInterval()) {
            aval = asser.getBaseRetransmissionInterval().getMilliseconds();            
        }
        if (!RMUtils.equalLongs(cfg.getBaseRetransmissionInterval(), aval)) {
            return false;
        }
        
        aval = null;
        if (null != asser.getAcknowledgementInterval()) {
            aval = asser.getAcknowledgementInterval().getMilliseconds(); 
        }
        if (!RMUtils.equalLongs(cfg.getAcknowledgementInterval(), aval)) {
            return false;
        }
        
        return cfg.isExponentialBackoff()
            ? null == asser.getExponentialBackoff() 
            : null != asser.getExponentialBackoff();         
    }
    
    /**
     * Intersect a policy with a supplied configuration.
     * 
     * @param rma
     * @param cfg
     * @return result configuration
     */
    public static RMConfiguration intersect(PrimitiveAssertion rma, RMConfiguration cfg) {
        if (isCompatible(rma, cfg)) {
            return cfg;
        }
        RMConfiguration compatible = new RMConfiguration(cfg);
        String lname = rma.getName().getLocalPart();
        if (RM11Constants.RMASSERTION_NAME.equals(lname)) {
            compatible.setRMNamespace(RM11Constants.NAMESPACE_URI);
        } else if (RM12AssertionBuilder.SEQUENCESTR_NAME.equals(lname)) {
            compatible.setSequenceSTRRequired(true);
        } else if (RM12AssertionBuilder.SEQUENCETRANSEC_NAME.equals(lname)) {
            compatible.setSequenceTransportSecurityRequired(true);
        } else if (RM12AssertionBuilder.EXACTLYONCE_NAME.equals(lname)) {
            compatible.setDeliveryAssurance(DeliveryAssurance.EXACTLY_ONCE);
        } else if (RM12AssertionBuilder.ATLEASTONCE_NAME.equals(lname)) {
            compatible.setDeliveryAssurance(DeliveryAssurance.AT_LEAST_ONCE);
        } else if (RM12AssertionBuilder.ATMOSTONCE_NAME.equals(lname)) {
            compatible.setDeliveryAssurance(DeliveryAssurance.AT_MOST_ONCE);
        } else if (RM12AssertionBuilder.INORDER_NAME.equals(lname)) {
            compatible.setInOrder(true);
        }
        return compatible;
    }
    
    /**
     * Check if a policy is compatible with a supplied configuration.
     * 
     * @param rma
     * @param cfg
     * @return <code>true</code> if compatible, <code>false</code> if not
     */
    public static boolean isCompatible(PrimitiveAssertion rma, RMConfiguration cfg) {
        String lname = rma.getName().getLocalPart();
        boolean compatible = true;
        if (RM11Constants.RMASSERTION_NAME.equals(lname)) {
            compatible = RM11Constants.WSRMP_NAMESPACE_URI.equals(cfg.getRMNamespace());
        } else if (RM12AssertionBuilder.SEQUENCESTR_NAME.equals(lname)) {
            compatible = cfg.isSequenceSTRRequired();
        } else if (RM12AssertionBuilder.SEQUENCETRANSEC_NAME.equals(lname)) {
            compatible = cfg.isSequenceTransportSecurityRequired();
        } else if (RM12AssertionBuilder.EXACTLYONCE_NAME.equals(lname)) {
            compatible = cfg.getDeliveryAssurance() == DeliveryAssurance.EXACTLY_ONCE;
        } else if (RM12AssertionBuilder.ATLEASTONCE_NAME.equals(lname)) {
            compatible = cfg.getDeliveryAssurance() == DeliveryAssurance.AT_LEAST_ONCE;
        } else if (RM12AssertionBuilder.ATMOSTONCE_NAME.equals(lname)) {
            compatible = cfg.getDeliveryAssurance() == DeliveryAssurance.AT_MOST_ONCE;
        } else if (RM12AssertionBuilder.INORDER_NAME.equals(lname)) {
            compatible = cfg.isInOrder();
        }
        return compatible;
    }
}