from django.db import models
from files.models import File
from fields import HexColorField
from django.contrib.comments.signals import comment_was_posted
import simplejson
from django.core.mail import send_mail
import os
from PIL import Image
from django.template import Template, Context
from django.template.loader import get_template
from settings import MEDIA_ROOT
from django.contrib.auth.models import User
from tagging.fields import TagField
from tagging.models import Tag


def addPx(val):
  '''
  Adds 'px' to an integer value for a CSS property from DB
  '''
  r = str(int(val)) + "px"
  return r

def cleanCSS(prop):
  '''
  Takes a CSS property and returns it into a value safe for insertion into DB
  '''
  propStr = str(prop)
  if propStr[-2:] == 'px':
    r = int(propStr[:-2])
#  elif prop[0] == '#':
#    r = prop[1:]
  else:
    r = prop
  return r

def isPx(s):
  if s[:-2] == 'px':
    return True
  else:
    return False

def baseFileName(filename):
  r = filename.rindex('.')
  return filename[0:r]

def extFileName(filename):
  r = filename.rindex('.') + 1
  return filename[r:]

class LinkCategory(models.Model):
  title = models.CharField(max_length=255)
  is_onfront = models.BooleanField(default=False)

class Link(models.Model):
  product = models.ForeignKey("Product")
  category = models.ForeignKey("LinkCategory")
  thumbnail = models.ImageField(upload_to='images/link_thumbs/')
  description = models.TextField(blank=True, null=True)
  order = models.IntegerField()
  def __unicode__(self):
    return self.description

class ProductType(models.Model):
  name = models.CharField(max_length=255)
  aspect_ratio = models.FloatField(default=0.707, help_text="Default 0.707 as per http://en.wikipedia.org/wiki/Paper_size")
  print_width = models.IntegerField(help_text="Unit: mm")

  def __unicode__(self):
    return self.name

class Product(models.Model):
  title = models.CharField(max_length=255)
  published = models.BooleanField(default=False)
  date_published = models.DateField(blank=True, null=True)
  typ = models.ForeignKey("ProductType")
  tags = TagField()
  creator = models.ForeignKey(User)
  abstract = models.TextField(blank=True, null=True)
  videos = models.ManyToManyField("Video", blank=True)
  audios = models.ManyToManyField("Audio", blank=True)

  def __unicode__(self):
    return "%s" % (self.title)

  def get_print_multiplier(self, dpi):
#    product = Product.objects.get(pk=self.product.id)
    print_width_mm = self.typ.print_width
    dpm = dpi // 25.4
    pixel_width = print_width_mm * dpm
    m = pixel_width // 800.0
    return m

  def get_page_list(self):
    pages = []
    articles = Article.objects.filter(page=self).order_by('order')
    for a in articles:
      a_pages = Page.objects.filter(article=a).order_by('page_no')
      pages.extend(a_pages)
    return pages

  def get_view_size(self, width=800):
    aspect_ratio = self.typ.aspect_ratio
    width = width + .0
    height = int(width // aspect_ratio)    
    return (int(width), height,)

class Video(models.Model):
  fil = models.ForeignKey(File)
  srt = models.ManyToManyField("Srt", blank=True, null=True)

  def __unicode__(self):
    return self.fil.title

class Audio(models.Model):
  fil = models.ForeignKey(File)
  srt = models.ManyToManyField("Srt", blank=True, null=True)

  def __unicode__(self):
    return self.fil.title

SRT_LANGS = (
  ('en', 'English'),
  ('ar', 'Arabic'),
  )

SRT_TYPES = (
  ('transcript', 'Transcript'),
  ('description', 'Description'),
  )

class Srt(models.Model):
  fil = models.FileField(upload_to="srt/")
  lang = models.CharField(max_length=20, choices=SRT_LANGS)
  typ = models.CharField(max_length = 40, choices=SRT_TYPES)

'''
This models holds revisions, gets saved to whenever anything in an article changes - when a box changes, its pretty straightforward what's supposed to happen. When a new box or page is created, slightly strange things happen:
For a new box- prop = 'new_box', old_val='', new_val=String JSON representation of the new box.
For new page- prop = 'new_page', old_val='', new_val=String JSON representation of the new page.
For delete box, prop = 'delete_box'
For image_crop, prop = 'crop',
For image_resize, prop = 'image_resize'

(of course, this is ugly).
'''

class Revision(models.Model):
  article = models.ForeignKey("Article")
  page = models.ForeignKey("Page")
  box_type = models.CharField(max_length=100)
  box_id = models.IntegerField()
  prop = models.CharField(max_length=100)
  old_val = models.TextField()
  new_val = models.TextField()
  uuid = models.IntegerField()

def saveRevision(r):
  page = Page.objects.get(pk=r['page_id'])
  article = page.article
  rev = Revision(article=article, page=page, box_type=r['box_type'], box_id=r['box_id'], prop=r['prop'], old_val=r['old_val'], new_val=r['new_val'], uuid=r['uuid'])
  rev.save()
  return rev.id

class Article(models.Model):
  """
  Each page references an article. A single page cannot reference more than one article. The article is what people comment on (and potentially what audio & video are attached to). 
  """
  name = models.CharField(max_length=255)
  product = models.ForeignKey("Product")
  order = models.IntegerField()

  '''

  '''
  def changes(self, revision_no, uuid):
    if int(revision_no) == self.current_revision():
      return {'ok': 'ok'}
    else:
      d = []
      new_revisions_all = Revision.objects.filter(article=self).filter(id__gt=revision_no)
      new_revisions_others = new_revisions_all.exclude(uuid=uuid)
      for rev in new_revisions_others:
#If there are multiple changes on the same property of the same box, send back only the latest property value.
        if new_revisions_all.filter(id__gt=rev.id, prop=rev.prop, box_type=rev.box_type, page=rev.page).count() > 0:
          UGLY_HACK = True
        else: 
          d.append({
            'page_id': rev.page.id,
            'prop': rev.prop,
            'old_val': rev.old_val,
            'new_val': rev.new_val,
            'box_type': rev.box_type,
            'box_id': rev.box_id,
            'uuid': rev.uuid,
            'rev_no': rev.id
            })
      last_rev = self.current_revision()
      return {'revs': d, 'rev_id': last_rev}    

  def current_revision(self):
    try:
      rev = Revision.objects.filter(article=self).order_by('-id')[0]
      return rev.id
    except:
      return 0
   
  def editor_size(self):
    height = self.editor_width / self.aspect_ratio
    return (self.editor_width, height,)

  def view_size(self):
    product = Product.objects.get(pk=self.product.id)
    aspect_ratio = product.typ.aspect_ratio
    width = 800
    height = int(800.0 // aspect_ratio)    
    return (width, height,)

  def print_size(self, print_width):
    height = print_width / aspect_ratio
    multiplier = print_width / (self.editor_width + .0)
    return (self.print_width, height, multiplier,)  

  def get_print_multiplier(self, dpi):
    product = Product.objects.get(pk=self.product.id)
    print_width_mm = product.typ.print_width
    dpm = dpi // 25.4
    pixel_width = print_width_mm * dpm
    m = pixel_width // 800.0
    return m

  def __unicode__(self):
    return self.name

  def get_dict(self, *args):
    if len(args) > 0:
      m = args[0]
    else:
      m = 1
    if len(args) > 1:
      page_id = args[1]
      pages = Page.objects.filter(id__iexact=page_id)
    else:
      pages = Page.objects.filter(article=self).order_by('page_no')
    rList = []
    for p in pages:
      rList.append(p.get_dict(m))
    return rList

  class Meta:
    unique_together = ('product', 'order',)

class Page(models.Model):
# Question: Does Page need some custom CSS definitions like bg_color, borders, etc. ?
  page_no = models.IntegerField()
  article = models.ForeignKey('Article')
  videos = models.ManyToManyField(Video)
  audios = models.ManyToManyField(Audio)
  background_color = models.CharField(max_length=30)


  def __unicode__(self):
    return "%s: %s" % (self.page_no, self.article)

  def set_page_no(self, new_page_no):
    """
    Use this function to set a new page no. on a Page instance. Changes page numbers of other pages in article accordingly.
    """
    old_page_no = self.page_no
    self.page_no = new_page_no
    self.save()
    if new_page_no < old_page_no:
      pages_after = Page.objects.filter(article=self.article, page_no__gte=new_page_no).filter(page_no__lt=old_page_no).exclude(pk=self.id)
      for a in pages_after:
        a.page_no = a.page_no + 1
        a.save()
    else:
      pages_before = Page.objects.filter(article=self.article, page_no__lte=new_page_no).filter(page_no__gt=old_page_no).exclude(pk=self.id)
      for b in pages_before:         
        b.page_no = b.page_no - 1
        b.save()
    return self

  def deleteme(self):
    pages_after = Page.objects.filter(article=self.article, page_no__gt=self.page_no)
    for p in pages_after:
      p.page_no = p.page_no - 1
      p.save()
    self.delete()
    return

  def get_dict(self, m):
    """
    This function iterates through all boxes on the page and returns a Dict representation which the view converts to json to send to front-end.
    """
    # Image Boxes:
    imageBoxes = []
    for i in ImageBox.objects.filter(page = self).exclude(is_displayed = False):
      imageBoxes.append(i.to_dict(m))

    # Text Boxes:
    textBoxes = []
    for t in TextBox.objects.filter(page = self).exclude(is_displayed = False):
      textBoxes.append(t.to_dict())
    '''
    videos = []
    for v in self.videos:
      r = {
        'title' = v.fil.title,
        'description' = v.fil.description
        'srts' = []
        for s in v.srt:
    '''    

    #Resources:
    '''
    resources = []
    for res in self.resources.all():
      r = {}
      r = {
        'file': res.file,
        'title': res.title,
        'description': res.description,
        'tags': res.tags,
        'userID': res.userID,
        'added': res.added,
        'categories': res.categories
        }
      resources.append(r)
    '''
    rDict = {
      'id': self.id,
      'imageBoxes' : imageBoxes,
      'textBoxes': textBoxes,
#      'resources' : resources 
      }
    
    return rDict

  def save_from_dict(self, d):
    imageBoxes = d.imageBoxes
    textBoxes = d.textBoxes
    for i in imageBoxes:
      img = ImageBox.objects.get(pk=i.id)
      img.save_from_dict()
    for t in textBoxes:
      txt = TextBox.objects.get(pk=t.id)
      txt.save_from_dict()

#The next two functions are painful. Whoever writes them wins a trip to the moon.
  def save_revision(self):
    """
    This function saves the json for the page as a Page Revision.
    """
    return True

  def load_revision(self, rev_no):   
    """
    Loads the json data from a revision to populate the page. First, of course, it saves its current state as a revision
    """
    return True


  def deleteme(self):
    """
    Deletes self - needs to change next page numbers.
    """
    return True


class TextBox(models.Model):

# Positioning stuff:
  height = models.IntegerField()
  width = models.IntegerField()
  top = models.IntegerField()
  left = models.IntegerField()

# Store reference to file containing text, if any:
  file = models.ForeignKey(File, blank=True, null=True)

# Content, reference to page and whether is_displayed:
  html = models.TextField()
  page = models.ForeignKey(Page)
  is_displayed = models.BooleanField(default=True)

# CSS:
  z_index = models.IntegerField()
  background_color = models.CharField(max_length=16)
  border_style = models.CharField(max_length=16)
  border_width = models.IntegerField()
#  direction = models.CharField(max_length=8)
#  line_height = models.IntegerField(blank=True, null=True)
#  letter_spacing = models.IntegerField(blank=True, null=True)
#  word_spacing = models.IntegerField(blank=True, null=True)
  border_color = models.CharField(max_length=10)
  border_radius = models.IntegerField()
  padding_top = models.IntegerField()
  padding_left = models.IntegerField()
  padding_bottom = models.IntegerField()
  padding_right = models.IntegerField()
  opacity = models.FloatField()
  

  def set_css(self, prop, val):
    p = prop.replace("-", "_")
    self.__setattr__(p, cleanCSS(val))
    return self

  def get_css(self, prop):
    p = prop.replace("-", "_")
    r = self.__getattribute__(p)
    return r

  def to_dict(self):      
    return {
      'id': self.id,
      'html': self.html,
      'css': {
        'height': addPx(self.height),
        'width': addPx(self.width),
        'top': addPx(self.top),
        'left': addPx(self.left),
        'z-index': self.z_index,
        'opacity': self.opacity,
#        'direction': self.direction,
        'border-style': self.border_style,
        'border-width': addPx(self.border_width),
        'border-color': self.border_color,
        'background-color': self.background_color,
        'border-radius': addPx(self.border_radius),
        'padding-top': addPx(self.padding_top),
        'padding-left': addPx(self.padding_left),
        'padding-right': addPx(self.padding_right),
        'padding-bottom': addPx(self.padding_bottom)
        }
      }
    

  def save_from_dict(self, d):
    self.html = d.html
    self.save()
    for prop in d.css:
      self.set_css(prop, d.css[prop])
    return self

class ImageBox(models.Model):
# Positioning stuff:
  height = models.IntegerField()
  width = models.IntegerField()
  top = models.IntegerField()
  left = models.IntegerField()
# Data about the crop:
  is_cropped = models.BooleanField(default=False)
  crop_x1 = models.IntegerField(default = 0)
  crop_x2 = models.IntegerField(default = 0)
  crop_y1 = models.IntegerField(default = 0)
  crop_y2 = models.IntegerField(default = 0)

# Filename (originally uploaded), reference to page and whether is_displayed:
  file = models.ForeignKey(File)
  page = models.ForeignKey(Page)
  is_displayed = models.BooleanField(default=True)
# CSS:
  z_index = models.IntegerField()
  border_style = models.CharField(max_length=16)
  border_width = models.IntegerField()
  border_color = models.CharField(max_length=10)
  opacity = models.FloatField()

  def __unicode__(self):
    return self.id

  def set_css(self, prop, val):
    p = prop.replace("-", "_")
    self.__setattr__(p, cleanCSS(val))
    return self

  def get_css(self, prop):
    p = prop.replace("-", "_")
    r = self.__getattribute__(p)
    return r

  def to_dict(self, m):
    return {
    'id': self.id,
    'original_print': self.original_print(),
  #        'original_web': i.original_web(),
    'output_web': self.get_path(m),
    'css': {
      'height': addPx(self.height),
      'width': addPx(self.width),
      'top': addPx(self.top),
      'left': addPx(self.left),
      'z-index': int(self.z_index),
      'opacity': self.opacity,
      'border-style': self.border_style,
      'border-width': addPx(self.border_width),
      'border-color': self.border_color,
      },
  #DIRTY HACK: Creates a 'resource' thingie to match the front-end droppable behaviour. Try n fix at some point.
    'resource': {
      'height': self.height * m,
      'width': self.width * m,
      'resized': self.get_path(m)
      }
    }

  def save_from_dict(self, d):
    self.html = d.html
    for prop in d.css:
      self.set_css(prop, d.css[prop])
    return self

  def from_dict(self, d):
    self.html = d.html
    self.crop_top = d.crop_top
    self.crop_bottom = d.crop_bottom
    self.crop_left = d.crop_left
    self.crop_right = d.crop_right
    self.save()
    for prop in d.css:
      self.set_css(prop, d.css[prop])
    return self    

  def resize(self, width, height):
    '''
    self.width = width
    self.height = height
    self.save()
    '''
    return self


#Question: Can the following methods be constructed in such a way that if the filename (based on our naming conventions) exists, it returns the filename, else, it creates it ?

  """
    This function returns the filename of the highest res original dimensions file, converted to jpeg
  """
  def original_print(self):
    basePath = "media/images/original/"
    filename = os.path.basename(str(self.file.file))
    if extFileName(filename) != 'jpg':
      f = baseFileName(filename) + ".jpg"
    else:
      f = filename
    return basePath + f

  def crop(self, x1, y1, x2, y2, width, height):
    original_image = Image.open(MEDIA_ROOT + "/" + self.actual_unresized())
    original_width  = original_image.size[0]
    original_height = original_image.size[1]
    current_width = self.width
    current_height = self.height
    divisor = original_width / (current_width + .0)
    if self.is_cropped == False:      
      self.crop_x1 = int(x1 * divisor)
      self.crop_y1 = int(y1 * divisor)
      self.crop_x2 = int(x2 * divisor)
      self.crop_y2 = int(y2 * divisor)
      self.is_cropped = True
    else:      
      old_x1 = self.crop_x1
      old_y1 = self.crop_y1
      old_x2 = self.crop_x2
      old_y2 = self.crop_y2
      self.crop_x1 = int(x1 * divisor) + old_x1
      self.crop_y1 = int(y1 * divisor) + old_y1
      self.crop_x2 = int(x2 * divisor) + old_x1
      self.crop_y2 = int(y2 * divisor) + old_y1
    self.width = width
    self.height = height
    self.save()
    tpl = (self.crop_x1, self.crop_y1, self.crop_x2, self.crop_y2,)
    original_cropped = Image.open(MEDIA_ROOT + "/" + self.original_print()).crop(tpl)
    original_cropped.save(self.cropped_path())
    return self

  def cropped_fname(self):
    filename = baseFileName(os.path.basename(self.original_print()))
    cropped_fname = "%s_%d_%d_%d_%d.jpg" % (filename, self.crop_x1, self.crop_y1, self.crop_x2, self.crop_y2) 
    return cropped_fname

  def cropped_path(self):
    return MEDIA_ROOT + "/media/images/cropped/" + self.cropped_fname()

  def actual_unresized(self):
    if self.is_cropped:
      return "media/images/cropped/" + self.cropped_fname()
    else:
      return self.original_print()

  def get_path(self, *args):
    if len(args) > 0:
      m = args[0]
    else:
      m = 1
    path = self.actual_unresized()
    context = {
      'path': path,
      'size': (self.width * m, self.height * m,)
      }
    return get_template("thumbnailTmp.txt").render(Context(context))



  '''
  def original_web(self):
    """
    This function returns the filename of the original dimensions file, converted to jpeg, low res for web
    """ 
    return self

  def output_print(self):
    """
    This function returns the filename of the high-res cropped file
    """
    
    return self

  def output_web(self):
    """
    This function returns the filename of the low-res cropped file
    """
    return self
  '''
class PageRev(models.Model):
  """
  Stores states of the page (json representations of all boxes on page) with revision numbers so user can revert to a previous state of the page
  """
  page = models.ForeignKey(Page)
  pickle = models.TextField()
  rev_no = models.IntegerField()
  comment = models.TextField(blank=True)

  def __unicode__(self):
    return "%s: %s" % (self.page.page_no, self.rev_no)

class SliderImage(models.Model):
  small_image = models.ImageField(upload_to='images/small/', width_field='width', height_field='height', blank=False, null=False)
  big_image =  models.ImageField(upload_to='images/big/', width_field='width', height_field='height', blank=False, null=False)
  width = models.IntegerField(editable=False)
  height = models.IntegerField(editable=False)
  caption = models.CharField(max_length=255, blank=True)
  def __unicode__(self):
    return self.caption    

def comments_notify(sender, **kwargs):
  comment = kwargs['comment']
  name = comment.name
  email = comment.email
  content = comment.comment
  img_id = comment.content_object.id
  url = "http://edgwareroad.org/slider/%d" % (img_id)
  message = "Page: %s \n Name: %s \n Email: %s \n Comment: %s" % (url, name, email, content)
  send_mail("New comment on edgwareroad.org", message, "do_not_reply@edgwareroad.org", ["hello@edgwareroad.org"])
#  f = open("/home/sanj/tmp/edgeTest.txt", "w")
#  f.write(message)
  return True

comment_was_posted.connect(comments_notify)
