package marcelocf.janusgraph;

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.Edge;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

import static marcelocf.janusgraph.Schema.*;
import static org.apache.tinkerpop.gremlin.process.traversal.Order.decr;
import static org.apache.tinkerpop.gremlin.process.traversal.P.eq;
import static org.apache.tinkerpop.gremlin.process.traversal.P.without;

public class QueryRunner {


  private static final Logger LOGGER = LoggerFactory.getLogger(QueryRunner.class);

  public static final String userVertex = "userVertex";
  public static final String postsEdge = "postsEdge";
  public static final String statusUpdateVertex = "statusUpdateEdge";


  ///////////////////////
  // Instance Methods //
  /////////////////////

  private final GraphTraversalSource g;
  private final String userName;

  public QueryRunner(GraphTraversalSource traversalSource, String userName) throws Exception {
    this.g = traversalSource;
    this.userName = userName;
  }

  public void close() throws Exception {
    g.close();
  }

  public GraphTraversal<Vertex, Vertex> getUser() {
    return g.V().hasLabel(Schema.USER).has(USER_NAME, eq(userName));
  }

  public GraphTraversal<Vertex, Vertex> getStatusUpdate() {
    return getUser().out(Schema.POSTS);
  }

  public GraphTraversal<Vertex, Vertex> getFollowedUsers() {
    return getUser().out(Schema.FOLLOWS);
  }

  public GraphTraversal<Vertex, Vertex> getFollowers() {
    return getUser().aggregate("ignore").in(Schema.FOLLOWS);
  }

  public GraphTraversal<Vertex, Vertex> getFollowersOfFollowedUsers() {
    return getFollowedUsers().in(Schema.FOLLOWS);
  }

  public GraphTraversal<Vertex, Vertex> getFollowRecommendation(){
    return getUser().
        aggregate("me").
        aggregate("ignore").
        out(Schema.FOLLOWS).
        aggregate("ignore").
        cap("me").
        unfold().
        in(Schema.FOLLOWS).
        where(without("ignore"));
  }

  public GraphTraversal<Vertex, Vertex> getTimeline(int limit){
    return getUser().
        aggregate("users").
        out(FOLLOWS).
        aggregate("users").
        cap("users").
        unfold().
        outE(POSTS).
        order().by(CREATED_AT, decr).
        limit(limit).
        inV();
  }

  public GraphTraversal<Vertex, Map<String, Map<String,Object>>> getTimeline2(int limit){
    return getUser().
        aggregate("users").
        out(FOLLOWS).
        aggregate("users").
        cap("users").
        unfold().
        as(userVertex).
        outE(POSTS).
        as(postsEdge).
        order().by(CREATED_AT, decr).
        limit(limit).
        inV().
        as(statusUpdateVertex).
        select(userVertex, postsEdge, statusUpdateVertex);
  }


  public void runQueries() {

    LOGGER.info("Getting user:");
    print(getUser());

    LOGGER.info("Getting status updates:");
    print(getStatusUpdate());

    LOGGER.info("Getting followed users");
    print(getFollowedUsers());

    LOGGER.info("Getting followers users");
    print(getFollowers());

    LOGGER.info("Getting followers of followed users");
    print(getFollowersOfFollowedUsers());

    LOGGER.info("Getting recommendations of users to follow");
    print(getFollowRecommendation());

    LOGGER.info("Printing timeline");
    print(getTimeline(100));

    LOGGER.info("Printing timeline");
    printTimeline(getTimeline2(100));

    System.exit(0);
  }

  /**
   * Just a simple print method for every property returned from a vertex traversal
   * @param traversal
   */
  private static void print(GraphTraversal<Vertex, Vertex> traversal) {
    GraphTraversal<Vertex, Map<String, Object>> valueMap = traversal.valueMap(true);
    int count = 0;

    for (GraphTraversal<Vertex, Map<String, Object>> it = valueMap; it.hasNext(); ) {
      Map<String, Object> item = it.next();
      LOGGER.info(" {}: {} ", count++, item.toString());
    }
    LOGGER.info("Printed {} element(s)", count);
  }

  private static void printTimeline(GraphTraversal<Vertex, Map<String, Map<String, Object>>> traversal) {
    int count = 0;

    while (traversal.hasNext()) {
      Map<String, Map<String, Object>> item = traversal.next();
      Vertex user = (Vertex) item.get(userVertex);
      Edge posts = (Edge) item.get(postsEdge);
      Vertex statusUpdate = (Vertex) item.get(statusUpdateVertex);
      LOGGER.info(
          " {}: @{} {}: {}",
          count++,
          user.value(USER_NAME),
          posts.value(CREATED_AT),
          statusUpdate.value(CONTENT)
      );
    }
    LOGGER.info("Printed {} element(s)", count);
  }
}

@@ -354,16 +354,39 @@
 ctory;%0A%0A
+import java.util.Date;%0A
 import j
@@ -4714,16 +4714,32 @@
         
+formatTimestamp(
 posts.va
@@ -4753,16 +4753,17 @@
 ATED_AT)
+)
 ,%0A      
@@ -4801,32 +4801,32 @@
 %0A      );%0A    %7D%0A
-
     LOGGER.info(
@@ -4861,11 +4861,133 @@
 nt);%0A  %7D
+%0A%0A  private static String formatTimestamp(Long timestamp) %7B%0A    Date d = new Date(timestamp);%0A    return d.toString();%0A  %7D
 %0A%7D%0A

timestamps can be output as string