package org.neo4j.tutor.security;

import static org.neo4j.tutor.security.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.TraversalPosition;
import org.neo4j.api.core.Traverser;
import org.neo4j.api.core.Traverser.Order;
import org.neo4j.impl.event.EventData;
import org.neo4j.impl.event.EventManager;

public class File
{
	// property key for the file name
	public static final String NAME = "filename";
	// property key for security property
	public static final String SECURITY = "secsetting";
	
	private Node underlyingNode;
	
	File( Node underlyingNode )
	{
		this.underlyingNode = underlyingNode;
	}
	
	public Node getUnderlyingNode()
	{
		return underlyingNode;
	}
	
	public String getFileName()
	{
		return (String) underlyingNode.getProperty( NAME );
	}
	
	public void changeFileName( String newName )
	{
		underlyingNode.setProperty( NAME, newName );
	}
	
	public File getParent()
	{
		Iterator<Relationship> relItr = underlyingNode.getRelationships( FILE, 
			Direction.INCOMING ).iterator();
		if ( relItr.hasNext() )
		{
			return new File( relItr.next().getStartNode() );
		}
		return null;
	}
	
	public void addChild( File child )
	{
		if ( child.getParent() != null )
		{
			throw new IllegalArgumentException( "Child already has a parent" );
		}
		underlyingNode.createRelationshipTo( child.getUnderlyingNode(), FILE );
	}

	public void disconnectFromParent()
	{
		Iterator<Relationship> relItr = underlyingNode.getRelationships( FILE, 
			Direction.INCOMING ).iterator();
		if ( relItr.hasNext() )
		{
			relItr.next().delete();
		}
	}
	
	public Iterable<File> getChildren()
	{
		return getChildren( true );
	}
	
	private Iterable<File> getChildren( boolean filterSecurity )
	{
		ReturnableEvaluator rte = ReturnableEvaluator.ALL_BUT_START_NODE;
		if ( filterSecurity )
		{
			rte = new ReturnableEvaluator() // security evaluator
			{
				public boolean isReturnableNode( 
					TraversalPosition currentPosition )
				{
					if ( currentPosition.depth() == 0 )
					{
						return false;
					}
					// ask security manager if we get access to this file
					return EventManager.getManager().generateProActiveEvent( 
						SecurityEvent.ACCESS, 
						new EventData( currentPosition.currentNode() ) );
				}
			};
		}
		return new FileIterable( 
			underlyingNode.traverse( Order.BREADTH_FIRST, 
			StopEvaluator.DEPTH_ONE, rte, FILE, Direction.OUTGOING ) );
	}
	
	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()
	{
		disconnectFromParent();
		for ( File file : getChildren( false ) )
		{
			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();
	}
}
