/***********************************************************
Copyright (C) 2018 VeriSign, Inc.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library 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.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

http://www.verisign.com/nds/naming/namestore/techdocs.html
 ***********************************************************/
package com.verisign.epp.codec.gen;

import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;

import com.verisign.epp.exception.EPPException;
import com.verisign.epp.util.EPPCatFactory;

/**
 * {@code EPPXmlMsgPollMessageFilter} is a utility class used to filter poll
 * message {@link EPPResponse} instances against the list of client-specified
 * object services and extension services. Any non-supported services (object or
 * extension) will be removed from the poll message and the XML namepace will be
 * encoded in the &lt;msgQ&gt;&lt;msg&gt; element with the ABNF:<br>
 * 
 * <pre>
 * msg                   = msg-text *LWSP *(not-supported-service *LWSP)
 * msg-text              = *VCHAR
 * not-supported-service = “&lt;notSupported&gt;” service-namespace “&lt;/notSupported&gt;”
 * service-namespace     = 1*VCHAR
 * </pre>
 * 
 * <br>
 * The class does support a no-operation option to simply identify and log
 * non-supported services without removing them from the poll message or
 * encoding the &lt;msgQ&gt;&lt;msg&gt; element.
 */
public class EPPXmlMsgPollMessageFilter implements EPPPollMessageFilter {

	/**
	 * Log4j category for logging
	 */
	private static Logger cat = Logger.getLogger(EPPXmlMsgPollMessageFilter.class.getName(),
	      EPPCatFactory.getInstance().getFactory());

	/**
	 * Filter any poll messages that are not supported by the client based on the
	 * passed in login services (object extensions) and extension services
	 * (command response extensions) from the poll message and encode their XML
	 * namespaces in the message queue message element.
	 * 
	 * @param aResponse
	 *           Source poll message
	 * @param aServices
	 *           {@code List} of {@link EPPService} login services (object
	 *           extensions) supported by the client.
	 * @param aExtServices
	 *           {@code List} of {@link EPPService} login extension services
	 *           (command response extensions) supported by the client.
	 * @return Filtered poll message {@link EPPResponse} that contains extensions
	 *         that the client supports.
	 * @throws EPPException
	 *            Exception filtering the poll message
	 */
	public EPPResponse filter(final EPPResponse aResponse, List<EPPService> aServices, List<EPPService> aExtServices)
	      throws EPPException {
		return filter(aResponse, aServices, aExtServices, false);
	}

	/**
	 * Filter any poll messages that are not supported by the client based on the
	 * passed in login services (object extensions) and extension services
	 * (command response extensions) from the poll message and encode their XML
	 * namespaces in the message queue message element.
	 * 
	 * @param aResponse
	 *           Source poll message
	 * @param aServices
	 *           {@code List} of {@link EPPService} login services (object
	 *           extensions) supported by the client.
	 * @param aExtServices
	 *           {@code List} of {@link EPPService} login extension services
	 *           (command response extensions) supported by the client.
	 * @param aNoOp
	 *           Set to {@code true} to only identify unsupported services by
	 *           logging them and not removing them or encoding them in the
	 *           &lt;msgQ&gt;&lt;msg&gt; value.
	 * @return Filtered poll message {@link EPPResponse} that contains extensions
	 *         that the client supports.
	 * @throws EPPException
	 *            Exception filtering the poll message
	 */
	public EPPResponse filter(final EPPResponse aResponse, List<EPPService> aServices, List<EPPService> aExtServices,
	      boolean aNoOp) throws EPPException {

		cat.debug("filter(EPPResponse, List, List, boolean): enter - NoOp = " + aNoOp);

		EPPResponse theResponse = null;
		boolean theResponseFiltered = false;
		String theMsg = null;

		// Pre-condition checks
		if (aResponse == null) {
			throw new EPPException("EPPXmlMsgPollMessageFilter.filter(): null poll message EPPResponse passed");
		}

		if (!aResponse.hasMsgQueue()) {
			cat.debug("filter(EPPResponse, List, List, boolean): exit - the response does not contain a poll message.");
			return aResponse;
		}

		try {
			// Set the original message queue message to append to if filtering is
			// required.
			theMsg = aResponse.getMsgQueueMsg();

			// -- Filter Object Extension --

			// Is the poll message a object extension (derived class of
			// EPPResponse)?
			if (!aResponse.getClass().getName().equals(EPPResponse.class.getName())) {

				// Is the object extension supported by the client?
				if (!supportsNamespace(aResponse.getNamespace(), aServices)) {

					if (!aNoOp) {
						// Append not supported message.
						theMsg = appendNotSupported(theMsg, aResponse.getNamespace());
						theResponseFiltered = true;

						// Create a standard response and copy the standard response
						// attributes.
						theResponse = new EPPResponse(aResponse.getTransId(), aResponse.getResult());
						theResponse.setMsgQueue(aResponse.getMsgQueue());
						theResponse.setExtensions(aResponse.getExtensions());
					}
					else {
						cat.info("filter(EPPResponse, List, List, boolean): object service [" + aResponse.getNamespace()
						      + "] not supported");
						theResponse = (EPPResponse) aResponse.clone();
					}
				}
				else {
					theResponse = (EPPResponse) aResponse.clone();
				}

			}
			else {
				theResponse = (EPPResponse) aResponse.clone();
			}

			// -- Filter Command Response Extensions --
			List theExtensions = theResponse.getExtensions();
			if (theExtensions != null) {
				Iterator iterator = theExtensions.iterator();

				while (iterator.hasNext()) {
					EPPCodecComponent theExtension = (EPPCodecComponent) iterator.next();

					// Is the object extension supported by the client?
					if (!supportsNamespace(theExtension.getNamespace(), aExtServices)) {

						if (!aNoOp) {
							// Append not supported message and remove extension from
							// response.
							theMsg = appendNotSupported(theMsg, theExtension.getNamespace());
							iterator.remove();
							theResponseFiltered = true;
						}
						else {
							cat.info("filter(EPPResponse, List, List, boolean): extension service ["
							      + theExtension.getNamespace() + "] not supported");
						}
					}
				}
			}
		}
		catch (Exception ex) {
			cat.error("Unexpected exception: " + ex + " filtering the poll message: " + aResponse);
			throw new EPPException("Exception filtering poll message", ex);
		}

		if (theResponseFiltered) {
			theResponse.getMsgQueue().setMsg(theMsg);
			cat.info("Filtered poll message [" + aResponse + "] to [" + theResponse + "]");
		}
		else {
			cat.debug("Not filtered poll message [" + aResponse + "]");
		}

		cat.debug("filter(EPPResponse, List, List): exit");
		return theResponse;
	}

	/**
	 * Is the specified XML namespace in the list of services?
	 * 
	 * @param aNamespace
	 *           XML namespace string
	 * @param aServices
	 *           {@code List} of {@link EPPService} objects.
	 * @return {@code true} if the XML namespace is supported; {@code false}
	 *         otherwise.
	 */
	private boolean supportsNamespace(String aNamespace, List<EPPService> aServices) {

		cat.debug("supportsNamespace(String, List); Looking for namespace " + aNamespace + " support");

		if (aServices != null) {

			Iterator iterator = aServices.iterator();

			while (iterator.hasNext()) {
				EPPService theService = (EPPService) iterator.next();

				if (theService.getNamespaceURI().equals(aNamespace)) {
					cat.debug("supportsNamespace(String, List); namespace " + aNamespace + " supported");
					return true;
				}
			}
		}

		cat.debug("supportsNamespace(String, List); namespace " + aNamespace + " not supported");
		return false;
	}

	/**
	 * Appends the not supported XML namespace to the input message and returns
	 * it. The
	 * <a href="https://en.wikipedia.org/wiki/Augmented_Backus–Naur_form">ABNF
	 * </a> for the poll message is:<br>
	 * 
	 * <pre>
	 * msg                   = msg-text *LWSP *(not-supported-service *LWSP)
	 * msg-text              = *VCHAR
	 * not-supported-service = “<notSupported>” service-namespace “</notSupported>”
	 * service-namespace     = 1*VCHAR
	 * </pre>
	 * 
	 * <br>
	 * The encoded format used is: <br>
	 * 
	 * <pre>
	 * msg                   = (msg-text LF not-supported-service) / not-supported-service
	 * msg-text              = 1*VCHAR
	 * not-supported-service = “<notSupported>” service-namespace “</notSupported>”
	 * service-namespace     = 1*VCHAR
	 * </pre>
	 * 
	 * @param aMsg
	 *           Poll &lt;msgQ&gt;&lt;msg&gt; value to encode with the
	 *           unsupported XML namespace.
	 * @param aNamespace
	 *           Unsupported XML namespace to append to the
	 *           &lt;msgQ&gt;&lt;msg&gt; value
	 * @return The &lt;msgQ&gt;&lt;msg&gt; value appended with the unsupported
	 *         XML namespace.
	 */
	private String appendNotSupported(String aMsg, String aNamespace) {
		String theNotSupportedStr = "<notSupported>" + aNamespace + "</notSupported>";

		cat.debug("appendNotSupported(String, String): appending " + theNotSupportedStr);

		if (aMsg == null || aMsg.isEmpty()) {
			return theNotSupportedStr;
		}
		else {
			return aMsg + "\n" + theNotSupportedStr;
		}
	}

}
