package eu.dnetlib.enabling.aas;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;


import com.sun.xacml.Obligation;
import com.sun.xacml.attr.AttributeDesignator;
import com.sun.xacml.attr.AttributeValue;
import com.sun.xacml.attr.BagAttribute;
import com.sun.xacml.cond.EvaluationResult;
import com.sun.xacml.ctx.Attribute;
import com.sun.xacml.ctx.ResponseCtx;
import com.sun.xacml.ctx.Result;

import eu.dnetlib.enabling.aas.finder.ctx.DriverEvaluationCtx;

/**
 * Util class for resolving RequestCtx obligations.
 * @author mhorst
 *
 */
public class ObligationsResolver {
	
	public static final String OBLIGATIONS_RESOLVER_ISSUER	= "ObligationsResolver";
	
	public static final String ACTION_DESIGNATOR_TYPE		= "action";
	public static final String ENVIRONMENT_DESIGNATOR_TYPE	= "environment";
	public static final String SUBJECT_DESIGNATOR_TYPE		= "subject";
	public static final String RESOURCE_DESIGNATOR_TYPE		= "resource";
	public static final String UNSPECIFIED_DESIGNATOR_TYPE	= "unspecified";
	
	static final String DESIGNATE						= "designate";
	static final char BEGIN_DESIGNATOR_TYPE 			= '/';
	static final char END_DESIGNATOR_TYPE 				= '/';
	public static final String RESOLVABLE_OBLIGATION_PREFIX = DESIGNATE + ":";
	static final String TYPED_RESOLVABLE_OBLIGATION_PREFIX	= DESIGNATE + BEGIN_DESIGNATOR_TYPE;
	
	protected static final Logger log = Logger.getLogger(ObligationsResolver.class);
	
	/**
	 * Resolves obligations if any defined.
	 * @param responseCtx
	 * @param driverEvalCtx
	 * @return ResponseCtx
	 */
	@SuppressWarnings("unchecked")
	public static ResponseCtx resolveObligations(ResponseCtx responseCtx, DriverEvaluationCtx driverEvalCtx) {
		if (responseCtx.getResults() != null) {
			Set results = responseCtx.getResults();
			Iterator it = results.iterator();
//			only first result is processed (multiple results only for hierarchical resources)
			if (it.hasNext()) {
				Result res = (Result) it.next();
				Set<Obligation> obligations = res.getObligations();
				Set<Obligation> resolvedObligations = new HashSet<Obligation>();
				if (obligations.size()>0) {
					Iterator<Obligation> itOblig = obligations.iterator();
					while (itOblig.hasNext()) {
						resolvedObligations.add(resolveObligation(itOblig.next(), driverEvalCtx));
					}
					Result modifiedResult = new Result(res.getDecision(),res.getStatus(),res.getResource(),resolvedObligations);
					return new ResponseCtx(modifiedResult);
				} else {
					return responseCtx;
				}
			}
		} 
		return responseCtx;
	}
	
	/**
	 * Returns resolved obligation. 
	 * If its assignment is resolvable but it cannot be resolved it is removed from assignments.
	 * @param sourceOblig
	 * @param driverEvalCtx
	 * @return resolved Obligation
	 */
	@SuppressWarnings("unchecked")
	public static Obligation resolveObligation(Obligation sourceOblig, DriverEvaluationCtx driverEvalCtx) {
		List<Attribute> assignments = sourceOblig.getAssignments();
		List<Attribute> resultAssignments = new ArrayList<Attribute>();
		if (assignments!=null && assignments.size()>0) {
			Iterator<Attribute> itAttr = assignments.iterator();
			while (itAttr.hasNext()) {
				Attribute currentAttr = itAttr.next();
				if (!(currentAttr.getValue() instanceof BagAttribute)) {
					String currentAssignmentStr = currentAttr.getValue().encode().trim();
					if (currentAssignmentStr.startsWith(RESOLVABLE_OBLIGATION_PREFIX) ||
							currentAssignmentStr.startsWith(TYPED_RESOLVABLE_OBLIGATION_PREFIX)) {
						String[] result = normalizeDesignatorId(currentAssignmentStr);
						String attrValue = null;
						if (result[0]!=null && result[0].startsWith(RESOLVABLE_OBLIGATION_PREFIX))
							attrValue = result[0].substring(RESOLVABLE_OBLIGATION_PREFIX.length());
						else {
							log.error("Invalid resolvable assignment: "+currentAssignmentStr);
//							adding not resolved attribute
							resultAssignments.add(currentAttr);
							continue;
						}
						String desigType = result[1];
						try {
							EvaluationResult evalResult = null;
							if (desigType==null)
								desigType = UNSPECIFIED_DESIGNATOR_TYPE;
														
							if (desigType.equals(RESOURCE_DESIGNATOR_TYPE)) {
								evalResult = driverEvalCtx.getResourceAttribute(currentAttr.getType(),  new URI(attrValue), 
//										new URI(OBLIGATIONS_RESOLVER_ISSUER));
										null);
							} else if (desigType.equals(SUBJECT_DESIGNATOR_TYPE)) {
//								subject has an extra level of indirection that needs to be handled first
								URI subjectCategory = new URI(AttributeDesignator.SUBJECT_CATEGORY_DEFAULT);
								evalResult = driverEvalCtx.getSubjectAttribute(currentAttr.getType(),  new URI(attrValue), 
//										new URI(OBLIGATIONS_RESOLVER_ISSUER), 
										null,
										subjectCategory);
							} else if (desigType.equals(ACTION_DESIGNATOR_TYPE)) {
								evalResult = driverEvalCtx.getActionAttribute(currentAttr.getType(),  new URI(attrValue), 
//										new URI(OBLIGATIONS_RESOLVER_ISSUER));
										null);
							} else if (desigType.equals(ENVIRONMENT_DESIGNATOR_TYPE)) {
								evalResult = driverEvalCtx.getEnvironmentAttribute(currentAttr.getType(),  new URI(attrValue), 
//										new URI(OBLIGATIONS_RESOLVER_ISSUER));
										null);
							} else {
//								TODO be careful when checking issuers in custom Attribute Finders!
								evalResult = driverEvalCtx.getEnvironmentAttribute(currentAttr.getType(),  new URI(attrValue), 
										new URI(OBLIGATIONS_RESOLVER_ISSUER));
							}
							
							AttributeValue foundAttrValue = evalResult.getAttributeValue();
							if (foundAttrValue!=null) {
								if (foundAttrValue instanceof BagAttribute) {
									BagAttribute foundBagAttribute = (BagAttribute) foundAttrValue;
									if (foundBagAttribute.size()==1) {
//										if only one attribute found, extracting its value from bag
										Iterator bagIt = foundBagAttribute.iterator();
										resultAssignments.add(new Attribute(currentAttr.getId(), null, null, (AttributeValue) bagIt.next()));
									} else
										resultAssignments.add(new Attribute(currentAttr.getId(), null, null, foundAttrValue));
								} else
									resultAssignments.add(new Attribute(currentAttr.getId(), null, null, foundAttrValue));
							}
							
						} catch (URISyntaxException e) {
							log.error("Exception occured when creating URI for value :"+attrValue, e);
	//						copying current attribute without resolving
							resultAssignments.add(currentAttr);
							continue;
						} 
					} else {
	//					no need for resolving
						resultAssignments.add(currentAttr);
					}
				} else {
					log.error("BagAttribute is unsupported when resolving obligations!");
				}
			}
			return new Obligation(sourceOblig.getId(),sourceOblig.getFulfillOn(),resultAssignments);
		}
		return sourceOblig;
	}
	
	
	/**
	 * Normalizes typed designator id to simple designator id.
	 * Returns normalized designator id as String[0] and designator type (or null if not specified) as String[1].  
	 * @param attrSource
	 * @return normalized designator id as String[0] and designator type (or null if not specified) as String[1].
	 */
	static String[] normalizeDesignatorId(String designatorSource) {
		String[] result = new String[2]; 
		if (designatorSource==null)
			return result;
		if (designatorSource.startsWith(RESOLVABLE_OBLIGATION_PREFIX)) {
			result[0] = designatorSource;
			result[1] = null;
			return result;
		} else if (designatorSource.startsWith(TYPED_RESOLVABLE_OBLIGATION_PREFIX)) {
			String workStr = designatorSource.substring(TYPED_RESOLVABLE_OBLIGATION_PREFIX.length(), designatorSource.length());
//			int typeEndCharIndex = workStr.lastIndexOf(END_SECCTX_TYPE);
			int typeEndCharIndex = workStr.indexOf(END_DESIGNATOR_TYPE);
			if (typeEndCharIndex==-1)
				return result;
			String designatorType = workStr.substring(0,typeEndCharIndex);
			String normalizedDesignatorName = DESIGNATE;
			if (typeEndCharIndex+1<workStr.length())
				normalizedDesignatorName += workStr.substring(typeEndCharIndex+1, workStr.length());
			result[0] = normalizedDesignatorName;
			result[1] = designatorType;
			return result;
		} else
			return result;
	}

}
