/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.clerezza.platform.concepts.core;

import java.net.URISyntaxException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import org.apache.clerezza.platform.concepts.ontologies.QUERYRESULT;
import org.apache.clerezza.platform.config.PlatformConfig;
import org.apache.clerezza.platform.graphprovider.content.ContentGraphProvider;
import org.apache.clerezza.platform.typerendering.RenderletManager;
import org.apache.clerezza.platform.typerendering.scalaserverpages.ScalaServerPagesRenderlet;
import org.apache.clerezza.rdf.core.BNode;
import org.apache.clerezza.rdf.core.Graph;
import org.apache.clerezza.rdf.core.Literal;
import org.apache.clerezza.rdf.core.LiteralFactory;
import org.apache.clerezza.rdf.core.MGraph;
import org.apache.clerezza.rdf.core.NonLiteral;
import org.apache.clerezza.rdf.core.Resource;
import org.apache.clerezza.rdf.core.Triple;
import org.apache.clerezza.rdf.core.UriRef;
import org.apache.clerezza.rdf.core.access.TcManager;
import org.apache.clerezza.rdf.core.impl.SimpleMGraph;
import org.apache.clerezza.rdf.core.impl.TripleImpl;
import org.apache.clerezza.rdf.ontologies.OWL;
import org.apache.clerezza.rdf.ontologies.RDF;
import org.apache.clerezza.rdf.ontologies.RDFS;
import org.apache.clerezza.rdf.ontologies.SKOS;
import org.apache.clerezza.rdf.utils.GraphNode;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;

/**
 * This JAX-RS resource can be used to search concepts accessible through
 * registered {@link ConceptProvider}s. If no {@link LocalConceptProvider} is
 * registered for {@link ConceptManipulator.FREE_CONCEPT_SCHEME}, then one is
 * created to find free concepts in the content graph.
 * Concept providers are prioritized.
 * The URI, SKOS:prefLabel and RDFS:comment of a concept from a provider of a 
 * higher priority will be used instead of those concepts having an OWL:sameAs
 * relation with this concept, but from a provider of lower priority.
 * Implicitly created {@link LocalConceptProvider} for free concepts has the
 * lowest priority.
 * 
 * The URI path of this service is /concepts/find.
 * 
 * @author hasan
 */
@Component
@Service(Object.class)
@Property(name = "javax.ws.rs", boolValue = true)
@Path("/concepts/find")
public class ConceptsFinder {

    @Reference
    private RenderletManager renderletManager;

    @Reference
    protected ConceptProviderManager conceptProviderManager;

    @Reference
    private TcManager tcManager;

    @Reference
    private ContentGraphProvider cgProvider;

    @Reference
    private PlatformConfig platformConfig;

    private LocalConceptProvider freeConceptProvider = null;

    private UriRef freeConceptScheme = null;

    protected void activate(ComponentContext context) throws URISyntaxException {
        URL template = getClass().getResource("skos-collection-json.ssp");
        renderletManager.registerRenderlet(ScalaServerPagesRenderlet.class.getName(),
                new UriRef(template.toURI().toString()),
                SKOS.Collection, null,
                MediaType.APPLICATION_JSON_TYPE, true);

        freeConceptScheme =
                new UriRef(platformConfig.getDefaultBaseUri().getUnicodeString()
                + ConceptManipulator.FREE_CONCEPT_SCHEME);
        freeConceptProvider = new LocalConceptProvider(tcManager, cgProvider,
                freeConceptScheme);
    }

    /**
     * Searches concepts for a specified search term. The actual search task
     * is delegated to each {@link ConceptProvider} instance. The results from
     * each {@link ConceptProvider} are merged into a single result graph.
     * However, concepts from providers of lower priority are only considered if
     * they are not staying in an OWL:sameAs relation with concepts from
     * providers of higher priority.
     * 
     * @param searchTerm
     *            The search term in form of a String.
     * @return
     *        A GraphNode containing the search results.
     */
    @GET
    @Produces("application/rdf+json")
    public GraphNode findConcepts(@QueryParam(value="searchTerm")
            String searchTerm) {

        boolean freeConceptProviderFound = false;

        List<ConceptProvider> conceptProviderList = conceptProviderManager
                .getConceptProviders();

        MGraph resultMGraph = new SimpleMGraph();
        GraphNode resultNode = new GraphNode(new BNode(), resultMGraph);
        boolean first = true;
        for (ConceptProvider cp : conceptProviderList) {
            if (!freeConceptProviderFound) {
                if (cp instanceof LocalConceptProvider) {
                    if (((LocalConceptProvider) cp).getSelectedScheme().equals(
                            freeConceptScheme)) {
                        freeConceptProviderFound = true;
                    }
                }
            }
            retrieveConcepts(cp, first, resultNode, searchTerm);
            if (first) {
                first = false;
            }
        }
        if (!freeConceptProviderFound && freeConceptProvider != null) {
            retrieveConcepts(freeConceptProvider, first, resultNode, searchTerm);
        }
        addCreationOfNewFreeConceptSuggested(resultNode, searchTerm);
        resultNode.addProperty(RDF.type, QUERYRESULT.QueryResult);
        return resultNode;
    }

    /**
     * Adds a boolean value that answers whether the UI shall suggest to create
     * a new free concept. A new free concept may not be added if the has the
     * same base uri and search term. Therefore the consumer shall be suggested
     * not to propose creation.
     * 
     * @param resultNode
     *            the result node to add the property to
     * @param searchTerm
     *            the search term the data was searched for
     */
    private void addCreationOfNewFreeConceptSuggested(GraphNode resultNode,
            String searchTerm) {
        UriRef conceptUriRef = ConceptManipulator.getConceptUriRef(
                platformConfig, searchTerm);
        resultNode.addProperty(QUERYRESULT.creationOfNewFreeConceptSuggested,
                LiteralFactory.getInstance().createTypedLiteral(
                        !cgProvider.getContentGraph().contains(
                                new TripleImpl(conceptUriRef, RDF.type,
                                        SKOS.Concept))));
    }

    /**
     * Retrieve concepts for the given search term.
     * 
     * @param conceptProvider
     *            the provider delivers concepts
     * @param first
     *            is this the first execution
     * @param resultNode
     *            the node to attach the concepts to
     * @param searchTerm
     *            the search term that the concepts have to match against
     */
    private void retrieveConcepts(ConceptProvider conceptProvider,
            boolean first, GraphNode resultNode, String searchTerm) {
        MGraph resultMGraph = (MGraph) resultNode.getGraph();
        Graph graph = conceptProvider.retrieveConcepts(searchTerm);
        Iterator<Triple> concepts = graph.filter(null, RDF.type, SKOS.Concept);
        if (first) {
            while (concepts.hasNext()) {
                resultNode.addProperty(QUERYRESULT.concept, concepts.next()
                        .getSubject());
            }
            resultMGraph.addAll(graph);
        } else {
            while (concepts.hasNext()) {
                NonLiteral concept = concepts.next().getSubject();
                GraphNode conceptGraphNode = new GraphNode(concept, graph);
                Iterator<Resource> sameAsConcepts = conceptGraphNode
                        .getObjects(OWL.sameAs);
                if (!(hasSameAs(resultMGraph, concept) || hasAnyConcept(
                        resultMGraph, sameAsConcepts))) {
                    resultNode.addProperty(QUERYRESULT.concept, concept);
                    addConceptToResultMGraph(resultMGraph, conceptGraphNode);
                }

            }
        }
    }

    private boolean hasSameAs(MGraph graph, NonLiteral sameAsConcept) {
        Iterator<Triple> concepts = graph.filter(null, RDF.type, SKOS.Concept);
        while (concepts.hasNext()) {
            NonLiteral concept = concepts.next().getSubject();
            if (graph.filter(concept, OWL.sameAs, sameAsConcept).hasNext()) {
                return true;
            }
        }
        return false;
    }

    private boolean hasAnyConcept(MGraph graph, Iterator<Resource> concepts) {
        while (concepts.hasNext()) {
            NonLiteral concept = (NonLiteral) concepts.next();
            if (graph.filter(concept, RDF.type, SKOS.Concept).hasNext()) {
                return true;
            }
        }
        return false;
    }

    private void addConceptToResultMGraph(MGraph resultMGraph,
            GraphNode graphNode) {
        NonLiteral concept = (NonLiteral) graphNode.getNode();
        resultMGraph.add(new TripleImpl(concept, RDF.type, SKOS.Concept));

        Iterator<Literal> prefLabelStatements = graphNode.getLiterals(SKOS.prefLabel);
        while (prefLabelStatements.hasNext()) {
            resultMGraph.add(new TripleImpl(concept, SKOS.prefLabel,
                    prefLabelStatements.next()));
        }
        Iterator<Literal> commentStatements = graphNode.getLiterals(RDFS.comment);
        while (commentStatements.hasNext()) {
            resultMGraph.add(new TripleImpl(concept, RDFS.comment,
                    commentStatements.next()));
        }
        Iterator<UriRef> sameAsStatements = graphNode.getUriRefObjects(OWL.sameAs);
        while (sameAsStatements.hasNext()) {
            resultMGraph.add(new TripleImpl(concept, OWL.sameAs,
                    sameAsStatements.next()));
        }
    }
}
