# http://docs.neo4j.org/chunked/snapshot/rest-api-traverse.html#rest-api-traversal-returning-nodes-below-a-certain-depth
try:
    import simplejson as json
except ImportError:
    import json

from neo4jrestclient import client
from neo4jrestclient.request import Request
from neo4jrestclient.request import NotFoundError
from neo4jrestclient.request import StatusException

class Order(object):
    BREADTH_FIRST = "breadth_first"
    DEPTH_FIRST = "depth_first"

class Uniqueness(object):
    NODE_GLOBAL = "node_global"
    NONE = "none"
    RELATIONSHIP_GLOBAL = "relationship_global"
    NODE_PATH = "node_path"
    RELATIONSHIP_PATH =  "relationship_path"

class RelationshipDirection(object):
    ALL = "all"
    INCOMING = "in"
    OUTGOING = "out"

class Filters(object):
    """Filters answer the question (return true/false) 
    Evaluation.INCLUDE_AND_CONTINUE
    Evaluation.EXCLUDE_AND_CONTINUE
    """
    ALL = {"language":"builtin", "name":"all"}
    ALL_BUT_START_NODE = {"language":"builtin", "name":"all_but_start_node"}

class PruneEvaluators(object):
    """PruneEvaluators answer the question (return true/false) 
    Evaluation.INCLUDE_AND_PRUNE
    Evaluation.EXCLUDE_AND_PRUNE
    """
    pass


class Traverser(object):
    NODE = "node"
    RELATIONSHIP = "relationship"
    PATH = "path"
    FULLPATH = "fullpath"

    def __init__(self, start_node, data):
        self._data = data
        self._endpoint = "http://localhost:7474/db/data/node/" + str(start_node.id) + "/traverse"
        self._cache = {}

    def request(self, return_type):
        try:
            return self._cache[return_type]
        except KeyError:
            response, content =  Request().post(self._endpoint + '/' + return_type, data=self._data)
            
            if response.status == 200:
                results_list = json.loads(content)
                self._cache[return_type] = results_list
                return results_list
            elif response.status == 404:
                raise NotFoundError(response.status, "Node or relationship " \
                                                         "not found")

            raise StatusException(response.status, "Invalid data sent")

    @property
    def nodes(self):
        results = self.request(Traverser.NODE)
        return client.Iterable(client.Node, results, "self")

    @property
    def relationships(self):
        results = self.request(Traverser.RELATIONSHIP)
        return client.Iterable(client.Relationship, results, "self")

    @property 
    def fullpaths(self):
        raise NotImplementedError()

    def __iter__(self):
        results = self.request(Traverser.PATH)
        return client.Iterable(client.Path, results, "self")


class TraversalDescription(object):
    """https://github.com/neo4j/community/blob/master/kernel/src/main/java/org/neo4j/graphdb/traversal/TraversalDescription.java"""

    def __init__(self):
        self._data = {}
        self.uniqueness(Uniqueness.NODE_GLOBAL)
        self.max_depth(1)

    def uniqueness(self, value):
        self._data["uniqueness"] = value
        return self

    def filter(self, value):
        try:
            value["language"]
            self._data["return_filter"] = value
        except KeyError:
            self._data["return_filter"] = {"language":"javascript", "body":value}
        return self

    def prune(self, value, language="javascript"):
        try:
            value["language"]
            self._data["prune_evaluator"] = value
        except KeyError:
            self._data["prune_evaluator"] = {"language":language, "body":value}

    def order(self, value):
        self._data["order"] = value
        return self

    def depthFirst(self, value):
        self.order(Order.DEPTH_FIRST)
        return self

    def breadthFirst(self, value):
        self.order(Order.BREADTH_FIRST)
        return self

    def relationships(self, name, direction=RelationshipDirection.ALL):
        self._data["relationships"] = []
        self.relationships_append(name, direction)
        self.relationships = self.relationships_append
        return self

    def relationships_append(self, name, direction=RelationshipDirection.ALL):
        self._data["relationships"].append({"direction":direction, "type":name})
        return self

    def max_depth(self, value):
        self._data["max_depth"] = value

    def traverse(self, start_node):
        try:
            self._data['prune_evaluator']
            del self._data["max_depth"]
        except KeyError:
            pass
        
        return Traverser(start_node, self._data)

class Traversal(object):

    def __init__(self, start_node):
        self._description = Traversal.description()
        self._start_node  = start_node

    @property
    def description(self):
        return self._description

    def __iter__(self):
        return self._description.traverse(self._start_node)