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

import org.apache.commons.configuration2.MapConfiguration;
import org.apache.tinkerpop.gremlin.jsr223.TranslatorCustomizer;
import org.apache.tinkerpop.gremlin.process.traversal.Compare;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.Script;
import org.apache.tinkerpop.gremlin.process.traversal.Translator;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.TranslationStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.util.AndP;
import org.apache.tinkerpop.gremlin.structure.Column;
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge;
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex;
import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory;
import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
import org.apache.tinkerpop.gremlin.util.function.Lambda;
import org.junit.Test;

import javax.script.Bindings;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.UUID;
import java.util.function.Function;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
 *  test {@link GroovyTranslator} which return parameterized result, covers:
 *   - parameterized script checking
 *   - binding checking
 *   - eval result checking
 *
 *  <p>
 *  {@link GroovyTranslatorTest } is used to test {@link GroovyTranslator}, both test cases looks the same
 *  <p>
 *
 * @author Marko A. Rodriguez (http://markorodriguez.com)
 * @author Stephen Mallette (http://stephen.genoprime.com)
 * @author Stark Arya (sandszhou.zj@alibaba-inc.com)
 */
public class ParameterizedGroovyTranslatorTest {

    private Graph graph = TinkerGraph.open();
    private GraphTraversalSource g = graph.traversal();
    private static final GremlinGroovyScriptEngine parameterizedEngine = new GremlinGroovyScriptEngine(new TranslatorCustomizer() {
        @Override
        public Translator.ScriptTranslator.TypeTranslator createTypeTranslator() {
            return new GroovyTranslator.DefaultTypeTranslator(true);
        }
    });

    @Test
    public void shouldHandleStrategies() throws Exception {
        final TinkerGraph graph = TinkerFactory.createModern();
        final GraphTraversalSource g = graph.traversal().withStrategies(SubgraphStrategy.create(new MapConfiguration(new HashMap<String, Object>() {{
            put(SubgraphStrategy.VERTICES, __.has("name", "marko"));
        }})));
        final Bindings bindings = new SimpleBindings();
        bindings.put("g", g);
        Traversal.Admin<Vertex, Object> traversal = parameterizedEngine.eval(g.V().values("name").asAdmin().getBytecode(), bindings, "g");
        assertEquals("marko", traversal.next());
        assertFalse(traversal.hasNext());
        //
        traversal = parameterizedEngine.eval(g.withoutStrategies(SubgraphStrategy.class).V().count().asAdmin().getBytecode(), bindings, "g");
        assertEquals(new Long(6), traversal.next());
        assertFalse(traversal.hasNext());
        //
        traversal = parameterizedEngine.eval(g.withStrategies(SubgraphStrategy.create(new MapConfiguration(new HashMap<String, Object>() {{
            put(SubgraphStrategy.VERTICES, __.has("name", "marko"));
        }})), ReadOnlyStrategy.instance()).V().values("name").asAdmin().getBytecode(), bindings, "g");
        assertEquals("marko", traversal.next());
        assertFalse(traversal.hasNext());
    }

    @Test
    public void shouldSupportStringSupplierLambdas() {
        final TinkerGraph graph = TinkerFactory.createModern();
        GraphTraversalSource g = graph.traversal();
        g = g.withStrategies(new TranslationStrategy(g, GroovyTranslator.of("g", true), false));
        final GraphTraversal.Admin<Vertex, Integer> t = g.withSideEffect("lengthSum", 0).withSack(1)
                .V()
                .filter(Lambda.predicate("it.get().label().equals('person')"))
                .flatMap(Lambda.function("it.get().vertices(Direction.OUT)"))
                .map(Lambda.<Traverser<Object>, Integer>function("it.get().value('name').length()"))
                .sideEffect(Lambda.consumer("{ x -> x.sideEffects(\"lengthSum\", x.<Integer>sideEffects('lengthSum') + x.get()) }"))
                .order().by(Lambda.comparator("a,b -> a <=> b"))
                .sack(Lambda.biFunction("{ a,b -> a + b }"))
                .asAdmin();

        final List<Integer> sacks = new ArrayList<>();
        final List<Integer> lengths = new ArrayList<>();
        while (t.hasNext()) {
            final Traverser.Admin<Integer> traverser = t.nextTraverser();
            sacks.add(traverser.sack());
            lengths.add(traverser.get());
        }
        assertFalse(t.hasNext());
        //
        assertEquals(6, lengths.size());
        assertEquals(3, lengths.get(0).intValue());
        assertEquals(3, lengths.get(1).intValue());
        assertEquals(3, lengths.get(2).intValue());
        assertEquals(4, lengths.get(3).intValue());
        assertEquals(5, lengths.get(4).intValue());
        assertEquals(6, lengths.get(5).intValue());
        ///
        assertEquals(6, sacks.size());
        assertEquals(4, sacks.get(0).intValue());
        assertEquals(4, sacks.get(1).intValue());
        assertEquals(4, sacks.get(2).intValue());
        assertEquals(5, sacks.get(3).intValue());
        assertEquals(6, sacks.get(4).intValue());
        assertEquals(7, sacks.get(5).intValue());
        //
        assertEquals(24, t.getSideEffects().<Number>get("lengthSum").intValue());

        final Script script = GroovyTranslator.of("g", true).translate(t.getBytecode());
        final Bindings bindings = new SimpleBindings();
        script.getParameters().ifPresent(bindings::putAll);
        assertEquals(9, bindings.size());
        assertEquals("lengthSum", bindings.get("_args_0"));
        assertEquals(Integer.valueOf(0), bindings.get("_args_1"));
        assertEquals(Integer.valueOf(1), bindings.get("_args_2"));
        assertEquals(Lambda.predicate("it.get().label().equals('person')"), bindings.get("_args_3"));
        assertEquals(Lambda.function("it.get().vertices(Direction.OUT)"), bindings.get("_args_4"));
        assertEquals(Lambda.<Traverser<Object>, Integer>function("it.get().value('name').length()"), bindings.get("_args_5"));
        assertEquals(Lambda.consumer("{ x -> x.sideEffects(\"lengthSum\", x.<Integer>sideEffects('lengthSum') + x.get()) }"), bindings.get("_args_6"));
        assertEquals(Lambda.comparator("a,b -> a <=> b"), bindings.get("_args_7"));
        assertEquals(Lambda.biFunction("{ a,b -> a + b }"), bindings.get("_args_8"));
        assertEquals("g.withStrategies(org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.TranslationStrategy.instance())" +
                        ".withSideEffect(_args_0,_args_1).withSack(_args_2)" +
                        ".V()" +
                        ".filter(_args_3)" +
                        ".flatMap(_args_4)" +
                        ".map(_args_5)" +
                        ".sideEffect(_args_6)" +
                        ".order().by(_args_7)" +
                        ".sack(_args_8)",
                script.getScript());
    }

    @Test
    public void shouldHandleArray() {
        final TinkerGraph graph = TinkerFactory.createModern();
        final GraphTraversalSource g = graph.traversal();
        final Script script = GroovyTranslator.of("g", true).translate(g.V().has(T.id, P.within(new ArrayList() {{
            add(1);
            add(2);
            add(3);
            add(4);
            add(5);
        }})).asAdmin().getBytecode());
        final Bindings bindings = new SimpleBindings();
        script.getParameters().ifPresent(bindings::putAll);
        assertEquals(5, bindings.size());
        assertEquals(Integer.valueOf(1), bindings.get("_args_0"));
        assertEquals(Integer.valueOf(2), bindings.get("_args_1"));
        assertEquals(Integer.valueOf(3), bindings.get("_args_2"));
        assertEquals(Integer.valueOf(4), bindings.get("_args_3"));
        assertEquals(Integer.valueOf(5), bindings.get("_args_4"));
        assertEquals("g.V().has(T.id,P.within([_args_0, _args_1, _args_2, _args_3, _args_4]))", script.getScript());

        final Script standard = GroovyTranslator.of("g").translate(g.V().has(T.id, P.within(new ArrayList() {{
            add(1);
            add(2);
            add(3);
            add(4);
            add(5);
        }})).asAdmin().getBytecode());

        bindings.put("g", g);
        assertParameterizedScriptOk(standard.getScript(), script.getScript(), bindings, true);
    }

    @Test
    public void shouldHandleSet() {
        final TinkerGraph graph = TinkerFactory.createModern();
        final GraphTraversalSource g = graph.traversal();
        final Script script = GroovyTranslator.of("g", true).translate(g.V().id().is(new HashSet<Object>() {{
            add(3);
            add(Arrays.asList(1, 2, 3.1d));
            add(3);
            add("3");
        }}).asAdmin().getBytecode());
        final Bindings bindings = new SimpleBindings();
        script.getParameters().ifPresent(bindings::putAll);
        assertEquals(5, bindings.size());
        assertEquals(Integer.valueOf(3), bindings.get("_args_0"));
        assertEquals("3", bindings.get("_args_1"));
        assertEquals(Integer.valueOf(1), bindings.get("_args_2"));
        assertEquals(Integer.valueOf(2), bindings.get("_args_3"));
        assertEquals(Double.valueOf(3.1), bindings.get("_args_4"));
        assertEquals("g.V().id().is([_args_0, _args_1, [_args_2, _args_3, _args_4]] as Set)", script.getScript());

        final Script standard = GroovyTranslator.of("g" ).translate(g.V().id().is(new HashSet<Object>() {{
            add(3);
            add(Arrays.asList(1, 2, 3.1d));
            add(3);
            add("3");
        }}).asAdmin().getBytecode());
        bindings.put("g", g);
        assertParameterizedScriptOk(standard.getScript(), script.getScript(), bindings, true);
    }

    @Test
    public void shouldHandleMaps() {
        final TinkerGraph graph = TinkerFactory.createModern();
        final GraphTraversalSource g = graph.traversal();
        final Script script = GroovyTranslator.of("g", true).translate(g.V().id().is(new LinkedHashMap<Object,Object>() {{
            put(3, "32");
            put(Arrays.asList(1, 2, 3.1d), 4);
        }}).asAdmin().getBytecode());
        final Bindings bindings = new SimpleBindings();
        script.getParameters().ifPresent(bindings::putAll);
        assertEquals(6, bindings.size());
        assertEquals(Integer.valueOf(3), bindings.get("_args_0"));
        assertEquals("32", bindings.get("_args_1"));
        assertEquals(Integer.valueOf(1), bindings.get("_args_2"));
        assertEquals(Integer.valueOf(2), bindings.get("_args_3"));
        assertEquals(Double.valueOf(3.1), bindings.get("_args_4"));
        assertEquals(Integer.valueOf(4), bindings.get("_args_5"));
        assertEquals("g.V().id().is([(_args_0):(_args_1),([_args_2, _args_3, _args_4]):(_args_5)])", script.getScript());
        final Script standard = GroovyTranslator.of("g").translate(g.V().id().is(new LinkedHashMap<Object,Object>() {{
            put(3, "32");
            put(Arrays.asList(1, 2, 3.1d), 4);
        }}).asAdmin().getBytecode());
        bindings.put("g", g);
        assertParameterizedScriptOk(standard.getScript(), script.getScript(), bindings, true);
    }

    @Test
    public void shouldHandleEmptyMaps() {
        final TinkerGraph graph = TinkerFactory.createModern();
        final GraphTraversalSource g = graph.traversal();
        final Function identity = new Lambda.OneArgLambda("it.get()", "gremlin-groovy");
        final Script script = GroovyTranslator.of("g", true).translate(g.inject(Collections.emptyMap()).map(identity).asAdmin().getBytecode());
        final Bindings bindings = new SimpleBindings();
        script.getParameters().ifPresent(bindings::putAll);
        assertEquals(1, bindings.size());
        assertEquals(identity, bindings.get("_args_0"));
        assertEquals("g.inject([]).map(_args_0)", script.getScript());
        bindings.put("g", g);
        assertThatScriptOk(script.getScript(), bindings);
    }

    @Test
    public void shouldHandleDate() {
        final Calendar c = Calendar.getInstance();
        c.set(1975, Calendar.SEPTEMBER, 7);
        final Date d = c.getTime();
        assertParameterizedTranslation(String.format("new java.util.Date(%s)", d.getTime()), d);
    }

    @Test
    public void shouldHandlePredicate() {
        final P p = new P(Compare.eq, 10);
        assertParameterizedTranslation(String.format("new java.util.Date(%s)", p.toString()), 10);
    }

    @Test
    public void shouldHandleTimestamp() {
        final Calendar c = Calendar.getInstance();
        c.set(1975, Calendar.SEPTEMBER, 7);
        final Timestamp t = new Timestamp(c.getTime().getTime());
        assertParameterizedTranslation(String.format("new java.sql.Timestamp(%s)", t.getTime()), t);
    }

    @Test
    public void shouldHandleUuid() {
        final UUID uuid = UUID.fromString("ffffffff-fd49-1e4b-0000-00000d4b8a1d");
        assertParameterizedTranslation(String.format("java.util.UUID.fromString('%s')", uuid), uuid);
    }

    @Test
    public void shouldHandleClass() {
        final Class cls = Vertex.class;
        assertParameterizedTranslation(String.format("%s", cls), cls.getCanonicalName());
    }

    @Test
    public void shouldHandleBarrier() {
        final SackFunctions.Barrier barrier = SackFunctions.Barrier.normSack;
        assertNonParameterizedTranslation(String.format("SackFunctions.Barrier.%s", barrier), barrier);
    }

    @Test
    public void shouldHandleCardinality() {
        final VertexProperty.Cardinality cardinality = VertexProperty.Cardinality.set;
        assertNonParameterizedTranslation(String.format("VertexProperty.Cardinality.%s", cardinality), cardinality);
    }

    @Test
    public void shouldHandlePick() {
        final TraversalOptionParent.Pick pick = TraversalOptionParent.Pick.any;
        assertNonParameterizedTranslation(String.format("TraversalOptionParent.Pick.%s", pick), pick);
    }

    @Test
    public void shouldHandleColumn() {
        assertNonParameterizedTranslation("Column.keys", Column.keys);
    }

    @Test
    public void shouldHandleDirection() {
        assertNonParameterizedTranslation("Direction.BOTH", Direction.BOTH);
    }

    @Test
    public void shouldHandleOrder() {
        assertNonParameterizedTranslation("Order.shuffle", Order.shuffle);
    }

    @Test
    public void shouldHandlePop() {
        assertNonParameterizedTranslation("Pop.last", Pop.last);
    }

    @Test
    public void shouldHandleScope() {
        assertNonParameterizedTranslation("Scope.local", Scope.local);
    }

    @Test
    public void shouldIncludeCustomTypeTranslationForSomethingSilly() throws Exception {
        final TinkerGraph graph = TinkerGraph.open();
        final ParameterizedSillyClass notSillyEnough = ParameterizedSillyClass.from("not silly enough", 100);
        final GraphTraversalSource g = graph.traversal();

        // without type translation we get uglinesss
        final Script parameterizedScriptBad = GroovyTranslator.of("g", true).
                translate(g.inject(notSillyEnough).asAdmin().getBytecode());
        Bindings bindings = new SimpleBindings();
        parameterizedScriptBad.getParameters().ifPresent(bindings::putAll);
        assertEquals(String.format("g.inject(%s)", "_args_0"), parameterizedScriptBad.getScript());
        assertEquals(1, bindings.size());
        assertEquals(notSillyEnough, bindings.get("_args_0"));
        bindings.clear();

        // with type translation we get valid gremlin
        final Script parameterizedScriptGood = GroovyTranslator.of("g", new ParameterizedSillyClassTranslatorCustomizer().createTypeTranslator()).
                translate(g.inject(notSillyEnough).asAdmin().getBytecode());
        parameterizedScriptGood.getParameters().ifPresent(bindings::putAll);
        assertEquals(2, bindings.size());
        assertEquals(notSillyEnough.getX(), bindings.get("_args_0"));
        assertEquals(notSillyEnough.getY(), bindings.get("_args_1"));
        assertEquals("g.inject(org.apache.tinkerpop.gremlin.groovy.jsr223.ParameterizedGroovyTranslatorTest.ParameterizedSillyClass.from(_args_0,_args_1))", parameterizedScriptGood.getScript());
        bindings.put("g", g);
        assertThatScriptOk(parameterizedScriptGood.getScript(), bindings);

        final GremlinGroovyScriptEngine customEngine = new GremlinGroovyScriptEngine(new ParameterizedSillyClassTranslatorCustomizer());
        final Traversal t = customEngine.eval(g.inject(notSillyEnough).asAdmin().getBytecode(), bindings, "g");
        final ParameterizedSillyClass sc = (ParameterizedSillyClass) t.next();
        assertEquals(notSillyEnough.getX(), sc.getX());
        assertEquals(notSillyEnough.getY(), sc.getY());
        assertThat(t.hasNext(), is(false));
    }

    @Test
    public void shouldHaveValidToString() {
        assertEquals("translator[h:gremlin-groovy]", GroovyTranslator.of("h", true).toString());
    }

    @Test
    public void shouldEscapeStrings() {
        final TinkerGraph graph = TinkerFactory.createModern();
        final GraphTraversalSource g = graph.traversal();
        final Script script = GroovyTranslator.of("g", true).translate(g.addV("customer")
                .property("customer_id", 501L)
                .property("name", "Foo\u0020Bar")
                .property("age", 25)
                .property("special", "`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")
                .asAdmin().getBytecode());
        final Bindings bindings = new SimpleBindings();
        script.getParameters().ifPresent(bindings::putAll);
        assertEquals(9, bindings.size());
        assertEquals("customer", bindings.get("_args_0"));
        assertEquals("customer_id", bindings.get("_args_1"));
        assertEquals(Long.valueOf(501), bindings.get("_args_2"));
        assertEquals("name", bindings.get("_args_3"));
        assertEquals("Foo\u0020Bar", bindings.get("_args_4"));
        assertEquals("age", bindings.get("_args_5"));
        assertEquals(Integer.valueOf(25), bindings.get("_args_6"));
        assertEquals("special", bindings.get("_args_7"));
        assertEquals("`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", bindings.get("_args_8"));
        assertEquals("g.addV(_args_0).property(_args_1,_args_2).property(_args_3,_args_4).property(_args_5,_args_6).property(_args_7,_args_8)", script.getScript());

        final Script standard = GroovyTranslator.of("g").translate(g.addV("customer")
                .property("customer_id", 501L)
                .property("name", "Foo\u0020Bar")
                .property("age", 25)
                .property("special", "`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")
                .asAdmin().getBytecode());

        bindings.put("g", g);
        //add vertex will return different vertex id
        assertParameterizedScriptOk(standard.getScript(), script.getScript(), bindings, false);
    }

    @Test
    public void shouldHandleVertexAndEdge() {
        final TinkerGraph graph = TinkerFactory.createModern();
        final GraphTraversalSource g = graph.traversal();

        final Object id1 = "customer:10:foo\u0020bar\u0020\u0024100#90"; // customer:10:foo bar $100#90
        final Vertex vertex1 = DetachedVertex.build().setLabel("customer").setId(id1)
                .create();
        final Script script1 = GroovyTranslator.of("g", true).translate(g.inject(vertex1).asAdmin().getBytecode());
        final Bindings bindings = new SimpleBindings();
        script1.getParameters().ifPresent(bindings::putAll);
        assertEquals(2, bindings.size());
        assertEquals(id1, bindings.get("_args_0"));
        assertEquals("customer", bindings.get("_args_1"));
        assertEquals("g.inject(new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex(_args_0,_args_1, Collections.emptyMap()))", script1.getScript());
        final Script standard1 = GroovyTranslator.of("g").translate(g.inject(vertex1).asAdmin().getBytecode());
        bindings.put("g", g);
        // TinkerGraph not support string id
        assertParameterizedScriptOk(standard1.getScript(), script1.getScript(), bindings, false);
        bindings.clear();

        final Object id2 = "user:20:foo\\u0020bar\\u005c\\u0022mr\\u005c\\u0022\\u00241000#50"; // user:20:foo\u0020bar\u005c\u0022mr\u005c\u0022\u00241000#50
        final Vertex vertex2 = DetachedVertex.build().setLabel("user").setId(id2)
                .create();
        final Script script2 = GroovyTranslator.of("g", true).translate(g.inject(vertex2).asAdmin().getBytecode());
        script2.getParameters().ifPresent(bindings::putAll);
        assertEquals(2, bindings.size());
        assertEquals(id2, bindings.get("_args_0"));
        assertEquals("user", bindings.get("_args_1"));
        assertEquals("g.inject(new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex(_args_0,_args_1, Collections.emptyMap()))", script2.getScript());
        final Script standard2 = GroovyTranslator.of("g").translate(g.inject(vertex2).asAdmin().getBytecode());
        bindings.put("g", g);
        assertParameterizedScriptOk(standard2.getScript(), script2.getScript(), bindings, false);
        bindings.clear();

        final Object id3 = "knows:30:foo\u0020bar\u0020\u0024100:\\u0020\\u0024500#70";
        final Edge edge = DetachedEdge.build().setLabel("knows").setId(id3)
                .setOutV((DetachedVertex) vertex1)
                .setInV((DetachedVertex) vertex2)
                .create();
        final Script script3 = GroovyTranslator.of("g", true).translate(g.inject(edge).asAdmin().getBytecode());
        script3.getParameters().ifPresent(bindings::putAll);
        assertEquals(6, bindings.size());
        assertEquals(id3, bindings.get("_args_0"));
        assertEquals("knows", bindings.get("_args_1"));
        assertEquals(id1, bindings.get("_args_2"));
        assertEquals("customer", bindings.get("_args_3"));
        assertEquals(id2, bindings.get("_args_4"));
        assertEquals("user", bindings.get("_args_5"));
        assertEquals("g.inject(new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge(_args_0,_args_1,Collections.emptyMap(),_args_2,_args_3,_args_4,_args_5))", script3.getScript());
        final Script standard3 = GroovyTranslator.of("g").translate(g.inject(edge).asAdmin().getBytecode());
        bindings.put("g", g);
        assertParameterizedScriptOk(standard3.getScript(), script3.getScript(), bindings, false);
        bindings.clear();

        final Script script4 = GroovyTranslator.of("g", true).translate(
                g.addE("knows").from(vertex1).to(vertex2).property("when", "2018/09/21")
                        .asAdmin().getBytecode());
        script4.getParameters().ifPresent(bindings::putAll);
        assertEquals(7, bindings.size());
        assertEquals("knows", bindings.get("_args_0"));
        assertEquals(id1, bindings.get("_args_1"));
        assertEquals("customer", bindings.get("_args_2"));
        assertEquals(id2, bindings.get("_args_3"));
        assertEquals("user", bindings.get("_args_4"));
        assertEquals("when", bindings.get("_args_5"));
        assertEquals("2018/09/21", bindings.get("_args_6"));
        assertEquals("g.addE(_args_0).from(new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex(_args_1,_args_2, Collections.emptyMap())).to(new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex(_args_3,_args_4, Collections.emptyMap())).property(_args_5,_args_6)", script4.getScript());
        final Script standard4 = GroovyTranslator.of("g").translate(
                g.addE("knows").from(vertex1).to(vertex2).property("when", "2018/09/21")
                        .asAdmin().getBytecode());
        bindings.put("g", g);
        assertParameterizedScriptOk(standard4.getScript(), script4.getScript(), bindings, false);
        bindings.clear();

        final Script script5 = GroovyTranslator.of("g", true).translate(g.V().has("age").asAdmin().getBytecode());
        script5.getParameters().ifPresent(bindings::putAll);
        assertEquals(1, bindings.size());
        assertEquals("age", bindings.get("_args_0"));
        assertEquals("g.V().has(_args_0)", script5.getScript());
        final Script standard5 = GroovyTranslator.of("g").translate(g.V().has("age").asAdmin().getBytecode());
        bindings.put("g", g);
        // Ok, here we checking all the result
        assertParameterizedScriptOk(standard5.getScript(), script5.getScript(), bindings, true);
        bindings.clear();
    }

    private Object eval(final String s, final Bindings b) throws ScriptException {
        return parameterizedEngine.eval(s, b);
    }

    private void assertParameterizedTranslation(final String expectedTranslation, final Object... objs) {
        final Script script = GroovyTranslator.of("g", true).translate(g.inject(objs).asAdmin().getBytecode());
        final Bindings bindings = new SimpleBindings();
        script.getParameters().ifPresent(bindings::putAll);
        assertEquals(1, bindings.size());
        assertEquals(objs[0], bindings.get("_args_0"));
        assertEquals(String.format("g.inject(_args_0)", expectedTranslation), script.getScript());

        final Script standard = GroovyTranslator.of("g").translate(g.inject(objs).asAdmin().getBytecode());
        bindings.put("g", g);
        assertParameterizedScriptOk(standard.getScript(), script.getScript(), bindings, true);
    }

    private void assertNonParameterizedTranslation(final String expectedTranslation, final Object... objs) {
        final Script script = GroovyTranslator.of("g", true).translate(g.inject(objs).asAdmin().getBytecode());
        final Bindings bindings = new SimpleBindings();
        script.getParameters().ifPresent(bindings::putAll);
        assertEquals(0, bindings.size());
        assertEquals(String.format("g.inject(%s)", expectedTranslation),  script.getScript());

        final Script standard = GroovyTranslator.of("g").translate(g.inject(objs).asAdmin().getBytecode());
        assertEquals(standard.getScript(), script.getScript());
    }

    private void assertThatScriptOk(final String s, Bindings bindings) {
        try {
            assertNotNull(eval(s, bindings));
        } catch (ScriptException se) {
            se.printStackTrace();
            fail("Script should have eval'd");
        }
    }

    private void assertParameterizedScriptOk(final String standardScript, final String checkingScript, final Bindings bindings, final boolean resultChecking) {
        try {
            System.out.println(standardScript + " " + checkingScript);
            assertTrue(!standardScript.equals(checkingScript));
            assertThatScriptOk(checkingScript, bindings);
            assertEquals(eval(standardScript, bindings), eval(checkingScript, bindings));
            if (resultChecking) {
                Traversal.Admin<Vertex, Object> standardTraversal = (Traversal.Admin) eval(standardScript, bindings);
                Traversal.Admin<Vertex, Object> checkingTraversal = (Traversal.Admin) eval(checkingScript, bindings);
                while (standardTraversal.hasNext() && checkingTraversal.hasNext()) {
                    // assertEquals(standardTraversal.next(), checkingTraversal.next());
                    System.out.println(standardTraversal.next() +  " " + checkingTraversal.next());
                }
                assertEquals(standardTraversal.hasNext(), checkingTraversal.hasNext());
            }
        } catch (ScriptException se) {
            se.printStackTrace();
            fail("Script should have eval'd");
        }
    }

    public static class ParameterizedSillyClass {

        private final String x;
        private final int y;

        private ParameterizedSillyClass(final String x, final int y) {
            this.x = x;
            this.y = y;
        }

        public static ParameterizedSillyClass from(final String x, final int y) {
            return new ParameterizedSillyClass(x, y);
        }

        public String getX() {
            return x;
        }

        public int getY() {
            return y;
        }

        public Object[] getArguments() {
            return new Object[] {x,y};
        }

        @Override
        public String toString() {
            return String.format("org.apache.tinkerpop.gremlin.groovy.jsr223.ParameterizedGroovyTranslatorTest.ParameterizedSillyClass.from('%s', (int) %s)", getX(), getY());
        }
    }

    public static class ParameterizedSillyClassTranslator extends  GroovyTranslator.DefaultTypeTranslator {
        public ParameterizedSillyClassTranslator(final boolean withParameters) {
           super(withParameters);
        }

        @Override
        protected Script convertToScript(final Object object) {
            if (object instanceof ParameterizedSillyClass) {
                ParameterizedSillyClass obj = (ParameterizedSillyClass) object;
                script.append(obj.getClass().getCanonicalName());
                if (0 == obj.getArguments().length) {
                    script.append(".").append("from").append("()");
                } else {
                    script.append(".").append("from").append("(");
                    for (final Object argument: obj.getArguments()) {
                        convertToScript(argument);
                        script.append(",");
                    }
                    script.setCharAtEnd(')');
                }
                return script;
            } else {
                return super.convertToScript(object);
            }
        }
    }

    public static class ParameterizedSillyClassTranslatorCustomizer implements TranslatorCustomizer {

        @Override
        public Translator.ScriptTranslator.TypeTranslator createTypeTranslator() {
            return new ParameterizedSillyClassTranslator(true);
        }
    }
}

