package com.tinkerpop.gremlin.structure.util.detached;

import com.tinkerpop.gremlin.process.T;
import com.tinkerpop.gremlin.process.graph.GraphTraversal;
import com.tinkerpop.gremlin.structure.Direction;
import com.tinkerpop.gremlin.structure.Edge;
import com.tinkerpop.gremlin.structure.Graph;
import com.tinkerpop.gremlin.structure.VertexProperty;
import com.tinkerpop.gremlin.structure.Property;
import com.tinkerpop.gremlin.structure.Vertex;
import com.tinkerpop.gremlin.structure.util.StringFactory;
import org.javatuples.Pair;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Represents a {@link Vertex} that is disconnected from a {@link Graph}.  "Disconnection" can mean detachment from
 * a {@link Graph} in the sense that a {@link Vertex} was constructed from a {@link Graph} instance and this reference
 * was removed or it can mean that the {@code DetachedVertex} could have been constructed independently of a
 * {@link Graph} instance in the first place.
 * <br/>
 * A {@code DetachedVertex} only has reference to the properties that are associated with it at the time of detachment
 * (or construction) and is not traversable or mutable.
 *
 * @author Stephen Mallette (http://stephen.genoprime.com)
 * @author Marko A. Rodriguez (http://markorodriguez.com)
 */
public class DetachedVertex extends DetachedElement<Vertex> implements Vertex {

    private final transient Vertex.Iterators iterators = new Iterators();

    private DetachedVertex() { }

    public DetachedVertex(final Object id, final String label, final Map<String, Object> properties, final Map<String, Object> hiddenProperties) {
        super(id, label);

        // todo: wrong?
        if (null != properties) this.properties.putAll(convertToDetachedVertexProperties(properties, false));
        if (null != hiddenProperties) this.properties.putAll(convertToDetachedVertexProperties(hiddenProperties, true));
    }

    protected DetachedVertex(final Object id, final String label) {
        super(id, label);
    }

    private DetachedVertex(final Vertex vertex) {
        super(vertex);

        vertex.iterators().properties().forEachRemaining(p -> putToList(p.key(), p instanceof DetachedVertexProperty ? p : new DetachedVertexProperty(p, this)));
        vertex.iterators().hiddens().forEachRemaining(p -> putToList(Graph.Key.hide(p.key()), p instanceof DetachedVertexProperty ? p : new DetachedVertexProperty(p, this)));
    }

    @Override
    public <V> VertexProperty<V> property(final String key, final V value) {
        throw new UnsupportedOperationException("Detached elements are readonly: " + this);
    }

    @Override
    public <V> VertexProperty<V> property(final String key) {
        if (this.properties.containsKey(key)) {
            final List<VertexProperty> list = (List) this.properties.get(key);
            if (list.size() > 1)
                throw Vertex.Exceptions.multiplePropertiesExistForProvidedKey(key);
            else
                return list.get(0);
        } else
            return VertexProperty.<V>empty();
    }

    @Override
    public Edge addEdge(final String label, final Vertex inVertex, final Object... keyValues) {
        throw new UnsupportedOperationException("Detached vertices do not store edges: " + this);
    }

    @Override
    public String toString() {
        return StringFactory.vertexString(this);
    }

    @Override
    public GraphTraversal<Vertex, Vertex> start() {
        throw new UnsupportedOperationException("Detached vertices cannot be traversed: " + this);
    }

    @Override
    public Vertex attach(final Vertex hostVertex) {
        if (!hostVertex.equals(this))
            throw new IllegalStateException("The host vertex must be the vertex trying to be attached: " +
                    hostVertex.id() + "!=" + this.id() + " or " +
                    hostVertex.id().getClass() + "!=" + this.id().getClass());
        return hostVertex;
    }

    @Override
    public Vertex attach(final Graph hostGraph) {
        return hostGraph.v(this.id);
    }

    public static DetachedVertex detach(final Vertex vertex) {
        if (null == vertex) throw Graph.Exceptions.argumentCanNotBeNull("vertex");
        if (vertex instanceof DetachedVertex) throw new IllegalArgumentException("Vertex is already detached");
        return new DetachedVertex(vertex);
    }

    public static Vertex addTo(final Graph graph, final DetachedVertex detachedVertex) {
        final Vertex vertex = graph.addVertex(T.id, detachedVertex.id(), T.label, detachedVertex.label());
        detachedVertex.properties.entrySet().forEach(kv ->
            kv.getValue().forEach(property -> {
                final VertexProperty vertexProperty = (VertexProperty) property;
                final List<Object> propsOnProps = new ArrayList<>();
                vertexProperty.iterators().hiddens().forEachRemaining(h -> {
                    propsOnProps.add(Graph.Key.hide(h.key()));
                    propsOnProps.add(h.value());
                });

                vertexProperty.iterators().properties().forEachRemaining(h -> {
                    propsOnProps.add(h.key());
                    propsOnProps.add(h.value());
                });

                propsOnProps.add(T.id);
                propsOnProps.add(vertexProperty.id());
                vertex.property(kv.getKey(), property.value(), propsOnProps.toArray());
            })
        );

        return vertex;
    }

    @Override
    public Vertex.Iterators iterators() {
        return this.iterators;
    }

    private Map<String, List<Property>> convertToDetachedVertexProperties(final Map<String, Object> properties, final boolean hide) {
        return properties.entrySet().stream()
                .map(entry -> Pair.with(hide ? Graph.Key.hide(entry.getKey()) : entry.getKey(), ((List<Map<String, Object>>) entry.getValue()).stream()
                                .map(m -> (Property) new DetachedVertexProperty(m.get("id"), (String) m.get("label"), hide ? Graph.Key.hide(entry.getKey()) : entry.getKey(),
                                        m.get("value"), (Map<String, Object>) m.get("properties"), (Map<String, Object>) m.get("hidden"), this))
                                .collect(Collectors.toList()))
                ).collect(Collectors.toMap(Pair::getValue0, Pair::getValue1));
    }

    protected class Iterators extends DetachedElement<Vertex>.Iterators implements Vertex.Iterators, Serializable {

        @Override
        public <V> Iterator<VertexProperty<V>> properties(final String... propertyKeys) {
            return (Iterator) super.properties(propertyKeys);
        }

        @Override
        public <V> Iterator<VertexProperty<V>> hiddens(final String... propertyKeys) {
            return (Iterator) super.hiddens(propertyKeys);
        }

        @Override
        public GraphTraversal<Vertex, Edge> edges(final Direction direction, final int branchFactor, final String... labels) {
            throw new UnsupportedOperationException();
        }

        @Override
        public GraphTraversal<Vertex, Vertex> vertices(final Direction direction, final int branchFactor, final String... labels) {
            throw new UnsupportedOperationException();
        }
    }

    private void putToList(final String key, final Property p) {
        if (!this.properties.containsKey(key))
            this.properties.put(key, new ArrayList<>());

        ((List) this.properties.get(key)).add(p);
    }
}

@@ -1764,32 +1764,8 @@
 );%0A%0A
-        // todo: wrong?%0A
     

Remove todo comment.