package eu.dnetlib.enabling.aas.nh;

import java.io.StringReader;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import eu.dnetlib.enabling.aas.is.ISServicesVersionHolder;
import eu.dnetlib.enabling.aas.is.ISUtils;
import eu.dnetlib.enabling.aas.nh.rmi.INotificationHandler;
import eu.dnetlib.enabling.aas.service.A2Constants;
import eu.dnetlib.enabling.aas.service.A2Exception;
import eu.dnetlib.enabling.is.sn.rmi.ISSNException;
import eu.dnetlib.enabling.is.sn.rmi.ISSNService;



/**
 * AAS Notification Handler. Implements notification consumer functionalities.
 * @author mhorst
 *
 */
public class AASNotificationHandler implements INotificationHandler, INotificationSubscriber  {

	protected static final Logger log = Logger.getLogger(AASNotificationHandler.class);
	
	/**
	 * ISSN service.
	 */
	private ISSNService snService;
	
	/**
	 * Notification consumer reference URL.
	 */
	private String consumerReferenceURL;
	
	/**
	 * Notification consumer reference WSDL location.
	 */
	private String consumerReferenceWSDL;
	
	/**
	 * Notification consumer reference EPR generated at service startup.
	 */
	private W3CEndpointReference generatedConsumerReferenceEPR;
	
	/**
	 * Security Context notification manager.
	 */
	private ISecurityContextNotificationManager secCtxNotificationManager;
	
	/**
	 * Security Profile notification manager.
	 */
	private ISecurityProfileNotificationManager secProfNotificationManager;
	
	/**
	 * Security Policy notification manager.
	 */
	private ISecurityPolicyNotificationManager secPolicyNotificationManager;
	
	
	boolean checkingNotificationProducersEnabled = false;
	
	/**
	 * Set of allowed notification producers.
	 */
	private Set<String> allowedNotificationProducers;
	
	/**
	 * IS services version holder.
	 */
	private ISServicesVersionHolder servicesVersionHolder;
	
	/**
	 * Notification initialization.
	 */
	public void init() {
//		initializing local EPR
		this.generatedConsumerReferenceEPR = (consumerReferenceWSDL!=null)?
				ISUtils.buildEPR(consumerReferenceURL, consumerReferenceWSDL):ISUtils.buildEPR(consumerReferenceURL);
				
//		subscribing to create security policy topic
		try {
		String subscrId = snService.subscribe(this.generatedConsumerReferenceEPR, 
				NotificationConstants.SUBSCR_TOPIC_CREATE_SEC_POLICY, 
				NotificationConstants.TERMINATION_TIME_INFINITE);
		if (subscrId!=null)
			log.info("Subscribed to create security policy topic with subscrId: " + subscrId);
		else
			log.warn("Null subscription id returned when subscribing to topic: " + NotificationConstants.SUBSCR_TOPIC_CREATE_SEC_POLICY);
		} catch (ISSNException e) {
			log.error("Exception occured when subscribing to " + 
					NotificationConstants.SUBSCR_TOPIC_CREATE_SEC_POLICY + " topic!", e);
		}
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.nh.rmi.INotificationHandler#notify(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
	 */
	public boolean notify(String subscrId, String topic, String isId,
			String message) {

		if (isCheckingNotificationProducersEnabled()) {
			if (!checkNotificationProducer(isId))
				return false;
		} else {
			log.info("Checking notification producer address is disabled");
		}
		
//		TODO remove info messages after finalizing test phase
		log.info("Notification has been issued!");
		log.info("subscrId: "+subscrId);
		log.info("topic: "+topic);
		log.info("isId: "+isId);
		log.info("message: "+message);
		
		if (topic==null || message==null) {
			log.error("Aither topic or messsage are unavailaible in notification request!");
			return false;
		}
		
		String resourceType = NotificationUtils.extractResourceType(message);
		if (resourceType==null) {
			log.error("Couldn't find resource type in profile content from message: "+message);
			return false;
		}
		try {
			if (resourceType.equals(A2Constants.RESOURCE_TYPE_SECURITY_CONTEXT)) {
				if (topic.startsWith(NotificationConstants.TOPIC_PREFIX_DELETE)) {
					log.info("Deleting security context with topic: "+topic);
					secCtxNotificationManager.deleteSecurityContext(message);
					return true;
				} else if (topic.startsWith(NotificationConstants.TOPIC_PREFIX_UPDATE)) {
					log.info("Updating security context with topic: "+topic);
					secCtxNotificationManager.updateSecurityContext(message);
					return true;
				} else {
					log.error("Unsupported topic type for security context: "+topic);
					return false;
				}
				
			} else if (resourceType.equals(A2Constants.RESOURCE_TYPE_SECURITY_PROFILE)) {
				if (topic.startsWith(NotificationConstants.TOPIC_PREFIX_DELETE)) {
					log.info("Deleting security profile with topic: "+topic);
					String resourceId = secProfNotificationManager.deleteSecurityProfile(message);
					if (resourceId!=null) {
						secCtxNotificationManager.deleteContextForResourceId(resourceId);
					}
					return true;
				} else if (topic.startsWith(NotificationConstants.TOPIC_PREFIX_UPDATE)) {
					log.info("Updating security profile with topic: "+topic);
					String resourceId = secProfNotificationManager.updateSecurityProfile(message);
//					TODO when secProf update: should secCtx be deleted?
					if (resourceId!=null) {
						secCtxNotificationManager.deleteContextForResourceId(resourceId);
					}
					return true;
				} else {
					log.error("Unsupported topic type for security profile: "+topic);
					return false;
				}
	
			} else if (resourceType.equals(A2Constants.RESOURCE_TYPE_SECURITY_POLICY)) {
				if (topic.startsWith(NotificationConstants.TOPIC_PREFIX_DELETE)) {
					log.info("Deleting security policy with topic: "+topic);
					secPolicyNotificationManager.deleteSecurityPolicy(message);
					return true;
				} else if (topic.startsWith(NotificationConstants.TOPIC_PREFIX_UPDATE)) {
					log.info("Updating security policy with topic: "+topic);
					secPolicyNotificationManager.updateSecurityPolicy(message);
					return true;
				} else if (topic.startsWith(NotificationConstants.TOPIC_CREATE_SEC_POLICY)) {
					log.info("Creating security policy with topic: "+topic);
					secPolicyNotificationManager.createSecurityPolicy(message);
					return true;
				} else {
					log.error("Unsupported topic type for security policy: "+topic);
					return false;
				}
	
	 		} else {
	 			log.error("Unsupported resource type found in profile content from message: "+message);
				return false;
	 		}
		} catch (A2Exception e) {
			log.error("Exception occured when maintaining notification of topic: " 
					+ topic + " and message: " + message, e);
			return true;
		}

	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.nh.rmi.INotificationSubscriber#subscribe(java.lang.String, java.lang.String, java.lang.String)
	 */
	public String subscribe(String topicType, String resourceType, String profId) {
		if (topicType==null) {
			log.error("Cannot subscribe: Null topicType found!");
			return null;
		}
		if (resourceType==null) {
			log.error("Cannot subscribe: Null resourceType found!");
			return null;
		}
		if (profId==null) {
			log.error("Cannot subscribe: Null profId found!");
			return null;
		}
			
		String newTopic = NotificationUtils.buildTopicForProfId(topicType, resourceType, 
				profId, servicesVersionHolder.getSnServiceVersion());
		try {
			String subscrId = snService.subscribe(this.generatedConsumerReferenceEPR, 
					newTopic, NotificationConstants.TERMINATION_TIME_INFINITE);
			if (subscrId!=null)
				log.info("Subscribed to topic: "+newTopic+" with subscrId: " + subscrId);
			else
				log.warn("Null subscription id returned when subscribing to topic: " + newTopic);
			return subscrId;
		} catch (ISSNException e) {
			log.error("Exception occured when subscribing to " + newTopic + " topic!", e);
			return null;
		}
	}
	
	/**
	 * Checks if notification producer is allowed.
	 * @param isId
	 * @return true if notification producer is allowed
	 */
	boolean checkNotificationProducer(String isId) {
		if (isId==null) {
			log.warn("Couldn't find notification producer address. Null isId!");
			return false;
		}
		String npAddress = extractAddressFromIsId(isId);
		if (npAddress==null) {
			log.error("No notification producer address found");
			return false;
		}
		if (allowedNotificationProducers.contains(npAddress))
			return true;
		else {
			log.warn("Notifications issued by " + npAddress + " are not allowed!");
			return false;
		}
	}
	
	/**
	 * Extracts ISSN service address from isId notification part.
	 * @param isId
	 * @return service address
	 */
	static String extractAddressFromIsId(String isId) {
		try {
			DocumentBuilderFactory factory =
	            DocumentBuilderFactory.newInstance();
	        factory.setIgnoringComments(true);
	        DocumentBuilder db = null;
	        factory.setNamespaceAware(true);
	        factory.setValidating(false);
	        db = factory.newDocumentBuilder();
	        Document doc = db.parse(new InputSource(new StringReader(isId)));
	        String addressTagName = "wsa:Address";
	        NodeList nodeList = doc.getElementsByTagName(addressTagName);
	        if (nodeList.getLength()!=1) {
	    		log.error("Invalid list of nodes for "+addressTagName+" element. Expected 1, found: "+nodeList.getLength());
	    		return null;
	    	}
	        Element addressElement = (Element) nodeList.item(0);
	        if (addressElement==null || addressElement.getFirstChild()==null 
	        		|| addressElement.getFirstChild().getTextContent()==null) {
	        	log.error("Couldn't find "+addressTagName+" element!");
	        	return null;
	        }
	        return addressElement.getFirstChild().getTextContent();
	        
		} catch (Exception e) {
			log.error("Couldn't parse isId content!",e);
			return null;
		}
	}
	
	/**
	 * Returns ISSN service.
	 * @return ISSN service
	 */
	public ISSNService getSnService() {
		return snService;
	}

	/**
	 * Sets ISSN service.
	 * @param snService
	 */
	public void setSnService(ISSNService snService) {
		this.snService = snService;
	}

	/**
	 * Sets consumer reference URL for ISSN subscription.
	 * @param consumerReference
	 */
	public void setConsumerReferenceURL(String consumerReferenceURL) {
		this.consumerReferenceURL = consumerReferenceURL;
	}

	/**
	 * Sets consumer reference WSDL for ISSN subscription.
	 * @param consumerReference
	 */
	public void setConsumerReferenceWSDL(String consumerReferenceWSDL) {
		this.consumerReferenceWSDL = consumerReferenceWSDL;
	}
	
	/**
	 * Returns security context notification manager.
	 * @return security context notification manager
	 */
	public ISecurityContextNotificationManager getSecCtxNotificationManager() {
		return secCtxNotificationManager;
	}

	/**
	 * Sets security context notification manager.
	 * @param secCtxNotificationManager
	 */
	public void setSecCtxNotificationManager(
			ISecurityContextNotificationManager secCtxNotificationManager) {
		this.secCtxNotificationManager = secCtxNotificationManager;
	}

	/**
	 * Returns security policy notification manager.
	 * @return security policy notification manager
	 */
	public ISecurityPolicyNotificationManager getSecPolicyNotificationManager() {
		return secPolicyNotificationManager;
	}

	/**
	 * Sets security policy notification manager.
	 * @param secPolicyNotificationManager
	 */
	public void setSecPolicyNotificationManager(
			ISecurityPolicyNotificationManager secPolicyNotificationManager) {
		this.secPolicyNotificationManager = secPolicyNotificationManager;
	}

	/**
	 * Returns security profile notification manager.
	 * @return security profile notification manager
	 */
	public ISecurityProfileNotificationManager getSecProfNotificationManager() {
		return secProfNotificationManager;
	}

	/**
	 * Sets security profile notification manager.
	 * @param secProfNotificationManager
	 */
	public void setSecProfNotificationManager(
			ISecurityProfileNotificationManager secProfNotificationManager) {
		this.secProfNotificationManager = secProfNotificationManager;
	}

	/**
	 * Returns allowed notification producers addresses.
	 * @return allowed notification producers addresses
	 */
	public Set<String> getAllowedNotificationProducers() {
		return allowedNotificationProducers;
	}

	/**
	 * Sets allowed notification producers addresses.
	 * @param allowedNotificationProducers
	 */
	public void setAllowedNotificationProducers(
			Set<String> allowedNotificationProducers) {
		this.allowedNotificationProducers = allowedNotificationProducers;
	}

	/**
	 * Returns true if checking notification producers is enabled.
	 * @return true if checking notification producers is enabled
	 */
	public boolean isCheckingNotificationProducersEnabled() {
		return checkingNotificationProducersEnabled;
	}

	/**
	 * Sets checking notification producers.
	 * @param checkingNotificationProducersEnabled
	 */
	public void setCheckingNotificationProducersEnabled(
			boolean checkingNotificationProducersEnabled) {
		this.checkingNotificationProducersEnabled = checkingNotificationProducersEnabled;
	}

	/**
	 * Returns IS services version holder.
	 * @return IS services version holder
	 */
	public ISServicesVersionHolder getServicesVersionHolder() {
		return servicesVersionHolder;
	}

	/**
	 * Sets IS services version holder.
	 * @param servicesVersionHolder
	 */
	public void setServicesVersionHolder(
			ISServicesVersionHolder servicesVersionHolder) {
		this.servicesVersionHolder = servicesVersionHolder;
	}

}
