import os
import datetime
import xml
from xml.parsers.expat import *
from xml.dom.minidom import parse, parseString
from dynastie.generators.generator import DynastieGenerator, StrictUTF8Writer
from django.db import models

class Index(DynastieGenerator):

    cur_page = 0
    nb_pages = 0
    cur_post = 0
    cur_post_obj = None
    posts_per_page = 0
    filename = 'index'
    dirname = ''
    blog = None

    def __init__(self, hash_posts=None, hash_posts_content=None):
        DynastieGenerator.__init__(self, hash_posts, hash_posts_content)

        self.hooks = {'posts' : self.createPosts,
                      'title' : self.createTitle,
                      'date'  : self.createDate,
                      'navigation' : self.createNavigation,
                      'recents'    : self.createRecents,
                      'tags' : self.createTags,
                      'replace'    : self.createReplace}

        self.first_try = True

    def createReplace(self, posts, dom, root, replace_elem):
        if not replace_elem.hasAttribute('div_name'):
            self.addError('No attribute div_name for a replace tag')
            return

        div_element = replace_elem.cloneNode(True)
        div_element.tagName = replace_elem.getAttribute('div_name')
        div_element.removeAttribute('div_name')
        for key,value in replace_elem.attributes.items():
            if key == 'div_name': continue

            value = value.replace('dyn:blog_id', str(self.blog.id))
                
            div_element.setAttribute(key, value)

        root.replaceChild(div_element, replace_elem)
        return div_element

    def createNavigation(self, posts, dom, root, node):
        if self.nb_pages == 0 or self.nb_pages == 1:
            return None
        
        if self.dirname != '':
            if self.dirname.startswith('/'):
                href = '<a href="' + self.dirname + '/' + self.filename
            else:
                href = '<a href="/' + self.dirname + '/' + self.filename
        else:
            href = '<a href="/' + self.filename

        nav = ''
        if self.cur_page != 0:
            nav = nav + href + '.html">&lt;&lt; First</a>  '
            if self.cur_page == 1:
                nav = nav + href + '.html">&lt; Prev</a> '
            else:
                nav = nav + href + str(self.cur_page-1) + '.html">&lt; Prev</a> '

        start = self.cur_page-5
        if start < 0:
            start = 0
        end = start + 10
        
        if end > self.nb_pages:
            end = self.nb_pages

        for i in range(start, end):
            if i == self.cur_page:
                nav = nav + str(i+1) + ' '
            else:
                if i == 0:
                    nav = nav + href + '.html">1</a> '
                else:
                    nav = nav + href + str(i) + '.html">' + str(i+1) + '</a> '

        if self.cur_page < self.nb_pages-1:
            nav = nav + href + str(self.cur_page+1) + '.html">Next &gt;</a>  '
            nav = nav + href + str(self.nb_pages-1) + '.html">Last &gt;&gt;</a>'

        new_dom = parseString('<div class="navigation">' + nav + '</div>')
        new_node = new_dom.getElementsByTagName('div')[0]
        res = new_node.cloneNode(True)
        root.replaceChild(res, node)
        
        return res

    def createTitle(self, posts, dom, root, title_elem):
        create_link = (title_elem.getAttribute('link') == '1')
        post = self.cur_post_obj
        if create_link == True:
            node = self.createElement(dom, 'title')
            node.appendChild(self.createLinkElem(dom, post.getPath(), post.title))
        else:
            node = self.createElement(dom, 'title', post.title)

        root.replaceChild(node, title_elem)

    def createDate(self, posts, dom, root, date_elem):
        date_format = date_elem.getAttribute('format')
        if date_format == '':
            date_format = '%A, %d %B %Y %H:%m'

        post = self.cur_post_obj
        node = self.createElement(dom, 'date', post.creation_date.strftime(date_format))

        root.replaceChild(node, date_elem)

    def createPost(self, posts, dom, post_elem, root):
        post = self.cur_post_obj

        if post.id in self.hash_posts and not self.first_try:
            node = self.hash_posts[post.id]
            return node.cloneNode(0)

        values = {}
        values['author'] = post.author.first_name + ' ' + post.author.last_name
        values['post_content'] = ''

        blog = post.blog
        blog.create_paths()

        filename = blog.src_path + '/_post/' + str(post.id)

        if not os.path.exists(filename):
            self.addError('File does not exists ' + filename)
            return None

        if not filename in self.hash_posts_content:
            f = open(filename, 'rb')
            post_content = f.read()
            f.close()
            self.hash_posts_content[filename] = post_content
        else:
            post_content = self.hash_posts_content[filename]

        while True:
            start = post_content.find('<dyn:code')

            if start == -1: break
            end = post_content.find('</dyn:code>')

            if end < start:
                self.addError('Invalid <dyn:code> tags in ' + filename)
                break
            
            try:
                dom2 = parseString(post_content[start:end+11])
            except xml.dom.DOMException as e:
                self.addError('Error parsing ' + filename)
                break
            
            res = self.createCode(dom2, dom2.firstChild)
            post_content = post_content.replace(post_content[start:end+11], res)

        self.simpleTransform(values, dom, post_elem, root)
        
        content_nodes = post_elem.getElementsByTagName('div')
        post_transform = ['post_content']
        for content_node in content_nodes:
            the_class = content_node.getAttribute('class')
            if not the_class in post_transform:
                continue
            new_node = dom.createTextNode(post_content)
            content_node.appendChild(new_node)
        
        if post.id in self.hash_posts:
            import hashlib

            # Here, we are in first_try, check that computed
            # post has the same result than the one in cache
            self.first_try = False

            writer = StrictUTF8Writer()
            node = self.hash_posts[post.id]
            node.writexml(writer)
            content1 = writer.getvalue().encode('utf-8')

            writer = StrictUTF8Writer()
            post_elem.writexml(writer)
            content2 = writer.getvalue().encode('utf-8')
            
            md5_1 = hashlib.md5()
            md5_1.update(content1)

            md5_2 = hashlib.md5()
            md5_2.update(content2)

            # If not, clear cache
            if md5_1.digest() != md5_2.digest():
                self.hash_posts = {}

        self.hash_posts[post.id] = post_elem.cloneNode(0)

        return post_elem

    def createPosts(self, posts, dom, root, node):
        posts_elem = self.createElement(dom, 'posts')
        create_link = (node.getAttribute('link') == '1')
        for i in range(0, self.posts_per_page):
            post_elem = self.createElement(dom, 'post')
            if len(posts) > self.cur_post:
                self.cur_post_obj = posts[self.cur_post]
                post_elem = self.createPost(posts, dom, post_elem, node)
            else:
                post_elem = self.createElement(dom, '', '<b>No posts yet</b>')
                self.cur_post_obj = None
            if not post_elem is None:
                posts_elem.appendChild(post_elem)

            # Parse inner HTML
            self._parse(self.hooks, posts, dom, post_elem)

            self.cur_post = self.cur_post + 1
            if self.cur_post == len(posts):
                break
        root.replaceChild(posts_elem, node)
        return posts_elem

    def createRecents(self, posts, dom, root, node):
        if self.cur_post == len(posts):
            root.removeChild(node)
            return

        if node.hasAttribute("limit"):
            nb_recents = int(node.getAttribute("limit"))
        else:
            nb_recents = 5
        recents_elem = self.createElement(dom, 'recents')
        for child in node.childNodes:
            recents_elem.appendChild(child.cloneNode(True))
        list_elem = dom.createElement('ul')
        for i in range(0, nb_recents):
            post_elem = dom.createElement('li')
            if self.cur_post+i < len(posts):
                post = posts[self.cur_post+i]
                link_elem = self.createLinkElem(dom, post.getPath(), post.title)
                post_elem.appendChild(link_elem)
            else:
                break
            list_elem.appendChild(post_elem)

        recents_elem.appendChild(list_elem)
        root.replaceChild(recents_elem, node)
        
        return recents_elem

    def createTags(self, posts, dom, root, node):
        from dynastie.models import Post
        tags_elem = self.createElement(dom, 'tags')
        create_link = (node.getAttribute('link') == '1')
        if type(posts) == models.query.QuerySet:
            if len(posts) > self.cur_post:
                cur_post = posts[self.cur_post]
            else:
                cur_post = None
        elif type(posts) == Post:
            cur_post = posts
        else:
            cur_post = None

        if not cur_post is None:
            for tag in cur_post.tags.all():
                if create_link:
                    tag_elem = self.createElement(dom, 'tag')
                    link_elem = self.createLinkElem(dom, '/tag/' + tag.name_slug, '#' + tag.name)
                    tag_elem.appendChild(link_elem)
                else:
                    tag_elem = self.createElement(dom, 'tag', '#' + tag.name)
                tags_elem.appendChild(tag_elem)

            if len(cur_post.tags.all()) == 0:
                root.removeChild(node)
                return None
            else:
                root.replaceChild(tags_elem, node)
        else:
            root.removeChild(node)
            return None

        return tags_elem

    def createCode(self, dom, node):
        try:
            from pygments import highlight
            from pygments.util import ClassNotFound
            from pygments.lexers import get_lexer_by_name
            from pygments.formatters import get_formatter_by_name
        except ImportError:
            self.addError('Pygments package is missing, please install it in order to use <dyn:code>')
            return

        language = node.getAttribute('language')
        
        if language is None:
            self.addWarning('No language defined for <dyn:code> assuming C language')
            language = "c"
        else:
            language = language.lower()

        lexer_options = {}
        try:
            lexer = get_lexer_by_name(language, **lexer_options)
        except ClassNotFound:
            self.addWarning('Language ' + language + ' not supported by current version of pygments')
            lexer = get_lexer_by_name('c', **lexer_options)
        
        formatter_options = {'classprefix' : 'color_emacs_', 'style' : 'emacs'}

        for k in node.attributes.keys():
            attr = node.attributes[k]
            if attr.prefix != '': continue
            if attr.name == 'language': continue
            name = attr.name
            value = attr.value
            if name == 'colouring': 
                name = 'style'
                formatter_options['classprefix'] = 'color_' + value + '_' 
            formatter_options[name] = value

        formatter = get_formatter_by_name('html', **formatter_options)

        lexer.encoding = 'utf-8'
        formatter.encoding = 'utf-8'
        
        writer = StrictUTF8Writer()
        node.firstChild.writexml(writer)
        code = writer.getvalue().encode('utf-8')

        r,w = os.pipe()
        r,w=os.fdopen(r,'r',0), os.fdopen(w,'w',0)
        highlight(code, lexer, formatter, w)
        w.close()
        
        code = r.read()
        r.close()

        # Remove <pre> after <div class="highlight">
        code = code[28:-13]
        code = '<div class="highlight">' + code + '</div>'

        return code

    def parseTemplate(self, blog, src, output, name, directory=None):
        self.blog = blog

        if not os.path.exists(src + '/_%s.html' % name):
            self.addError('No _%s.html found, exiting' % name)
            return None

        try:
            dom = parse(src + '/_%s.html' % name)
        except xml.dom.DOMException as e:
            self.addError('Error parsing _%s.html : ' + e)
            return None
            
        if not directory is None and not os.path.exists(output + '/' + directory):
            os.mkdir(output + '/' + directory)

        post_nodes = dom.getElementsByTagNameNS(self.URI, "posts")

        if not post_nodes is None:
            if post_nodes[0].hasAttribute("limit"):
                self.posts_per_page = int(post_nodes[0].getAttribute("limit"))
            else:
                self.posts_per_page = 5
        else:
            self.addError('No tag dyn:posts found')

        return dom

    def generatePages(self, dom, posts, src, output, name, directory=None):
        if len(posts) > self.posts_per_page:
            self.nb_pages = self.computeNbPages(len(posts), self.posts_per_page)

        if not directory is None and not os.path.exists(output + self.dirname):
            os.mkdir(output + self.dirname)

        filename = self.dirname + '/' + self.filename + '.html'
        
        impl = xml.dom.getDOMImplementation()

        while self.cur_page <= self.nb_pages:
            #print 'Generate ' + filename
            dom_ = impl.createDocument('', 'xml', None)
            dom_.replaceChild(dom.firstChild.cloneNode(0), dom_.firstChild)
            nodes = dom.getElementsByTagName("*")
            nodes[0] = self.parse(src, self.hooks, posts, dom_, nodes[0])
            self.writeIfNotTheSame(output + filename, nodes[0])
            self.cur_page = self.cur_page + 1
            filename = self.dirname + '/' + self.filename + str(self.cur_page) + '.html'

        filename = output + filename

        while os.path.exists(filename):
            self.addReport('Removing unused ' + filename)
            os.unlink(filename)
            filename = filename + '.gz'
            if os.path.exists(filename):
                self.addReport('Removing unused ' + filename)
                os.unlink(filename)
            self.cur_page = self.cur_page + 1
            filename = self.dirname + '/' + self.filename + str(self.cur_page) + '.html'
            filename = output + filename

    def generate(self, blog, src, output):
        from dynastie.models import Post, Blog

        dom = self.parseTemplate(blog, src, output, 'index')
        if dom is None: return self.report

        now = datetime.datetime.now()
        cur_year = now.year
        posts = Post.objects.filter(creation_date__year=cur_year, published=True, front_page=True).order_by('-creation_date')

        if posts.count() < self.posts_per_page:
            posts = Post.objects.filter(published=True, front_page=True).order_by('-creation_date')[:self.posts_per_page]

        self.dirname = ''
        self.generatePages(dom, posts, src, output, 'index')

        if not self.somethingWrote:
            self.addReport('Nothing changed')

        return self.report
