/**
 * 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.rya.indexing.external;


import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.hadoop.conf.Configuration;
import org.apache.rya.indexing.external.tupleSet.ExternalTupleSet;
import org.apache.rya.indexing.external.tupleSet.SimpleExternalTupleSet;
import org.apache.rya.indexing.mongodb.pcj.MongoPcjIndexSetProvider;
import org.apache.rya.indexing.pcj.matching.PCJOptimizer;
import org.apache.rya.indexing.pcj.matching.provider.AbstractPcjIndexSetProvider;
import org.apache.rya.indexing.pcj.matching.provider.AccumuloIndexSetProvider;
import org.apache.rya.mongodb.EmbeddedMongoSingleton;
import org.apache.rya.mongodb.StatefulMongoDBRdfConfiguration;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.openrdf.query.algebra.Projection;
import org.openrdf.query.algebra.QueryModelNode;
import org.openrdf.query.algebra.StatementPattern;
import org.openrdf.query.algebra.TupleExpr;
import org.openrdf.query.algebra.helpers.QueryModelVisitorBase;
import org.openrdf.query.algebra.helpers.StatementPatternCollector;
import org.openrdf.query.parser.ParsedQuery;
import org.openrdf.query.parser.sparql.SPARQLParser;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

@RunWith(Parameterized.class)
public class PrecompJoinOptimizerTest {
    private final AbstractPcjIndexSetProvider provider;

    @Parameterized.Parameters
    public static Collection providers() throws Exception {
        final StatefulMongoDBRdfConfiguration conf = new StatefulMongoDBRdfConfiguration(new Configuration(), EmbeddedMongoSingleton.getNewMongoClient());
        return Lists.<AbstractPcjIndexSetProvider> newArrayList(
                new AccumuloIndexSetProvider(new Configuration()),
                new MongoPcjIndexSetProvider(conf)
                );
    }

    public PrecompJoinOptimizerTest(final AbstractPcjIndexSetProvider provider) {
        this.provider = provider;
    }
    @Test
    public void testThreeIndex() throws Exception {

        final String q7 = ""//
                + "SELECT ?s ?t ?u " //
                + "{" //
                + "  ?s a ?t ."//
                + "  ?t <http://www.w3.org/2000/01/rdf-schema#label> ?u ."//
                + "  ?u <uri:talksTo> ?s . "//
                + "}";//

        final String q8 = ""//
                + "SELECT ?e ?l ?c " //
                + "{" //
                + "  ?e a ?l ."//
                + "  ?l <http://www.w3.org/2000/01/rdf-schema#label> ?c ."//
                + "  ?c <uri:talksTo> ?e . "//
                + "}";//

        final String q9 = ""//
                + "SELECT ?f ?m ?d " //
                + "{" //
                + "  ?f a ?m ."//
                + "  ?m <http://www.w3.org/2000/01/rdf-schema#label> ?d ."//
                + "  ?d <uri:talksTo> ?f . "//
                + "}";//

        final String q15 = ""//
                + "SELECT ?f ?m ?d ?e ?l ?c " //
                + "{" //
                + "  ?f a ?m ."//
                + "  ?e a ?l ."//
                + "  ?d <uri:talksTo> ?f . "//
                + "  ?c <uri:talksTo> ?e . "//
                + "  ?m <http://www.w3.org/2000/01/rdf-schema#label> ?d ."//
                + "  ?l <http://www.w3.org/2000/01/rdf-schema#label> ?c ."//
                + "}";//

        final SPARQLParser parser = new SPARQLParser();

        final ParsedQuery pq1 = parser.parseQuery(q15, null);
        final ParsedQuery pq2 = parser.parseQuery(q7, null);
        final ParsedQuery pq3 = parser.parseQuery(q8, null);
        final ParsedQuery pq4 = parser.parseQuery(q9, null);

        final SimpleExternalTupleSet extTup1 = new SimpleExternalTupleSet(
                (Projection) pq2.getTupleExpr());
        final SimpleExternalTupleSet extTup2 = new SimpleExternalTupleSet(
                (Projection) pq3.getTupleExpr());
        final SimpleExternalTupleSet extTup3 = new SimpleExternalTupleSet(
                (Projection) pq4.getTupleExpr());

        final List<ExternalTupleSet> list = new ArrayList<ExternalTupleSet>();

        list.add(extTup1);

        final List<QueryModelNode> optTupNodes = Lists.newArrayList();
        optTupNodes.add(extTup2);
        optTupNodes.add(extTup3);

        provider.setIndices(list);
        final PCJOptimizer pcj = new PCJOptimizer(list, true, provider);
        final TupleExpr te = pq1.getTupleExpr();
        pcj.optimize(te, null, null);

        final NodeCollector nc = new NodeCollector();
        te.visit(nc);

        Assert.assertEquals(nc.qNodes.size(), optTupNodes.size());
        for (final QueryModelNode node : nc.qNodes) {
            Assert.assertTrue(optTupNodes.contains(node));
        }

    }

    @Test
    public void testThreeIndex2() throws Exception {

        final String q1 = ""//
                + "SELECT ?f ?m ?d ?e ?l ?c " //
                + "{" //
                + "  ?f a ?m ."//
                + "  ?c a ?l ."//
                + "  ?d <uri:talksTo> ?f . "//
                + "  ?e <uri:talksTo> ?c . "//
                + "  ?m <http://www.w3.org/2000/01/rdf-schema#label> ?d ."//
                + "  ?l <http://www.w3.org/2000/01/rdf-schema#label> ?e ."//
                + "  ?m <uri:talksTo> ?e . "//
                + "}";//

        final String q2 = ""//
                + "SELECT ?u ?s ?t " //
                + "{" //
                + "  ?s a ?t ."//
                + "  ?t <http://www.w3.org/2000/01/rdf-schema#label> ?u ."//
                + "  ?u <uri:talksTo> ?s . "//
                + "}";//

        final String q3 = ""//
                + "SELECT ?e ?c ?l " //
                + "{" //
                + "  ?c a ?l ."//
                + "  ?l <http://www.w3.org/2000/01/rdf-schema#label> ?e ."//
                + "  ?e <uri:talksTo> ?c . "//
                + "}";//

        final String q4 = ""//
                + "SELECT ?d ?f ?m " //
                + "{" //
                + "  ?f a ?m ."//
                + "  ?m <http://www.w3.org/2000/01/rdf-schema#label> ?d ."//
                + "  ?d <uri:talksTo> ?f . "//
                + "}";//

        final SPARQLParser parser = new SPARQLParser();

        final ParsedQuery pq1 = parser.parseQuery(q1, null);
        final ParsedQuery pq2 = parser.parseQuery(q2, null);
        final ParsedQuery pq3 = parser.parseQuery(q3, null);
        final ParsedQuery pq4 = parser.parseQuery(q4, null);

        final SimpleExternalTupleSet extTup1 = new SimpleExternalTupleSet(
                (Projection) pq2.getTupleExpr());
        final SimpleExternalTupleSet extTup2 = new SimpleExternalTupleSet(
                (Projection) pq3.getTupleExpr());
        final SimpleExternalTupleSet extTup3 = new SimpleExternalTupleSet(
                (Projection) pq4.getTupleExpr());

        final List<ExternalTupleSet> list = new ArrayList<ExternalTupleSet>();

        list.add(extTup1);

        final List<StatementPattern> spList = StatementPatternCollector
                .process(pq1.getTupleExpr());
        final List<QueryModelNode> optTupNodes = Lists.newArrayList();
        optTupNodes.add(extTup3);
        optTupNodes.add(spList.get(6));
        optTupNodes.add(extTup2);

        provider.setIndices(list);
        final PCJOptimizer pcj = new PCJOptimizer(list, true, provider);
        final TupleExpr te = pq1.getTupleExpr();
        pcj.optimize(te, null, null);

        final NodeCollector nc = new NodeCollector();
        te.visit(nc);

        Assert.assertEquals(nc.qNodes, Sets.newHashSet(optTupNodes));

    }

    @Test
    public void testSixIndex() throws Exception {

        final String q1 = ""//
                + "SELECT ?f ?m ?d ?h ?i " //
                + "{" //
                + "  ?f a ?m ."//
                + "  ?m <http://www.w3.org/2000/01/rdf-schema#label> ?d ."//
                + "  ?d <uri:talksTo> ?f . "//
                + "  ?d <uri:hangOutWith> ?f ." //
                + "  ?f <uri:hangOutWith> ?h ." //
                + "  ?f <uri:associatesWith> ?i ." //
                + "  ?i <uri:associatesWith> ?h ." //
                + "}";//

        final String q2 = ""//
                + "SELECT ?t ?s ?u " //
                + "{" //
                + "  ?s a ?t ."//
                + "  ?t <http://www.w3.org/2000/01/rdf-schema#label> ?u ."//
                + "  ?u <uri:talksTo> ?s . "//
                + "}";//

        final String q3 = ""//
                + "SELECT ?s ?t ?u " //
                + "{" //
                + "  ?s <uri:hangOutWith> ?t ." //
                + "  ?t <uri:hangOutWith> ?u ." //
                + "}";//

        final String q4 = ""//
                + "SELECT ?s ?t ?u " //
                + "{" //
                + "  ?s <uri:associatesWith> ?t ." //
                + "  ?t <uri:associatesWith> ?u ." //
                + "}";//

        final String q5 = ""//
                + "SELECT ?m ?f ?d " //
                + "{" //
                + "  ?f a ?m ."//
                + "  ?m <http://www.w3.org/2000/01/rdf-schema#label> ?d ."//
                + "  ?d <uri:talksTo> ?f . "//
                + "}";//

        final String q6 = ""//
                + "SELECT ?d ?f ?h " //
                + "{" //
                + "  ?d <uri:hangOutWith> ?f ." //
                + "  ?f <uri:hangOutWith> ?h ." //
                + "}";//

        final String q7 = ""//
                + "SELECT ?f ?i ?h " //
                + "{" //
                + "  ?f <uri:associatesWith> ?i ." //
                + "  ?i <uri:associatesWith> ?h ." //
                + "}";//

        final SPARQLParser parser = new SPARQLParser();

        final ParsedQuery pq1 = parser.parseQuery(q1, null);
        final ParsedQuery pq2 = parser.parseQuery(q2, null);
        final ParsedQuery pq3 = parser.parseQuery(q3, null);
        final ParsedQuery pq4 = parser.parseQuery(q4, null);
        final ParsedQuery pq5 = parser.parseQuery(q5, null);
        final ParsedQuery pq6 = parser.parseQuery(q6, null);
        final ParsedQuery pq7 = parser.parseQuery(q7, null);

        final SimpleExternalTupleSet extTup1 = new SimpleExternalTupleSet(
                (Projection) pq2.getTupleExpr());
        final SimpleExternalTupleSet extTup2 = new SimpleExternalTupleSet(
                (Projection) pq3.getTupleExpr());
        final SimpleExternalTupleSet extTup3 = new SimpleExternalTupleSet(
                (Projection) pq4.getTupleExpr());
        final SimpleExternalTupleSet extTup4 = new SimpleExternalTupleSet(
                (Projection) pq5.getTupleExpr());
        final SimpleExternalTupleSet extTup5 = new SimpleExternalTupleSet(
                (Projection) pq6.getTupleExpr());
        final SimpleExternalTupleSet extTup6 = new SimpleExternalTupleSet(
                (Projection) pq7.getTupleExpr());

        final List<ExternalTupleSet> list = new ArrayList<ExternalTupleSet>();

        list.add(extTup2);
        list.add(extTup1);
        list.add(extTup3);

        final List<QueryModelNode> optTupNodes = Lists.newArrayList();
        optTupNodes.add(extTup4);
        optTupNodes.add(extTup6);
        optTupNodes.add(extTup5);

        provider.setIndices(list);
        final PCJOptimizer pcj = new PCJOptimizer(list, true, provider);
        final TupleExpr te = pq1.getTupleExpr();
        pcj.optimize(te, null, null);

        System.out.println(te);

        final NodeCollector nc = new NodeCollector();
        te.visit(nc);

        Assert.assertEquals(nc.qNodes, Sets.newHashSet(optTupNodes));

    }

    @Test
    public void twoFourIndexWithFilterTest() throws Exception {

        final String q1 = ""//
                + "SELECT ?f ?m ?d ?e ?l ?c " //
                + "{" //
                + "  Filter(?f > \"5\")." //
                + "  Filter(?e > \"5\")." //
                + "  ?f a ?m ."//
                + "  ?e a ?l ."//
                + "  ?d <uri:talksTo> ?f . "//
                + "  ?c <uri:talksTo> ?e . "//
                + "  ?m <http://www.w3.org/2000/01/rdf-schema#label> ?d ."//
                + "  ?l <http://www.w3.org/2000/01/rdf-schema#label> ?c ."//
                + "}";//

        final String q2 = ""//
                + "SELECT ?s ?t ?u " //
                + "{" //
                + "  ?s a ?t ."//
                + "  ?t <http://www.w3.org/2000/01/rdf-schema#label> ?u ."//
                + "  ?u <uri:talksTo> ?s . "//
                + "}";//

        final String q3 = ""//
                + "SELECT ?s ?t ?u " //
                + "{" //
                + " Filter(?s > \"5\") ."//
                + "  ?s a ?t ."//
                + "  ?t <http://www.w3.org/2000/01/rdf-schema#label> ?u ."//
                + "  ?u <uri:talksTo> ?s . "//
                + "}";//

        final String q4 = ""//
                + "SELECT ?f ?m ?d " //
                + "{" //
                + " Filter(?f > \"5\") ."//
                + "  ?f a ?m ."//
                + "  ?m <http://www.w3.org/2000/01/rdf-schema#label> ?d ."//
                + "  ?d <uri:talksTo> ?f . "//
                + "}";//

        final String q5 = ""//
                + "SELECT ?e ?l ?c " //
                + "{" //
                + " Filter(?e > \"5\") ."//
                + "  ?e a ?l ."//
                + "  ?l <http://www.w3.org/2000/01/rdf-schema#label> ?c ."//
                + "  ?c <uri:talksTo> ?e . "//
                + "}";//

        final SPARQLParser parser = new SPARQLParser();

        ParsedQuery pq1 = null;
        ParsedQuery pq2 = null;
        ParsedQuery pq3 = null;
        ParsedQuery pq4 = null;
        ParsedQuery pq5 = null;

        try {
            pq1 = parser.parseQuery(q1, null);
            pq2 = parser.parseQuery(q2, null);
            pq3 = parser.parseQuery(q3, null);
            pq4 = parser.parseQuery(q4, null);
            pq5 = parser.parseQuery(q5, null);

        } catch (final Exception e) {
            e.printStackTrace();
        }

        final SimpleExternalTupleSet extTup1 = new SimpleExternalTupleSet(
                (Projection) pq2.getTupleExpr());
        final SimpleExternalTupleSet extTup2 = new SimpleExternalTupleSet(
                (Projection) pq3.getTupleExpr());
        final SimpleExternalTupleSet extTup3 = new SimpleExternalTupleSet(
                (Projection) pq4.getTupleExpr());
        final SimpleExternalTupleSet extTup4 = new SimpleExternalTupleSet(
                (Projection) pq5.getTupleExpr());

        final List<ExternalTupleSet> list = new ArrayList<ExternalTupleSet>();

        list.add(extTup2);
        list.add(extTup1);

        final List<ExternalTupleSet> list2 = new ArrayList<ExternalTupleSet>();

        list2.add(extTup3);
        list2.add(extTup4);

        provider.setIndices(list);
        final PCJOptimizer pcj = new PCJOptimizer(list, true, provider);
        final TupleExpr te = pq1.getTupleExpr();
        pcj.optimize(te, null, null);

        System.out.println(te);

        final NodeCollector nc = new NodeCollector();
        te.visit(nc);

        Assert.assertEquals(nc.qNodes.size(), list2.size());

        for (final QueryModelNode e : nc.qNodes) {
            Assert.assertTrue(list2.contains(e));
        }

    }

    public static class NodeCollector extends
    QueryModelVisitorBase<RuntimeException> {

        Set<QueryModelNode> qNodes = new HashSet<>();

        @Override
        public void meetNode(final QueryModelNode node) {
            if (node instanceof StatementPattern
                    || node instanceof ExternalTupleSet) {
                qNodes.add(node);
            }
            super.meetNode(node);

        }

        public List<QueryModelNode> getNodes() {
            return Lists.newArrayList(qNodes);
        }

    }

}
