from modelverse_state import status
import sys
from collections import defaultdict
import os
import rdflib
from rdflib.plugins.sparql import prepareQuery
import json

# Work around Python 2 where a 'big integer' automatically becomes a long
if sys.version > '3': # pragma: no cover
    integer_types = (int,)
    primitive_types = (int, float, str, bool)
else: # pragma: no cover
    integer_types = (int, long)
    primitive_types = (int, long, float, str, bool, unicode)
complex_primitives = frozenset(["if", "while", "assign", "call", "break", "continue", "return","resolve","access", "constant", "input", "output", "declare", "global"])

def instance_to_string(value):
    return value["value"]

def string_to_instance(value):
    return {'value': value}

class ModelverseState(object):
    def __init__(self, bootfile = None):
        self.graph = rdflib.Graph()
        self.mv = rdflib.Namespace("http://modelverse.mv/#")
        self.graph.bind("MV", self.mv)
        self.free_id = -1
        self.prepared_queries = {
            "read_value": """
                            SELECT ?value
                            WHERE {
                                ?var1 MV:hasValue ?value .
                            }
                          """,
            "read_outgoing": """
                            SELECT ?link
                            WHERE {
                                ?link MV:hasSource ?var1 .
                            }
                            """,
            "read_incoming": """
                            SELECT ?link
                            WHERE {
                                ?link MV:hasTarget ?var1 .
                            }
                            """,
            "read_edge": """
                            SELECT ?source ?target
                            WHERE {
                                ?var1 MV:hasSource ?source ;
                                      MV:hasTarget ?target .
                            }
                            """,
            "read_dict_keys": """
                            SELECT ?key
                            WHERE {
                                ?main_edge MV:hasSource ?var1 .
                                ?attr_edge MV:hasSource ?main_edge ;
                                           MV:hasTarget ?key .
                            }
                            """,
            "read_dict_node": """
                            SELECT ?value_node
                            WHERE {
                                ?main_edge MV:hasSource ?var1 ;
                                           MV:hasTarget ?value_node .
                                ?attr_edge MV:hasSource ?main_edge ;
                                           MV:hasTarget ?var2 .
                            }
                            """,
            "read_dict_node_edge": """
                            SELECT ?main_edge
                            WHERE {
                                ?main_edge MV:hasSource ?var1 .
                                ?attr_edge MV:hasSource ?main_edge ;
                                           MV:hasTarget ?var2 .
                            }
                            """,
            "delete_node": """
                            SELECT ?edge
                            WHERE {
                                { ?edge MV:hasTarget ?var1 . }
                                UNION
                                { ?edge MV:hasSource ?var1 . }
                            }
                            """,
            "delete_edge": """
                            SELECT ?edge
                            WHERE {
                                { ?edge MV:hasTarget ?var1 . }
                                UNION
                                { ?edge MV:hasSource ?var1 . }
                            }
                            """,
            }

        for k, v in self.prepared_queries.iteritems():
            self.prepared_queries[k] = prepareQuery(self.prepared_queries[k], initNs={"MV": self.mv})

        self.root = self.parse(bootfile)

    def parse(self, filename):
        symbols = {}

        def resolve(symb):
            try:
                return int(symb)
            except:
                if symb[0] == "?":
                    derefs = symb[1:].split("/")
                    v, _ = self.read_dict(symbols["root"], "__hierarchy")
                    for deref in derefs:
                        v, _ = self.read_dict(v, deref)
                    return v
                else:
                    return symbols[symb]

        with open(filename, 'r') as f:
            for line in f:
                element_type, constructor = line.split(None, 1)
                name, values = constructor.split("(", 1)
                name = name.split()[0]
                values, _ = values.rsplit(")", 1)

                if element_type == "Node":
                    if values == "":
                        symbols[name], status = self.create_node()
                    else:
                        value = values
                        if value in complex_primitives:
                            value = string_to_instance(value)
                        else:
                            value = eval(value)
                        symbols[name], status = self.create_nodevalue(value)
                elif element_type == "Edge":
                    values = [v.split()[0] for v in values.split(",")]
                    symbols[name], status = self.create_edge(resolve(values[0]), resolve(values[1]))
                else:
                    raise Exception("Unknown element type: %s" % element_type)

                if status != 100:
                    raise Exception("Failed to process line for reason %s: %s" % (status, line))

            return symbols["root"]

    def read_root(self):
        return (self.root, status.SUCCESS)

    def create_node(self):
        self.free_id += 1
        return (rdflib.URIRef("http://modelverse.mv/#%s" % self.free_id), status.SUCCESS)

    def create_edge(self, source, target):
        if not isinstance(source, rdflib.URIRef):
            return (None, status.FAIL_CE_SOURCE)
        if not isinstance(target, rdflib.URIRef):
            return (None, status.FAIL_CE_TARGET)
        self.free_id += 1
        edge = rdflib.URIRef("http://modelverse.mv/#%s" % self.free_id)
        self.graph.add((edge, self.mv.hasSource, source))
        self.graph.add((edge, self.mv.hasTarget, target))
        return (edge, status.SUCCESS)
        
    def is_valid_datavalue(self, value):
        if isinstance(value, dict):
            if "value" in value and value["value"] in complex_primitives:
                return True
            else:
                return False
        elif not isinstance(value, primitive_types):
            return False
        elif isinstance(value, integer_types) and not (-2**63 <= value <= 2**64 - 1):
            return False
        return True

    def create_nodevalue(self, value):
        if not self.is_valid_datavalue(value):
            return (None, status.FAIL_CNV_OOB)
        self.free_id += 1
        node = rdflib.URIRef("http://modelverse.mv/#%s" % self.free_id)
        self.graph.add((node, self.mv.hasValue, rdflib.Literal(json.dumps(value))))
        return (node, status.SUCCESS)

    def create_dict(self, source, data, destination):
        if not isinstance(source, rdflib.URIRef):
            return (None, status.FAIL_CDICT_SOURCE)
        if not isinstance(destination, rdflib.URIRef):
            return (None, status.FAIL_CDICT_TARGET)
        if not self.is_valid_datavalue(data):
            return (None, status.FAIL_CDICT_OOB)

        n = self.create_nodevalue(data)[0]
        e = self.create_edge(source, destination)[0]
        self.create_edge(e, n)
        return (None, status.SUCCESS)

    def read_value(self, node):
        if not isinstance(node, rdflib.URIRef):
            return (None, status.FAIL_RV_UNKNOWN)
        result = self.graph.query(self.prepared_queries["read_value"], initBindings={"var1": node})
        if len(result) == 0:
            return (None, status.FAIL_RV_NO_VALUE)
        return (json.loads(list(result)[0][0]), status.SUCCESS)

    def read_outgoing(self, elem):
        if not isinstance(elem, rdflib.URIRef):
            return (None, status.FAIL_RO_UNKNOWN)
        result = self.graph.query(self.prepared_queries["read_outgoing"], initBindings={"var1": elem})
        return ([i[0] for i in result], status.SUCCESS)

    def read_incoming(self, elem):
        if not isinstance(elem, rdflib.URIRef):
            return (None, status.FAIL_RI_UNKNOWN)
        result = self.graph.query(self.prepared_queries["read_incoming"], initBindings={"var1": elem})
        return ([i[0] for i in result], status.SUCCESS)

    def read_edge(self, edge):
        result = self.graph.query(self.prepared_queries["read_edge"], initBindings={"var1": edge})
        if len(result) == 0:
            return ([None, None], status.FAIL_RE_UNKNOWN)
        else:
            return ([list(result)[0][0], list(result)[0][1]], status.SUCCESS)

    def read_dict(self, node, value):
        if not isinstance(node, rdflib.URIRef):
            return (None, status.FAIL_RDICT_UNKNOWN)
        if not self.is_valid_datavalue(value):
            return (None, status.FAIL_RDICT_OOB)

        q = """
            SELECT ?value_node
            WHERE {
                ?main_edge MV:hasSource <%s> ;
                           MV:hasTarget ?value_node .
                ?attr_edge MV:hasSource ?main_edge ;
                           MV:hasTarget ?attr_node .
                ?attr_node MV:hasValue '%s' .
            }
            """ % (node, json.dumps(value))
        result = self.graph.query(q)
        if len(result) == 0:
            return (None, status.FAIL_RDICT_NOT_FOUND)
        if len(result) != 1:
            print(value)
            raise Exception("Error!")
        return (list(result)[0][0], status.SUCCESS)

    def read_dict_keys(self, node):
        if not isinstance(node, rdflib.URIRef):
            return (None, status.FAIL_RDICT_UNKNOWN)

        result = self.graph.query(self.prepared_queries["read_dict_keys"], initBindings={"var1": node})
        return ([i[0] for i in result], status.SUCCESS)

    def read_dict_edge(self, node, value):
        if not isinstance(node, rdflib.URIRef):
            return (None, status.FAIL_RDICTE_UNKNOWN)
        if not self.is_valid_datavalue(value):
            return (None, status.FAIL_RDICTE_OOB)
        result = self.graph.query(
            """
            SELECT ?main_edge
            WHERE {
                ?main_edge MV:hasSource <%s> ;
                           MV:hasTarget ?value_node .
                ?attr_edge MV:hasSource ?main_edge ;
                           MV:hasTarget ?attr_node .
                ?attr_node MV:hasValue '%s' .
            }
            """ % (node, json.dumps(value)))
        if len(result) == 0:
            return (None, status.FAIL_RDICTE_NOT_FOUND)
        return (list(result)[0][0], status.SUCCESS)

    def read_dict_node(self, node, value_node):
        if not isinstance(node, rdflib.URIRef):
            return (None, status.FAIL_RDICTN_UNKNOWN)
        result = self.graph.query(self.prepared_queries["read_dict_node"], initBindings={"var1": node, "var2": value_node})
        if len(result) == 0:
            return (None, status.FAIL_RDICTN_NOT_FOUND)
        return (list(result)[0][0], status.SUCCESS)

    def read_dict_node_edge(self, node, value_node):
        if not isinstance(node, rdflib.URIRef):
            return (None, status.FAIL_RDICTNE_UNKNOWN)
        result = self.graph.query(self.prepared_queries["read_dict_node_edge"], initBindings={"var1": node, "var2": value_node})
        if len(result) == 0:
            return (None, status.FAIL_RDICTNE_NOT_FOUND)
        return (list(result)[0][0], status.SUCCESS)

    def read_reverse_dict(self, node, value):
        if not isinstance(node, rdflib.URIRef):
            return (None, status.FAIL_RRDICT_UNKNOWN)
        if not self.is_valid_datavalue(value):
            return (None, status.FAIL_RRDICT_OOB)
        result = self.graph.query(
            """
            SELECT ?source_node
            WHERE {
                ?main_edge MV:hasTarget <%s> ;
                           MV:hasSource ?source_node .
                ?attr_edge MV:hasSource ?main_edge ;
                           MV:hasTarget ?value_node .
                ?value_node MV:hasValue '%s' .
            }
            """ % (node, json.dumps(value)))

        return ([i[0] for i in result], status.SUCCESS)

    def delete_node(self, node):
        if node == self.root:
            return (None, status.FAIL_DN_UNKNOWN)
        if not isinstance(node, rdflib.URIRef):
            return (None, status.FAIL_DN_UNKNOWN)
        # Remove its value if it exists
        self.graph.remove((node, None, None))

        # Get all edges connecting this
        result = self.graph.query(self.prepared_queries["delete_node"], initBindings={"var1": node})
        # ... and remove them
        print("Got related nodes: " + str(len(result)))
        for e in result:
            self.delete_edge(e[0])

        return (None, status.SUCCESS)

    def delete_edge(self, edge):
        if not isinstance(edge, rdflib.URIRef):
            return (None, status.FAIL_DN_UNKNOWN)
        # Remove its links
        self.graph.remove((edge, None, None))

        # Get all edges connecting this
        result = self.graph.query(self.prepared_queries["delete_edge"], initBindings={"var1": edge})
        # ... and remove them
        for e in result:
            self.delete_edge(e[0])

        return (None, status.SUCCESS)

    def garbage_collect(self):  
        pass

    def purge(self):
        pass
