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

import org.apache.tinkerpop.gremlin.language.corpus.DocumentationReader;
import org.apache.tinkerpop.gremlin.language.corpus.FeatureReader;
import org.javatuples.Pair;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.hamcrest.Matchers.is;
import static org.junit.Assume.assumeThat;

/**
 * Builds a corpus of Gremlin statements from Recipes, "The Traversal" in Reference Documentation and the Gherkin
 * test suite and passes them all through the grammar parser.
 */
@RunWith(Parameterized.class)
public class ReferenceGrammarTest extends AbstractGrammarTest {
    private static final String featureDir = Paths.get("..", "gremlin-test", "features").toString();
    private static final String docsDir = Paths.get("..", "docs", "src").toString();

    private static final Pattern edgePattern = Pattern.compile(".*e\\d.*");

    private static final List<Pair<Pattern, BiFunction<String,String,String>>> stringMatcherConverters = new ArrayList<Pair<Pattern, BiFunction<String,String,String>>>() {{
        add(Pair.with(Pattern.compile("m\\[(.*)\\]"), (k,v) -> {
            // can't handled embedded maps because of the string replace below on the curly braces
            final String[] items = v.replace("{", "").replace("}", "").split(",");
            final String listItems = Stream.of(items).map(String::trim).map(x -> {
                final String[] pair = x.split(":");
                final String convertedKey = String.format("%s", pair[0]);
                final String convertedValue = String.format("%s", pair[1]);
                return String.format("%s:%s", convertedKey, convertedValue);
            }).collect(Collectors.joining(","));
            return String.format("[%s]", listItems);
        }));
        add(Pair.with(Pattern.compile("l\\[\\]"), (k,v) -> "[]"));
        add(Pair.with(Pattern.compile("l\\[(.*)\\]"), (k,v) -> {
            final String[] items = v.split(",");
            final String listItems = Stream.of(items).map(String::trim).map(x -> String.format("\"%s\"", x)).collect(Collectors.joining(","));
            return String.format("[%s]", listItems);
        }));
        add(Pair.with(Pattern.compile("v\\[(.+)\\]"), (k,v) -> "\"1\""));
        add(Pair.with(Pattern.compile("v(\\d)"), (k,v) -> String.format("new Vertex(%s, \"vertex\")", v)));
        add(Pair.with(Pattern.compile("e\\[(.+)\\]"), (k,v) -> "\"1\""));
        add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.?.*"), (k,v) -> v));
        add(Pair.with(Pattern.compile("m\\[(.*)\\]"), (k,v) -> v.replace('{','[').replace('}', ']')));
        add(Pair.with(Pattern.compile("t\\[(.*)\\]"), (k,v) -> String.format("T.%s", v)));
        add(Pair.with(Pattern.compile("D\\[(.*)\\]"), (k,v) -> String.format("Direction.%s", v)));

        // the grammar doesn't support all the Gremlin we have in the gherkin set, so try to coerce it into
        // something that can be parsed so that we get maximum exercise over the parser itself.
        add(Pair.with(Pattern.compile("c\\[(.*)\\]"), (k,v) -> k.equals("c1") || k.equals("c2") ? "Order.desc" : "__.identity()"));  // closure -> Comparator || Traversal
        add(Pair.with(Pattern.compile("s\\[\\]"), (k,v) -> "[]"));  // set -> list
        add(Pair.with(Pattern.compile("s\\[(.*)\\]"), (k,v) -> "[]"));  // set -> list
        add(Pair.with(Pattern.compile("(null)"), (k,v) -> "null"));
        add(Pair.with(Pattern.compile("(true)"), (k,v) -> "true"));
        add(Pair.with(Pattern.compile("(false)"), (k,v) -> "false"));

    }};

    @Parameterized.Parameters(name = "{0}")
    public static Iterable<String> queries() throws IOException {
        final Set<String> gremlins = new LinkedHashSet<>(DocumentationReader.parse(docsDir));
        gremlins.addAll(FeatureReader.parseGrouped(featureDir, stringMatcherConverters).values().stream().flatMap(Collection::stream).collect(Collectors.toList()));
        return gremlins;
    }

    @Parameterized.Parameter
    public String query;

    @Test
    public void test_parse() {
        // can't handle maps with complex embedded types at the moment - basically one Gremlin statement at this point
        assumeThat("Complex embedded types are not supported", query.contains("l[\"666\"]"), is(false));
        assumeThat("Lambdas are not supported", query.contains("Lambda.function("), is(false));
        // start of a closure
        assumeThat("Lambdas are not supported", query.contains("{"), is(false));
        assumeThat("withComputer() step is not supported", query.startsWith("g.withComputer("), is(false));
        assumeThat("Edge instances are not supported", edgePattern.matcher(query).matches(), is(false));
        assumeThat("fill() terminator is not supported", query.contains("fill("), is(false));
        assumeThat("withoutStrategies() is not supported", query.contains("withoutStrategies("), is(false));
        assumeThat("program() is not supported", query.contains("program("), is(false));
        assumeThat("Casts are not supported", query.contains("(Map)"), is(false));

        parse(query);
    }
}
