package eu.dnetlib.enabling.aas.service;

import javax.jws.WebService;

import org.apache.log4j.Logger;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SAMLObjectBuilder;
import org.opensaml.lite.common.SAMLObject;
import org.opensaml.lite.saml2.core.Assertion;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.ProxyRestriction;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObjectBuilderFactory;

import eu.dnetlib.enabling.aas.DNetAuthenticateRequest;
import eu.dnetlib.enabling.aas.DNetAuthenticateResponse;
import eu.dnetlib.enabling.aas.DNetAuthorizeRequest;
import eu.dnetlib.enabling.aas.DNetAuthorizeResponse;
import eu.dnetlib.enabling.aas.IAAService;
import eu.dnetlib.enabling.aas.holder.IDataHolder;
import eu.dnetlib.enabling.aas.saml.adapter.AdapterException;
import eu.dnetlib.enabling.aas.saml.adapter.AdapterFactoryRegistry;
import eu.dnetlib.enabling.aas.saml.adapter.IAdapter;
import eu.dnetlib.enabling.aas.saml.adapter.IAdapterFactory;


/**
 * Dummy AAService Implementation.
 * @author mhorst
 *
 */
@WebService(endpointInterface="eu.dnetlib.enabling.aas.IAAService",
		targetNamespace = "http://aas.enabling.dnetlib.eu")
public class DummyAAServiceImpl implements IAAService {

	protected final static Logger log = Logger.getLogger(DummyAAServiceImpl.class);
	
	/**
	 * SAML data holder for passing {@link SAMLObject} data from interceptors.
	 */
	protected IDataHolder<SAMLObject[]> inputSamlDataHolder;
	
	/**
	 * SAML data holder for passing {@link SAMLObject} data to interceptors.
	 */
	protected IDataHolder<SAMLObject[]> outputSamlDataHolder;
	
	static {
		try {
//			this is required for openSAML working properly
			DefaultBootstrap.bootstrap();
//			required by adapters
			AdapterFactoryRegistry.bootstrap();
		} catch (ConfigurationException e) {
			log.error("couldn't load openSAML bootstrap!", e);
		}
	}
	
	private String aasVersion;
	

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.IAAService#authenticate(eu.dnetlib.enabling.aas.DNetAuthenticateRequest)
	 */
	@Override
	public DNetAuthenticateResponse authenticate(DNetAuthenticateRequest request) {
		try {
			processSAMLObjects(inputSamlDataHolder.getData());
			return new DNetAuthenticateResponse();
		} catch (Exception e) {
			log.error(e);
			throw new RuntimeException(e);
		} finally {
			outputSamlDataHolder.storeData(prepareOutputAssertions(inputSamlDataHolder.getData()));
		}
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.IAAService#authorize(eu.dnetlib.enabling.aas.DNetAuthorizeRequest)
	 */
	@Override
	public DNetAuthorizeResponse authorize(DNetAuthorizeRequest request) {
		try {
			processSAMLObjects(inputSamlDataHolder.getData());
			return new DNetAuthorizeResponse();
		} catch (Exception e) {
			log.error(e);
			throw new RuntimeException(e);
		} finally {
			outputSamlDataHolder.storeData(prepareOutputAssertions(inputSamlDataHolder.getData()));
		}
	}

	/**
	 * Processes SAMLObjects
	 * @param samlObjects
	 */
	protected void processSAMLObjects(SAMLObject[] samlObjects) {
		if (samlObjects==null || samlObjects.length==0) {
			log.warn("got no assertions in request!");
		} else {
			for (SAMLObject currentSAMLObj : samlObjects) {
				if (currentSAMLObj instanceof Assertion) {
					log.info("assertion's issuer: " + 
							((Assertion)currentSAMLObj).getIssuer().getValue());
				} else {
					log.warn("unsupported SAMLObject instance: " + 
							currentSAMLObj.getClass().getName() + ", Assertion expected!");
				}
			}
		}
	}
	
	/**
	 * Prepares output assertions.
	 * Modifies assertions recevied in request by setting proxycount condition.
	 * @return assertions to be stored in response header.
	 */
	@SuppressWarnings("unchecked")
	SAMLObject[] prepareOutputAssertions(SAMLObject[] source) {
		if (source!=null) {
			SAMLObject[] result = new SAMLObject[source.length];
			for (int i=0; i< source.length; i++) {
				if (source[i] instanceof org.opensaml.lite.saml2.core.Assertion
						&& source[i] instanceof IAdapter) {
					org.opensaml.saml2.core.Assertion assertion = (org.opensaml.saml2.core.Assertion) ((IAdapter) source[i]).getAdaptedObject();
					XMLObjectBuilderFactory factory = Configuration.getBuilderFactory();
					SAMLObjectBuilder<Conditions> conditionsBuilder = (SAMLObjectBuilder<Conditions>) factory.getBuilder(Conditions.DEFAULT_ELEMENT_NAME);
					Conditions conds = conditionsBuilder.buildObject();
					assertion.setConditions(conds);
					SAMLObjectBuilder<ProxyRestriction> proxyRestrBuilder = (SAMLObjectBuilder<ProxyRestriction>) factory.getBuilder(ProxyRestriction.DEFAULT_ELEMENT_NAME);
					ProxyRestriction proxyRestr = proxyRestrBuilder.buildObject();
					conds.getConditions().add(proxyRestr);
					proxyRestr.setProxyCount(1);
					IAdapterFactory<org.opensaml.saml2.core.Assertion, 
						Assertion> adapterFactory = (IAdapterFactory<org.opensaml.saml2.core.Assertion, Assertion>) AdapterFactoryRegistry.lookup(assertion.getClass());
					try {
						result[i] = adapterFactory.newInstance(assertion);
					} catch (AdapterException e) {
						throw new RuntimeException("Problem occured when creating adapter!", e);
					}
				} else {
					result[i] = source[i];
				}
			}
			return result;
		} else {
			log.warn("got no SAMLObjects to be processed!");
			return null;
		}
		
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.common.rmi.BaseService#identify()
	 */
	@Override
	public String identify() {
		return aasVersion;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.common.rmi.BaseService#notify(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	public void notify(String arg0, String arg1, String arg2, String arg3) {
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.common.rmi.BaseService#start()
	 */
	@Override
	public void start() {
	}
	
	/**
	 * Sets AAS version.
	 * @param aasVersion
	 */
	public void setAasVersion(String aasVersion) {
		this.aasVersion = aasVersion;
	}

	/**
	 * Sets SAML data holder for passing {@link SAMLObject} data from interceptors.
	 * @param inputSamlDataHolder
	 */
	public void setInputSamlDataHolder(IDataHolder<SAMLObject[]> inputSamlDataHolder) {
		this.inputSamlDataHolder = inputSamlDataHolder;
	}

	/**
	 * Sets SAML data holder for passing {@link SAMLObject} data to interceptors.
	 * @param outputSamlDataHolder
	 */
	public void setOutputSamlDataHolder(IDataHolder<SAMLObject[]> outputSamlDataHolder) {
		this.outputSamlDataHolder = outputSamlDataHolder;
	}

}
