package eu.dnetlib.r2d2.neo4j.dao;

import org.apache.log4j.Logger;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
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;
import eu.dnetlib.r2d2.neo4j.util.DepthStopEvaluator;

@SuppressWarnings("deprecation")
public class Neo4jRLEntryDao extends Neo4JDao<Neo4jRLEntry> implements RLEntryDao {
	private static Logger logger = Logger.getLogger(Neo4jRLEntryDao.class);

	public Neo4jRLEntryDao() {
		super(Relationships._ENTRIES, Relationships._ENTRY, Neo4jRLEntry.class);
	}

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

	@Override
	public Iterable<Neo4jRLEntry> getReadingListEntries(String rlId) {
		return this.findRelatedNodesIncoming(rlId, Relationships.ENTRY);
	}
	
	@Override
	public Iterable<Neo4jRLEntry> getEntriesForItem(String itemId) {
//		return this.findRelatedNodesOutgoing(itemId, Relationships.ITEM_ENTRY);
		Node entryNode = this.getNode(itemId);
		
		Traverser tr = entryNode.traverse(
				Order.DEPTH_FIRST, StopEvaluator.DEPTH_ONE,
				new ReturnableEvaluator() {

					public boolean isReturnableNode(TraversalPosition pos) {
						if (pos.depth() == 1) {
							Node n = pos.currentNode();
							String type = (String) n.getProperty(Neo4jBean.PREFIX, null);
							
							if (type != null && type.equals(Neo4jRLEntry.class.getName()))
								return true;
						}
						return false;
					}
				},
				Relationships.IO_ENTRY, Direction.OUTGOING);
		
		return new BeanIterable(tr);
	}
	
	
	@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);
							
							// don't return dangling garbage entries
							if(entry.getKind().equals(kind))
								return currentNode.hasRelationship(Relationships.IO_ENTRY);
							
							return false;
						} 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);
		
		Traverser tr = ioNode.traverse(
				Order.DEPTH_FIRST, new DepthStopEvaluator(2),
				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;
					}
				},
				Relationships.IO_ENTRY, Direction.OUTGOING);
		
		Neo4jRLEntry entry = Iterables.getOnlyElement(new BeanIterable(tr), null);
		
		return entry;
	}

	@Override
	public void setInformationObject(String entryId, String ioId) {
		logger.debug("Setting io " + ioId + " as info object of entry " + entryId);
		
		Node entryNode = this.getNode(entryId);
		Node ioNode = this.getNode(ioId);

		// deleting all possible information objects from he entry
		for (Relationship rel:entryNode.getRelationships(Relationships.IO_ENTRY, Direction.INCOMING)) {
			if (rel.getEndNode().getId() != ioNode.getId())
				rel.delete();
		}
		
		this.createRelationship(ioId, entryId, Relationships.IO_ENTRY);
	}
	
	@Override
	public Double getInformationObjectRating(String ioId) {
		Node itemNode = this.getNode(ioId);
		Traverser tr = itemNode.traverse(
				Order.DEPTH_FIRST, StopEvaluator.DEPTH_ONE,
				new ReturnableEvaluator() {
					public boolean isReturnableNode(TraversalPosition pos) {
						if (pos.depth() == 1) {
							// we are at an entry
							Node n = pos.currentNode();
							
							// search for the containing rl
							for (Relationship rel:n.getRelationships(Relationships.ENTRY)) {
								Node rl = rel.getOtherNode(n);
								
								// check if rl is someone's MVL
								if (Iterables.size(rl.getRelationships(Relationships.USER_MVL)) > 0)
									return true;
							}
						}
						
						return false;
					}
				},
				Relationships.IO_ENTRY, Direction.OUTGOING);
		
		int count = 0;
		double sum = 0;
		
		for (Node n:tr) {
			Neo4jRLEntry entry = new Neo4jRLEntry();
			entry.setNode(n);
			
			if (entry.getRating() != null) {
				double rating = entry.getRating();
				
				logger.debug("Entry rating: " + rating);
				
				sum += rating;
				count++;
			}
		}
		
		if (count > 0)
			return sum/count;
		else
			return null;
	}
}