/*
 * 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.language;

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import org.apache.clerezza.platform.config.PlatformConfig;
import org.apache.clerezza.platform.config.SystemConfig;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.clerezza.rdf.core.BNode;
import org.apache.clerezza.rdf.core.Graph;
import org.apache.clerezza.rdf.core.Language;
import org.apache.clerezza.rdf.core.MGraph;
import org.osgi.service.component.ComponentContext;
import org.apache.clerezza.rdf.core.NonLiteral;
import org.apache.clerezza.rdf.core.PlainLiteral;
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.LockableMGraph;
import org.apache.clerezza.rdf.core.access.SecuredMGraph;
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.core.serializedform.ParsingProvider;
import org.apache.clerezza.rdf.core.serializedform.SupportedFormat;
import org.apache.clerezza.rdf.ontologies.LINGVOJ;
import org.apache.clerezza.rdf.ontologies.PLATFORM;
import org.apache.clerezza.rdf.ontologies.RDF;
import org.apache.clerezza.rdf.ontologies.RDFS;
import org.apache.clerezza.rdf.utils.GraphNode;
import org.apache.clerezza.rdf.utils.RdfList;

/**
 * This class provides a OSGi service for managing languages in the Clerezza
 * platform.
 * 
 * @author mir
 */
@Component(immediate=true, enabled= true)
@Service(LanguageService.class)
public class LanguageService {    

    @Reference
    private TcManager tcManager;

    /**
     * this is linked to the system-graph, accessing requires respective
     * permission
     */
    private List<Resource> languageList;
    /**
     * no permission on the system graph required to access this
     */
    private List<Resource> languageListCache;

    private static final String PARSER_FILTER =
            "(supportedFormat=" + SupportedFormat.RDF_XML +")";

    @Reference(target=PARSER_FILTER)
    private ParsingProvider parser;

    @Reference(target = SystemConfig.SYSTEM_GRAPH_FILTER)
    private MGraph securedSystemGraph;

    @Reference(target = PlatformConfig.CONFIG_GRAPH_FILTER)
    private MGraph securedConfigGraph;
    
    private SoftReference<Graph> softLingvojGraph = new SoftReference<Graph>(null);


    private LockableMGraph getSystemGraph() {
        return ((SecuredMGraph) securedSystemGraph).getUnsecuredMGraph();
    }

    private LockableMGraph getConfigGraph() {
        return ((SecuredMGraph) securedConfigGraph).getUnsecuredMGraph();
    }

    /**
     * Returns a <code>List</code> of <code>LanguageDescription</code>s which
     * describe the languages which are supported by the platform. The first
     * entry describes the default language of the platform.
     * @return a list containing all language descriptions.
     */
    public List<LanguageDescription> getLanguages() {
        List<LanguageDescription> langList = new ArrayList<LanguageDescription>();
        Iterator<Resource> languages = languageListCache.iterator();
        while (languages.hasNext()) {
            UriRef language = (UriRef) languages.next();
            langList.add(new LanguageDescription(new GraphNode(language, getConfigGraph())));
        }
        return langList;
    }

    /**
     * Returns the <code>LanguageDescription</code> of the default language
     * of the platform.
     * @return the language description of the default language.
     */
    public LanguageDescription getDefaultLanguage() {
        return new LanguageDescription(
                new GraphNode(languageListCache.get(0), getConfigGraph()));
    }

    /**
     * Returns a set containg all language uris which are in the
     * <http://www.lingvoj.org/lingvoj> graph which is included in this bundle.
     * @return a set containing all language uris. This uris can be used to 
     * add the language to Clerezza over the addLanguage()-method in this class.
     */
    public Set<UriRef> getAllLanguages() {
        Set<UriRef> result = new HashSet<UriRef>();
        Graph lingvojGraph = getLingvojGraph();
        Iterator<Triple> languages = lingvojGraph.filter(null, RDFS.isDefinedBy,
                null);
        while (languages.hasNext()) {
            UriRef languageUri = (UriRef) languages.next().getSubject();
            result.add(languageUri);
        }
        return result;
    }

    /**
     * Returns a language uri of a language which has a label containing the
     * specified languageName. The label itself is in the language specified through
     * inLanguage. If inLanguage is null, then all labels of a language of searched.
     * If no language was found in the <http://www.lingvoj.org/lingvoj>
     * graph, then null is returned. The returned uri can be used to
     * add the language to Clerezza over the addLanguage()-method in this class.
     * @return a language uris
     */
    public UriRef getLanguage(String languageName, Language inLanguage) {
        Graph lingvojGraph = getLingvojGraph();
        Iterator<Triple> languages = lingvojGraph.filter(null, RDFS.isDefinedBy, null);
        while (languages.hasNext()) {
            GraphNode languageNode = new GraphNode((UriRef) languages.next().getSubject(), lingvojGraph);
            Iterator<Resource> labels = languageNode.getObjects(RDFS.label);
            while (labels.hasNext()) {
                PlainLiteral label = (PlainLiteral) labels.next();
                if (label.getLanguage().equals(inLanguage) || inLanguage == null) {
                    if (label.getLexicalForm().contains(languageName)) {
                        return (UriRef) languageNode.getNode();
                    }
                }
            }
        }
        return null;
    }
    
    /**
     * Get the language as <code>GraphNode</code> specified through languageUri. 
     * The languageUri has to be a <http://www.lingvoj.org/ontology#Lingvo>
     * according to the graph <http://www.lingvoj.org/lingvoj> included in this
     * bundle., e.g. "http://www.lingvoj.org/lang/de".
     *
     * @param languageUri The language uri which specifies the language 
     */
    public GraphNode getLanguageNode(NonLiteral langUri) {
        Graph lingvojRdf = getLingvojGraph();
        return new GraphNode(langUri, lingvojRdf);
    }

    /**
     * Adds the language specified through languageUri to the Clerezza
     * platform. The languageUri has to be a <http://www.lingvoj.org/ontology#Lingvo>
     * according to the graph <http://www.lingvoj.org/lingvoj> included in this
     * bundle., e.g. "http://www.lingvoj.org/lang/de" adds German.
     * The uri is added to the system graph and its context to the config graph.
     * The context added is the context provided by lingvoj.rdf.
     * @param languageUri The language uri which specifies the language to be
     *        added to the platform.
     */
    public void addLanguage(UriRef languageUri) {
        if (!languageListCache.contains(languageUri)) {
            LockableMGraph systemGraph = getSystemGraph();
            Lock writeLock = systemGraph.getLock().writeLock();
            writeLock.lock();
            try {
                if (languageList.add(languageUri)) {
                    addToLanguageConfigGraph(languageUri);
                }
            } finally {
                writeLock.unlock();
            }
            languageListCache.add(languageUri);
        }
    }

    private void synchronizeContentGraph() {
        for (Resource resource : languageListCache) {
            addToLanguageConfigGraph((UriRef)resource);
        }
    }
    /**
     * Adds the language information of the language specified through
     * languageUri to the config graph. The languageUri has to be of type 
     * <http://www.lingvoj.org/ontology#Lingvo> according to the graph 
     * <http://www.lingvoj.org/lingvoj> included in this
     * bundle., e.g. "http://www.lingvoj.org/lang/de" adds German.
     * 
     * The added language will not be a platform language.
     *
     * @param languageUri
     */
    public void addToLanguageConfigGraph(NonLiteral languageUri) {
        LockableMGraph configGraph = getConfigGraph();
        Lock writeLock = configGraph.getLock().writeLock();
        writeLock.lock();
        try {
            if (!configGraph.filter(languageUri, LINGVOJ.iso1, null).hasNext()) {
                configGraph.addAll(getLanguageContext(languageUri));
            }
        } finally {
            writeLock.unlock();
        }
    }

    private Graph getLanguageContext(NonLiteral langUri) {
        Graph lingvojRdf = getLingvojGraph();
        GraphNode languageNode = new GraphNode(langUri, lingvojRdf);
        return languageNode.getNodeContext();
    }
    
    private Graph getLingvojGraph() {
        Graph lingvojGraph = softLingvojGraph.get();
        if (lingvojGraph != null) {
            return lingvojGraph;
        }
        URL config = getClass().getResource("lingvoj.rdf");
        if (config == null) {
            throw new RuntimeException("no file found");
        }
        try {
            MGraph lingvojMGraph = new SimpleMGraph();
            parser.parse(lingvojMGraph, config.openStream(), SupportedFormat.RDF_XML, null);
            lingvojGraph = lingvojMGraph.getGraph();
            softLingvojGraph = new SoftReference<Graph>(lingvojGraph);
            return lingvojGraph;
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * The activate method is called when SCR activates the component configuration.
     * 
     * @param componentContext
     */
    protected void activate(ComponentContext componentContext) {
        LockableMGraph systemGraph = getSystemGraph();
        NonLiteral listNode = getListNode(systemGraph);

        Lock writeLock = systemGraph.getLock().writeLock();
        writeLock.lock();
        try {
            // the constructor of RdfList might write to the graph! => requires a write lock
            final RdfList rdfList = new RdfList(listNode, systemGraph);
            languageList = Collections.synchronizedList(rdfList);
            //access to languages should not require access to system graph,
            //so copying the resources to an ArrayList
            languageListCache = Collections.synchronizedList(
                    new ArrayList<Resource>(rdfList));
            if (languageListCache.size() == 0) {
                addLanguage(new UriRef("http://www.lingvoj.org/lang/en"));
            }
            //this is to make sure the content graph contains the relevant data
            synchronizeContentGraph();
        } finally {
            writeLock.unlock();
        }
    }

    private NonLiteral getListNode(LockableMGraph systemGraph) {
        NonLiteral instance = null;
        Lock readLock = systemGraph.getLock().readLock();
        readLock.lock();
        try {
            Iterator<Triple> instances = systemGraph.filter(null, RDF.type, PLATFORM.Instance);
            if (!instances.hasNext()) {
                throw new RuntimeException("No Platform:Instance in system graph.");
            }
            instance = instances.next().getSubject();
            Iterator<Triple> langListIter = systemGraph.filter(instance,
                    PLATFORM.languages, null);
            if (langListIter.hasNext()) {
                return (NonLiteral) langListIter.next().getObject();
            }
        } finally {
            readLock.unlock();
        }
        BNode listNode = new BNode();
        Lock writeLock = systemGraph.getLock().writeLock();
        writeLock.lock();
        try {
            systemGraph.add(new TripleImpl(instance, PLATFORM.languages, listNode));
        } finally {
            writeLock.unlock();
        }
        return listNode;
    }
}
