import os
import shutil
import hashlib
import inspect
import gzip
from unicodedata import normalize
from re import sub
from datetime import datetime
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_init, pre_delete, post_delete, post_save
from django.dispatch import receiver
from dynastie.generators import *

def slugify(name):
    name = name.strip()
    name = normalize('NFKD', name).encode('ascii', 'ignore').replace(' ', '-').lower()
        #remove `other` characters
    name = sub('[^a-zA-Z0-9_-]', '', name)
        #nomalize dashes
    name = sub('-+', '-', name)
    return name

class Blog(models.Model):
    name = models.CharField(max_length=255, unique=True)
    title = models.CharField(max_length=255)
    description = models.TextField(max_length=255, blank=True)
    keywords = models.TextField(blank=True)
    writers = models.ManyToManyField(User)
    
    engines = list()

    src_path = ''
    output_path = ''
    report = ''

    def create_paths(self):
        self.src_path = os.environ['DYNASTIE_ROOT'] + 'sites/' + self.name
        self.output_path = os.environ['DYNASTIE_ROOT'] + 'sites/' + self.name + '_output'
        
    def create(self):
        self.create_paths()
        if not os.path.exists('sites'):
            os.mkdir('sites')

        if not os.path.exists(self.src_path):
            os.mkdir(self.src_path)

        if not os.path.exists(self.output_path):
            os.mkdir(self.output_path)
        
    def remove(self):
        if os.path.exists(self.src_path):
            shutil.rmtree(self.src_path)
        if os.path.exists(self.output_path):
            shutil.rmtree(self.output_path)

    def load_generators(self):
        if os.path.exists(self.src_path + '/_generators'):
            f = open(self.src_path + '/_generators', 'r')
            for line in f:
                if line.startswith("#"):
                    continue
                engine = line.strip()
                if not engine in globals():
                    print 'Engine ' + engine + ' doesn\'t exists'
                self.engines.append(globals()[engine])
            f.close()
        else:
            self.engines.append(globals()['post'])
            self.engines.append(globals()['index'])
            self.engines.append(globals()['category'])
            self.engines.append(globals()['tag'])
            self.engines.append(globals()['archive'])
            self.engines.append(globals()['atom'])
            self.engines.append(globals()['rss'])
            
    def copytree(self, src, dst):
        names = os.listdir(src)

        errors = []
        for name in names:
            if name.startswith('_') or name.endswith('~'):
                continue
            srcname = os.path.join(src, name)
            dstname = os.path.join(dst, name)
            try:
                if os.path.islink(srcname) and not os.path.exists(dstname):
                    linkto = os.readlink(srcname)
                    os.symlink(linkto, dstname)
                if os.path.isdir(srcname):
                    if not os.path.exists(dstname):
                        os.makedirs(dstname)
                    self.copytree(srcname, dstname)
                else:
                    copied = False
                    if os.path.exists(dstname):
                        src_md5 = hashlib.md5()
                        f = open(srcname,'rb') 
                        src_md5.update(f.read())
                        f.close()

                        dst_md5 = hashlib.md5()
                        f = open(dstname,'rb') 
                        dst_md5.update(f.read())
                        f.close()

                        if src_md5.digest() != dst_md5.digest():
                            self.report = self.report + 'Update ' + dstname + '<br/>\n'
                            shutil.copy2(srcname, dstname)
                            copied = True
                            
                    else:
                        self.report = self.report + 'Add ' + dstname + '<br/>\n'
                        shutil.copy2(srcname, dstname)
                        copied = True

                    if copied:
                        exts = ('css', 'html', 'htm', 'xhtml', 'js')
                        found = False
                        for ext in exts:
                            if srcname.endswith(ext):
                                found = True
                                break
                        if found:
                            dstname = dstname + '.gz'
                            if os.path.exists(dstname):
                                os.unlink(dstname)
                            f = open(srcname)
                            content = f.read()
                            f.close()
                            f = gzip.open(dstname, 'wb')
                            f.write(content)
                            f.close()


        # XXX What about devices, sockets etc.?
            except (IOError, os.error), why:
                errors.append((srcname, dstname, str(why)))
        # catch the Error from the recursive copytree so that we can
        # continue with other files
            except Exception, err:
                errors.extend(err.args[0])
        if errors:
            raise Exception(errors)

    def generate(self):
        self.report = '<br/><br/>Generation of ' + datetime.now().strftime("%d/%m/%Y at %H:%M:%S") + '<br/>\n'
        self.load_generators()
        self.copytree(self.src_path, self.output_path)
        generated = []
        hash_posts = {}
        hash_posts_content = {}
        for engine in self.engines:
            if not inspect.ismodule(engine):
                continue
            for name, obj in inspect.getmembers(engine):
                if inspect.isclass(obj) and obj.__module__.startswith("dynastie.generators"):
                    if obj.__module__ in generated: continue
                    e = obj(hash_posts, hash_posts_content)
                    r = e.generate(self, self.src_path, self.output_path)
                    generated.append(obj.__module__)
                    if not r is None:
                        self.report = self.report + '<br/>\n' + r

        return self.report

class Editor(models.Model):
    name = models.CharField(max_length=255, unique=True)

class Category(models.Model):
    name = models.CharField(max_length=255, unique=True)
    name_slug = models.CharField(max_length=255)
    parent = models.ForeignKey('self', blank=True, null=True)
    description = models.TextField(max_length=255, blank=True)
    blog = models.ForeignKey(Blog)

    def save(self):
        self.name_slug = slugify(self.name)
        super(Category, self).save()

    def remove(self):
        blog = Blog.objects.get(pk=self.blog)
        output = blog.output_path + '/category/' + self.name_slug
        if os.path.exists(output):
            shutil.rmtree(output)


class Tag(models.Model):
    name = models.CharField(max_length=255, unique=True)
    name_slug = models.CharField(max_length=255)
    blog = models.ForeignKey(Blog)
    
    def save(self):
        self.name_slug = slugify(self.name)
        super(Tag, self).save()

    def remove(self):
        blog = Blog.objects.get(pk=self.blog)
        output = blog.output_path + '/tag/' + self.name_slug
        if os.path.exists(output):
            shutil.rmtree(output)

class Post(models.Model):
    title = models.CharField(max_length=255)
    title_slug = models.CharField(max_length=255)
    category = models.ForeignKey(Category, blank=True, null=True, on_delete=models.SET_NULL)
    published = models.BooleanField()
    creation_date = models.DateTimeField()
    modification_date = models.DateTimeField()
    front_page = models.BooleanField()
    author = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
    description = models.TextField(max_length=255, blank=True)
    keywords = models.TextField(blank=True)
    tags = models.ManyToManyField(Tag, blank=True, null=True)
    blog = models.ForeignKey(Blog)

    def getPath(self):
        filename = '/post/' 
        filename = filename + self.creation_date.strftime("%Y") + '/' + self.creation_date.strftime("%m") + '/' 
        filename = filename + self.title_slug + '.html'
        return filename

    def save(self):
        self.title = self.title.strip()
        self.title_slug = slugify(self.title)
        super(Post, self).save()

    def createPost(self, content, tags):
        b = self.blog
        output = b.src_path
        if not os.path.exists(output + '/_post'):
            os.mkdir(output + '/_post')

        filename = output + '/_post/' + str(self.pk)
        content = unicode(content)
        content = content.encode('utf-8')
        modif = True

        if os.path.exists(filename):
            f = open(filename, 'rb')
            src_md5 = hashlib.md5()
            src_md5.update(f.read())
            f.close()

            dst_md5 = hashlib.md5()
            dst_md5.update(content)

            if src_md5.digest() == dst_md5.digest():
                modif = False
            else:
                os.unlink(filename)

        if modif:
            f = open(filename, 'wb')
            f.write(content)
            f.close()
            self.modification_date=datetime.now()

        tags_list = Tag.objects.filter(blog_id=self.blog.id)
        my_tags = []
        # Create new tags
        for tag in tags.split(','):
            if tag == '': continue
            tag_slug = slugify(tag)
            found = False
            for t in tags_list:
                if t.name_slug == tag_slug:
                    found = True
                    break
            if not found and not tag in my_tags:
                t = Tag(blog=self.blog, name=tag.strip(), name_slug=tag_slug)
                t.save()
                # print 'Create ' + tag_slug
                my_tags.append(tag)

        # Add new tags
        post_tags_list = Tag.objects.filter(post=self.id)
        for tag in tags.split(','):
            if tag == '': continue
            tag_slug = slugify(tag)
            found = False
            for t in post_tags_list:
                if t.name_slug == tag_slug:
                    found = True
                    break
            if not found:
                for t in tags_list:
                    if t.name_slug == tag_slug:
                        self.tags.add(t)
                        # print 'Add ' + tag_slug 
                        break

        # Remove old tags
        if tags == '':
            for t in post_tags_list:
                self.tags.remove(t)
        else:
            for t in post_tags_list:
                found = False
                for tag in tags.split(','):
                    tag_slug = slugify(tag)
                    if t.name_slug == tag_slug:
                        found = True
                        break
                if not found:
                # print 'Remove ' + t.name_slug
                    self.tags.remove(t)

        self.save()

    def remove(self):
        b = self.blog

        output = b.src_path
        filename = output + '/_post/' + str(self.pk)
        if os.path.exists(filename):
            os.unlink(filename)

        output = b.output_path + self.getPath()
        if os.path.exists(filename):
            os.unlink(filename)
        filename = filename + '.gz'
        if os.path.exists(filename):
            os.unlink(filename)

        filename = b.src_path + '/post/'
        filename = filename + self.creation_date.strftime("%Y") + '/' + self.creation_date.strftime("%m") + '/' 
        if os.path.exists(filename) and len(os.listdir(filename)) == 0:
            os.rmdir(filename)

        filename = b.output_path + '/post/' 
        filename = filename + self.creation_date.strftime("%Y") + '/'
        if os.path.exists(filename) and len(os.listdir(filename)) == 0:
            os.rmdir(filename)

class Comment(models.Model):
    post = models.ForeignKey(Post)
    parent = models.ForeignKey('self', null=True, blank=True)
    date = models.DateTimeField()
    author = models.CharField(max_length=255)
    email = models.EmailField(max_length=255, blank=True)
    the_comment = models.TextField(max_length=255)
    ip = models.GenericIPAddressField()

@receiver(post_init, sender=Blog)
def init_blog_signal(sender, **kwargs):
    kwargs['instance'].create_paths()

@receiver(post_delete, sender=Blog)
def delete_blog_signal(sender, **kwargs):
    kwargs['instance'].remove()

@receiver(pre_delete, sender=Category)
def delete_category_signal(sender, **kwargs):
    kwargs['instance'].remove()

@receiver(pre_delete, sender=Tag)
def delete_tag_signal(sender, **kwargs):
    kwargs['instance'].remove()

@receiver(post_delete, sender=Post)
def delete_post_signal(sender, **kwargs):
    kwargs['instance'].remove()
