/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     [email protected]

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
package com.blazegraph.gremlin.structure;

import java.util.Iterator;
import java.util.stream.Stream;

import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Property;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.util.StringFactory;

import com.bigdata.rdf.model.BigdataBNode;
import com.bigdata.rdf.model.BigdataStatement;
import com.bigdata.rdf.model.BigdataURI;
import com.bigdata.rdf.model.BigdataValueFactory;
import com.blazegraph.gremlin.structure.BlazeGraphFeatures.Graph;
import com.blazegraph.gremlin.util.CloseableIterator;

/**
 * Concrete edge implementation for Blazegraph.
 * <p>
 * Edge existence is represented as two triples as follows:
 * </p>
 * <pre>
 * {@code
 *     :fromId :edgeId :toId .
 *     <<:fromId :edgeId :toId>> rdf:type :label .
 * }
 * </pre>
 * <p>
 * Edge properties are represented as follows:
 * </p>
 * <pre>
 * {@code
 *     <<:fromId :edgeId :toId>> :key "val" .
 * }
 * </pre>
 * 
 * @author mikepersonick
 */
public class BlazeEdge extends AbstractBlazeElement implements Edge, BlazeReifiedElement {

    /**
     * The sid (reified statement) for this edge.
     */
    private final BigdataBNode sid;

    /**
     * The from and to vertices.
     */
    private final Vertices vertices;
    
    /**
     * Construct an instance.
     */
    BlazeEdge(final BlazeGraph graph, final BigdataStatement stmt, 
            final BigdataURI label, final BlazeVertex from, final BlazeVertex to) {
        super(graph, stmt.getPredicate(), label);
        final BigdataValueFactory rdfvf = graph.rdfValueFactory();
        this.sid = rdfvf.createBNode(stmt);
        this.vertices = new Vertices(from, to);
    }
    
    /**
     * Construct an instance without vertices.  Used by 
     * {@link BlazeGraph#bulkLoad(Graph)}
     */
    BlazeEdge(final BlazeGraph graph, final BigdataStatement stmt, 
            final BigdataURI label) {
        super(graph, stmt.getPredicate(), label);
        final BigdataValueFactory rdfvf = graph.rdfValueFactory();
        this.sid = rdfvf.createBNode(stmt);
        this.vertices = null;
    }
    
    @Override
    public String toString() {
        return StringFactory.edgeString(this);
    }
    
    /**
     * Return the sid (reified statement) for this edge.
     */
    @Override
    public BigdataBNode rdfId() {
        return sid;
    }
    
    /**
     * @see Edge#remove()
     * @see BlazeGraph#remove(BlazeReifiedElement)
     */
    @Override
    public void remove() {
        graph.remove(this);
    }

    /**
     * Strengthen return type to {@link BlazeProperty}.
     */
    @Override
    public <V> BlazeProperty<V> property(String key, V val) {
        return BlazeReifiedElement.super.property(key, val);
    }

    /**
     * Strength return type to {@link CloseableIterator}.  You MUST close this
     * iterator when finished.
     */
    @Override
    public <V> CloseableIterator<Property<V>> properties(final String... keys) {
        return BlazeReifiedElement.super.properties(keys);
    }
    
    /**
     * @see  Edge#outVertex()
     */
    @Override
    public Vertex outVertex() {
        return vertices.from;
    }

    /**
     * @see Edge#inVertex()
     */
    @Override
    public Vertex inVertex() {
        return vertices.to;
    }

    /**
     * @see Edge#bothVertices()
     */
    @Override
    public Iterator<Vertex> bothVertices() {
        return this.vertices(Direction.BOTH);
    }

    /**
     * @see Edge#vertices(Direction)
     */
    @Override
    public Iterator<Vertex> vertices(final Direction direction) {
        final BlazeVertex from = vertices.from;
        final BlazeVertex to = vertices.to;
        final Stream<BlazeVertex> stream;
        switch (direction) {
            case OUT:
                stream = Stream.of(from); break;
            case IN:
                stream = Stream.of(to); break;
            default:
                stream = Stream.of(from, to); break;
        }
        return stream.map(Vertex.class::cast).iterator();
    }

    private static class Vertices {
        private final BlazeVertex from, to;
        public Vertices(final BlazeVertex from, final BlazeVertex to) {
            this.from = from;
            this.to = to;
        }
    }
    
}