func movieHandler(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	query := req.URL.Path[len("/movie/"):]
	cypher := `
	MATCH
		(movie:Movie {title:{title}})
	OPTIONAL MATCH
		(movie)<-[r]-(person:Person)
	WITH
		movie.title as title,
		collect({name:person.name, job:head(split(lower(type(r)),'_')), role:r.roles}) as cast
	LIMIT 1
	UNWIND cast as c
	RETURN title, c.name as name, c.job as job, c.role as role`

	db, err := driver.NewDriver().OpenNeo(neo4jURL)
	if err != nil {
		log.Println("error connecting to neo4j:", err)
		w.WriteHeader(500)
		w.Write([]byte("An error occurred connecting to the DB"))
		return
	}
	defer db.Close()

	data, _, _, err := db.QueryNeoAll(cypher, map[string]interface{}{"title": query})
	if err != nil {
		log.Println("error querying movie:", err)
		w.WriteHeader(500)
		w.Write([]byte("An error occurred querying the DB"))
		return
	} else if len(data) == 0 {
		w.WriteHeader(404)
		return
	}

	movie := Movie{
		Title: data[0][0].(string),
		Cast:  make([]Person, len(data)),
	}

	for idx, row := range data {
		movie.Cast[idx] = Person{
			Name: row[1].(string),
			Job:  row[2].(string),
		}
		if row[3] != nil {
			movie.Cast[idx].Role = interfaceSliceToString(row[3].([]interface{}))
		}
	}

	err = json.NewEncoder(w).Encode(movie)
	if err != nil {
		log.Println("error writing movie response:", err)
		w.WriteHeader(500)
		w.Write([]byte("An error occurred writing response"))
	}
}
func searchHandler(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	query := req.URL.Query()["q"][0]
	cypher := `
	MATCH
		(movie:Movie)
	WHERE
		movie.title =~ {query}
	RETURN
		movie.title as title, movie.tagline as tagline, movie.released as released`

	db, err := driver.NewDriver().OpenNeo(neo4jURL)
	if err != nil {
		log.Println("error connecting to neo4j:", err)
		w.WriteHeader(500)
		w.Write([]byte("An error occurred connecting to the DB"))
		return
	}
	defer db.Close()

	param := "(?i).*" + query + ".*"
	data, _, _, err := db.QueryNeoAll(cypher, map[string]interface{}{"query": param})
	if err != nil {
		log.Println("error querying search:", err)
		w.WriteHeader(500)
		w.Write([]byte("An error occurred querying the DB"))
		return
	} else if len(data) == 0 {
		w.WriteHeader(404)
		return
	}

	results := make([]MovieResult, len(data))
	for idx, row := range data {
		results[idx] = MovieResult{
			Movie{
				Title:    row[0].(string),
				Tagline:  row[1].(string),
				Released: int(row[2].(int64)),
			},
		}
	}

	err = json.NewEncoder(w).Encode(results)
	if err != nil {
		log.Println("error writing search response:", err)
		w.WriteHeader(500)
		w.Write([]byte("An error occurred writing response"))
	}
}
func main() {
	driver := bolt.NewDriver()
	conn, _ := driver.OpenNeo("bolt://localhost:7687")
	defer conn.Close()

	// Start by creating a node
	result, _ := conn.ExecNeo("CREATE (n:NODE {foo: {foo}, bar: {bar}})", map[string]interface{}{"foo": 1, "bar": 2.2})
	numResult, _ := result.RowsAffected()
	fmt.Printf("CREATED ROWS: %d\n", numResult) // CREATED ROWS: 1

	// Lets get the node
	data, rowsMetadata, _, _ := conn.QueryNeoAll("MATCH (n:NODE) RETURN n.foo, n.bar", nil)
	fmt.Printf("COLUMNS: %#v\n", rowsMetadata["fields"].([]interface{}))    // COLUMNS: n.foo,n.bar
	fmt.Printf("FIELDS: %d %f\n", data[0][0].(int64), data[0][1].(float64)) // FIELDS: 1 2.2

	// oh cool, that worked. lets blast this baby and tell it to run a bunch of statements
	// in neo concurrently with a pipeline
	results, _ := conn.ExecPipeline([]string{
		"MATCH (n:NODE) CREATE (n)-[:REL]->(f:FOO)",
		"MATCH (n:NODE) CREATE (n)-[:REL]->(b:BAR)",
		"MATCH (n:NODE) CREATE (n)-[:REL]->(z:BAZ)",
		"MATCH (n:NODE) CREATE (n)-[:REL]->(f:FOO)",
		"MATCH (n:NODE) CREATE (n)-[:REL]->(b:BAR)",
		"MATCH (n:NODE) CREATE (n)-[:REL]->(z:BAZ)",
	}, nil, nil, nil, nil, nil, nil)
	for _, result := range results {
		numResult, _ := result.RowsAffected()
		fmt.Printf("CREATED ROWS: %d\n", numResult) // CREATED ROWS: 2 (per each iteration)
	}

	data, _, _, _ = conn.QueryNeoAll("MATCH (n:NODE)-[:REL]->(m) RETURN m", nil)
	for _, row := range data {
		fmt.Printf("NODE: %#v\n", row[0].(graph.Node)) // Prints all nodes
	}

	result, _ = conn.ExecNeo(`MATCH (n) DETACH DELETE n`, nil)
	numResult, _ = result.RowsAffected()
	fmt.Printf("Rows Deleted: %d", numResult) // Rows Deleted: 13
}
func main() {
	driver := bolt.NewDriver()
	conn, err := driver.OpenNeo("bolt://localhost:7687")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	// Here we prepare a new statement. This gives us the flexibility to
	// cancel that statement without any request sent to Neo
	stmt, err := conn.PrepareNeo("CREATE (n:NODE {foo: {foo}, bar: {bar}})")
	if err != nil {
		panic(err)
	}

	// Executing a statement just returns summary information
	result, err := stmt.ExecNeo(map[string]interface{}{"foo": 1, "bar": 2.2})
	if err != nil {
		panic(err)
	}
	numResult, err := result.RowsAffected()
	if err != nil {
		panic(err)
	}
	fmt.Printf("CREATED ROWS: %d\n", numResult) // CREATED ROWS: 1

	// Closing the statment will also close the rows
	stmt.Close()

	// Lets get the node. Once again I can cancel this with no penalty
	stmt, err = conn.PrepareNeo("MATCH (n:NODE) RETURN n.foo, n.bar")
	if err != nil {
		panic(err)
	}

	// Even once I get the rows, if I do not consume them and close the
	// rows, Neo will discard and not send the data
	rows, err := stmt.QueryNeo(nil)
	if err != nil {
		panic(err)
	}

	// This interface allows you to consume rows one-by-one, as they
	// come off the bolt stream. This is more efficient especially
	// if you're only looking for a particular row/set of rows, as
	// you don't need to load up the entire dataset into memory
	data, _, err := rows.NextNeo()
	if err != nil {
		panic(err)
	}

	// This query only returns 1 row, so once it's done, it will return
	// the metadata associated with the query completion, along with
	// io.EOF as the error
	_, _, err = rows.NextNeo()
	if err != io.EOF {
		panic(err)
	}
	fmt.Printf("COLUMNS: %#v\n", rows.Metadata()["fields"].([]interface{})) // COLUMNS: n.foo,n.bar
	fmt.Printf("FIELDS: %d %f\n", data[0].(int64), data[1].(float64))       // FIELDS: 1 2.2

	stmt.Close()

	// Here we prepare a new pipeline statement for running multiple
	// queries concurrently
	pipeline, err := conn.PreparePipeline(
		"MATCH (n:NODE) CREATE (n)-[:REL]->(f:FOO)",
		"MATCH (n:NODE) CREATE (n)-[:REL]->(b:BAR)",
		"MATCH (n:NODE) CREATE (n)-[:REL]->(z:BAZ)",
		"MATCH (n:NODE) CREATE (n)-[:REL]->(f:FOO)",
		"MATCH (n:NODE) CREATE (n)-[:REL]->(b:BAR)",
		"MATCH (n:NODE) CREATE (n)-[:REL]->(z:BAZ)",
	)
	if err != nil {
		panic(err)
	}

	pipelineResults, err := pipeline.ExecPipeline(nil, nil, nil, nil, nil, nil)
	if err != nil {
		panic(err)
	}

	for _, result := range pipelineResults {
		numResult, _ := result.RowsAffected()
		fmt.Printf("CREATED ROWS: %d\n", numResult) // CREATED ROWS: 2 (per each iteration)
	}

	err = pipeline.Close()
	if err != nil {
		panic(err)
	}

	stmt, err = conn.PrepareNeo("MATCH path=(n:NODE)-[:REL]->(m) RETURN path")
	if err != nil {
		panic(err)
	}

	rows, err = stmt.QueryNeo(nil)
	if err != nil {
		panic(err)
	}

	// Here we loop through the rows until we get the metadata object
	// back, meaning the row stream has been fully consumed
	for err == nil {
		var row []interface{}
		row, _, err = rows.NextNeo()
		if err != nil && err != io.EOF {
			panic(err)
		} else if err != io.EOF {
			fmt.Printf("PATH: %#v\n", row[0].(graph.Path)) // Prints all paths
		}
	}

	stmt.Close()

	result, _ = conn.ExecNeo(`MATCH (n) DETACH DELETE n`, nil)
	fmt.Println(result)
	numResult, _ = result.RowsAffected()
	fmt.Printf("Rows Deleted: %d", numResult) // Rows Deleted: 13
}
func graphHandler(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	limits := req.URL.Query()["limit"]
	limit := 50
	var err error
	if len(limits) > 0 {
		limit, err = strconv.Atoi(limits[0])
		if err != nil {
			w.WriteHeader(400)
			w.Write([]byte("Limit must be an integer"))
		}
	}

	cypher := `
	MATCH
		(m:Movie)<-[:ACTED_IN]-(a:Person)
	RETURN
		m.title as movie, collect(a.name) as cast
	LIMIT
		{limit}`

	db, err := driver.NewDriver().OpenNeo(neo4jURL)
	if err != nil {
		log.Println("error connecting to neo4j:", err)
		w.WriteHeader(500)
		w.Write([]byte("An error occurred connecting to the DB"))
		return
	}
	defer db.Close()

	stmt, err := db.PrepareNeo(cypher)
	if err != nil {
		log.Println("error preparing graph:", err)
		w.WriteHeader(500)
		w.Write([]byte("An error occurred querying the DB"))
		return
	}
	defer stmt.Close()

	rows, err := stmt.QueryNeo(map[string]interface{}{"limit": limit})
	if err != nil {
		log.Println("error querying graph:", err)
		w.WriteHeader(500)
		w.Write([]byte("An error occurred querying the DB"))
		return
	}

	d3Resp := D3Response{}
	row, _, err := rows.NextNeo()
	for row != nil && err == nil {
		title := row[0].(string)
		actors := interfaceSliceToString(row[1].([]interface{}))
		d3Resp.Nodes = append(d3Resp.Nodes, Node{Title: title, Label: "movie"})
		movIdx := len(d3Resp.Nodes) - 1
		for _, actor := range actors {
			idx := -1
			for i, node := range d3Resp.Nodes {
				if actor == node.Title && node.Label == "actor" {
					idx = i
					break
				}
			}
			if idx == -1 {
				d3Resp.Nodes = append(d3Resp.Nodes, Node{Title: actor, Label: "actor"})
				d3Resp.Links = append(d3Resp.Links, Link{Source: len(d3Resp.Nodes) - 1, Target: movIdx})
			} else {
				d3Resp.Links = append(d3Resp.Links, Link{Source: idx, Target: movIdx})
			}
		}
		row, _, err = rows.NextNeo()
	}

	if err != nil && err != io.EOF {
		log.Println("error querying graph:", err)
		w.WriteHeader(500)
		w.Write([]byte("An error occurred querying the DB"))
		return
	} else if len(d3Resp.Nodes) == 0 {
		w.WriteHeader(404)
		return
	}

	err = json.NewEncoder(w).Encode(d3Resp)
	if err != nil {
		log.Println("error writing graph response:", err)
		w.WriteHeader(500)
		w.Write([]byte("An error occurred writing response"))
	}
}