# libgo - script to build library.gnome.org
# Copyright (C) 2007-2009  Frederic Peters
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301  USA

import logging
import os
import stat
import subprocess

try:
    import elementtree.ElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET

from utils import version_cmp


def assert_elementtree_node(node):
    '''Assert en ElementTree node can be serialized'''
    try:
        ET.ElementTree(node).write('/dev/null')
    except:
        raise AssertionError('node cannot be serialized')

class Document:
    '''Base class for documents displayed on library.gnome.org'''

    channel = None # one of ('users', 'devel', 'about', 'admin')
    module = None
    path = None
    category = None
    toc_id = None
    subsection = None
    weight = 0.5
    single_page_alternative = False

    title = None # indexed on language, most recent version
    abstract = None # indexed on language, most recent version
    href = None # for external docs, indexed on language

    languages = None # list of available languages
    versions = None # list of available versions
    version_keywords = None
    version_mapping = None # mapping of two-number version to full-numbers version
                           # like {'2.18': '2.18.3', '2.19': '2.19.90'}
    tarballs = None # indexed on two-number version number

    def __init__(self):
        self.title = {}
        self.abstract = {}
        self.versions = []
        self.version_keywords = {}
        self.version_mapping = {}
        self.keywords = []
        self.tarballs = {}

    def create_element(self, parent, language, original_language = None):
        if not language in self.languages:
            return
        doc = ET.SubElement(parent, 'document')
        if language == 'C':
            language = 'en'
        if not original_language:
            original_language = language
        href_language = None
        if self.module:
            doc.set('modulename', self.module)
        if self.path:
            doc.set('path', self.path)
        elif self.href:
            if self.href.get(original_language) and self.href.get(original_language) != '-':
                href_language = original_language
                doc.set('href', self.href.get(original_language))
            else:
                href_language = 'en'
                doc.set('href', self.href.get('en'))
        else:
            logging.error('no path and no href in module %s ' % self.module)
            return
        title = self.title.get(original_language)
        if not title:
            title = self.title.get(language)
            if not title:
                title = self.module
        ET.SubElement(doc, 'title').text = title

        abstract = self.abstract.get(original_language)
        if not abstract:
            abstract = self.abstract.get(language)
        if abstract:
            ET.SubElement(doc, 'abstract').text = abstract

        doc.set('channel', self.channel)
        doc.set('weight', str(self.weight))

        if href_language:
            doc.set('lang', href_language)
        else:
            doc.set('lang', language)

        #if self.category:
        #    doc.set('category', self.category)
        if self.toc_id:
            doc.set('toc_id', self.toc_id)

        langs = ET.SubElement(doc, 'other-languages')
        for l in self.languages:
            if l == language or l == 'C':
                continue
            ET.SubElement(langs, 'lang').text = l

        if self.versions:
            versions = ET.SubElement(doc, 'versions')
            for v in sorted(self.versions, version_cmp):
                version = ET.SubElement(versions, 'version')
                version.set('href', v)
                if v in self.version_mapping:
                    version.text = self.version_mapping[v]
                else:
                    version.text = v
                if v in self.version_keywords:
                    version.set('keyword', self.version_keywords[v])

        if self.keywords:
            keywords = ET.SubElement(doc, 'keywords')
            for k in self.keywords:
                keyword = ET.SubElement(keywords, 'keyword')
                keyword.text = k

        if self.tarballs:
            tarballs = ET.SubElement(doc, 'tarballs')
            for k in reversed(sorted(self.tarballs.keys(), version_cmp)):
                tarball = ET.SubElement(tarballs, 'tarball')
                tarball.text = self.tarballs[k]
                tarball.set('version', k)

        if self.single_page_alternative:
            doc.set('single_page_alternative', 'true')

        assert_elementtree_node(doc)

        return doc


class RemoteDocument(Document):
    '''Class for documentation files downloaded from remote servers and
    formatted according to local layout

    Sample description:

    <document doc_module="deployment-guide" channel="admin" category="guides">
      <local/> <!-- this is the important part -->
      <title>Desktop Administrators' Guide to GNOME Lockdown and Preconfiguration</title>
      <href>http://sayamindu.randomink.org/soc/deployment_guide/deployment_guide.html</href>
    </document>
    '''
    html2html_xsl_file = os.path.join(data_dir, 'xslt', 'html2html.xsl')

    def __init__(self, overlay):
        Document.__init__(self)
        self.overlay = overlay
        if 'doc_module' in overlay.attrib:
            self.module = overlay.attrib['doc_module']
        self.channel = overlay.attrib['channel']
        self.category = overlay.attrib['category']
        self.toc_id = self.category
        self.title = {}
        self.href = {}
        self.abstract = {}
        for title in overlay.findall('title'):
            lang = title.attrib.get(
                    '{http://www.w3.org/XML/1998/namespace}lang', 'en')
            self.title[lang] = title.text
        for abstract in overlay.findall('abstract'):
            lang = abstract.attrib.get(
                    '{http://www.w3.org/XML/1998/namespace}lang', 'en')
            self.abstract[lang] = abstract.text
        for href in overlay.findall('href'):
            if href.text == '-':
                continue
            lang = href.attrib.get(
                    '{http://www.w3.org/XML/1998/namespace}lang', 'en')
            self.href[lang] = href.text
        self.languages = self.title.keys()

        if overlay.find('subsection') is not None:
            self.subsection = overlay.find('subsection').text

    def retrieve_if_necessary(self, overlay):
        if overlay.find('local') is not None:
            self.remote_to_local(overlay.find('local').attrib)

    def remote_to_local(self, attribs):
        self.path = app.config.doc_path_template % {
                'channel': self.channel,
                'module': self.module }
        web_output_dir = os.path.join(app.config.output_dir, self.path.lstrip('/'))

        mtime_xsl = os.stat(self.html2html_xsl_file)[stat.ST_MTIME]

        try:
            import html5lib
        except ImportError:
            logging.error('missing html5lib, unable to convert %s' % self.module)
            html5lib = None
            return

        for lang in self.href:
            if self.href[lang] == '-':
                continue
            filename = self.download(self.href[lang])
            if not filename:
                continue
            dst = os.path.join(web_output_dir, 'index.html.%s' % lang)
            if os.path.exists(dst) and (
                    os.stat(dst)[stat.ST_MTIME] > max(mtime_xsl, os.stat(filename)[stat.ST_MTIME])):
                continue

            parser = html5lib.HTMLParser()
            doc = parser.parse(open(filename))
            doc.childNodes[-1].attributes['xmlns'] = 'http://www.w3.org/1999/xhtml'
            del doc.childNodes[:-1]
            cmd = ['xsltproc', '--output', dst,
                    '--stringparam', 'libgo.originalhref', self.href[lang],
                    '--stringparam', 'libgo.channel', self.channel,
                    '--nonet', '--xinclude', self.html2html_xsl_file, '-']
            for k in attribs:
                cmd.insert(-2, '--stringparam')
                cmd.insert(-2, 'libgo.%s' % k)
                cmd.insert(-2, attribs[k])

            logging.debug('executing %s' % ' '.join(cmd))
            xsltproc = subprocess.Popen(cmd, stdin = subprocess.PIPE)
            xsltproc.communicate(doc.toxml())
            xsltproc.wait()
            if xsltproc.returncode:
                logging.warn('%s failed with error %d' % (' '.join(cmd), xsltproc.returncode))

    def download(self, href):
        # TODO: add some support (think <local update="daily"/>) so the file
        # can be "watched" for changes
        if self.overlay.find('local').attrib.get('nocache'):
            return app.download(href, use_cache=False)
        return app.download(href)

    def create_element(self, parent, language, original_language = None):
        doc = Document.create_element(self, parent, language, original_language)
        if not doc:
            return
        doc.set('single', 'true')
