
package org.fcrepo.server.security.xacml.pdp.finder.attribute;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Set;

import org.fcrepo.common.rdf.SimpleURIReference;
import org.fcrepo.server.resourceIndex.ResourceIndex;
import org.fcrepo.server.security.PolicyFinderModule;
import org.fcrepo.server.security.xacml.pdp.finder.AttributeFinderException;
import org.jrdf.graph.PredicateNode;
import org.jrdf.graph.SubjectNode;
import org.jrdf.graph.Triple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.trippi.TripleIterator;
import org.trippi.TrippiException;

import org.jboss.security.xacml.sunxacml.EvaluationCtx;
import org.jboss.security.xacml.sunxacml.attr.AttributeFactory;
import org.jboss.security.xacml.sunxacml.attr.AttributeValue;
import org.jboss.security.xacml.sunxacml.attr.BagAttribute;
import org.jboss.security.xacml.sunxacml.attr.StandardAttributeFactory;
import org.jboss.security.xacml.sunxacml.cond.EvaluationResult;

public class RITriplesAttributeFinder
        extends DesignatorAttributeFinderModule {

    private static final Logger logger =
            LoggerFactory.getLogger(RITriplesAttributeFinder.class);

    private final AttributeFactory m_attributeFactory = StandardAttributeFactory.getFactory();

    private final ResourceIndex m_resourceIndex;

    public RITriplesAttributeFinder(ResourceIndex resourceIndex) {
        m_resourceIndex = resourceIndex;
    }

    public void init() throws AttributeFinderException {
        if (emptyAttributeMap()) {
            logger.warn(this.getClass().getName() + " configured with no registered attributes");
            return;
        }
        if (logger.isDebugEnabled()) {
            logger.debug("registering the following attributes: ");
            for (int desNum : m_attributes.keySet()) {
                for (String attrName : m_attributes.get(desNum).keySet()) {
                    logger.debug(desNum + ": " + attrName);
                }
            }
        }
        logger.info("Initialised AttributeFinder:"
                    + this.getClass().getName());
    }

    /**
     * Returns true always because this module supports designators.
     *
     * @return true always
     */
    @Override
    public boolean isDesignatorSupported() {
        return true;
    }

    @Override
    public Set<Integer> getSupportedDesignatorTypes() {
        return m_attributes.keySet();
    }

    /**
     * Used to get an attribute. If one of those values isn't being asked for,
     * or if the types are wrong, then an empty bag is returned.
     *
     * @param attributeType
     *        the datatype of the attributes to find, which must be time, date,
     *        or dateTime for this module to resolve a value
     * @param attributeId
     *        the identifier of the attributes to find, which must be one of the
     *        three ENVIRONMENT_* fields for this module to resolve a value
     * @param issuer
     *        the issuer of the attributes, or null if unspecified
     * @param subjectCategory
     *        the category of the attribute or null, which ignored since this
     *        only handles non-subjects
     * @param context
     *        the representation of the request data
     * @param designatorType
     *        the type of designator, which must be ENVIRONMENT_TARGET for this
     *        module to resolve a value
     * @return the result of attribute retrieval, which will be a bag with a
     *         single attribute, an empty bag, or an error
     */
    @Override
    public EvaluationResult findAttribute(URI attributeType,
                                          URI attributeId,
                                          URI issuer,
                                          URI subjectCategory,
                                          EvaluationCtx context,
                                          int designatorType) {

        String resourceId = context.getResourceId().encode();
        if (resourceId == null || resourceId.isEmpty()) {
            String pid = PolicyFinderModule.getPid(context);
            if (pid != null) {
                resourceId = "info:fedora/" + pid;
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("RITriplesAttributeFinder: [" + attributeType.toString() + "] "
                    + attributeId + ", rid=" + resourceId);
        }

        if (resourceId == null || resourceId.isEmpty()) {
            return new EvaluationResult(BagAttribute
                    .createEmptyBag(attributeType));
        }

        if (resourceId.equals("/FedoraRepository")) {
            return new EvaluationResult(BagAttribute
                                        .createEmptyBag(attributeType));
        }

        // figure out which attribute we're looking for
        String attrName = attributeId.toString();

        // we only know about registered attributes from Spring config
        if (!m_attributes.containsKey(designatorType)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Does not know about designatorType: "
                        + designatorType);
            }
            return new EvaluationResult(BagAttribute
                    .createEmptyBag(attributeType));
        }

        Set<String> allowedAttributes =
            m_attributes.get(designatorType).keySet();
        if (!allowedAttributes.contains(attrName)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Does not know about attribute: " + attrName);
            }
            return new EvaluationResult(BagAttribute
                    .createEmptyBag(attributeType));
        }

        EvaluationResult result = null;
        try {
            result = getEvaluationResult(resourceId, attrName, designatorType, attributeType);
        } catch (Exception e) {
            logger.error("Error finding attribute: " + e.getMessage(), e);
            return new EvaluationResult(BagAttribute
                    .createEmptyBag(attributeType));
        }

        return result;
    }

    /**
     *
     * @param resourceID - the hierarchical XACML resource ID
     * @param attribute - attribute to get - this is a URI that maps to a Fedora relationship name
     * @param type
     * @return
     * @throws AttributeFinderException
     */
    private EvaluationResult getEvaluationResult(String resourceID,
                                                 String attribute,
                                                 int designatorType,
                                                 URI type)
            throws AttributeFinderException {

        // split up the path of the hierarchical resource id
        String resourceParts[] = resourceID.split("/");
        Set<String> results = null;

        // either the last part is the pid, or the last-but one is the pid and the last is the datastream
        // if we have a pid, we query on that, if we have a datastream we query on the datastream
        if (resourceParts.length <= 1) {
            // eg /FedoraRepository, not a valid path to PID or PID/DS
            logger.debug("Resource ID not valid path to PID or datastream: " + resourceID);
            return new EvaluationResult(BagAttribute.createEmptyBag(type));
        }

        if (logger.isDebugEnabled()){
            logger.debug("Getting attribute " + attribute +" for resource " + resourceID);
        }

        try{
            SubjectNode snode = new SimpleURIReference(new URI(resourceID));
            PredicateNode pnode = new SimpleURIReference(new URI(attribute));
            TripleIterator triples = m_resourceIndex.findTriples(snode, pnode, null, 0);
            results = new HashSet<String>();
            while (triples.hasNext()){
                Triple triple = triples.next();
                String object = triple.getObject().stringValue();
                results.add(object);
            }
        }
        catch (TrippiException e){
            logger.warn("Error retreiving triples in attributeFinder",e);
        }
        catch (URISyntaxException e){
            logger.warn("Error retreiving triples in attributeFinder",e);
        }
        finally {}

        if (results == null || results.isEmpty()) {
            logger.debug("Attribute values found: 0");
            return new EvaluationResult(BagAttribute.createEmptyBag(type));
        }


        Set<AttributeValue> bagValues = new HashSet<AttributeValue>();
        logger.debug("Attribute values found: " + results.size());
        for (String s : results) {
            AttributeValue attributeValue = null;
            try {
                attributeValue = m_attributeFactory.createValue(type, s);
            } catch (Exception e) {
                logger.error("Error creating attribute: " + e.getMessage(), e);
                continue;
            }

            bagValues.add(attributeValue);

            if (logger.isDebugEnabled()) {
                logger.debug("AttributeValue found: [" + type.toASCIIString()
                             + "] " + s);
            }

        }

        BagAttribute bag = new BagAttribute(type, bagValues);
        return new EvaluationResult(bag);

    }

}
