from django.db import models
from django.utils import timezone
from django.conf import settings

from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from django.db.utils import IntegrityError

from .hierarchicalData import AncestorLevelsOf, SiblingLevelsOf

from treebeard.mp_tree import MP_Node

import os

# Create your models here.

class CharNullField(models.CharField):
    """
    Subclass of the CharField that allows empty strings to be stored as NULL.
    This class is used to set charfield be optional but unique when added.
    Set blank=True, null=True when declaring the field
    """
    description = "CharField that stores NULL but returns ''."
    
    def get_prep_value(self, value):
        """
        Catches value right before sending to db.
        """
        if value == '': # If Django tries to save an empty string, send the db None (NULL).
            return None
        else: # Otherwise, just pass the value.
            return value

    def from_db_value(self, value, expression, connection):
        """
        Gets value right out of the db and changes it if its ``None``.
        """
        if value is None:
            return ''
        else:
            return value

    def to_python(self, value):
        """
        Gets value right out of the db or an instance, and changes it if its ``None``.
        """
        if isinstance(value, models.CharField): # If an instance, just return the instance.
            return value
        if value is None: # If db has NULL, convert it to ''.
            return ''
        return value # Otherwise, just return the value.

class ImageNullField(models.ImageField):
    """
    Subclass of the CharField that allows empty strings to be stored as NULL.
    This class is used to set charfield be optional but unique when added.
    Set blank=True, null=True when declaring the field
    """
    description = "ImageField that stores NULL but returns ''."
    
    def get_prep_value(self, value):
        """
        Catches value right before sending to db.
        """
        if value == '': # If Django tries to save an empty string, send the db None (NULL).
            return None
        else: # Otherwise, just pass the value.
            return value

    def from_db_value(self, value, expression, connection):
        """
        Gets value right out of the db and changes it if its ``None``.
        """
        if value is None:
            return ''
        else:
            return value

    def to_python(self, value):
        """
        Gets value right out of the db or an instance, and changes it if its ``None``.
        """
        if isinstance(value, models.ImageField): # If an instance, just return the instance.
            return value
        if value is None: # If db has NULL, convert it to ''.
            return ''
        return value # Otherwise, just return the value.


class ReceipeImage(models.Model):
    #RowId = models.IntegerField(primary_key=True)
    
    checksum = models.CharField(
        "checksum",
        max_length=32,
        editable=False,
        unique=True,
        help_text="The checksum of the original image."
    )
    
    thrashed_checksum = models.CharField(
        "threshed checksum",
        max_length=32,
        editable=False,
        unique=True,
        help_text="The checksum of the thrashed image."
    )
    
    filename = models.FilePathField(
        "filename",
        max_length=1024,
        editable=False,
        default=None,
        unique=True,
        null=True,
        help_text="Current filename in storage"
    )
    
    filename_trashed = models.FilePathField(
        "filename trashed",
        max_length=1024,
        editable=False,
        default=None,
        unique=True,
        null=True,
        help_text="Current filename of the modified picture in storage"
    )    

    created = models.DateTimeField(
        "created",
        default=timezone.now, db_index=True)

    modified = models.DateTimeField(
        "modified",
        auto_now=True, editable=False, db_index=True)
    
    added = models.DateTimeField(
        "added",
        default=timezone.now, editable=False, db_index=True)

    @property
    def source_path(self):
        fname = str(self.filename)

        return os.path.join(
            settings.ORIGINALS_DIR,
            fname
        )

    @property
    def source_path_trashed(self):
        fname = str(self.filename_trashed)

        return os.path.join(
            settings.ORIGINALS_DIR,
            fname
        )
    
    @property
    def source_file(self):
        return open(self.source_path, "rb")
   
    @property
    def source_file_trashed(self):
        return open(self.source_path_trashed, "rb") 
   
class Allergenes(models.Model):
    #RowId = models.IntegerField(primary_key=True)
    name = models.CharField("allergene name", max_length=50, unique=True)

    def __str__(self):
        return 'PK: '+str(self.pk) + ' ' + self.name   

class Brand(models.Model):
    #RowId = models.IntegerField(primary_key=True)
    name = models.CharField("brand name", max_length=50, unique=True)

    def __str__(self):
        return 'PK: '+str(self.pk) + ' ' + self.name   

class Packaging(models.Model):
    #RowId = models.IntegerField(primary_key=True)
    name = models.CharField("packaging name", max_length=50, unique=True)

    def __str__(self):
        return 'PK: '+str(self.pk) + ' ' + self.name   


class Ingredients(models.Model):
    #RowId = models.IntegerField(primary_key=True)
    name = models.CharField("ingredient name", max_length=100)
    allergenes = models.ManyToManyField(Allergenes, blank=True)
    vegan = models.BooleanField("vegan?",default=False)
    vegetarian = models.BooleanField("vegetarian?",default=False)
    palmoilfree = models.BooleanField("Palm oil free?",default=True)
    additive = models.BooleanField("Additiv?",default=False)
    eNumber = CharNullField("E number", max_length=7,null=True, blank=True)
    subIngredients = models.ManyToManyField('self', blank=True, symmetrical=False)

    #class Meta:
    #    unique_together = ('name', 'subIngredients',)

    def __unicode__(self):
        return "Ingredients: "+str(self.id)+" "+self.name

    def __str__(self):
        return 'PK: '+str(self.pk) + ' ' + self.name    

@receiver(m2m_changed, sender=Ingredients.subIngredients.through)
def verify_uniqueness(sender, **kwargs):
    ingredient = kwargs.get('instance', None)
    action = kwargs.get('action', None)
    subIngredients = kwargs.get('pk_set', None)

    if action == 'pre_add':
        for subIngredient in subIngredients:
            if Ingredients.objects.filter(name=ingredient.name).filter(subIngredients=subIngredient):
                raise IntegrityError('Ingredient with name %s already exists for subIngredient %s' % (ingredient.name, Ingredients.objects.get(pk=subIngredient)))

class Traces(models.Model):
    #RowId = models.IntegerField(primary_key=True)
    name = models.CharField("trace name", max_length=50, unique=True)
    
class Labels(models.Model):
    #RowId = models.IntegerField(primary_key=True)
    name = models.CharField("label name", max_length=50, unique=True)

class EMBs(models.Model):
    #RowId = models.IntegerField(primary_key=True)
    name = models.CharField("emb name", max_length=50, unique=True)
    
class ManufacturingPlaces(models.Model):
    #RowId = models.IntegerField(primary_key=True)
    name = models.CharField("manufacturing places name", max_length=50, unique=True)
    
class OriginIngredients(models.Model):
    #RowId = models.IntegerField(primary_key=True)
    name = models.CharField("origin of igredients", max_length=50, unique=True)
    
class Category(MP_Node):
    name = models.CharField("category name",max_length=30,unique=True)

    node_order_by = ['name']

    def __str__(self):
        return 'Category: {}'.format(self.name)


class Nutrient(models.Model):   
    name = models.CharField("nutrient name", max_length=50, unique=True)
    isMacroNutrient = models.BooleanField("Is macronutrient?", default=False)
     
    def __str__(self):
        return 'PK: '+str(self.pk) + ' ' + self.name  

class NutrientAlias(models.Model):   
    nutrient = models.ForeignKey(
        Nutrient,
        on_delete=models.CASCADE,
        verbose_name="Nutrient"
    )
    name = models.CharField("nutrient alias name", max_length=50, unique=True)
     
    def __str__(self):
        return 'PK: '+str(self.pk) + ' ' + self.name  

#-------------------------------------------------------------------------------    
class Article(models.Model):
    #RowId = models.IntegerField(primary_key=True)
    name = models.CharField("article name", max_length=100)
    
    
    brand = models.ForeignKey(
        Brand,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        verbose_name="brand id"
    )
    
    EAN = CharNullField(
                "EAN", max_length=50, 
                unique=True, 
                blank=True, 
                null=True
            )
    offlink = models.URLField("Open Food Fact link", blank=True, null=True)
    
    category = models.ForeignKey(
        Category,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        verbose_name="category id"
    )
    
    quantityPerPackage = models.PositiveIntegerField("quantity per packages", blank=True, null=True)
    unit = CharNullField("unit",max_length=4, blank=True, null=True)
    novaGroup = models.PositiveIntegerField("NOVA group", blank=True, null=True) 
    nutritionGrade = models.CharField("nutri grade",max_length=1, blank=True, null=True)
    ecoGrade = models.CharField("eco grade",max_length=1, blank=True, null=True)
    MwSt = models.PositiveIntegerField("taxes", blank=True, null=True)
    
    allergenes = models.ManyToManyField(Allergenes, blank=True)
    ingredients = models.ManyToManyField(Ingredients, through='IngredientsArticle', blank=True)
    traces = models.ManyToManyField(Traces, blank=True)
    embs = models.ManyToManyField(EMBs, blank=True)
    manufacturingPlaces = models.ManyToManyField(ManufacturingPlaces, blank=True)
    originIngredients = models.ManyToManyField(OriginIngredients, blank=True)
    
    packagings = models.ManyToManyField(Packaging, through='PackagingArticle', blank=True)

    nutrients = models.ManyToManyField(Nutrient, through='NutrientArticle', blank=True)
    
    labels = models.ManyToManyField(Labels, blank=True)
    
    models.ForeignKey(
        Category,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        verbose_name="category id"
    )

    created = models.DateTimeField(
        "created", 
        default=timezone.now, 
        db_index=True)
    
    modified = models.DateTimeField(
        "modified", 
        auto_now=True, 
        editable=False, 
        db_index=True)


    
    nutritionImage = models.ImageField(
        "nutriton image filepath",
        default=None,
        #unique=True,
        blank=True,
        null=True,
        help_text="Nutrition image",
        upload_to='nutritionImages'
    )
    
    ingredientsImage = models.ImageField(
        "ingredients image filepath",
        default=None,
        #unique=True,
        blank=True,
        null=True,
        help_text="Ingredients image",
        upload_to='ingredientsImages'
    )
    
    frontImage = models.ImageField(
        "front image",
        default=None,
        #unique=True,
        null=True,
        blank=True,
        help_text="Front image",
        upload_to='frontImages'
    )

    def __str__(self):
        return 'PK: '+str(self.pk) + ' ' + self.name    
    
#-------------------------------------------------------------------------------

class IngredientsArticle(models.Model):
    ingredient = models.ForeignKey(
        Ingredients,
        blank=True,
        null=True,
        on_delete=models.CASCADE,
        verbose_name="Ingredient"
    )
    
    article = models.ForeignKey(
        Article,
        blank=True,
        null=True,
        on_delete=models.CASCADE,
        verbose_name="article"
    )
    
    position = models.PositiveIntegerField("Position") 

    percent = models.FloatField(
        "Percentage on total weight",
        blank=True,
        null=True,
        default=None
    ) 

#-------------------------------------------------------------------------------
class PackagingArticle(models.Model):
    packaging_id = models.ForeignKey(
        Packaging,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        verbose_name="packaging id"
    )
    
    article_id = models.ForeignKey(
        Article,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        verbose_name="article id"
    )
    
    weight = models.PositiveIntegerField("weight")


    
#------------------------------------------------------------------------------- 
 

class NutrientArticle(models.Model):
    nutrient = models.ForeignKey(
        Nutrient,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        verbose_name="nutrient"
    )

    article = models.ForeignKey(
        Article,
        blank=True,
        null=True,
        on_delete=models.CASCADE,
        verbose_name="article"
    )

    value = models.DecimalField("value", max_digits=7, decimal_places=2, blank=True, null=True)
    unit = models.CharField(max_length=4, blank=True, null=True)

    isEstimated = models.BooleanField("Is nutrient exstimated?", default=False)

    class Meta:
        unique_together = ('article', 'nutrient',)


# Deprectated, will be deleted later
class NutritionalValues(models.Model):                
    article = models.OneToOneField(
        Article,
        on_delete=models.CASCADE,
        primary_key=True,
    )
    energy = models.DecimalField("energy",max_digits=7,decimal_places=2,blank=True, null=True)
    energyUnit = models.CharField(max_length=4,blank=True, null=True,default='kJ')
    fat = models.DecimalField("fat",max_digits=7,decimal_places=2,blank=True, null=True)
    fatUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
    saturatedFat = models.DecimalField("saturatedFat",max_digits=7,decimal_places=2,blank=True, null=True)
    saturatedFatUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
    carbohydrate = models.DecimalField("carbohydrate",max_digits=7,decimal_places=2,blank=True, null=True)
    carbohydrateUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
    sugars = models.DecimalField("sugars",max_digits=7,decimal_places=2,blank=True, null=True)
    sugarsUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
    dietaryFiber = models.DecimalField("dietaryFiber",max_digits=7,decimal_places=2,blank=True, null=True)
    dietaryFiberUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
    proteins = models.DecimalField("proteins",max_digits=7,decimal_places=2,blank=True, null=True)
    proteinsUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
    salt = models.DecimalField("salt",max_digits=7,decimal_places=2,blank=True, null=True)
    saltUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
    sodium = models.DecimalField("sodium",max_digits=7,decimal_places=2,blank=True, null=True)
    sodiumUnit = models.CharField(max_length=2,blank=True, null=True)
    vitamin_a = models.DecimalField("vitamin_a",max_digits=7,decimal_places=2,blank=True, null=True)
    vitamin_aUnit = models.CharField(max_length=2,blank=True, null=True)
    vitamin_b1 = models.DecimalField("vitamin_b1",max_digits=7,decimal_places=2,blank=True, null=True)
    vitamin_b1Unit = models.CharField(max_length=2,blank=True, null=True)
    vitamin_b2 = models.DecimalField("vitamin_b2",max_digits=7,decimal_places=2,blank=True, null=True)
    vitamin_b2Unit = models.CharField(max_length=2,blank=True, null=True)
    vitamin_b3 = models.DecimalField("vitamin_b3",max_digits=7,decimal_places=2,blank=True, null=True)
    vitamin_b3Unit = models.CharField(max_length=2,blank=True, null=True)
    vitamin_b5 = models.DecimalField("vitamin_b5",max_digits=7,decimal_places=2,blank=True, null=True)
    vitamin_b5Unit = models.CharField(max_length=2,blank=True, null=True)
    vitamin_b7 = models.DecimalField("vitamin_b7",max_digits=7,decimal_places=2,blank=True, null=True)
    vitamin_b7Unit = models.CharField(max_length=2,blank=True, null=True)
    vitamin_b9 = models.DecimalField("vitamin_b9",max_digits=7,decimal_places=2,blank=True, null=True)
    vitamin_b9Unit = models.CharField(max_length=2,blank=True, null=True)
    vitamin_b12 = models.DecimalField("vitamin_b12",max_digits=7,decimal_places=2,blank=True, null=True)
    vitamin_b12Unit = models.CharField(max_length=2,blank=True, null=True)
    vitamin_c = models.DecimalField("vitamin_c",max_digits=7,decimal_places=2,blank=True, null=True)
    vitamin_cUnit = models.CharField(max_length=2,blank=True, null=True)
    vitamin_d = models.DecimalField("vitamin_d",max_digits=7,decimal_places=2,blank=True, null=True)
    vitamin_dUnit = models.CharField(max_length=2,blank=True, null=True)
    vitamin_e = models.DecimalField("vitamin_e",max_digits=7,decimal_places=2,blank=True, null=True)
    vitamin_eUnit = models.CharField(max_length=2,blank=True, null=True)
    vitamin_k = models.DecimalField("vitamin_k",max_digits=7,decimal_places=2,blank=True, null=True)
    vitamin_kUnit = models.CharField(max_length=2,blank=True, null=True)
    potassium = models.DecimalField("potassium",max_digits=7,decimal_places=2,blank=True, null=True)
    potassiumUnit = models.CharField(max_length=2,blank=True, null=True)
    calcium = models.DecimalField("calcium",max_digits=7,decimal_places=2,blank=True, null=True)
    calciumUnit = models.CharField(max_length=2,blank=True, null=True)
    magnesium = models.DecimalField("magnesium",max_digits=7,decimal_places=2,blank=True, null=True)
    magnesiumUnit = models.CharField(max_length=2,blank=True, null=True)
    iron = models.DecimalField("iron",max_digits=7,decimal_places=2,blank=True, null=True)
    ironUnit = models.CharField(max_length=2,blank=True, null=True)
    zinc = models.DecimalField("zinc",max_digits=7,decimal_places=2,blank=True, null=True)
    zincUnit = models.CharField(max_length=2,blank=True, null=True)
    sulfat = models.DecimalField("sulfat",max_digits=7,decimal_places=2,blank=True, null=True)
    sulfatUnit = models.CharField(max_length=2,blank=True, null=True)
    chlorid = models.DecimalField("chlorid",max_digits=7,decimal_places=2,blank=True, null=True)
    chloridUnit = models.CharField(max_length=2,blank=True, null=True)
    hydrogencarbonat = models.DecimalField("hydrogencarbonat",max_digits=7,decimal_places=2,blank=True, null=True)
    hydrogencarbonatUnit = models.CharField(max_length=2,blank=True, null=True)

class ReceipeString(models.Model):
    name = models.CharField("receipe string", max_length=50, unique=True)
    
    def __str__(self):
        return 'PK: '+str(self.pk) + ' ' + self.receipeString
    
class ArticleMaps(models.Model):
    article = models.ForeignKey(
        Article,
        blank=True,
        null=True,
        on_delete=models.CASCADE,
        verbose_name="article id"
    )
    #receipeString = models.CharField("receipe string", max_length=50)
    receipeString = models.ForeignKey(
        ReceipeString,
        on_delete=models.CASCADE,
        null=True,
        verbose_name="receipe string"
    )
    location_x = models.PositiveIntegerField("x coordiante in image", null=True)
    location_y = models.PositiveIntegerField("y coordiante in image", null=True)
    location_h = models.PositiveIntegerField("height in image", null=True)
    location_w = models.PositiveIntegerField("width in image", null=True)
    
    receipeImage = models.ForeignKey(ReceipeImage, on_delete=models.CASCADE)
    
    class Meta:
        unique_together = ('article', 'receipeImage',)
    def __str__(self):
        return 'PK: '+str(self.pk) + ' ' +  ' ' + self.receipeString.name

class Market(models.Model):
    #RowId = models.AutoField(primary_key=True)
    name = models.CharField("market name", max_length=50)
    street = models.CharField("street name", max_length=50)
    street_number = models.PositiveIntegerField("street number")
    zip_code = models.CharField("zip code", max_length=5)
    city = models.CharField("city", max_length=50)
    phone = CharNullField("phone number",max_length=50, blank=True, null=True)
    articles = models.ManyToManyField(Article,blank=True)
    created = models.DateTimeField(
        "created", 
        default=timezone.now, 
        db_index=True)
    
    modified = models.DateTimeField(
        "modified", 
        auto_now=True, 
        editable=False, 
        db_index=True)

    def __str__(self):
        return 'PK: '+str(self.pk) + ' ' + self.name + ' ' + self.street + ' ' + str(self.street_number) + ' ' + self.city 

class Purchase(models.Model):
    purchase_date = models.DateField("purchase date")
    payment_type = models.CharField("payment type",max_length=10,default='EC')
    total_price = models.DecimalField("total price",max_digits=7,decimal_places=2,default=0)
    
    articles = models.ManyToManyField(Article, through='PurchaseArticle')
    
    market = models.ForeignKey(
        Market,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        verbose_name="market id"
    )
    
    created = models.DateTimeField(
        "created", 
        default=timezone.now, 
        db_index=True)
    
    modified = models.DateTimeField(
        "modified", 
        auto_now=True, 
        editable=False, 
        db_index=True)
    
    receipeImage = models.OneToOneField(
        ReceipeImage,
        on_delete=models.CASCADE,
        primary_key=True,
        blank = True,
    )

    edit_finished = models.BooleanField("edit finished", default=False)

    def __str__(self):
        return 'PK: '+str(self.pk) + ' Datum: ' + self.purchase_date.strftime("%m/%d/%Y") + ' Preis: ' +str(self.total_price)    

#-------------------------------------------------------------------------------
class PurchaseArticle(models.Model):
    #shoud be renamed to article, but migrations will delete the current database
    article_id = models.ForeignKey(
        Article,
        blank=True,
        null=True,
        on_delete=models.CASCADE,
        verbose_name="article id"
    )
    
    purchase_id = models.ForeignKey(
        Purchase,
        blank=True,
        null=True,
        on_delete=models.CASCADE,
        verbose_name="purchase id"
    )
    
    quantity = models.PositiveIntegerField("quantity")
    net_weight = models.DecimalField("net weight",max_digits=7,decimal_places=3, blank=True, null=True)
    price = models.DecimalField("price",max_digits=7,decimal_places=2,default=0)
    inSale = models.BooleanField("in sale",default=False)
    
