/*
 * 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
 *
 *   https://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.
 *
 *   SPDX-License-Identifier: Apache-2.0
 */

package org.apache.jena.sparql.core;

import static org.apache.jena.system.G.nullAsAny;

import java.util.*;
import java.util.stream.Stream;

import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
import org.apache.jena.graph.impl.GraphBase;
import org.apache.jena.riot.system.PrefixMap;
import org.apache.jena.riot.system.PrefixMapStd;
import org.apache.jena.util.iterator.ExtendedIterator;
import org.apache.jena.util.iterator.WrappedIterator;

/** Very simple, non-scalable DatasetGraph implementation
 * of a triples+quads style for testing the {@link DatasetGraphTriplesQuads}
 * style implementation framework.
 */
public class DatasetGraphSimpleMem extends DatasetGraphTriplesQuads implements TransactionalNotSupportedMixin
{
    private MiniSet<Triple> triples = new MiniSet<>();
    private MiniSet<Quad> quads = new MiniSet<>();
    private PrefixMap prefixes = new PrefixMapStd();

    /** Simple abstraction of a Set */
    private static class MiniSet<T> implements Iterable<T> {
        final Collection<T> store;

        MiniSet(Collection<T> store) {
            this.store = store;
        }

        MiniSet() {
            this.store = new ArrayList<>();
        }

        void add(T t) {
            if ( !store.contains(t) )
                store.add(t);
        }

        void remove(T t) {
            store.remove(t);
        }

        @Override
        public Iterator<T> iterator() {
            return store.iterator();
        }

        boolean isEmpty() {
            return store.isEmpty();
        }

        int size() {
            return store.size();
        }

        void clear() {
            store.clear();
        }
    }

    public DatasetGraphSimpleMem() {}

    @Override
    public boolean supportsTransactions() {
        return false;
    }

    private List<Quad> collectInDftGraph(Node s, Node p, Node o) {
        List<Quad> results = new ArrayList<>();
        for ( Triple t : triples )
            if ( matches(t, s, p, o) )
                // ?? Quad.defaultGraphNodeGenerated
                // Quad.defaultGraphIRI
                results.add(Quad.create(Quad.defaultGraphIRI, t));
        return results;
    }

    @Override
    public Iterator<Quad> findInDftGraph(Node s, Node p, Node o) {
        return collectInDftGraph(s, p, o).iterator();
    }

    @Override
    protected Stream<Quad> streamInDftGraph(Node s, Node p, Node o) {
        return collectInDftGraph(s, p, o).stream();
    }

    private List<Quad> collectInSpecificNamedGraph(Node g, Node s, Node p, Node o) {
        List<Quad> results = new ArrayList<>();
        for ( Quad q : quads )
            if ( matches(q, g, s, p, o) )
                results.add(q);
        return results;
    }

    @Override
    public Iterator<Quad> findInSpecificNamedGraph(Node g, Node s, Node p, Node o) {
        return collectInSpecificNamedGraph(g, s, p, o).iterator();
    }

    @Override
    protected Stream<Quad> streamInSpecificNamedGraph(Node g, Node s, Node p, Node o) {
        return collectInSpecificNamedGraph(g, s, p, o).stream();
    }

    private List<Quad> collectInAnyNamedGraphs(Node s, Node p, Node o) {
        List<Quad> results = new ArrayList<>();
        for ( Quad q : quads )
            if ( matches(q, Node.ANY, s, p, o) )
                results.add(q);
        return results;
    }

    @Override
    public Iterator<Quad> findInAnyNamedGraphs(Node s, Node p, Node o) {
        return collectInAnyNamedGraphs(s, p, o).iterator();
    }

    @Override
    protected Stream<Quad> streamInAnyNamedGraphs(Node s, Node p, Node o) {
        return collectInAnyNamedGraphs(s, p, o).stream();
    }

    private boolean matches(Triple t, Node s, Node p, Node o) {
        s = nullAsAny(s);
        p = nullAsAny(p);
        o = nullAsAny(o);
        return t.matches(s, p, o);
    }

    private boolean matches(Quad q, Node g, Node s, Node p, Node o) {
        g = nullAsAny(g);
        s = nullAsAny(s);
        p = nullAsAny(p);
        o = nullAsAny(o);
        return q.matches(g, s, p, o);
    }

    @Override
    protected void addToDftGraph(Node s, Node p, Node o) {
        Triple t = Triple.create(s, p, o);
        triples.add(t);
    }

    @Override
    protected void addToNamedGraph(Node g, Node s, Node p, Node o) {
        Quad q = Quad.create(g, s, p, o);
        quads.add(q);
    }

    @Override
    protected void deleteFromDftGraph(Node s, Node p, Node o) {
        triples.remove(Triple.create(s, p, o));
    }

    @Override
    protected void deleteFromNamedGraph(Node g, Node s, Node p, Node o) {
        quads.remove(Quad.create(g, s, p, o));
    }

    class GraphDft extends GraphBase {
        @Override
        public void performAdd(Triple t) {
            triples.add(t);
        }

        @Override
        public void performDelete(Triple t) {
            triples.remove(t);
        }

        @Override
        protected ExtendedIterator<Triple> graphBaseFind(Triple m) {
            List<Triple> results = new ArrayList<>();
            for ( Triple t : triples )
                if ( m.matches(t.getSubject(), t.getPredicate(), t.getObject()) )
                    results.add(t);
            return WrappedIterator.create(results.iterator());
        }
    }

    class GraphNamed extends GraphBase {
        private final Node graphName;

        GraphNamed(Node gname) {
            this.graphName = gname;
        }

        @Override
        public void performAdd(Triple t) {
            Quad q = Quad.create(graphName, t);
            quads.add(q);
        }

        @Override
        public void performDelete(Triple t) {
            Quad q = Quad.create(graphName, t);
            quads.remove(q);
        }

        @Override
        protected ExtendedIterator<Triple> graphBaseFind(Triple m) {
            List<Triple> results = new ArrayList<>();

            Iterator<Quad> iter = findNG(graphName, m.getSubject(), m.getPredicate(), m.getObject());
            for (; iter.hasNext(); )
                results.add(iter.next().asTriple());
            return WrappedIterator.create(results.iterator());
        }
    }

    @Override
    public Graph getDefaultGraph() {
        return new GraphDft();
    }

    @Override
    public Graph getGraph(Node graphNode) {
        return new GraphNamed(graphNode);
    }

    @Override
    public void clear() {
        triples.clear();
        quads.clear();
    }

    @Override
    public long size() {
        return graphNodes().size();
    }

    @Override
    public boolean containsGraph(Node graphNode) {
        return graphNodes().contains(graphNode);
    }

    @Override
    public Iterator<Node> listGraphNodes() {
        return graphNodes().iterator();
    }

    @Override
    public PrefixMap prefixes() {
        return prefixes;
    }

    private Set<Node> graphNodes() {
        Set<Node> x = new HashSet<>();
        for ( Quad q : quads )
            x.add(q.getGraph());
        return x;
    }

    @Override
    public void close() {}

    @Override
    public boolean supportsTransactionAbort() {
        return false;
    }
}
