from django.db import models
from django.conf import settings
import hashlib, json
from django.core.exceptions import ValidationError
from rdflib import ConjunctiveGraph, URIRef, Literal, RDF
from rdflib_sqlalchemy.store import SQLAlchemy
from rdflib_sqlalchemy.termutils import type_to_term_combination
from rdflib_sqlalchemy.termutils import statement_to_term_combination

graph = settings.GRAPH
identifier = settings.IDENTIFIER

class Resource(models.Model):
  subject = models.URLField(primary_key=True)

  def __str__(self):
    return json.dumps(self.subject)

class Klass(models.Model):
  value = models.URLField(primary_key=True)

  class Meta:
    verbose_name = 'Class'
    verbose_name_plural = 'Classes'

  def __str__(self):
    return self.value

class Predicate(models.Model):
  value = models.URLField(primary_key=True)

  def __str__(self):
    return self.value

class Context(models.Model):
  value = models.TextField(primary_key=True)

  def __str__(self):
    return self.value

#unmanaged models from rdflib_sqlalchemy
class Namespace(models.Model):
  prefix = models.CharField(max_length=200, primary_key=True)
  # Because namespace URIs can end in # or /, it is unsafe to URLify them
  uri = models.CharField(max_length=200)

  class Meta:
    db_table = 'kb_' + identifier + '_namespace_binds'
    managed = False

  def __str__(self):
    return self.prefix + "|" + self.uri

class AssertedStatement(models.Model):
  id = models.AutoField(primary_key=True)
  subject = models.ForeignKey(Resource, to_field='subject', on_delete=models.CASCADE, db_column='subject', related_name='asserted_statements')
  predicate = models.ForeignKey(Predicate, to_field='value', on_delete=models.CASCADE, db_column='predicate')
  object = models.ForeignKey(Resource, to_field='subject', on_delete=models.CASCADE, db_column='object', related_name='as_object')
  context = models.ForeignKey(Context, to_field='value', on_delete=models.CASCADE, db_column='context')
  # termcomb here is calculated via int(statement_to_term_combination(subject, predicate, obj, context))
  termcomb = models.IntegerField(default=0)

  class Meta:
    db_table = 'kb_' + identifier + '_asserted_statements'
    managed = False

  def __str__(self):
    return graph.qname(self.subject.subject) + " " + graph.qname(self.predicate.value) + " " + graph.qname(self.object.subject)

  #overrides
  def save(self, *args, **kwargs):
    #assumption is all three are URIRef. This might not be true.
    self.termcomb = int(statement_to_term_combination(URIRef(self.subject.subject), URIRef(self.predicate.value), URIRef(self.object.subject), graph.get_context(self.context.value)))
    super(AssertedStatement, self).save(*args, **kwargs)

class LiteralStatement(models.Model):
  id = models.AutoField(primary_key=True)
  subject = models.ForeignKey(Resource, to_field='subject', on_delete=models.CASCADE, db_column='subject', related_name='literal_statements')
  predicate = models.ForeignKey(Predicate, to_field='value', on_delete=models.CASCADE, db_column='predicate')
  object = models.TextField()
  context = models.ForeignKey(Context, to_field='value', on_delete=models.CASCADE, db_column='context')
  # here, termcomb is built via int(statement_to_term_combination(subject, predicate, obj, context))
  termcomb = models.IntegerField(default=0)
  # pre-save validation should try to catch that only one of the following is filled in
  objlanguage = models.CharField(max_length=255, blank=True, null=True)
  objdatatype = models.CharField(max_length=255, blank=True, null=True)

  class Meta:
    db_table = 'kb_' + identifier + '_literal_statements'
    managed = False

  def __str__(self):
    return graph.qname(self.subject.subject) + " " + graph.qname(self.predicate.value) + " " + self.object

  #overrides
  def save(self, *args, **kwargs):
    self.termcomb = int(statement_to_term_combination(URIRef(self.subject.subject), URIRef(self.predicate.value), Literal(self.object), graph.get_context(self.context.value)))
    super(LiteralStatement, self).save(*args, **kwargs)


# None of the triple sets I have worked with so far end up in the QuotedStatement model, so I am not
# making use of it in the admin. If you know what it is/does, by all means feel free to connect it up.
# I have made it ready and will maintain it to the same degree as the other *Statement models.
class QuotedStatement(models.Model):
  id = models.AutoField(primary_key=True)
  subject = models.ForeignKey(Resource, to_field='subject', on_delete=models.CASCADE, db_column='subject', related_name='quoted_statements')
  predicate = models.URLField()
  object = models.TextField()
  context = models.ForeignKey(Context, to_field='value', on_delete=models.CASCADE, db_column='context')
  termcomb = models.IntegerField(default=0)
  objlanguage = models.CharField(max_length=255, blank=True, null=True)
  objdatatype = models.CharField(max_length=255, blank=True, null=True)

  class Meta:
    db_table = 'kb_' + identifier + '_quoted_statements'
    managed = False

  def __str__(self):
    return graph.qname(self.subject) + " " + graph.qname(self.predicate) + " " + self.object

  #overrides
  def save(self, *args, **kwargs):
    self.termcomb = int(statement_to_term_combination(URIRef(self.subject.subject), URIRef(self.predicate.value), Literal(self.object), graph.get_context(self.context.value)))
    super(QuotedStatement, self).save(*args, **kwargs)

class TypeStatement(models.Model):
  id = models.AutoField(primary_key=True)
  member = models.ForeignKey(Resource, to_field='subject', on_delete=models.CASCADE, db_column='member', related_name='types')
  klass = models.URLField()
  context = models.ForeignKey(Context, to_field='value', on_delete=models.CASCADE, db_column='context')
  # termcomb needs to be calculated via int(rdflib_sqlalchemy.termutils.type_to_term_combination(member,klass,context))
  termcomb = models.IntegerField(default=0)

  class Meta:
    db_table = 'kb_' + identifier + '_type_statements'
    managed = False

  def __str__(self):
    return graph.qname(self.member.subject) + " " + graph.qname(RDF.type) + " " + graph.qname(self.klass) + " (" + self.context.value + ")"

  #overrides
  def save(self, *args, **kwargs):
    # The only problem is that we can't know ahead of time whether our member/klass/context values are of type
    # BNode, Literal, URIRef, or Variable. Casting and testing for exceptions isn't helpful, so I'm not sure
    # what approach to take here.
    self.termcomb = int(type_to_term_combination(URIRef(self.member.subject), URIRef(self.klass), graph.get_context(self.context.value)))
    super(TypeStatement, self).save(*args, **kwargs)
