package eu.dnetlib.r2d2.neo4j.dao;

import org.junit.Ignore;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.ReturnableEvaluator;
import org.neo4j.graphdb.StopEvaluator;
import org.neo4j.graphdb.TraversalPosition;
import org.neo4j.graphdb.Traverser;
import org.neo4j.graphdb.Traverser.Order;

import com.google.common.collect.Iterables;

import eu.dnetlib.r2d2.neo4j.Neo4jBean;
import eu.dnetlib.r2d2.neo4j.domain.Neo4jInformationObject;
import eu.dnetlib.r2d2.neo4j.domain.Neo4jRLEntry;
import eu.dnetlib.r2d2.neo4j.domain.Neo4jReadingList;
import eu.dnetlib.r2d2.neo4j.domain.Relationships;
import eu.dnetlib.r2d2.neo4j.domain.Neo4jRLEntry.Kind;

public class Neo4jRLEntryDao extends Neo4JDao<Neo4jRLEntry> implements RLEntryDao {

	public Neo4jRLEntryDao() {
		super(Relationships._RL_ENTRIES, Relationships._RL_ENTRY, Neo4jRLEntry.class.getName());
	}

	@Override
	protected Neo4jRLEntry createBean() {
		return new Neo4jRLEntry();
	}

	@Override
	public Iterable<Neo4jRLEntry> getReadingListEntries(String rlId) {
		return this.findRelatedNodes(rlId, Relationships.ENTRY);
	}
	
	@Override
	public Iterable<Neo4jRLEntry> getEntriesForItem(String itemId) {
		return this.findRelatedNodesOutgoing(itemId, Relationships.ITEM_ENTRY);
	}
	
	
	@Override
	public void addEntryToReadingList(String entryId, String rlId) {
		this.createRelationship(entryId, rlId, Relationships.ENTRY);
	}

	@Override
	public void removeEntryFromReadingList(String entryId, String rlId) {
		this.removeRelationship(entryId, rlId, Relationships.ENTRY);
	}

	@Override
	public Iterable<Neo4jRLEntry> getReadingListEntries(String rlId, final Kind kind) {
		Node rlNode = this.getNode(rlId);
		
		Traverser tr = rlNode.traverse(
				Order.DEPTH_FIRST, StopEvaluator.DEPTH_ONE,

				new ReturnableEvaluator() {
					@Override
					public boolean isReturnableNode(TraversalPosition pos) {
						if (!pos.isStartNode()) {
							Node currentNode = pos.currentNode();
							
							// assuming current node is an Entry node
							Neo4jRLEntry entry = new Neo4jRLEntry();
							
							entry.setNode(currentNode);
							
							return entry.getKind().equals(kind);
						} else {
							return false;
						}
					}
				},
				Relationships.ENTRY, Direction.INCOMING);
		
		return new BeanIterable(tr);
	}

	@Override
	public Neo4jRLEntry getEntry(final Neo4jReadingList rl, final Neo4jInformationObject io) {
		return getEntry(rl.getId(), io.getId());
	}
	
	@Override
	public Neo4jRLEntry getEntry(final String rlId, final String IOId) {
		Node ioNode = this.getNode(IOId);
		
		// Cannot traverse with 2 possible initial relationship types,
		// so creating one traverser per possible IO type.
		Traverser tr = findEntry(rlId, ioNode, Relationships.ITEM_ENTRY);
		Neo4jRLEntry entry = Iterables.getOnlyElement(new BeanIterable(tr), null);
		
		if (entry == null) {
			tr = findEntry(rlId, ioNode, Relationships.RL_ENTRY);
			entry = Iterables.getOnlyElement(new BeanIterable(tr), null);
		}
		
		return entry;
	}

	private Traverser findEntry(final String rlId, Node ioNode,
			RelationshipType relType) {
		Traverser tr = ioNode.traverse(Order.DEPTH_FIRST, 
				new StopEvaluator() {
				// a simple depth 2 StopEvaluator
					@Override
					public boolean isStopNode(TraversalPosition pos) {
						return (pos.depth() > 1);
					}
				},
				new ReturnableEvaluator() {
					@Override
					public boolean isReturnableNode(TraversalPosition pos) {
						Node node = pos.currentNode();

						if (pos.depth() == 1 && node.hasRelationship(Relationships.ENTRY, Direction.OUTGOING)) {
							// node is an entry
							
							for (Relationship rel:node.getRelationships(Relationships.ENTRY, Direction.OUTGOING)) {
								Node rlNode = rel.getEndNode();
								
								if (rlNode.hasProperty(Neo4jBean.ID) && rlNode.getProperty(Neo4jBean.ID).equals(rlId))
									return true;
							}
						}
						
						return false;
					}
				},
				relType, Direction.OUTGOING);
		return tr;
	}
}