/*
 * 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.neo4j.structure;

import org.apache.tinkerpop.gremlin.FeatureRequirement;
import org.apache.tinkerpop.gremlin.groovy.jsr223.GremlinGroovyScriptEngine;
import org.apache.tinkerpop.gremlin.neo4j.AbstractNeo4jGremlinTest;
import org.apache.tinkerpop.gremlin.neo4j.process.traversal.LabelP;
import org.apache.tinkerpop.gremlin.neo4j.structure.trait.MultiMetaNeo4jTrait;
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.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.util.iterator.IteratorUtils;
import org.junit.Test;
import org.neo4j.tinkerpop.api.Neo4jDirection;
import org.neo4j.tinkerpop.api.Neo4jGraphAPI;
import org.neo4j.tinkerpop.api.Neo4jNode;
import org.neo4j.tinkerpop.api.Neo4jRelationship;
import org.neo4j.tinkerpop.api.Neo4jTx;

import javax.script.Bindings;
import javax.script.ScriptException;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.Assert.*;

/**
 * @author Marko A. Rodriguez (http://markorodriguez.com)
 */
public class NativeNeo4jStructureCheck extends AbstractNeo4jGremlinTest {

    @Test
    public void shouldOpenWithOverriddenConfig() throws Exception {
        assertNotNull(this.graph);
    }

    @Test
    public void shouldSupportHasContainersWithMultiLabels() throws Exception {
        final Neo4jVertex vertex = (Neo4jVertex) this.graph.addVertex(T.label, "person", "name", "marko");
        graph.tx().commit();
        assertTrue(g.V().has(T.label, "person").hasNext());
        assertEquals("marko", g.V().has(T.label, LabelP.of("person")).values("name").next());
        assertEquals("marko", g.V().has(T.label, "person").values("name").next());
        // more labels
        vertex.addLabel("animal");
        vertex.addLabel("object");
        graph.tx().commit();
        // no indices (neo4j graph step)
        assertFalse(g.V().has(T.label, "person").hasNext());
        assertEquals("marko", g.V().has(T.label, LabelP.of("person")).values("name").next());
        assertEquals("marko", g.V().has(T.label, LabelP.of("animal")).values("name").next());
        assertEquals("marko", g.V().has(T.label, LabelP.of("object")).values("name").next());
        // no indices (has step)
        assertFalse(g.V().as("a").select("a").has(T.label, "person").hasNext());
        assertEquals("marko", g.V().as("a").select("a").has(T.label, LabelP.of("person")).values("name").next());
        assertEquals("marko", g.V().as("a").select("a").has(T.label, LabelP.of("animal")).values("name").next());
        assertEquals("marko", g.V().as("a").select("a").has(T.label, LabelP.of("object")).values("name").next());
        // indices (neo4j graph step)
        this.getGraph().cypher("CREATE INDEX ON :person(name)").iterate();
        graph.tx().commit();
        Thread.sleep(500);
        assertFalse(g.V().has(T.label, "person").has("name", "marko").hasNext());
        assertEquals("marko", g.V().has(T.label, LabelP.of("person")).has("name", "marko").values("name").next());
        assertEquals("marko", g.V().has(T.label, LabelP.of("animal")).has("name", "marko").values("name").next());
        assertEquals("marko", g.V().has(T.label, LabelP.of("object")).has("name", "marko").values("name").next());
        this.getGraph().cypher("CREATE INDEX ON :animal(name)").iterate();
        graph.tx().commit();
        Thread.sleep(500);
        assertFalse(g.V().has(T.label, "animal").has("name", "marko").hasNext());
        assertEquals("marko", g.V().has(T.label, LabelP.of("person")).has("name", "marko").values("name").next());
        assertEquals("marko", g.V().has(T.label, LabelP.of("animal")).has("name", "marko").values("name").next());
        assertEquals("marko", g.V().has(T.label, LabelP.of("object")).has("name", "marko").values("name").next());
        this.getGraph().cypher("CREATE INDEX ON :object(name)").iterate();
        graph.tx().commit();
        Thread.sleep(500);
        assertFalse(g.V().has(T.label, "object").has("name", "marko").hasNext());
        assertEquals("marko", g.V().has(T.label, LabelP.of("person")).has("name", "marko").values("name").next());
        assertEquals("marko", g.V().has(T.label, LabelP.of("animal")).has("name", "marko").values("name").next());
        assertEquals("marko", g.V().has(T.label, LabelP.of("object")).has("name", "marko").values("name").next());
    }

    @Test
    public void shouldNotThrowConcurrentModificationException() {
        this.graph.addVertex("name", "a");
        this.graph.addVertex("name", "b");
        this.graph.addVertex("name", "c");
        this.graph.addVertex("name", "d");
        this.graph.vertices().forEachRemaining(Vertex::remove);
        this.graph.tx().commit();
        assertEquals(0, IteratorUtils.count(this.graph.vertices()), 0);
    }

    @Test
    public void shouldTraverseWithoutLabels() {
        final Neo4jGraphAPI service = this.getGraph().getBaseGraph();

        final Neo4jTx tx = service.tx();
        final Neo4jNode n = service.createNode();
        tx.success();
        tx.close();

        final Neo4jTx tx2 = service.tx();
        assertEquals(0, IteratorUtils.count(n.labels().iterator()));
        assertEquals(1, IteratorUtils.count(graph.vertices()));
        graph.tx().close();
        tx2.close();
    }

    @Test
    public void shouldEnsureTraverseRelationshipNeedsTx() throws ScriptException {
        final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine();
        final Bindings bindings = engine.createBindings();
        bindings.put("g", graph.traversal());
        bindings.put("#jsr223.groovy.engine.keep.globals", "phantom");

        Vertex marko = this.graph.addVertex(T.label, "Person", "name", "marko");
        Vertex john = this.graph.addVertex(T.label, "Person", "name", "john");
        Vertex pete = this.graph.addVertex(T.label, "Person", "name", "pete");
        marko.addEdge("friend", john);
        marko.addEdge("friend", pete);
        this.graph.tx().commit();

        Object result = engine.eval("g.V(" + marko.id().toString() + ").outE('friend')", bindings);
        assertTrue(result instanceof GraphTraversal);

        this.graph.tx().commit();
        assertEquals(2L, ((GraphTraversal) result).count().next());
    }

    @Test
    public void shouldEnsureTraversalOfVerticesNeedsTx() throws ScriptException {
        final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine();
        final Bindings bindings = engine.createBindings();
        bindings.put("g", graph.traversal());
        bindings.put("#jsr223.groovy.engine.keep.globals", "phantom");

        Vertex marko = this.graph.addVertex(T.label, "Person", "name", "marko");
        Vertex john = this.graph.addVertex(T.label, "Person", "name", "john");
        Vertex pete = this.graph.addVertex(T.label, "Person", "name", "pete");
        marko.addEdge("friend", john);
        marko.addEdge("friend", pete);
        this.graph.tx().commit();

        Object result = engine.eval("g.V(" + marko.id().toString() + ").out('friend')", bindings);
        assertTrue(result instanceof GraphTraversal);

        this.graph.tx().commit();
        assertEquals(2L, ((GraphTraversal) result).count().next());
    }

    @Test
    public void shouldDoLabelSearch() {
        this.graph.addVertex(T.label, "Person", "name", "marko");
        this.graph.addVertex(T.label, "Person", "name", "john");
        Vertex pete = this.graph.addVertex(T.label, "Person", "name", "pete");
        this.graph.addVertex(T.label, "Monkey", "name", "pete");
        this.graph.tx().commit();
        assertEquals(3, this.g.V().has(T.label, "Person").count().next(), 0);
        pete.remove();
        this.graph.tx().commit();
        assertEquals(2, this.g.V().has(T.label, "Person").count().next(), 0);
    }

    @Test
    public void shouldNotGenerateVerticesOrEdgesForGraphVariables() {
        graph.tx().readWrite();
        graph.variables().set("namespace", "rdf-xml");
        tryCommit(graph, graph -> {
            assertEquals("rdf-xml", graph.variables().get("namespace").get());
            assertEquals(0, g.V().count().next().intValue());
            assertEquals(0, g.E().count().next().intValue());
            assertEquals(0, IteratorUtils.count(this.getBaseGraph().allNodes()));
            assertEquals(0, IteratorUtils.count(this.getBaseGraph().allRelationships()));
        });
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_MULTI_PROPERTIES, supported = false)
    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_META_PROPERTIES, supported = false)
    public void shouldNotGenerateNodesAndRelationshipsForNoMultiPropertiesNoMetaProperties() {
        graph.tx().readWrite();
        tryCommit(graph, graph -> validateCounts(0, 0, 0, 0));
        Vertex vertex = graph.addVertex(T.label, "person");
        tryCommit(graph, graph -> validateCounts(1, 0, 1, 0));
        vertex.property("name", "marko");
        assertEquals("marko", vertex.value("name"));
        tryCommit(graph, graph -> validateCounts(1, 0, 1, 0));
        vertex.property("name", "okram");
        tryCommit(graph, g -> {
            validateCounts(1, 0, 1, 0);
            assertEquals("okram", vertex.value("name"));
        });
        VertexProperty vertexProperty = vertex.property("name");
        tryCommit(graph, graph -> {
            assertTrue(vertexProperty.isPresent());
            assertEquals("name", vertexProperty.key());
            assertEquals("okram", vertexProperty.value());
            validateCounts(1, 0, 1, 0);
        });
        try {
            vertexProperty.property("acl", "private");
        } catch (UnsupportedOperationException e) {
            assertEquals(VertexProperty.Exceptions.metaPropertiesNotSupported().getMessage(), e.getMessage());
        }
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_MULTI_PROPERTIES)
    public void shouldGenerateNodesAndRelationshipsCorrectlyForVertexProperties() {

        graph.tx().readWrite();
        Neo4jVertex a = (Neo4jVertex) graph.addVertex("name", "marko", "name", "okram");
        Neo4jVertex b = (Neo4jVertex) graph.addVertex("name", "stephen", "location", "virginia");

        tryCommit(graph, graph -> {
            assertEquals(2, g.V().count().next().intValue());
            // assertEquals(2, a.properties("name").count().next().intValue());
            // assertEquals(1, b.properties("name").count().next().intValue());
            // assertEquals(1, b.properties("location").count().next().intValue());
            assertEquals(0, g.E().count().next().intValue());

            assertEquals(4l, this.getBaseGraph().execute("MATCH n RETURN COUNT(n)", null).next().get("COUNT(n)"));
            assertEquals(2l, this.getBaseGraph().execute("MATCH (n)-[r]->(m) RETURN COUNT(r)", null).next().get("COUNT(r)"));
            assertEquals(2l, this.getBaseGraph().execute("MATCH (a)-[r]->() WHERE id(a) = " + a.id() + " RETURN COUNT(r)", null).next().get("COUNT(r)"));
            final AtomicInteger counter = new AtomicInteger(0);
            a.getBaseVertex().relationships(Neo4jDirection.OUTGOING).forEach(relationship -> {
                assertEquals(Graph.Hidden.hide("name"), relationship.type());
                counter.incrementAndGet();
            });
            assertEquals(2, counter.getAndSet(0));
            this.getBaseGraph().execute("MATCH (a)-[]->(m) WHERE id(a) = " + a.id() + " RETURN labels(m)", null).forEachRemaining(results -> {
                assertEquals(VertexProperty.DEFAULT_LABEL, ((List<String>) results.get("labels(m)")).get(0));
                counter.incrementAndGet();
            });
            assertEquals(2, counter.getAndSet(0));
            IteratorUtils.stream(a.getBaseVertex().relationships(Neo4jDirection.OUTGOING)).map(Neo4jRelationship::end).forEach(node -> {
                assertEquals(3, IteratorUtils.count(node.getKeys()));  // T.key, T.value, key
                assertEquals("name", node.getProperty(T.key.getAccessor()));
                assertTrue("marko".equals(node.getProperty(T.value.getAccessor())) || "okram".equals(node.getProperty(T.value.getAccessor())));
                assertEquals(0, node.degree(Neo4jDirection.OUTGOING, null));
                assertEquals(1, node.degree(Neo4jDirection.INCOMING, null));
                assertEquals(Graph.Hidden.hide("name"), node.relationships(Neo4jDirection.INCOMING).iterator().next().type());
                counter.incrementAndGet();
            });
            assertEquals(2, counter.getAndSet(0));

            assertEquals(2, IteratorUtils.count(b.getBaseVertex().getKeys()));
            assertEquals("stephen", b.getBaseVertex().getProperty("name"));
            assertEquals("virginia", b.getBaseVertex().getProperty("location"));
        });

        a.property("name", "the marko");
        tryCommit(graph, graph -> {
            assertEquals(2, g.V().count().next().intValue());
            //assertEquals(1, a.prope rties().count().next().intValue());
            //  assertEquals(1, b.properties("name").count().next().intValue());
            // assertEquals(1, b.properties("location").count().next().intValue());
            assertEquals(0, g.E().count().next().intValue());
            assertEquals(2l, this.getBaseGraph().execute("MATCH n RETURN COUNT(n)", null).next().get("COUNT(n)"));
            assertEquals(0l, this.getBaseGraph().execute("MATCH (n)-[r]->(m) RETURN COUNT(r)", null).next().get("COUNT(r)"));

            assertEquals(1, IteratorUtils.count(a.getBaseVertex().getKeys()));
            assertEquals("the marko", a.getBaseVertex().getProperty("name"));
            assertEquals(2, IteratorUtils.count(b.getBaseVertex().getKeys()));
            assertEquals("stephen", b.getBaseVertex().getProperty("name"));
            assertEquals("virginia", b.getBaseVertex().getProperty("location"));
        });

        a.property("name").remove();
        tryCommit(graph, graph -> {
            assertEquals(2, g.V().count().next().intValue());
            //    assertEquals(0, a.properties().count().next().intValue());
            //   assertEquals(2, b.properties().count().next().intValue());
            assertEquals(0, g.E().count().next().intValue());
            assertEquals(2l, this.getBaseGraph().execute("MATCH n RETURN COUNT(n)", null).next().get("COUNT(n)"));
            assertEquals(0l, this.getBaseGraph().execute("MATCH (n)-[r]->(m) RETURN COUNT(r)", null).next().get("COUNT(r)"));
            assertEquals(0, IteratorUtils.count(a.getBaseVertex().getKeys()));
            assertEquals(2, IteratorUtils.count(b.getBaseVertex().getKeys()));
        });

        graph.tx().commit();
        a.property("name", "the marko", "acl", "private");
        tryCommit(graph, graph -> {
            assertEquals(2, g.V().count().next().intValue());
            // assertEquals(1, a.properties("name").count().next().intValue());
            // assertEquals(1, b.properties("name").count().next().intValue());
            // assertEquals(1, b.properties("location").count().next().intValue());
            assertEquals(0, g.E().count().next().intValue());

            assertEquals(3l, this.getBaseGraph().execute("MATCH n RETURN COUNT(n)", null).next().get("COUNT(n)"));
            assertEquals(1l, this.getBaseGraph().execute("MATCH (n)-[r]->(m) RETURN COUNT(r)", null).next().get("COUNT(r)"));
            assertEquals(1l, this.getBaseGraph().execute("MATCH (a)-[r]->() WHERE id(a) = " + a.id() + " RETURN COUNT(r)", null).next().get("COUNT(r)"));
            final AtomicInteger counter = new AtomicInteger(0);
            a.getBaseVertex().relationships(Neo4jDirection.OUTGOING).forEach(relationship -> {
                assertEquals(Graph.Hidden.hide("name"), relationship.type());
                counter.incrementAndGet();
            });
            assertEquals(1, counter.getAndSet(0));
            this.getBaseGraph().execute("MATCH (a)-[]->(m) WHERE id(a) = " + a.id() + " RETURN labels(m)", null).forEachRemaining(results -> {
                assertEquals(VertexProperty.DEFAULT_LABEL, ((List<String>) results.get("labels(m)")).get(0));
                counter.incrementAndGet();
            });
            assertEquals(1, counter.getAndSet(0));
            IteratorUtils.stream(a.getBaseVertex().relationships(Neo4jDirection.OUTGOING)).map(Neo4jRelationship::end).forEach(node -> {
                assertEquals(4, IteratorUtils.count(node.getKeys()));
                assertEquals("name", node.getProperty(T.key.getAccessor()));
                assertEquals("the marko", node.getProperty(T.value.getAccessor()));
                assertEquals("private", node.getProperty("acl"));
                assertEquals(0, node.degree(Neo4jDirection.OUTGOING, null));
                assertEquals(1, node.degree(Neo4jDirection.INCOMING, null));
                assertEquals(Graph.Hidden.hide("name"), node.relationships(Neo4jDirection.INCOMING).iterator().next().type());
                counter.incrementAndGet();
            });
            assertEquals(1, counter.getAndSet(0));

            assertEquals(1, IteratorUtils.count(a.getBaseVertex().getKeys()));
            assertTrue(a.getBaseVertex().hasProperty("name"));
            assertEquals(MultiMetaNeo4jTrait.VERTEX_PROPERTY_TOKEN, a.getBaseVertex().getProperty("name"));
            assertEquals(2, IteratorUtils.count(b.getBaseVertex().getKeys()));
            assertEquals("stephen", b.getBaseVertex().getProperty("name"));
            assertEquals("virginia", b.getBaseVertex().getProperty("location"));
        });

        a.property(VertexProperty.Cardinality.list, "name", "marko", "acl", "private");
        a.property(VertexProperty.Cardinality.list, "name", "okram", "acl", "public");
        a.property(VertexProperty.Cardinality.single, "name", "the marko", "acl", "private");
        tryCommit(graph, graph -> {
            assertEquals(2, g.V().count().next().intValue());
            //assertEquals(1, a.properties("name").count().next().intValue());
            //assertEquals(1, b.properties("name").count().next().intValue());
            //assertEquals(1, b.properties("location").count().next().intValue());
            assertEquals(0, g.E().count().next().intValue());

            assertEquals(3l, this.getBaseGraph().execute("MATCH n RETURN COUNT(n)", null).next().get("COUNT(n)"));
            assertEquals(1l, this.getBaseGraph().execute("MATCH (n)-[r]->(m) RETURN COUNT(r)", null).next().get("COUNT(r)"));
            assertEquals(1l, this.getBaseGraph().execute("MATCH (a)-[r]->() WHERE id(a) = " + a.id() + " RETURN COUNT(r)", null).next().get("COUNT(r)"));
            final AtomicInteger counter = new AtomicInteger(0);
            a.getBaseVertex().relationships(Neo4jDirection.OUTGOING).forEach(relationship -> {
                assertEquals(Graph.Hidden.hide("name"), relationship.type());
                counter.incrementAndGet();
            });
            assertEquals(1, counter.getAndSet(0));
            this.getBaseGraph().execute("MATCH (a)-[]->(m) WHERE id(a) = " + a.id() + " RETURN labels(m)", null).forEachRemaining(results -> {
                assertEquals(VertexProperty.DEFAULT_LABEL, ((List<String>) results.get("labels(m)")).get(0));
                counter.incrementAndGet();
            });
            assertEquals(1, counter.getAndSet(0));
            IteratorUtils.stream(a.getBaseVertex().relationships(Neo4jDirection.OUTGOING)).map(Neo4jRelationship::end).forEach(node -> {
                assertEquals(4, IteratorUtils.count(node.getKeys()));
                assertEquals("name", node.getProperty(T.key.getAccessor()));
                assertEquals("the marko", node.getProperty(T.value.getAccessor()));
                assertEquals("private", node.getProperty("acl"));
                assertEquals(0, node.degree(Neo4jDirection.OUTGOING, null));
                assertEquals(1, node.degree(Neo4jDirection.INCOMING, null));
                assertEquals(Graph.Hidden.hide("name"), node.relationships(Neo4jDirection.INCOMING).iterator().next().type());
                counter.incrementAndGet();
            });
            assertEquals(1, counter.getAndSet(0));

            assertEquals(1, IteratorUtils.count(a.getBaseVertex().getKeys()));
            assertTrue(a.getBaseVertex().hasProperty("name"));
            assertEquals(MultiMetaNeo4jTrait.VERTEX_PROPERTY_TOKEN, a.getBaseVertex().getProperty("name"));
            assertEquals(2, IteratorUtils.count(b.getBaseVertex().getKeys()));
            assertEquals("stephen", b.getBaseVertex().getProperty("name"));
            assertEquals("virginia", b.getBaseVertex().getProperty("location"));
        });
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_MULTI_PROPERTIES)
    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_META_PROPERTIES)
    public void shouldNotGenerateNodesAndRelationshipsForMultiPropertiesWithSingle() {
        graph.tx().readWrite();
        tryCommit(graph, graph -> validateCounts(0, 0, 0, 0));
        Vertex vertex = graph.addVertex(T.label, "person");
        tryCommit(graph, graph -> validateCounts(1, 0, 1, 0));
        vertex.property(VertexProperty.Cardinality.list, "name", "marko");
        assertEquals("marko", vertex.value("name"));
        tryCommit(graph, graph -> validateCounts(1, 0, 1, 0));
        vertex.property(VertexProperty.Cardinality.single, "name", "okram");
        tryCommit(graph, graph -> {
            validateCounts(1, 0, 1, 0);
            assertEquals("okram", vertex.value("name"));
        });
        VertexProperty vertexProperty = vertex.property("name");
        tryCommit(graph, graph -> {
            assertTrue(vertexProperty.isPresent());
            assertEquals("name", vertexProperty.key());
            assertEquals("okram", vertexProperty.value());
            validateCounts(1, 0, 1, 0);
        });

        // now make it a meta property (and thus, force node/relationship creation)
        vertexProperty.property("acl", "private");
        tryCommit(graph, graph -> {
            assertEquals("private", vertexProperty.value("acl"));
            validateCounts(1, 0, 2, 1);
        });

    }

    @Test
    public void shouldSupportNeo4jMultiLabels() {
        final Neo4jVertex vertex = (Neo4jVertex) graph.addVertex(T.label, "animal::person", "name", "marko");
        tryCommit(graph, graph -> {
            assertTrue(vertex.label().equals("animal::person"));
            assertEquals(2, vertex.labels().size());
            assertTrue(vertex.labels().contains("person"));
            assertTrue(vertex.labels().contains("animal"));
            assertEquals(2, IteratorUtils.count(vertex.getBaseVertex().labels().iterator()));
        });

        vertex.addLabel("organism");
        tryCommit(graph, graph -> {
            assertTrue(vertex.label().equals("animal::organism::person"));
            assertEquals(3, vertex.labels().size());
            assertTrue(vertex.labels().contains("person"));
            assertTrue(vertex.labels().contains("animal"));
            assertTrue(vertex.labels().contains("organism"));
            assertEquals(3, IteratorUtils.count(vertex.getBaseVertex().labels().iterator()));
        });

        vertex.removeLabel("person");
        tryCommit(graph, graph -> {
            assertTrue(vertex.label().equals("animal::organism"));
            assertEquals(2, vertex.labels().size());
            assertTrue(vertex.labels().contains("animal"));
            assertTrue(vertex.labels().contains("organism"));
        });

        vertex.addLabel("organism"); // repeat add
        vertex.removeLabel("person"); // repeat remove
        tryCommit(graph, graph -> {
            assertTrue(vertex.label().equals("animal::organism"));
            assertEquals(2, vertex.labels().size());
            assertTrue(vertex.labels().contains("animal"));
            assertTrue(vertex.labels().contains("organism"));
            assertEquals(2, IteratorUtils.count(vertex.getBaseVertex().labels().iterator()));
        });

        assertEquals(Long.valueOf(0), g.V().has(T.label, "organism").count().next());
        assertEquals(Long.valueOf(1), g.V().has(T.label, LabelP.of("organism")).count().next());
        assertEquals(Long.valueOf(1), g.V().has(T.label, LabelP.of("animal")).count().next());

        vertex.removeLabel("organism");
        vertex.removeLabel("animal");
        assertEquals(0, vertex.labels().size());
        vertex.addLabel("organism-animal");
        tryCommit(graph, graph -> {
            assertEquals(Long.valueOf(0), g.V().has(T.label, LabelP.of("organism")).count().next());
            assertEquals(Long.valueOf(0), g.V().has(T.label, LabelP.of("animal")).count().next());
            assertEquals(Long.valueOf(0), g.V().map(Traverser::get).has(T.label, LabelP.of("organism")).count().next());
            assertEquals(Long.valueOf(0), g.V().map(Traverser::get).has(T.label, LabelP.of("animal")).count().next());
            //
            assertEquals(Long.valueOf(1), g.V().has(T.label, LabelP.of("organism-animal")).count().next());
            assertEquals(Long.valueOf(1), g.V().has(T.label, "organism-animal").count().next());
            assertEquals(Long.valueOf(1), g.V().map(Traverser::get).has(T.label, LabelP.of("organism-animal")).count().next());
            assertEquals(Long.valueOf(1), g.V().map(Traverser::get).has(T.label, "organism-animal").count().next());
        });
    }

}