/*
 * 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.process.traversal.strategy.decoration;

import org.apache.tinkerpop.gremlin.FeatureRequirementSet;
import org.apache.tinkerpop.gremlin.process.AbstractGremlinProcessTest;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.util.function.ConstantSupplier;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

/**
 * @author Stephen Mallette (http://stephen.genoprime.com)
 */
public class ElementIdStrategyProcessTest extends AbstractGremlinProcessTest {
    private static final Logger logger = LoggerFactory.getLogger(ElementIdStrategyProcessTest.class);

    @Test
    @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
    public void shouldGenerateDefaultIdOnGraphAddVWithGeneratedDefaultId() throws Exception {
        final ElementIdStrategy strategy = ElementIdStrategy.build().create();
        final GraphTraversalSource sg = create(strategy);
        final Vertex v = sg.addV().property("name", "stephen").next();
        assertEquals("stephen", v.value("name"));

        final Traversal t1 = graph.traversal().V(v);
        t1.asAdmin().applyStrategies();
        logger.info(t1.toString());

        final Traversal t2 = sg.V(v);
        t2.asAdmin().applyStrategies();
        logger.info(t2.toString());

        assertNotNull(UUID.fromString(sg.V(v).id().next().toString()));
    }

    @Test
    @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
    public void shouldGenerateDefaultIdOnGraphAddVWithGeneratedCustomId() throws Exception {
        final ElementIdStrategy strategy = ElementIdStrategy.build().idMaker(new ConstantSupplier<>("xxx")).create();
        final GraphTraversalSource sg = create(strategy);
        final Vertex v = sg.addV().property("name", "stephen").property("group", 10).next();
        assertEquals("stephen", v.value("name"));
        assertEquals("xxx", sg.V(v).id().next());
        assertEquals("xxx", sg.V("xxx").id().next());
        assertEquals("xxx", sg.V().has(T.id, "xxx").id().next());
        assertEquals("xxx", sg.V().hasId("xxx").id().next());
        assertEquals("xxx", sg.V().has(T.id, "xxx").has("group", 10).id().next());
        assertEquals("xxx", sg.V().has("group", 10).has(T.id, "xxx").id().next());
    }

    @Test
    @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
    public void shouldSetIdOnAddVWithIdPropertyKeySpecifiedAndNameSuppliedAsProperty() throws Exception {
        final ElementIdStrategy strategy = ElementIdStrategy.build().idPropertyKey("name").create();
        final GraphTraversalSource sg = create(strategy);
        final Vertex v = sg.addV().property("name", "stephen").next();
        assertEquals("stephen", v.value("name"));
        assertEquals("stephen", sg.V(v).id().next());
        assertEquals("stephen", sg.V("stephen").id().next());
    }

    @Test
    @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
    public void shouldSetIdOnAddVWithIdPropertyKeySpecifiedAndIdSuppliedAsProperty() throws Exception {
        final ElementIdStrategy strategy = ElementIdStrategy.build().idPropertyKey("name").create();
        final GraphTraversalSource sg = create(strategy);
        final Vertex v = sg.addV().property(T.id, "stephen").next();
        assertEquals("stephen", v.value("name"));
        assertEquals("stephen", sg.V(v).id().next());
        assertEquals("stephen", sg.V("stephen").id().next());
    }

    @Test
    @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
    public void shouldGenerateDefaultIdOnGraphAddVWithSpecifiedId() throws Exception {
        final ElementIdStrategy strategy = ElementIdStrategy.build().create();
        final GraphTraversalSource sg = create(strategy);
        final Vertex v = sg.addV().property(T.id, "STEPHEN").property("name", "stephen").next();
        assertEquals("stephen", v.value("name"));
        assertEquals("STEPHEN", sg.V(v).id().next());
        assertEquals("STEPHEN", sg.V("STEPHEN").id().next());
    }

    @Test
    @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
    public void shouldGenerateDefaultIdOnAddVWithGeneratedDefaultId() throws Exception {
        final ElementIdStrategy strategy = ElementIdStrategy.build().create();
        final GraphTraversalSource sg = create(strategy);
        sg.addV().next();
        assertEquals(1, IteratorUtils.count(sg.V()));

        final Vertex v = sg.V().addV().property("name", "stephen").next();
        assertEquals("stephen", v.value("name"));
        assertNotNull(UUID.fromString(sg.V(v).id().next().toString()));
    }

    @Test
    @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
    public void shouldGenerateDefaultIdOnAddVWithGeneratedCustomId() throws Exception {
        final AtomicBoolean first = new AtomicBoolean(false);
        final ElementIdStrategy strategy = ElementIdStrategy.build().idMaker(() -> {
            final String key = first.get() ? "xxx" : "yyy";
            first.set(true);
            return key;
        }).create();
        final GraphTraversalSource sg = create(strategy);
        sg.addV().next();
        assertEquals(1, IteratorUtils.count(sg.V()));

        final Vertex v = sg.V().addV().property("name", "stephen").next();
        assertEquals("stephen", v.value("name"));
        assertEquals("xxx", sg.V(v).id().next());
        assertEquals("xxx", sg.V("xxx").id().next());
        assertEquals("yyy", sg.V("yyy").id().next());
    }

    @Test
    @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
    public void shouldGenerateDefaultIdOnAddVWithSpecifiedId() throws Exception {
        final ElementIdStrategy strategy = ElementIdStrategy.build().create();
        final GraphTraversalSource sg = create(strategy);
        sg.addV().next();
        assertEquals(1, IteratorUtils.count(sg.V()));

        final Vertex v = sg.V().addV().property(T.id, "STEPHEN").property("name", "stephen").next();
        assertEquals("stephen", v.value("name"));
        assertEquals("STEPHEN", sg.V(v).id().next());
        assertEquals("STEPHEN", sg.V("STEPHEN").id().next());
    }

    @Test
    @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
    public void shouldGenerateDefaultIdOnAddEWithSpecifiedId() throws Exception {
        final ElementIdStrategy strategy = ElementIdStrategy.build().create();
        final GraphTraversalSource sg = create(strategy);
        final Vertex v = sg.addV().next();
        final Edge e = sg.withSideEffect("v", v).V(v).addE("self").to("v").property("test", "value").property(T.id, "some-id").next();
        assertEquals("value", e.value("test"));
        assertEquals("some-id", sg.E(e).id().next());
        assertEquals("some-id", sg.E("some-id").id().next());
    }

    @Test
    @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
    public void shouldGenerateDefaultIdOnAddEWithGeneratedId() throws Exception {
        final ElementIdStrategy strategy = ElementIdStrategy.build().create();
        final GraphTraversalSource sg = create(strategy);
        final Vertex v = sg.addV().next();
        final Edge e = sg.withSideEffect("v", v).V(v).addE("self").to("v").property("test", "value").next();
        assertEquals("value", e.value("test"));
        assertNotNull(UUID.fromString(sg.E(e).id().next().toString()));
    }

    @Test
    @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
    public void shouldSetIdOnAddEWithIdPropertyKeySpecifiedAndNameSuppliedAsProperty() throws Exception {
        final ElementIdStrategy strategy = ElementIdStrategy.build().idPropertyKey("name").create();
        final GraphTraversalSource sg = create(strategy);
        final Vertex v = sg.addV().next();
        final Edge e = sg.withSideEffect("v", v).V(v).addE("self").to("v").property("test", "value").property(T.id, "some-id").next();
        assertEquals("value", e.value("test"));
        assertEquals("some-id", e.value("name"));
        assertEquals("some-id", sg.E(e).id().next());
        assertEquals("some-id", sg.E("some-id").id().next());
    }

    @Test
    @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
    public void shouldSetIdOnAddEWithNamePropertyKeySpecifiedAndNameSuppliedAsProperty() throws Exception {
        final ElementIdStrategy strategy = ElementIdStrategy.build().idPropertyKey("name").create();
        final GraphTraversalSource sg = create(strategy);
        final Vertex v = sg.addV().next();
        final Edge e = sg.withSideEffect("v", v).V(v).addE("self").to("v").property("test", "value").property("name", "some-id").next();
        assertEquals("value", e.value("test"));
        assertEquals("some-id", e.value("name"));
        assertEquals("some-id", sg.E(e).id().next());
        assertEquals("some-id", sg.E("some-id").id().next());
        assertEquals("some-id", sg.E().hasId("some-id").id().next());
        assertEquals("some-id", sg.E().has(T.id, "some-id").has("test", "value").id().next());
        assertEquals("some-id", sg.E().has("test", "value").has(T.id, "some-id").id().next());
    }

    private GraphTraversalSource create(final ElementIdStrategy strategy) {
        return graphProvider.traversal(graph, strategy);
    }
}
