# 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 __builtin__
import glob
import logging
import os
import sys
from optparse import OptionParser
import shutil
import stat
import subprocess
import urllib2

try:
    from pysqlite2 import dbapi2 as sqlite
except ImportError:
    sqlite = None

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

from config import Config
import utils

class App:
    default_indexes_xsl_file = os.path.join(data_dir, 'xslt', 'indexes.xsl')
    javascript_dir = os.path.join(data_dir, 'js')
    skin_dir = os.path.join(data_dir, 'skin')
    images_dir = os.path.join(data_dir, 'images')

    rebuild_all = False
    debug = False

    def __init__(self):
        __builtin__.__dict__['app'] = self
        self.documents = []

        parser = OptionParser()
        parser.add_option('-c', '--config', dest = 'config')
        parser.add_option('-v', '--verbose',
            action = 'count', dest = 'verbose', default = 0,
            help = 'verbosity level (more -v for more verbose)')
        parser.add_option('--rebuild', dest = 'rebuild_module',
            help = 'rebuild documentation from FILENAME', metavar = 'FILENAME')
        parser.add_option('--rebuild-all',
            action = 'store_true', dest = 'rebuild_all',
            help = 'rebuild all documents (even those that were already built)')
        parser.add_option('--rebuild-language', dest = 'rebuild_language',
            help = 'rebuild all documents in LANGUAGE', metavar = 'LANGUAGE')
        parser.add_option('--skip-extra-tarballs',
            action = 'store_false', dest = 'skip_extra_tarballs',
            help = "don't look for documentation extra tarballs")
        parser.add_option('--rebuild-remote-documents',
            action = 'store_true', dest = 'rebuild_remote',
            help = 'rebuild remote documents')
        self.options, args = parser.parse_args()

        logging.basicConfig(level = 10 + logging.CRITICAL - self.options.verbose*10,
            formatter = utils.LogFormatter())
        logging.getLogger().handlers[0].setFormatter(utils.LogFormatter())

        self.debug = (self.options.verbose >= 5)
        self.rebuild_all = self.options.rebuild_all
        self.rebuild_language = self.options.rebuild_language

        if self.options.config:
            self.config = Config(filename = self.options.config)
        else:
            self.config = Config()

        self.check_sanity()

    def check_sanity(self):
        for filename in [os.path.join(data_dir, 'overlay.xml'),
                    os.path.join(data_dir, 'catalog.xml')]:
            if not os.path.exists(filename):
                print >> sys.stderr, '%s is missing, you should run make' % filename
                sys.exit(1)

        if not self.config.output_dir.endswith(os.path.sep):
            logging.warning('output dir should end with slash')
            self.config.output_dir += os.path.sep

    def copy_static_files(self):
        if not os.path.exists(os.path.join(self.config.output_dir, 'js')):
            os.makedirs(os.path.join(self.config.output_dir, 'js'))

        for src in glob.glob('%s/*.js' % self.javascript_dir):
            dst = os.path.join(self.config.output_dir, 'js', os.path.basename(src))
            if not os.path.exists(dst) or \
                    os.stat(src)[stat.ST_MTIME] > os.stat(dst)[stat.ST_MTIME]:
                open(dst, 'w').write(open(src, 'r').read())

        skin_dir = os.path.join(self.config.output_dir, 'skin')
        if os.path.exists(skin_dir):
            shutil.rmtree(skin_dir)
        shutil.copytree(self.skin_dir, os.path.join(self.config.output_dir, 'skin'))

        if os.path.exists(self.images_dir):
            images_dir = os.path.join(self.config.output_dir, 'images')
            if os.path.exists(images_dir):
                shutil.rmtree(images_dir)
            shutil.copytree(self.images_dir, os.path.join(self.config.output_dir, 'images'))

    def download(self, url, use_cache=True):
        parsed_url = urllib2.urlparse.urlparse(url)
        if parsed_url[0] in ('file', '', None):
            return parsed_url[2]
        filename = '/'.join((parsed_url[1], parsed_url[2].replace('/', '__')))
        cache_filename = os.path.join(self.config.download_cache_dir, filename)
        cache_dir = os.path.split(cache_filename)[0]
        if not os.path.exists(cache_dir):
            os.makedirs(cache_dir)
        if not os.path.exists(cache_filename) or not use_cache:
            logging.info('downloading %s' % url)
            try:
                s = urllib2.urlopen(url).read()
            except urllib2.HTTPError, e:
                if os.path.exists(cache_filename):
                    logging.warning('error %s downloading %s (using cache now)' % (e.code, url))
                    return cache_filename
                logging.warning('error %s downloading %s' % (e.code, url))
                return None
            except urllib2.URLError, e:
                if os.path.exists(cache_filename):
                    logging.warning('error (URLError) downloading %s (using cache now)' % url)
                    return cache_filename
                logging.warning('error (URLError) downloading %s' % url)
                return None
            open(cache_filename, 'w').write(s)
        return cache_filename

    def apply_overlay(self):
        logging.info('Applying overlay')
        for doc in self.documents:
            self.overlay.apply(doc)
        self.documents.extend(self.overlay.get_new_docs())

    def generate_symbols_files(self):
        if not (self.config.symbols_dbm_filepath or self.config.symbols_sqlite_filepath):
            return
        logging.info('getting all symbols')

        if self.rebuild_all:
            if self.config.symbols_dbm_filepath and os.path.exists(
                    self.config.symbols_dbm_filepath):
                os.unlink(self.config.symbols_dbm_filepath)

        if self.config.symbols_sqlite_filepath and os.path.exists(
                self.config.symbols_sqlite_filepath):
            os.unlink(self.config.symbols_sqlite_filepath)

        if self.config.symbols_dbm_filepath:
            cmd = [self.config.httxt2dbm_path, '-i', '-', '-o', self.config.symbols_dbm_filepath]
            logging.debug('executing %s' % ' '.join(cmd))
            try:
                httxt2dbm = subprocess.Popen(cmd, stdin = subprocess.PIPE).stdin
            except OSError:
                logging.error('failed to generate dbm symbols file (OSError)')
                return
        else:
            httxt2dbm = None

        if sqlite and self.config.symbols_sqlite_filepath:
            sqlcon = sqlite.connect(self.config.symbols_sqlite_filepath, isolation_level=None)
            sqlcon.execute('create table symbols(symbol, path)')
            sqlcur = sqlcon.cursor()
        else:
            sqlcon = None
            sqlcur = None

        # if active, the dbm symbol file will be generated while iterating for
        # SQLite.
        if sqlcur:
            sqlcur.executemany('insert into symbols values (?, ?)',
                    self.symbols_iterator(httxt2dbm))
            sqlcur.execute('create index symbols_idx on symbols(symbol)')
        else:
            for x in self.symbols_iterator(httxt2dbm):
                pass

        if httxt2dbm:
            httxt2dbm.close()

    def symbols_iterator(self, httxt2dbm=None):
        for doc in self.documents:
            if doc.category != 'api':
                continue
            if not doc.module or not doc.path:
                continue

            web_dir = os.path.join(app.config.output_dir, doc.path[1:])

            devhelp_path = os.path.join(web_dir, '%s.devhelp2' % doc.module)
            if os.path.exists(devhelp_path):
                tree = ET.parse(devhelp_path)
                for keyword in tree.findall('//{http://www.devhelp.net/book}keyword'):
                    key = keyword.attrib.get('name').replace('()', '').strip()
                    if not key or ' ' in key:
                        # ignore keys with spaces in their name
                        continue
                    value = os.path.join(doc.path, keyword.attrib.get('link'))
                    if httxt2dbm:
                        print >> httxt2dbm, key, value
                    yield (key, value)
        return

