package org.neo4j.tutor.tree;

import static org.neo4j.tutor.tree.MyRelationshipTypes.FILE;

import java.util.AbstractCollection;
import java.util.Iterator;

import org.neo4j.api.core.Direction;
import org.neo4j.api.core.Node;
import org.neo4j.api.core.Relationship;
import org.neo4j.api.core.ReturnableEvaluator;
import org.neo4j.api.core.StopEvaluator;
import org.neo4j.api.core.Traverser;
import org.neo4j.api.core.Traverser.Order;

public class File
{
	// property key for the file name
	static final String NAME = "filename";
	
	private Node underlyingNode;
	
	File( Node underlyingNode )
	{
		this.underlyingNode = underlyingNode;
	}
	
	Node getUnderlyingNode()
	{
		return underlyingNode;
	}
	
	public String getFileName()
	{
		return (String) underlyingNode.getProperty( NAME );
	}
	
	public void changeFileName( String newName )
	{
		// --- TODO set the new file name property on the underlying node
		if ( newName == null )
		{
			throw new IllegalArgumentException( "Null name" );
		}
		underlyingNode.setProperty( NAME, newName );
	}
	
	public File getParent()
	{
		// --- TODO 
		// --- check if has parent then return it else return null
		Relationship rel = underlyingNode.getSingleRelationship( FILE, 
			Direction.INCOMING );
		if ( rel != null )
		{
			return new File( rel.getStartNode() );
		}
		return null;
	}
	
	public void addChild( File child )
	{
		// --- TODO
		// --- verify that child doesn't have parent, if it has parent throw
		// --- IllegalArgumentException
		// --- connect this file to child via FILE relationship
		if ( child == null )
		{
			throw new IllegalArgumentException( "Null child" );
		}
		if ( child.getParent() != null )
		{
			throw new IllegalArgumentException( "Child already has a parent" );
		}
		underlyingNode.createRelationshipTo( child.getUnderlyingNode(), FILE );
	}

	public void disconnectFromParent()
	{
		// --- TODO
		// --- delete FILE relationship between this and parent
		// --- if this file doesn't have a parent do nothing
		Relationship rel = underlyingNode.getSingleRelationship( FILE, 
			Direction.INCOMING );
		if ( rel != null )
		{
			rel.delete();
		}
	}
	
	public Iterable<File> getChildren()
	{
		// --- TODO 
		// --- create traverser that returns all files under this file with
		// --- a depth limit of 1
		// --- also the start node (this) shouldn't be returned
		// --- pass the traverser into a FileIterable instance and return
		return new FileIterable( 
			underlyingNode.traverse( Order.BREADTH_FIRST, 
			StopEvaluator.DEPTH_ONE,  
			ReturnableEvaluator.ALL_BUT_START_NODE, FILE, Direction.OUTGOING 
			) );
	}
	
	// use this to convert from a Iterable<Node> to Iterable<File>
	// ex. return new FileIterable( node.traverse( ... ) );
	private static class FileIterable extends AbstractCollection<File>
	{
		private Traverser fileTraverser;
		
		FileIterable( Traverser fileTraverser )
		{
			this.fileTraverser = fileTraverser;
		}
		
		@Override
		public Iterator<File> iterator()
		{
			return new Iterator<File>()
			{
				public boolean hasNext()
				{
					return fileTraverser.iterator().hasNext();
				}

				public File next()
				{
					return new File( fileTraverser.iterator().next() );
				}

				public void remove()
				{
					throw new UnsupportedOperationException();
				}
			};
		}

		@Override
		public int size()
		{
			throw new UnsupportedOperationException();		
		}
	}
	
	public void delete()
	{
		// --- TODO
		// --- if has parent disconnect from it
		// --- delete all children
		// --- delete this node
		disconnectFromParent();
		for ( File file : getChildren() )
		{
			file.delete();
		}
		underlyingNode.delete();
 	}
	
	@Override
	public boolean equals( Object o )
	{
		if ( !( o instanceof File ) )
		{
			return false;
		}
		return this.getUnderlyingNode().equals( 
			( ( File ) o ).getUnderlyingNode() );
	}

	@Override
	public int hashCode()
	{
		return underlyingNode.hashCode();
	}
}
