package org.neo4j.tutor.matrix;

import org.neo4j.api.core.Direction;
import org.neo4j.api.core.EmbeddedNeo;
import org.neo4j.api.core.Node;
import org.neo4j.api.core.Relationship;
import org.neo4j.api.core.RelationshipType;
import org.neo4j.api.core.ReturnableEvaluator;
import org.neo4j.api.core.StopEvaluator;
import org.neo4j.api.core.Transaction;
import org.neo4j.api.core.TraversalPosition;
import org.neo4j.api.core.Traverser;
import org.neo4j.api.core.Traverser.Order;
import org.neo4j.impl.core.NotFoundException;

public class MatrixWithShell
{
	private static EmbeddedNeo neo = null;
	
	private static enum MyRelationshipTypes implements RelationshipType
	{
		KNOWS,
		CODED_BY
	}
	
	public static void main( String args[] )
	{
		neo = new EmbeddedNeo( MyRelationshipTypes.class, "var/matrix-shell" );
		
		// add shutdown hook so ctrl+c makes a clean shutdown of neo
		Runtime.getRuntime().addShutdownHook( new ShutdownHook() );	
		
		// enable remote shell
		neo.enableRemoteShell( null );
		
		Transaction tx = Transaction.begin();
		try
		{
			Node neoNode = setupMatrix();
			System.out.println( "Thomas Anderson is " + neoNode );
			printFriends( neoNode );
			findHackers( neoNode );
			// mark tx as successful, meaning it will commit on finish()
			tx.success();
		}
		finally
		{
			// commit transaction, unless an exception has been raised and
			// we've therefore never reached tx.success()
			tx.finish();
		}
		// no shutdown, we wan't to be able to connect via shell
	}
	
	private static Node setupMatrix()
	{
		Node referenceNode = neo.getReferenceNode();
		if ( referenceNode.hasProperty( "thomas_id" ) )
		{
			long id = (Long) referenceNode.getProperty( "thomas_id" );
			try
			{
				return neo.getNodeById( id );
			}
			catch ( NotFoundException e )
			{ // ok we create the matrix again
			}
		}
		Node thomas = neo.createNode();
		referenceNode.setProperty( "thomas_id", thomas.getId() );
		thomas.setProperty( "name", "Thomas Andersson" );
		thomas.setProperty( "age", 29 );
		
		Node trinity = neo.createNode();
		trinity.setProperty( "name", "Trinity" );
		Relationship rel = thomas.createRelationshipTo( trinity, 
			MyRelationshipTypes.KNOWS );
		rel.setProperty( "age", "3 days" );
		
		Node morpheus = neo.createNode();
		morpheus.setProperty( "name", "Morpheus" );
		morpheus.setProperty( "rank", "Captain" );
		morpheus.setProperty( "occupation", "Total badass" );
		thomas.createRelationshipTo( morpheus, MyRelationshipTypes.KNOWS );		
		rel = morpheus.createRelationshipTo( trinity, 
			MyRelationshipTypes.KNOWS );
		rel.setProperty( "age", "12 years" );
		
		Node cypher = neo.createNode();
		cypher.setProperty( "name", "Cypher" );
		cypher.setProperty( "last name", "Reagan" );
		rel = morpheus.createRelationshipTo( cypher, 
			MyRelationshipTypes.KNOWS );
		rel.setProperty( "disclosure", "public" );
		
		Node smith = neo.createNode();
		smith.setProperty( "name", "Agent Smith" );
		smith.setProperty( "version", "1.0b" );
		smith.setProperty( "language", "C++" );
		rel = cypher.createRelationshipTo( smith, MyRelationshipTypes.KNOWS );
		rel.setProperty( "disclosure", "secret" );
		rel.setProperty( "age", "6 months" );
		
		Node architect = neo.createNode();
		architect.setProperty( "name", "The Architect" );
		smith.createRelationshipTo( architect, MyRelationshipTypes.CODED_BY );
		return thomas;
	}
	
	private static void printFriends( Node person )
	{
		System.out.println( person.getProperty( "name" ) + "'s friends:" );
		Traverser traverser = person.traverse( Order.BREADTH_FIRST, 
			StopEvaluator.END_OF_NETWORK, 
			ReturnableEvaluator.ALL_BUT_START_NODE, 
			MyRelationshipTypes.KNOWS, Direction.OUTGOING );
		for ( Node friend : traverser )
		{
			TraversalPosition position = traverser.currentPosition();
			System.out.println( "At depth " + position.depth() + 
				" => " + friend.getProperty( "name" ) );
		}
	}
	
	private static void findHackers( Node startNode )
	{
		System.out.println( "All hackers:" );
		Traverser traverser = startNode.traverse( Order.DEPTH_FIRST, 
			StopEvaluator.END_OF_NETWORK, new ReturnableEvaluator()
			{
				public boolean isReturnableNode( 
					TraversalPosition currentPosition )
				{
					Relationship rel = 
						currentPosition.lastRelationshipTraversed();
					if ( rel != null && rel.getType().equals( 
						MyRelationshipTypes.CODED_BY ) )
					{
						return true;
					}
					return false;
				}
			}, MyRelationshipTypes.CODED_BY, Direction.OUTGOING, 
			MyRelationshipTypes.KNOWS, Direction.OUTGOING );
		for ( Node hacker : traverser )
		{
			TraversalPosition position = traverser.currentPosition();
			System.out.println( "At depth " + position.depth() + 
				" => " + hacker.getProperty( "name" ) );
		}
	}

	private static class ShutdownHook extends Thread
	{
		ShutdownHook()
		{
		}
		
		public synchronized void run()
		{
			neo.shutdown();
		}
	}
}
