/**
 * Copyright (c) 2012-2014 "Indexia Technologies, ltd."
 * 
 * This file is part of Antiquity.
 * 
 * Antiquity is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package co.indexia.antiquity.graph;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.google.common.base.Preconditions;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Element;
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.GraphQuery;
import com.tinkerpop.blueprints.KeyIndexableGraph;
import com.tinkerpop.blueprints.Parameter;
import com.tinkerpop.blueprints.TransactionalGraph;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.util.wrappers.WrappedGraphQuery;
import com.tinkerpop.blueprints.util.wrappers.event.listener.GraphChangedListener;
import com.tinkerpop.blueprints.util.wrappers.id.IdGraph.IdFactory;
import co.indexia.antiquity.graph.blueprints.EventGraph;
import co.indexia.antiquity.graph.identifierBehavior.GraphIdentifierBehavior;
import co.indexia.antiquity.range.Range;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A {@link Graph} wrapper that adds versioning capabilities.
 * 
 * <p>
 * Antiquity maintains two separated disconnected graphs for high efficiency and
 * to separate the dirt historic data from the latest ('active') graph data.
 * </p>
 * The <i>active</i> graph contains the latest, up-to-date data, without
 * versioning support, in other words, the 'active' graph data looks exactly
 * like the graph as if Antiquity was not used at all.
 * <p>
 * 
 * </p>
 * The <i>historic</i> graph contains the elements history, every active element
 * has at least one corresponding historic element, each modification
 * (add/modify/delete) of elements in the active graph will produce new
 * corresponding historic element which stores the change.
 * 
 * use {@link #getHistoricGraph()} for a read-only {@link Graph} implementation
 * to query the historic data.
 * <p>
 * 
 * @see Graph
 * @see TransactionalGraph
 * @see NonTransactionalVersionedGraph
 * @see HistoricVersionedGraph
 */
public abstract class ActiveVersionedGraph<T extends KeyIndexableGraph, V extends Comparable<V>>
        extends VersionedGraphBase<T, V> implements GraphChangedListener, KeyIndexableGraph {
    Logger log = LoggerFactory.getLogger(ActiveVersionedGraph.class);

    /**
     * Wrapper graph with events support.
     */
    EventGraph<T> eventGraph;

    /**
     * Vertex ID factory. (required only if {@link #isNaturalIds()} is true)
     */
    private IdFactory vertexIdFactory;

    /**
     * Edge ID factory. (required only if {@link #isNaturalIds()} is true)
     */
    private IdFactory edgeIdFactory;

    /**
     * Reference to the historic graph instance
     */
    private HistoricVersionedGraph<T, V> hGraph;

    /**
     * Create an instance of this class.
     * 
     * @param baseGraph The base graph to wrap with versioning support.
     * @param identifierBehavior The graph identifier behavior implementation.
     * @param queue if true events will be queued, required for transactional
     *        implementation.
     */
    protected ActiveVersionedGraph(T baseGraph, GraphIdentifierBehavior<V> identifierBehavior, Boolean queue) {
        this(baseGraph, identifierBehavior, null, null, null, queue);
    }

    /**
     * Create an instance of {@link ActiveVersionedGraph} with the specified
     * underline {@link Graph}.
     * 
     * @param baseGraph the underline base graph
     * @param identifierBehavior the graph identifier behavior implementation
     * @param conf the configuration instance of this instance.
     * @param vertexIdFactory the {@link IdFactory} of new vertices.
     * @param edgeIdFactory the {@link IdFactory} of new edges.
     * @param queue if true events will be queued, required for transactional
     *        implementation.
     */
    public ActiveVersionedGraph(T baseGraph, GraphIdentifierBehavior<V> identifierBehavior, Configuration conf,
            final IdFactory vertexIdFactory, final IdFactory edgeIdFactory, boolean queue) {
        super(baseGraph, identifierBehavior, conf);

        this.eventGraph = new EventGraph<T>(baseGraph, queue);
        this.eventGraph.addListener(this);
        this.hGraph = new HistoricVersionedGraph<T, V>(baseGraph, identifierBehavior, conf);

        if (vertexIdFactory == null) this.vertexIdFactory = new DefaultIdFactory();
        if (edgeIdFactory == null) this.edgeIdFactory = new DefaultIdFactory();
    }


    // Graph general methods
    // --------------------------------------------------------------
    @Override
    public void validate() {
        super.validate();

        if (isNaturalIds()) {
            // TODO: This is needed?
            // ensure base graph supports vertex/edge,key indices
            Preconditions.checkState((getFeatures().supportsKeyIndices),
                    "With natural IDs enabled, The underline database must support vertex/edge,key indices.");

            this.vertexIdFactory = null == this.vertexIdFactory ? new DefaultIdFactory() : vertexIdFactory;
            this.edgeIdFactory = null == this.edgeIdFactory ? new DefaultIdFactory() : edgeIdFactory;
        }
    }

    /**
     * Initialize versioned graph data, this method expected to be invoked only
     * once for the whole life of the graph database.
     */
    public void init() {
        Vertex vertex = null;
        try {
            vertex = getRootVertex();
        } catch (IllegalStateException e) {
            log.info("Initializing graph...");
        }

        if (vertex != null) {
            return;
        }

        // Create the natural ID key indices
        KeyIndexableGraph keyIndexedGraph = getBaseGraph();
        if (!keyIndexedGraph.getIndexedKeys(Vertex.class).contains(VEProps.NATURAL_VERTEX_ID_PROP_KEY)) {
            keyIndexedGraph.createKeyIndex(VEProps.NATURAL_VERTEX_ID_PROP_KEY, Vertex.class);
        }

        if (!keyIndexedGraph.getIndexedKeys(Edge.class).contains(VEProps.NATURAL_EDGE_ID_PROP_KEY)) {
            keyIndexedGraph.createKeyIndex(VEProps.NATURAL_EDGE_ID_PROP_KEY, Edge.class);
        }

        //TODO: ROOT vertices should have a static unique known UUID for fast access
        Vertex historicRoot = utils.getNonEventableVertex(addActiveVertexInUnderline(null));
        historicRoot.setProperty(VEProps.ROOT_GRAPH_VERTEX_ID, VEProps.HISTORIC_ROOT_GRAPH_VERTEX_VALUE);
        historicRoot.setProperty(VEProps.HISTORIC_ELEMENT_PROP_KEY, true);

        Vertex activeRoot = utils.getNonEventableVertex(addActiveVertexInUnderline(null));
        activeRoot.setProperty(VEProps.ROOT_GRAPH_VERTEX_ID, VEProps.ACTIVE_ROOT_GRAPH_VERTEX_VALUE);
        activeRoot.setProperty(VEProps.HISTORIC_ELEMENT_PROP_KEY, false);

        if (getUneventableGraph() instanceof TransactionalGraph) {
            ((TransactionalGraph) getBaseGraph()).commit();
        }
    }


    /**
     * Return the unwrapped(Eventable)->unwrapped(The graph passed to {@link
     * this} to be wrapped) graph.
     * 
     * This is the concrete blueprints graph (e.g Neo4jGraph) wrapped by this
     * versioned graph.
     * 
     * This method is shell result the same instance as if
     * {@link #getBaseGraph()} was invoked.
     * 
     * @return The unwrapped->unwrapped graph.
     */
    public T getUneventableGraph() {
        return this.eventGraph.getBaseGraph();
    }

    /**
     * Return the underline eventable graph with the appropriate signature
     * 
     * @return An eventable graph
     */
    public EventGraph<T> getEventableGraph() {
        return this.eventGraph;
    }

    /**
     * Return the historic graph instance, used to query the historic graph.
     * 
     * @return A historic graph instance.
     */
    public HistoricVersionedGraph<T, V> getHistoricGraph() {
        return this.hGraph;
    }

    /**
     * Attach vertex to the corresponding {@link VEProps.GRAPH_TYPE} root vertex
     * 
     * @param vertex The vertex to be attached
     */
    public void attachToRoot(final Vertex vertex) {
        Vertex root = getRootVertex(utils.getElementType(vertex));
        addEdge(null, vertex, root, VEProps.ROOT_OF_EDGE_LABEL);
    }

    @Override
    public Vertex getRootVertex() {
        return getRootVertex(VEProps.GRAPH_TYPE.ACTIVE);
    }


    // Graph operation overrides
    // --------------------------------------------------------------
    @Override
    public Vertex addVertex(final Object id) {
        ActiveVersionedVertex vertex = addActiveVertexInUnderline(id);
        getEventableGraph().onVertexAdded(vertex.getEventableVertex());

        return vertex;
    }

    @Override
    public Vertex getVertex(final Object id) {
        Preconditions.checkNotNull(id, "id must be set.");

        Vertex vertex;
        if (isNaturalIds()) {
            vertex = getSingleVertex(VEProps.NATURAL_VERTEX_ID_PROP_KEY, id);
        } else {
            vertex = getEventableGraph().getVertex(id);
        }

        if (vertex != null) {
            if (vertex instanceof ActiveVersionedVertex) {
                return vertex;
            } else {
                utils.ensureActiveType(vertex);
                return new ActiveVersionedVertex<V>(vertex, this);
            }
        } else {
            log.debug("Vertex with [{}] was not found.", id);
            return null;
        }
    }

    @Override
    public Iterable<Vertex> getVertices() {
        return query().vertices();
    }

    @Override
    public Iterable<Vertex> getVertices(final String key, final Object value) {
        // We need to make Query to filter by key/value first to get a tinest
        // scope.
        // See DefaultQuery#hasContainers
        return query().has(key, value).vertices();
    }

    @Override
    public Edge addEdge(final Object id, final Vertex outVertex, final Vertex inVertex, final String label) {
        validateNewId(id, Edge.class);

        utils.ensureActiveType(outVertex);
        utils.ensureActiveType(inVertex);

        ActiveVersionedEdge<V> edge =
                addActiveEdgeInUnderline(id, (ActiveVersionedVertex<V>) outVertex, (ActiveVersionedVertex<V>) inVertex,
                        label);
        getEventableGraph().onEdgeAdded(edge);

        return edge;
    }

    @Override
    public Edge getEdge(Object id) {
        Preconditions.checkNotNull(id, "id must be set.");

        Edge edge;
        if (isNaturalIds()) {
            edge = ElementUtils.getSingleElement(this, VEProps.NATURAL_EDGE_ID_PROP_KEY, id, Edge.class);
        } else {
            edge = getEventableGraph().getEdge(id);
        }

        if (edge != null) {
            if (edge instanceof ActiveVersionedElement) {
                return edge;
            } else {
                utils.ensureActiveType(edge);
                return new ActiveVersionedEdge<V>(edge, this);
            }
        } else {
            log.debug("Edge with [{}] was not found.", id);
            return null;
        }
    }

    @Override
    public Iterable<Edge> getEdges() {
        return query().edges();
    }

    @Override
    public Iterable<Edge> getEdges(final String key, final Object value) {
        // TODO: Consider forbidding retrieving edges by internal keys
        // (especially NATURAL_EDGE_ID_PROP_KEY), otherwise throw exception.

        // We need to make Query to filter by key/value first to get a tinest
        // scope.
        // See DefaultQuery#hasContainers
        return query().has(key, value).edges();
    }

    @Override
    public void removeVertex(final Vertex vertex) {
        utils.ensureActiveType(vertex);
        getEventableGraph().removeVertex(((ActiveVersionedVertex) vertex).getEventableVertex());
    }

    @Override
    public void removeEdge(final Edge edge) {
        utils.ensureActiveType(edge);
        getEventableGraph().removeEdge(((ActiveVersionedEdge) edge).getEventableEdge());
    }

    @Override
    public GraphQuery query() {
        final ActiveVersionedGraph<T, V> ag = this;
        return new WrappedGraphQuery(getBaseGraph().query()) {
            @Override
            public Iterable<Edge> edges() {
                return new ActiveVersionedEdgeIterable<V>(getQuery().edges(), ag);
            }

            @Override
            public Iterable<Vertex> vertices() {
                return new ActiveVersionedVertexIterable<V>(getQuery().vertices(), ag);
            }

            public GraphQuery getQuery() {
                return this.query.has(VEProps.HISTORIC_ELEMENT_PROP_KEY, false);
            }
        };
    }

    @Override
    public <T extends Element> void dropKeyIndex(final String key, final Class<T> elementClass) {
        if (isNaturalIds()) {
            if (key.equals(VEProps.NATURAL_VERTEX_ID_PROP_KEY) || key.equals(VEProps.NATURAL_EDGE_ID_PROP_KEY)) {
                throw new IllegalArgumentException(String.format("Key [%s] is reserved and cannot be dropped.",
                        VEProps.NATURAL_VERTEX_ID_PROP_KEY));
            }
        }

        getEventableGraph().getBaseGraph().dropKeyIndex(key, elementClass);
    }

    @Override
    public <T extends Element> void createKeyIndex(final String key, final Class<T> elementClass,
            final Parameter... indexParameters) {
        if (key.equals(VEProps.NATURAL_VERTEX_ID_PROP_KEY) || key.equals(VEProps.NATURAL_EDGE_ID_PROP_KEY)) {
            throw new IllegalArgumentException(String.format("Index key [%s] is reserved and cannot be created",
                    VEProps.NATURAL_VERTEX_ID_PROP_KEY));
        }

        getEventableGraph().getBaseGraph().createKeyIndex(key, elementClass, indexParameters);
    }

    @Override
    public <T extends Element> Set<String> getIndexedKeys(final Class<T> elementClass) {
        final Set<String> keys = new HashSet<String>();
        keys.addAll(getEventableGraph().getBaseGraph().getIndexedKeys(elementClass));
        keys.remove(VEProps.NATURAL_VERTEX_ID_PROP_KEY);
        keys.remove(VEProps.NATURAL_EDGE_ID_PROP_KEY);
        return keys;
    }

    // Graph identifier methods
    // --------------------------------------------------------------

    /**
     * Get the next graph version.
     * 
     * @see #allocateNextGraphVersion(Comparable)
     * @param allocate whether to allocate the next version or not.
     * @return The next version of the graph
     */
    protected V getNextGraphVersion(boolean allocate) {
        V nextGraphVersion = identifierBehavior.getNextGraphVersion(getLatestGraphVersion());
        if (allocate) {
            allocateNextGraphVersion(nextGraphVersion);
        }

        // TODO: Unlock the configuration vertex if allocate=false, otherwise
        // unlock should occur during transaction
        // commit
        return nextGraphVersion;
    }

    /**
     * Allocate (persist) the specified next version in the graph in the
     * configuration vertex.
     * 
     * @param nextVersion The next version to allocate.
     */
    protected void allocateNextGraphVersion(V nextVersion) {
        // TODO: Unlock the configuration vertex
        getRootVertex(VEProps.GRAPH_TYPE.HISTORIC).setProperty(VEProps.LATEST_GRAPH_VERSION_PROP_KEY, nextVersion);
    }

    /**
     * Ensure that the new specified ID is valid.
     * 
     * Validation currently tests whether the specified ID already exist.
     * 
     * @param newId The new ID to verify
     * @param elementType The element type (Edge or Vertex)
     */
    public <T extends Element> void validateNewId(Object newId, Class<T> elementType) {
        if (isNaturalIds()) {
            boolean isVertex = (elementType.isAssignableFrom(Vertex.class));
            if (newId != null && (isVertex ? getVertex(newId) : getEdge(newId)) != null) {
                throw new IllegalArgumentException(String.format("%s with the given ID [%s] already exists.",
                        elementType.getSimpleName(), newId));
            }
        }
    }

    // Methods used by events responses
    // --------------------------------------------------------------
    /**
     * Version vertices in the graph.
     * 
     * Per created active vertex, create a corresponding historical one.
     * 
     * @param version The graph version that created the specified vertices
     * @param vertices The vertices to be versioned.
     */
    protected void versionAddedVertices(V version, Iterable<Vertex> vertices) {
        for (Vertex v : vertices) {
            utils.ensureActiveType(v);

            ActiveVersionedVertex<V> active = new ActiveVersionedVertex<V>(v, this);

            // Add corresponding historic vertex
            HistoricVersionedVertex<V> hv = addHistoricVertex(active, version, getMaxPossibleGraphVersion());
            utils.syncActiveAndLatestHistoric(active, hv);
            active.getRaw().setProperty(VEProps.REF_TO_LATEST_HISTORIC_ID_KEY, hv.getHardId());

            if (conf.getPrivateVertexHashEnabled()) {
                utils.setPrivateHash(active);
            }
        }
    }

    /**
     * Version modified vertex.
     * 
     * @param latestGraphVersion The latest graph version
     * @param newVersion The new version to be committed
     * @param vertex The modified vertex
     * @param oldValues The old properties values of the modified vertex
     * 
     * @return The historical created vertex
     */
    protected Vertex versionModifiedVertex(V latestGraphVersion, V newVersion, Vertex vertex,
            Map<String, Object> oldValues) {
        ActiveVersionedVertex<V> active = new ActiveVersionedVertex<V>(vertex, this);
        HistoricVersionedVertex<V> latestHV = getHistoricGraph().getLatestHistoricRevision(active);

        // Note: order matters here, we need latestHV before we override it.
        HistoricVersionedVertex<V> newHV =
                addHistoricVertex(active, utils.getStartVersion(latestHV), latestGraphVersion);
        Set<String> excludedProps = new HashSet<String>();
        excludedProps.add(VEProps.NATURAL_VERTEX_ID_PROP_KEY);
        ElementUtils.copyProps(latestHV.getRaw(), newHV.getRaw(), excludedProps);

        // here it's safe to modify latest historic vertex.
        utils.setStartVersion(latestHV, newVersion);
        utils.syncActiveAndLatestHistoric(active, latestHV);

        addHistoricalVertexInChain(latestGraphVersion, newVersion, active, latestHV, newHV);

        if (conf.getPrivateVertexHashEnabled()) {
            utils.setPrivateHash(active);
        }

        return newHV;
    }

    /**
     * Version removed vertices
     * 
     * @param nextVer next version (to be committed) of the graph
     * @param maxVer current max version of the graph
     * @param vertices A map of removed vertices where key=removed vertex &
     *        value=A map of the removed vertex's properties.
     */
    protected void versionRemovedVertices(V nextVer, V maxVer, Map<Vertex, Map<String, Object>> vertices) {
        for (Map.Entry<Vertex, Map<String, Object>> v : vertices.entrySet()) {
            // we can't touch the vertex as it's deleted already
            // utils.ensureActiveType(v.getKey());
            // ActiveVersionedVertex<V> av = new
            // ActiveVersionedVertex<V>(v.getKey(), this);
            if (!v.getValue().containsKey(VEProps.REF_TO_LATEST_HISTORIC_ID_KEY)) {
                throw new IllegalStateException("Expected removed vertx to contain key: "
                        + VEProps.REF_TO_LATEST_HISTORIC_ID_KEY);
            }

            HistoricVersionedVertex<V> hv =
                    getHistoricGraph().getLatestHistoricRevision(
                            (String) v.getValue().get(VEProps.REF_TO_LATEST_HISTORIC_ID_KEY));

            // Remove ALL vertex's edges, must be invoked on getRaw to avoid
            // filtering.
            for (Edge e : hv.getRaw().getEdges(Direction.BOTH)) {
                if (utils.isInternal(e)) {
                    continue;
                }

                utils.ensureHistoricType(e);
                e.setProperty(VEProps.REMOVED_PROP_KEY, nextVer);
                e.setProperty(VEProps.VALID_MAX_VERSION_PROP_KEY, maxVer);
            }


            hv.getRaw().setProperty(VEProps.REMOVED_PROP_KEY, nextVer);
            hv.getRaw().setProperty(VEProps.VALID_MAX_VERSION_PROP_KEY, maxVer);
        }
    }

    /**
     * Version added edges in the graph.
     * 
     * Per created active edge, create a corresponding historical one.
     * 
     * @param version The graph version that created the specified edges
     * @param edges The edges to be versioned.
     */
    protected void versionAddedEdges(V version, Iterable<Edge> edges) {
        Range<V> range = Range.range(version, identifierBehavior.getMaxPossibleGraphVersion());

        for (Edge e : edges) {
            utils.ensureActiveType(e);

            ActiveVersionedEdge<V> ae = (ActiveVersionedEdge<V>) e;
            HistoricVersionedEdge ve =
                    addHistoricEdge((ActiveVersionedEdge<V>) e, version, getMaxPossibleGraphVersion(), null);
            utils.setVersion(ve, range);
            utils.getNonEventableElement(e).setProperty(VEProps.REF_TO_LATEST_HISTORIC_ID_KEY, ve.getHardId());
            utils.syncActiveAndLatestHistoric(ae, ve);
        }
    }

    /**
     * Version removed edges
     * 
     * @param nextVer next version (to be committed) of the graph
     * @param maxVer current max version of the graph
     * @param edges A map of removed edges where key=removed edge & value=A map
     *        of the removed edge's properties.
     */
    protected void versionRemovedEdges(V nextVer, V maxVer, Map<Edge, Map<String, Object>> edges) {
        for (Map.Entry<Edge, Map<String, Object>> v : edges.entrySet()) {
            // we can't touch the edge as it's deleted already
            // utils.ensureActiveType(e);
            // ActiveVersionedEdge<V> av = new ActiveVersionedEdge<V>(e, this);
            // HistoricVersionedEdge<V> he =
            // getHistoricGraph().getLatestHistoricRevision(av);

            if (!v.getValue().containsKey(VEProps.REF_TO_LATEST_HISTORIC_ID_KEY)) {
                throw new IllegalStateException("Expected removed vertx to contain key: "
                        + VEProps.REF_TO_LATEST_HISTORIC_ID_KEY);
            }

            HistoricVersionedEdge<V> he =
                    getHistoricGraph()
                            .getEdgeByHardId((String) v.getValue().get(VEProps.REF_TO_LATEST_HISTORIC_ID_KEY));


            he.getRaw().setProperty(VEProps.REMOVED_PROP_KEY, nextVer);
            he.getRaw().setProperty(VEProps.VALID_MAX_VERSION_PROP_KEY, maxVer);
        }
    }

    /**
     * Version modified edge.
     * 
     * @param latestGraphVersion The latest graph version
     * @param newVersion The new version to be committed
     * @param edge The modified edge
     * @param oldValues The old properties values of the modified edge
     * 
     * @return The historical created edge
     */
    protected Edge versionModifiedEdge(V latestGraphVersion, V newVersion, Edge edge, Map<String, Object> oldValues) {
        ActiveVersionedEdge<V> active = new ActiveVersionedEdge<V>(edge, this);
        HistoricVersionedEdge<V> latestHE = getHistoricGraph().getLatestHistoricRevision(active);
        utils.syncActiveAndLatestHistoric(active, latestHE);


        return latestHE;
    }


    // Versioning helper methods
    // --------------------------------------------------------------
    /**
     * Add a plain vertex to the graph.
     * 
     * Note: this is not an eventable vertex thus no versioning will occur if
     * this element will be modified.
     * 
     * @param id The id of the vertex to set, if null, new id will be generated.
     * @return plain created vertex.
     */
    private Vertex addPlainVertexToGraph(Object id) {
        validateNewId(id, Vertex.class);
        final Vertex vertex;
        Object idVal = id == null ? vertexIdFactory.createId() : id;

        if (isNaturalIds()) {
            // we create an id just in case the underline doesn't ignore
            // supplied ids
            // and cannot recieve null but we ignore this id in the logic.
            vertex = getUneventableGraph().addVertex(vertexIdFactory.createId());
            vertex.setProperty(VEProps.NATURAL_VERTEX_ID_PROP_KEY, idVal);
        } else {
            vertex = getUneventableGraph().addVertex(idVal);
        }

        return vertex;
    }

    /**
     * Add an active vertex to the underline.
     * 
     * @param id specify explicit id, if null id will be generated, id may be
     *        ignored if underline ignores supplied ids.
     * @return The created active vertex.
     */
    private ActiveVersionedVertex addActiveVertexInUnderline(Object id) {
        Vertex vertex = addPlainVertexToGraph(id);
        vertex.setProperty(VEProps.HISTORIC_ELEMENT_PROP_KEY, false);

        return new ActiveVersionedVertex<V>(vertex, this);
    }

    /**
     * Add a corresponding historic vertex to the specified active vertex.
     * 
     * Note: This method does not copy the properties from the active vertex to
     * the historic one.
     * 
     * @param a active vertex to add corresponding historic vertex for
     * @param startVersion the start version the historic vertex
     * @param endVersion the end version the historic vertex
     * @return an added historic vertex.
     */
    private HistoricVersionedVertex addHistoricVertex(ActiveVersionedVertex a, V startVersion, V endVersion) {
        Vertex vertex = addPlainVertexToGraph(null);
        vertex.setProperty(VEProps.REF_TO_ACTIVE_ID_KEY, a.getId());
        vertex.setProperty(VEProps.HISTORIC_ELEMENT_PROP_KEY, true);

        // FIXME: Range is right?
        HistoricVersionedVertex hv =
                new HistoricVersionedVertex(vertex, this.getHistoricGraph(), Range.range(startVersion, startVersion));
        utils.setStartVersion(hv, startVersion);
        utils.setEndVersion(hv, endVersion);

        return hv;
    }

    /**
     * Add the historical vertex in the vertex versions chain
     * 
     * @param latestGraphVersion The latest graph version
     * @param newVersion The new version to be committed
     * @param activeModifiedVertex The active vertex that was modified.
     * @param latestHistoricVertex Latest historical vertex (the previous
     *        version of the newly created historic vertex)
     * @param newHistoricVertex The new historic vertex to be added in the chain
     */
    private void addHistoricalVertexInChain(V latestGraphVersion, V newVersion,
            ActiveVersionedVertex<V> activeModifiedVertex, HistoricVersionedVertex<V> latestHistoricVertex,
            HistoricVersionedVertex newHistoricVertex) {
        newHistoricVertex.getRaw().setProperty(VEProps.REF_TO_LATEST_HISTORIC_ID_KEY, latestHistoricVertex.getHardId());
        newHistoricVertex.getRaw().setProperty(VEProps.VALID_MAX_VERSION_PROP_KEY, latestGraphVersion);

        Edge prevEdge =
                ElementUtils.getSingleElement(latestHistoricVertex.getRaw().getEdges(Direction.OUT,
                        VEProps.PREV_VERSION_LABEL));

        if (prevEdge != null) {
            Vertex inVertex = prevEdge.getVertex(Direction.IN);
            getUneventableGraph().removeEdge(prevEdge);

            getBaseGraph().addEdge(edgeIdFactory.createId(), (Vertex) newHistoricVertex.getRaw(), inVertex,
                    VEProps.PREV_VERSION_LABEL);

        }

        getBaseGraph().addEdge(edgeIdFactory.createId(), latestHistoricVertex.getRaw(),
                (Vertex) newHistoricVertex.getRaw(), VEProps.PREV_VERSION_LABEL);
    }

    /**
     * Add the historical edge in the edge versions chain
     * 
     * @param latestGraphVersion The latest graph version
     * @param newVersion The new version to be committed
     * @param activeModifiedEdge The active edge that was modified.
     * @param latestHistoricEdge Latest historical edge (the previous version of
     *        the newly created historic edge)
     * @param newHistoricEdge The new historic edge to be added in the chain
     */
    private void addHistoricalEdgeInChain(V latestGraphVersion, V newVersion,
            ActiveVersionedEdge<V> activeModifiedEdge, HistoricVersionedEdge<V> latestHistoricEdge,
            HistoricVersionedEdge newHistoricEdge) {

        throw new UnsupportedOperationException(
                "This method is unsupported as version edges properties was configured to be disabled.");
    }



    /**
     * Add a plain edge to the graph.
     * 
     * Note: this is not an eventable vertex.
     * 
     * @param id The id of the edge to set, if null, new id will be generated.
     * @return plain created edge.
     */
    private Edge addPlainEdgeToGraph(Object id, Vertex out, Vertex in, String label) {
        validateNewId(id, Edge.class);
        final Edge edge;

        // TODO: Ensure we get raw vertices here.

        Object idVal = id == null ? edgeIdFactory.createId() : id;
        if (isNaturalIds()) {
            // we create an id just in case the underline doesn't ignore
            // supplied ids
            // and cannot recieve null but we ignore this id in the logic.
            edge = getUneventableGraph().addEdge(edgeIdFactory.createId(), out, in, label);
            edge.setProperty(VEProps.NATURAL_EDGE_ID_PROP_KEY, idVal);
        } else {
            edge = getUneventableGraph().addEdge(idVal, out, in, label);
        }

        return edge;
    }

    /**
     * Add an active edge to the underline.
     * 
     * @param id specify explicit id, if null id will be generated, id may be
     *        ignored if underline ignores supplied ids.
     * @param out the out vertex of the edge.
     * @param in the in vrtex of the edge.
     * @param label the label of the edge.
     * @return the created active edge.
     */
    private ActiveVersionedEdge addActiveEdgeInUnderline(Object id, ActiveVersionedVertex<V> out,
            ActiveVersionedVertex<V> in, String label) {
        Edge edge = addPlainEdgeToGraph(id, out.getRaw(), in.getRaw(), label);
        edge.setProperty(VEProps.HISTORIC_ELEMENT_PROP_KEY, false);

        return new ActiveVersionedEdge<V>(edge, this);
    }

    /**
     * Add a corresponding historic edge to the specified active edge
     * 
     * @param a active edge to add corresponding historic edge for
     * @param startVersion the start version the historic edge
     * @param endVersion the end version the historic edge
     * @param oldValues The old (before modification) values of the specified
     *        active edge.
     * @return an added historic edge.
     */
    private HistoricVersionedEdge<V> addHistoricEdge(ActiveVersionedEdge<V> a, V startVersion, V endVersion,
            Map<String, Object> oldValues) {

        ActiveVersionedVertex<V> out = (ActiveVersionedVertex<V>) a.getVertex(Direction.OUT);
        ActiveVersionedVertex<V> in = (ActiveVersionedVertex<V>) a.getVertex(Direction.IN);

        if (out == null) {
            throw new IllegalStateException("Expected out vertex to exist.");
        }

        if (in == null) {
            throw new IllegalStateException("Expected in vertex to exist.");
        }

        HistoricVersionedVertex<V> hOut = getHistoricGraph().getLatestHistoricRevision(out);
        HistoricVersionedVertex<V> hIn = getHistoricGraph().getLatestHistoricRevision(in);

        Edge edge = addPlainEdgeToGraph(null, hOut.getRaw(), hIn.getRaw(), a.getLabel());
        edge.setProperty(VEProps.REF_TO_ACTIVE_ID_KEY, a.getId());
        edge.setProperty(VEProps.HISTORIC_ELEMENT_PROP_KEY, true);

        // FIXME: Range is right?
        HistoricVersionedEdge<V> hv =
                new HistoricVersionedEdge(edge, this.getHistoricGraph(), Range.range(startVersion, startVersion));
        utils.setStartVersion(hv, startVersion);
        utils.setEndVersion(hv, endVersion);

        return hv;
    }

    /**
     * Graph Builder.
     */
    public abstract static class ActiveVersionedGraphBuilder<T extends KeyIndexableGraph, V extends Comparable<V>> {
        Boolean init = false;
        T baseGraph;
        GraphIdentifierBehavior<V> identifierBehavior;
        Configuration conf;
        IdFactory vertexIdFactory;
        IdFactory edgeIdFactory;

        public ActiveVersionedGraphBuilder(T baseGraph, GraphIdentifierBehavior<V> identifierBehavior) {
            this.baseGraph = baseGraph;
            this.identifierBehavior = identifierBehavior;
        }


        public ActiveVersionedGraphBuilder init(Boolean init) {
            this.init = init;
            return this;
        }

        public ActiveVersionedGraphBuilder conf(Configuration conf) {
            this.conf = conf;
            return this;
        }

        public ActiveVersionedGraphBuilder vertexIdFactory(IdFactory vertexIdFactory) {
            this.vertexIdFactory = vertexIdFactory;
            return this;
        }

        public ActiveVersionedGraphBuilder edgeIdFactory(IdFactory edgeIdFactory) {
            this.edgeIdFactory = edgeIdFactory;
            return this;
        }

        public ActiveVersionedGraph<T, V> build() {
            ActiveVersionedGraph<T, V> instance = createInstance();
            if (init) {
                instance.init();
            }

            instance.validate();

            return instance;
        }

        protected abstract ActiveVersionedGraph<T, V> createInstance();
    }

    public static class ActiveVersionedTransactionalGraphBuilder<T extends KeyIndexableGraph & TransactionalGraph, V extends Comparable<V>>
            extends ActiveVersionedGraphBuilder<T, V> {
        public ActiveVersionedTransactionalGraphBuilder(T baseGraph, GraphIdentifierBehavior<V> identifierBehavior) {
            super(baseGraph, identifierBehavior);
        }

        public TransactionalVersionedGraph<T, V> build() {
            return (TransactionalVersionedGraph<T, V>) super.build();
        }

        protected TransactionalVersionedGraph<T, V> createInstance() {
            TransactionalVersionedGraph<T, V> instance =
                    new TransactionalVersionedGraph<T, V>(baseGraph, identifierBehavior, conf, vertexIdFactory,
                            edgeIdFactory);
            return instance;
        }
    }

    public static class ActiveVersionedNonTransactionalGraphBuilder<T extends KeyIndexableGraph, V extends Comparable<V>>
            extends ActiveVersionedGraphBuilder<T, V> {
        public ActiveVersionedNonTransactionalGraphBuilder(T baseGraph, GraphIdentifierBehavior<V> identifierBehavior) {
            super(baseGraph, identifierBehavior);
        }

        public NonTransactionalVersionedGraph<T, V> build() {
            return (NonTransactionalVersionedGraph<T, V>) super.build();
        }

        protected NonTransactionalVersionedGraph<T, V> createInstance() {
            return new NonTransactionalVersionedGraph<T, V>(baseGraph, identifierBehavior, conf, vertexIdFactory,
                    edgeIdFactory);
        }
    }
}