/*
 *  soapUI, copyright (C) 2004-2012 smartbear.com 
 *
 *  soapUI is free software; you can redistribute it and/or modify it under the 
 *  terms of version 2.1 of the GNU Lesser General Public License as published by 
 *  the Free Software Foundation.
 *
 *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
 *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 *  See the GNU Lesser General Public License for more details at gnu.org.
 */

package com.eviware.soapui.impl.wsdl.support.policy;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.wsdl.Definition;
import javax.wsdl.extensions.ElementExtensible;
import javax.xml.namespace.QName;

import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.w3.x2007.x05.addressing.metadata.AddressingDocument.Addressing;
import org.w3c.dom.Element;
import org.xmlsoap.schemas.ws.x2004.x09.policy.OptionalType;
import org.xmlsoap.schemas.ws.x2004.x09.policy.Policy;
import org.xmlsoap.schemas.ws.x2004.x09.policy.PolicyDocument;

import com.eviware.soapui.impl.support.definition.InterfaceDefinitionPart;
import com.eviware.soapui.impl.wsdl.support.wsa.WsaUtils;
import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlContext;
import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlUtils;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.xml.XmlUtils;

public class PolicyUtils
{
	public final static String WS_XMLSOAP_POLICY_NAMESPACE = "http://schemas.xmlsoap.org/ws/2004/09/policy";
	public final static String WS_W3_POLICY_NAMESPACE = "http://www.w3.org/ns/ws-policy";
	public final static String WS_SECURITY_NAMESPACE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";

	public static List<Policy> getPolicies( WsdlContext wsdlContext )
	{

		List<Policy> policies = new ArrayList<Policy>();
		try
		{
			List<InterfaceDefinitionPart> parts = wsdlContext.getDefinitionCache().getDefinitionParts();
			for( int i = 0; i < parts.size(); i++ )
			{
				InterfaceDefinitionPart part = parts.get( i );
				String content = part.getContent();
				// XmlObject xml = XmlObject.Factory.parse( content );
				XmlObject xml = XmlUtils.createXmlObject( content );
				// include paths for both namespaces
				XmlObject[] paths = xml.selectPath( "declare namespace wsp='" + WS_W3_POLICY_NAMESPACE + "';"
						+ "//wsp:Policy" );
				List<XmlObject> listOfXmlObjcts = Arrays.asList( paths );

				XmlObject[] paths1 = xml.selectPath( "declare namespace wsp='" + WS_XMLSOAP_POLICY_NAMESPACE + "';"
						+ "//wsp:Policy" );
				listOfXmlObjcts.addAll( Arrays.asList( paths1 ) );
				paths = ( XmlObject[] )listOfXmlObjcts.toArray();

				for( XmlObject obj : paths )
				{
					String xx = obj.xmlText( new XmlOptions().setSaveOuter() );
					PolicyDocument policyDocument = PolicyDocument.Factory.parse( xx );
					org.xmlsoap.schemas.ws.x2004.x09.policy.Policy polc = ( org.xmlsoap.schemas.ws.x2004.x09.policy.Policy )policyDocument
							.getPolicy();
					policies.add( polc );
					// List<Addressing> addressingList = polc.getAddressingList();
					// Addressing a = null;
					// if (addressingList.size() > 0 )
					// {
					// a = addressingList.get(0);
					// }
					// AnonymousResponses ar = null;
					// List<AnonymousResponses> anList =
					// polc.getAnonymousResponsesList();
					// if (anList.size() > 0)
					// {
					// ar = anList.get(0);
					// }

				}

			}
		}
		catch( Exception e )
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return null;
	}

	public static boolean isAddressing( Policy policy )
	{

		if( policy.getAddressingList().size() > 0 )
		{
			return true;
		}

		return false;
	}

	public static List<Policy> getAddressingPolicies( WsdlContext wsdlContext )
	{
		List<Policy> addressingPolicies = new ArrayList<Policy>();
		List<Policy> policies = getPolicies( wsdlContext );
		for( Policy policy : policies )
		{
			if( isAddressing( policy ) )
			{
				addressingPolicies.add( policy );
			}
		}
		return addressingPolicies;
	}

	/*
	 * Functions currently not used, initially intended for policy to be
	 * normalized first
	 */
	// public static Policy normalize(Policy policy) {
	// 1.Start with the Element Information Item E (as defined in the XML
	// Information Set [XML Information Set]) of the policy expression.
	// The [namespace name] of E is always "http://www.w3.org/ns/ws-policy". In
	// the base case, the [local name] property of E is "Policy";
	// in the recursive case, the [local name] property of E is "Policy",
	// "ExactlyOne", or "All".
	//
	// 2.Expand Element Information Items (as defined in the XML Information Set
	// [XML Information Set]) in the [children] property of E that
	// are policy references per Section 4.3.5 Policy Inclusion.
	//
	// 3.Convert each Element Information Item C in the [children] property of E
	// into normal form.
	// List<OperatorContentType> eoList = policy.getExactlyOneList();
	// ExactlyOneDocument.Factory.newInstance();
	//
	// 3.1 If the [namespace name] property of C is
	// "http://www.w3.org/ns/ws-policy" and the [local name] property of C is
	// "Policy",
	// "ExactlyOne", or "All", C is an expression of a policy operator; normalize
	// C by recursively applying this procedure.
	//
	// 3.2 Otherwise the Element Information Item C is an assertion; normalize C
	// per Sections 4.3.1 Optional Policy Assertions and 4.3.2
	// Policy Assertion Nesting.
	//
	// 4.Apply the policy operator indicated by E to the normalized Element
	// Information Items in its [children] property and co1.nstruct a
	// normal form per Section 4.3.3 Policy Operators and 4.1 Normal Form Policy
	// Expression.
	//

	// return policy;
	// }
	/*
	 * Functions currently not used, initially intended for policy to be
	 * normalized first
	 */
	// public static Element normalize(Element policy)
	// {
	// // if (!StringUtils.isNullOrEmpty(nameSpace) &&
	// !StringUtils.isNullOrEmpty(localName))
	// // {
	// NodeList nl = policy.getChildNodes();
	// List<Element> listElms = new ArrayList<Element>();
	// for( int c = 0; c < nl.getLength(); c++ )
	// {
	// Node item = nl.item( c );
	// if( item.getParentNode() == policy && item.getNodeType() ==
	// Node.ELEMENT_NODE )
	// listElms.add( (Element) item );
	// }
	//
	// for (int i = 0; i < listElms.size(); i++)
	// {
	// Element elm = listElms.get(i);
	// Element newElm = null;
	// String nameSpace = elm.getNamespaceURI();
	// String localName = elm.getLocalName();
	// if (nameSpace.equals(WS_W3_POLICY_NAMESPACE)
	// && (localName.equals("Policy") || localName.equals("All") ||
	// localName.equals("ExactlyOne")))
	// {
	// newElm = normalize(elm);
	//
	// } else {
	//
	// Element allElm =
	// elm.getOwnerDocument().createElementNS(WS_W3_POLICY_NAMESPACE, "All");
	// allElm.appendChild(elm);
	//
	// Element exactlyOneElm =
	// elm.getOwnerDocument().createElementNS(WS_W3_POLICY_NAMESPACE,
	// "ExactlyOne");
	// exactlyOneElm.appendChild(allElm);
	//
	// String optional = elm.getAttributeNS(WS_W3_POLICY_NAMESPACE, "Optional");
	// if (!StringUtils.isNullOrEmpty(optional) && optional.equals("true"))
	// {
	// Element allElmEmpty =
	// elm.getOwnerDocument().createElementNS(WS_W3_POLICY_NAMESPACE, "All");
	// exactlyOneElm.appendChild(allElmEmpty);
	// }
	//
	// newElm = exactlyOneElm;
	// }
	// elm.getParentNode().replaceChild(elm, newElm);
	// }
	// // }
	//
	// return policy;
	// }
	public static Policy getAttachedPolicy( ElementExtensible item, Definition def )
	{

		Policy rtnPolicy = null;
		String usedPolicyNamespace = PolicyUtils.WS_W3_POLICY_NAMESPACE;
		Element[] policyReferences = WsdlUtils.getExentsibilityElements( item, new QName(
				PolicyUtils.WS_W3_POLICY_NAMESPACE, "PolicyReference" ) );
		if( policyReferences.length <= 0 )
		{
			policyReferences = WsdlUtils.getExentsibilityElements( item, new QName(
					PolicyUtils.WS_XMLSOAP_POLICY_NAMESPACE, "PolicyReference" ) );
			usedPolicyNamespace = PolicyUtils.WS_XMLSOAP_POLICY_NAMESPACE;
		}
		if( policyReferences.length > 0 )
		{
			String policyId = policyReferences[0].getAttribute( "URI" );
			if( !StringUtils.isNullOrEmpty( policyId ) )
			{
				Element[] policies = WsdlUtils.getExentsibilityElements( def, new QName( usedPolicyNamespace, "Policy" ) );
				Element policy = null;
				for( int i = 0; i < policies.length; i++ )
				{
					policy = policies[i];
					String policyIdx = policy.getAttributeNS( WS_SECURITY_NAMESPACE, "Id" );
					if( policyId.equals( "#" + policyIdx ) )
					{
						rtnPolicy = getPolicy( policy, usedPolicyNamespace );
						continue;
					}

				}
			}
		}
		else
		{
			// get policies of item itself
			Element[] itemPolicies = WsdlUtils.getExentsibilityElements( item, new QName( usedPolicyNamespace, "Policy" ) );
			if( itemPolicies.length > 0 )
			{
				for( int i = 0; i < itemPolicies.length; i++ )
				{
					Element policy = itemPolicies[i];
					rtnPolicy = getPolicy( policy, usedPolicyNamespace );

				}
			}
		}
		return rtnPolicy;
	}

	public static Policy getPolicy( Element policy, String usedPolicyNamespace )
	{
		// policy = PolicyUtils.normalize(policy);

		Policy newPolicy = null;
		// check for ExactlyOne and All
		// TODO ExactlyOne and All are idempotent all empty ones should be skipped
		// and found the real ones
		Element exactlyOne = XmlUtils.getFirstChildElementNS( policy, usedPolicyNamespace, "ExactlyOne" );
		if( exactlyOne != null )
		{
			Element all = XmlUtils.getFirstChildElementNS( exactlyOne, usedPolicyNamespace, "All" );
			if( all != null )
			{
				newPolicy = getAddressingPolicy( all, usedPolicyNamespace );
			}
		}
		else
		{
			newPolicy = getAddressingPolicy( policy, usedPolicyNamespace );
		}
		return newPolicy;
	}

	private static Policy getAddressingPolicy( Element wsamAddressingElm, String usedPolicyNamespace )
	{
		// check if found reference is addressing policy
		Element wsAddressing = XmlUtils.getFirstChildElementNS( wsamAddressingElm, WsaUtils.WS_A_NAMESPACE_200705,
				"Addressing" );
		Element addressingPolicy = null;
		Policy newPolicy = PolicyDocument.Factory.newInstance().addNewPolicy();
		Addressing newAddressing = null;
		if( wsAddressing != null )
		{
			newAddressing = newPolicy.addNewAddressing();
			String optional = wsAddressing.getAttributeNS( usedPolicyNamespace, "Optional" );
			if( !StringUtils.isNullOrEmpty( optional ) && optional.equals( OptionalType.TRUE.toString() ) )
			{
				newAddressing.setOptional( OptionalType.TRUE );
			}
			else
			{
				newAddressing.setOptional( OptionalType.FALSE );
			}
			addressingPolicy = XmlUtils.getFirstChildElementNS( wsAddressing, usedPolicyNamespace, "Policy" );
			if( addressingPolicy != null )
			{
				Element exactlyOne = XmlUtils.getFirstChildElementNS( addressingPolicy, usedPolicyNamespace, "ExactlyOne" );
				if( exactlyOne != null )
				{
					Element all = XmlUtils.getFirstChildElementNS( exactlyOne, usedPolicyNamespace, "All" );
					if( all != null )
					{
						getAddressingAnonymous( all, newAddressing );
					}
				}
				else
				{
					getAddressingAnonymous( addressingPolicy, newAddressing );
				}

			}
		}
		Element usingAddressing = XmlUtils.getFirstChildElementNS( wsamAddressingElm, WsaUtils.WS_A_NAMESPACE_200605,
				"UsingAddressing" );
		if( usingAddressing != null )
		{
			// add UsingAddressing to policy
			newPolicy.addNewUsingAddressing();
		}
		return newPolicy;
	}

	private static void getAddressingAnonymous( Element addressingPolicy, Addressing newAddressing )
	{
		Policy innerPolicy = newAddressing.addNewPolicy();
		// check if policy has Anonymous
		Element anonymousElm = XmlUtils.getFirstChildElementNS( addressingPolicy, new QName(
				WsaUtils.WS_A_NAMESPACE_200705, "AnonymousResponses" ) );
		if( anonymousElm != null )
		{
			innerPolicy.addNewAnonymousResponses();
		}
		else
		{
			Element nonAnonymousElement = XmlUtils.getFirstChildElementNS( addressingPolicy, new QName(
					WsaUtils.WS_A_NAMESPACE_200705, "NonAnonymousResponses" ) );
			if( nonAnonymousElement != null )
			{
				innerPolicy.addNewNonAnonymousResponses();
			}
		}
	}
}
