/*
 * 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.
 */

import org.apache.tinkerpop.gremlin.language.translator.GremlinTranslator
import org.apache.tinkerpop.gremlin.language.translator.Translator
import org.apache.tinkerpop.gremlin.language.translator.DotNetTranslateVisitor
import org.apache.tinkerpop.gremlin.language.corpus.FeatureReader

import java.nio.file.Paths

// file is overwritten on each generation
radishGremlinFile = new File("${projectBaseDir}/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs")

// assumes globally unique scenario names for keys with list of Gremlin traversals as they appear
gremlins = FeatureReader.parseGrouped(Paths.get("${projectBaseDir}", "gremlin-test", "src", "main", "resources", "org", "apache", "tinkerpop", "gremlin", "test", "features").toString())

radishGremlinFile.withWriter('UTF-8') { Writer writer ->
    writer.writeLine('#region License\n' +
            '\n' +
            '/*\n' +
            ' * Licensed to the Apache Software Foundation (ASF) under one\n' +
            ' * or more contributor license agreements.  See the NOTICE file\n' +
            ' * distributed with this work for additional information\n' +
            ' * regarding copyright ownership.  The ASF licenses this file\n' +
            ' * to you under the Apache License, Version 2.0 (the\n' +
            ' * "License"); you may not use this file except in compliance\n' +
            ' * with the License.  You may obtain a copy of the License at\n' +
            ' *\n' +
            ' *   http://www.apache.org/licenses/LICENSE-2.0\n' +
            ' *\n' +
            ' * Unless required by applicable law or agreed to in writing,\n' +
            ' * software distributed under the License is distributed on an\n' +
            ' * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n' +
            ' * KIND, either express or implied.  See the License for the\n' +
            ' * specific language governing permissions and limitations\n' +
            ' * under the License.\n' +
            ' */\n' +
            '\n' +
            '#endregion\n')

    writer.writeLine("\n\n//********************************************************************************")
    writer.writeLine("//* Do NOT edit this file directly - generated by build/generate.groovy")
    writer.writeLine("//********************************************************************************\n")
    writer.writeLine("#nullable disable\n")
    // CS0618 - feature tests intentionally cover deprecated steps
    // CS0078 - long literals translate with a lowercase suffix
    writer.writeLine("#pragma warning disable CS0618, CS0078\n")

    writer.writeLine('using System;\n' +
                     'using System.Numerics;\n' +
                     'using System.Collections.Generic;\n' +
                     'using Gremlin.Net.Structure;\n' +
                     'using Gremlin.Net.Process.Traversal;\n' +
                     'using Gremlin.Net.Process.Traversal.Strategy.Optimization;\n' +
                     'using Gremlin.Net.Process.Traversal.Strategy.Verification;\n' +
                     'using Gremlin.Net.Process.Traversal.Strategy.Finalization;\n' +
                     'using Gremlin.Net.Process.Traversal.Strategy.Decoration;\n')
    writer.writeLine('namespace Gremlin.Net.IntegrationTest.Gherkin\n' +
            '{\n' +
            '    public class Gremlin\n' +
            '    {\n' +
            '        public static void InstantiateTranslationsForTestRun()\n' +
            '        {\n' +
            '            // We need to copy the fixed translations as we remove translations from the list after using them\n' +
            '            // so we can enumerate through the translations while evaluating a scenario.\n' +
            '            _translationsForTestRun =\n' +
            '                new Dictionary<string, List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>>(\n' +
            '                    FixedTranslations.Count);\n' +
            '            foreach (var (traversal, translations) in FixedTranslations)\n' +
            '            {\n' +
            '                _translationsForTestRun.Add(traversal,\n' +
            '                    new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>(translations));\n' +
            '            }\n' +
            '        }\n')
    writer.writeLine(
            '        public static void InstantiateParameterizedTranslationsForTestRun()\n' +
            '        {\n' +
            '            _parameterizedTranslationsForTestRun =\n' +
            '                new Dictionary<string, List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>>(\n' +
            '                    ParameterizedTranslations.Count);\n' +
            '            foreach (var (traversal, translations) in ParameterizedTranslations)\n' +
            '            {\n' +
            '                _parameterizedTranslationsForTestRun.Add(traversal,\n' +
            '                    new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>(translations));\n' +
            '            }\n' +
            '        }\n')
    writer.writeLine(
            '        private static IDictionary<string, List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>>\n' +
            '            _translationsForTestRun;\n')
    writer.writeLine(
            '        private static IDictionary<string, List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>>\n' +
            '            _parameterizedTranslationsForTestRun;\n')
    writer.writeLine(
            '        private static readonly IDictionary<string, List<Func<GraphTraversalSource, IDictionary<string, object>,ITraversal>>> FixedTranslations = \n' +
            '            new Dictionary<string, List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>>\n' +
            '            {')


    // some traversals may require a static translation if the translator can't handle them for some reason
    def staticTranslate = [
            "g_withSackX_128bX_injectX1bX_sackXminusX_sack" : "               {\"g_withSackX_128bX_injectX1bX_sackXminusX_sack\", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithSack((short) -128).Inject<object>((short) 1).Sack(Operator.Minus).Sack<object>()}}, ",
            "g_V_injectX_128b__1bX_sumXX" : "               {\"g_V_injectX_128b__1bX_sumXX\", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>((short) -128, (short) -1).Sum<object>()}}, ",
            "g_withSackX_128bX_injectX_1bX_sackXdivX_sack" : "               {\"g_withSackX_128bX_injectX_1bX_sackXdivX_sack\", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithSack((short) -128).Inject<object>((short) -1).Sack(Operator.Div).Sack<object>()}}, "
    ]
    // SAMPLE: g_injectXnull_nullX: "               {\"g_injectXnull_nullX\", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,null)}}, ",1\"]).Values<object>(\"age\").Inject(null,null)}}, "

    // .NET generates TWO translation sets for every feature-test scenario: a non-parameterized set (FixedTranslations,
    // via Translator.DOTNET) and a parameterized set (ParameterizedTranslations, via a DotNetTranslateVisitor with
    // parameterize=true). This double generation is unique to .NET. Because gremlin-dotnet is statically typed and
    // exposes strongly-typed GValue<T> overloads, value-vs-GValue is a compile-time overload choice and so must be
    // decided at translation time rather than at runtime as the other GLVs do. Generating both sets lets the feature
    // tests exercise each scenario with and without GValue parameterization.
    //
    // Both sets share the same token substitution, which replaces the cucumber test-data tokens (xx1, v1, vid1, e1,
    // eid1, l1, pred1, c1) with strongly-typed lookups into the parameter map `p`. The two differ only in `quoteAware`:
    // the parameterized variant additionally excludes a double-quote from the lookbehind so that token-like substrings
    // appearing inside generated GValue names (e.g. new GValue<string>("v1", ...)) are left untouched. Routing both
    // call sites through this single closure keeps the two regex sets from drifting out of sync.
    def substituteCSharpParameters = { String translated, boolean quoteAware ->
        final String simpleLb = quoteAware ? "(?<!\")" : ""
        final String wordLb = quoteAware ? "(?<![\\w\\-\"])" : "(?<![\\w\\-])"
        return translated.
                replaceAll(simpleLb + "xx([0-9]+)", "p[\"xx\$1\"]").
                replaceAll(wordLb + "v([0-9]+)(?![\\w\\.])", "(Vertex) p[\"v\$1\"]").
                replaceAll(simpleLb + "vid([0-9]+)", "p[\"vid\$1\"]").
                replaceAll(wordLb + "e([0-9]+)(?![\\w\\.])", "p[\"e\$1\"]").
                replaceAll(simpleLb + "eid([0-9]+)", "p[\"eid\$1\"]").
                replaceAll(wordLb + "l([0-9]+)(?![\\w\\.])", "(IFunction) p[\"l\$1\"]").
                replaceAll(simpleLb + "pred([0-9]+)", "(IPredicate) p[\"pred\$1\"]").
                replaceAll(wordLb + "c([0-9]+)(?![\\w\\.])", "(IComparator) p[\"c\$1\"]")
    }

    gremlins.each { k,v ->
        if (staticTranslate.containsKey(k)) {
            writer.writeLine(staticTranslate[k])
        } else {
            writer.write("               {\"")
            writer.write(k)
            writer.write("\", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {")
            def collected = v.collect { GremlinTranslator.translate(it, Translator.DOTNET) }
            def gremlinItty = collected.iterator()
            while (gremlinItty.hasNext()) {
                def t = gremlinItty.next()
                writer.write("(g,p) =>")
                writer.write(substituteCSharpParameters(t.getTranslated(), false))
                if (gremlinItty.hasNext())
                    writer.write(', ')
                else
                    writer.write("}")
            }
            writer.writeLine('}, ')
        }
    }
    writer.writeLine('            };\n')

    // Generate parameterized translations
    writer.writeLine(
            '        private static readonly IDictionary<string, List<Func<GraphTraversalSource, IDictionary<string, object>,ITraversal>>> ParameterizedTranslations = \n' +
            '            new Dictionary<string, List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>>\n' +
            '            {')

    gremlins.each { k,v ->
        if (staticTranslate.containsKey(k)) {
            writer.writeLine(staticTranslate[k])
        } else {
            writer.write("               {\"")
            writer.write(k)
            writer.write("\", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {")
            def collected = v.collect { GremlinTranslator.translate(it, new DotNetTranslateVisitor("g", true)) }
            def gremlinItty = collected.iterator()
            while (gremlinItty.hasNext()) {
                def t = gremlinItty.next()
                writer.write("(g,p) =>")
                writer.write(substituteCSharpParameters(t.getTranslated(), true))
                if (gremlinItty.hasNext())
                    writer.write(', ')
                else
                    writer.write("}")
            }
            writer.writeLine('}, ')
        }
    }
    writer.writeLine('            };\n')

    writer.writeLine(
            '        public static ITraversal UseTraversal(string scenarioName, GraphTraversalSource g, IDictionary<string, object> parameters, IDictionary<string, object> sideEffects)\n' +
            '        {\n' +
            '            List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> list = _translationsForTestRun[scenarioName];\n' +
            '            Func<GraphTraversalSource, IDictionary<string, object>, ITraversal> f = list[0];\n' +
            '            list.RemoveAt(0);\n' +
            '            ITraversal traversal = f.Invoke(g, parameters);\n' +
            '            // Side effects need to be prepended as source steps (before traversal steps).\n' +
            '            // Build them in a temporary GremlinLang and prepend to the traversal\'s gremlin string.\n' +
            '            if (sideEffects.Count > 0)\n' +
            '            {\n' +
            '                var sideEffectLang = new GremlinLang();\n' +
            '                foreach (var sideEffect in sideEffects)\n' +
            '                {\n' +
            '                    sideEffectLang.AddSource("withSideEffect", sideEffect.Key, sideEffect.Value);\n' +
            '                }\n' +
            '                traversal.GremlinLang.Gremlin = sideEffectLang.Gremlin + traversal.GremlinLang.Gremlin;\n' +
            '            }\n' +
            '            return traversal;\n' +
            '        }\n')
    writer.writeLine(
            '        public static ITraversal UseParameterizedTraversal(string scenarioName, GraphTraversalSource g, IDictionary<string, object> parameters, IDictionary<string, object> sideEffects)\n' +
            '        {\n' +
            '            List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> list = _parameterizedTranslationsForTestRun[scenarioName];\n' +
            '            Func<GraphTraversalSource, IDictionary<string, object>, ITraversal> f = list[0];\n' +
            '            list.RemoveAt(0);\n' +
            '            ITraversal traversal = f.Invoke(g, parameters);\n' +
            '            if (sideEffects.Count > 0)\n' +
            '            {\n' +
            '                var sideEffectLang = new GremlinLang();\n' +
            '                foreach (var sideEffect in sideEffects)\n' +
            '                {\n' +
            '                    sideEffectLang.AddSource("withSideEffect", sideEffect.Key, sideEffect.Value);\n' +
            '                }\n' +
            '                traversal.GremlinLang.Gremlin = sideEffectLang.Gremlin + traversal.GremlinLang.Gremlin;\n' +
            '            }\n' +
            '            return traversal;\n' +
            '        }\n' +
            '    }\n' +
            '}\n')
    writer.writeLine("#nullable restore\n")
}


