/*
 * 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.jena.sparql.util.graph;

import java.util.Collection;
import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.jena.atlas.logging.Log;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
import org.apache.jena.sparql.expr.Expr;
import org.apache.jena.util.iterator.ExtendedIterator;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.RDFS;

public class GraphContainerUtils
{
    private static final Node RDFtype = RDF.type.asNode();
    private static final Node BAG = RDF.Bag.asNode();
    private static final Node ALT = RDF.Alt.asNode();
    private static final Node SEQ = RDF.Seq.asNode();
    private static final String membershipPattern$ = RDF.getURI()+"_(\\d+)";
    private static final Pattern membershipPattern = Pattern.compile(membershipPattern$);
    private static final int NOT_FOUND = -9999;

    public static Collection<Node> containerMembers(Graph graph, Node container)
    { return containerMembers(graph, container, null); }

    public static Collection<Node> containerMembers(Graph graph, Node container, Node containerType) {
        if ( ! isContainer(graph, container, containerType) )
            return null;

        ExtendedIterator<Triple> iter = graph.find(container, Node.ANY, Node.ANY);

        SortedMap<Integer, Node> triples = new TreeMap<>(order);
        try {
            for ( ; iter.hasNext() ; ) {
                Triple t = iter.next();
                int index = getIndex(t);
                if ( index == NOT_FOUND )
                    continue;
                // Insert
                triples.put(Integer.valueOf(index), t.getObject());
            }
        } finally { iter.close(); }
        return triples.values();
    }

    public static boolean isContainerMember(Graph graph, Node container, Node containerType, Node member) {
        return countContainerMember(graph, container, containerType, member, true) != 0;
    }

    public static int countContainerMember(Graph graph, Node container, Node containerType, Node member) {
        return countContainerMember(graph, container, containerType, member, false);
    }

    private static Node RDFSmember = RDFS.member.asNode();
    private static Predicate<Triple> filterRDFSmember = new Predicate<Triple>() {
        @Override
        public boolean test(Triple triple) {
            Node p = triple.getPredicate();
            if ( ! triple.getPredicate().isURI() )
                return false;
            if (RDFSmember.equals(p) )
                return true;
            String u = triple.getPredicate().getURI();
            return membershipPattern.matcher(u).matches();
        } };

    /** Calculate graph.find(?, rdfs:member, ?) */
    public static Iterator<Triple> rdfsMember(Graph graph, Node s, Node o) {
        ExtendedIterator<Triple> iter = graph.find(s, Node.ANY, o);
        return iter.filterKeep(filterRDFSmember) ;
    }

    private static int countContainerMember(Graph graph, Node container, Node containerType, Node member, boolean stopEarly) {
        if ( graph == null ) {
            Log.warn(GraphContainerUtils.class, "containerMember called with null graph");
            return 0;
        }

        if ( container == null ) {
            Log.warn(GraphContainerUtils.class, "containerMember called with null list");
            return 0;
        }
        if ( member == null ) {
            Log.warn(GraphContainerUtils.class, "containerMember called with null member");
            return 0;
        }

        if ( !isContainer(graph, container, containerType) )
            return 0;

        int count = 0;
        ExtendedIterator<Triple> iter = graph.find(container, Node.ANY, member);
        try {
            for ( ; iter.hasNext() ; ) {
                Triple t = iter.next();
                Node p = t.getPredicate();
                String u = p.getURI();

                if ( membershipPattern.matcher(u).matches() ) {
                    count++;
                    if ( stopEarly )
                        return count;
                }
            }
        }
        finally {
            iter.close();
        }
        return count;
    }

    public static boolean isContainer(Graph graph, Node container, Node containerType) {
        if ( containerType == null )
            return  isContainer(graph, container, BAG) ||
                    isContainer(graph, container, ALT) ||
                    isContainer(graph, container, SEQ);

        return graph.contains(container, RDFtype, containerType);
    }

    private static int getIndex(Triple triple) {
        String u = triple.getPredicate().getURI();
        // Must be _nnn.
        Matcher m = membershipPattern.matcher(u);
        if ( ! m.find() )
            return NOT_FOUND;
        String index = m.group(1);
        return Integer.parseInt(index);
    }

    static ContainerOrder order = new ContainerOrder();
    static private class ContainerOrder implements java.util.Comparator<Integer> {
        @Override
        public int compare(Integer i1, Integer i2) {
            int index1 = i1.intValue();
            int index2 = i2.intValue();

            if ( index1 < index2 )
                return Expr.CMP_LESS;
            if ( index1 > index2 )
                return Expr.CMP_GREATER;
            return Expr.CMP_EQUAL;
        }
    }
}
