# -*- coding: iso-8859-1 -*-
#
# Copyright (C) 2005 Edgewall Software
# Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.com/license.html.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://projects.edgewall.com/trac/.
#
# Author: Christopher Lenz <cmlenz@gmx.de>

from __future__ import generators
import os.path
import re

from trac import mimeview, util
from trac.core import *
from trac.env import IEnvironmentSetupParticipant
from trac.web.api import IRequestHandler
from trac.web.href import Href

def add_link(req, rel, href, title=None, mimetype=None, classname=None):
    """Add a link to the HDF data set that will be inserted as <link> element in
    the <head> of the generated HTML
    """
    link = {'href': href}
    if title:
        link['title'] = title
    if mimetype:
        link['type'] = mimetype
    if classname:
        link['class'] = classname
    idx = 0
    while req.hdf.get('chrome.links.%s.%d.href' % (rel, idx)):
        idx += 1
    req.hdf['chrome.links.%s.%d' % (rel, idx)] = link

def add_stylesheet(req, filename, mimetype='text/css'):
    """Add a link to a style sheet to the HDF data set so that it gets included
    in the generated HTML page.
    """
    if filename.startswith('common/') and 'htdocs_location' in req.hdf:
        href = Href(req.hdf['htdocs_location'])
        filename = filename[7:]
    else:
        href = Href(req.cgi_location).chrome
    add_link(req, 'stylesheet', href(filename), mimetype=mimetype)


class INavigationContributor(Interface):
    """Extension point interface for components that contribute items to the
    navigation.
    """

    def get_active_navigation_item(req):
        """This method is only called for the `IRequestHandler` processing the
        request.
        
        It should return the name of the navigation item that should be
        highlighted as active/current.
        """

    def get_navigation_items(req):
        """Should return an iterable object over the list of navigation items to
        add, each being a tuple in the form (category, name, text).
        """


class ITemplateProvider(Interface):
    """Extension point interface for components that provide their own
    ClearSilver templates and accompanying static resources.
    """

    def get_htdocs_dirs():
        """Return a list of directories with static resources (such as style
        sheets, images, etc.)

        Each item in the list must be a `(prefix, abspath)` tuple. The
        `prefix` part defines the path in the URL that requests to these
        resources are prefixed with.
        
        The `abspath` is the absolute path to the directory containing the
        resources on the local file system.
        """

    def get_templates_dirs():
        """Return a list of directories containing the provided ClearSilver
        templates.
        """


class Chrome(Component):
    """Responsible for assembling the web site chrome, i.e. everything that
    is not actual page content.
    """
    implements(IEnvironmentSetupParticipant, IRequestHandler, ITemplateProvider)

    navigation_contributors = ExtensionPoint(INavigationContributor)
    template_providers = ExtensionPoint(ITemplateProvider)

    # IEnvironmentSetupParticipant methods

    def environment_created(self):
        """Create the templates directory and some templates for
        customization.
        """
        def _create_file(filename, data=None):
            fd = open(filename, 'w')
            if data:
                fd.write(data)
            fd.close()

        if self.env.path:
            templates_dir = os.path.join(self.env.path, 'templates')
            if not os.path.exists(templates_dir):
                os.mkdir(templates_dir)
            _create_file(os.path.join(templates_dir, 'README'),
                        'This directory contains project-specific custom '
                        'templates and style sheet.\n')
            _create_file(os.path.join(templates_dir, 'site_header.cs'),
                         """<?cs
####################################################################
# Site header - Contents are automatically inserted above Trac HTML
?>
""")
            _create_file(os.path.join(templates_dir, 'site_footer.cs'),
                         """<?cs
#########################################################################
# Site footer - Contents are automatically inserted after main Trac HTML
?>
""")
            _create_file(os.path.join(templates_dir, 'site_css.cs'),
                         """<?cs
##################################################################
# Site CSS - Place custom CSS, including overriding styles here.
?>
""")

    def environment_needs_upgrade(self, db):
        return False

    def upgrade_environment(self, db):
        pass

    # IRequestHandler methods

    def match_request(self, req):
        match = re.match(r'/chrome/(?P<prefix>[^/]+)/(?P<filename>[/\w\-\.]+)',
                         req.path_info)
        if match:
            req.args['prefix'] = match.group('prefix')
            req.args['filename'] = match.group('filename')
            return True

    def process_request(self, req):
        prefix = req.args.get('prefix')
        filename = req.args.get('filename')

        dirs = []
        for provider in self.template_providers:
            for dir in [os.path.normpath(dir[1]) for dir
                        in provider.get_htdocs_dirs() if dir[0] == prefix]:
                dirs.append(dir)
                path = os.path.normpath(os.path.join(dir, filename))
                assert os.path.commonprefix([dir, path]) == dir
                if os.path.isfile(path):
                    req.send_file(path)

        # FIXME: Should return a 404 error
        self.log.warning('File %s not found in any of %s', filename, dirs)
        raise TracError, 'File not found'

    # ITemplateProvider methods

    def get_htdocs_dirs(self):
        from trac.config import default_dir
        return [('common', default_dir('htdocs')),
                ('site', self.env.get_htdocs_dir())]

    def get_templates_dirs(self):
        return [self.env.get_templates_dir(),
                self.config.get('trac', 'templates_dir')]

    # Public API methods

    def get_all_templates_dirs(self):
        """Return a list of the names of all known templates directories."""
        dirs = []
        for provider in self.template_providers:
            dirs += provider.get_templates_dirs()
        return dirs

    def populate_hdf(self, req, handler):
        """Add chrome-related data to the HDF."""

        # Provided for template customization
        req.hdf['HTTP.PathInfo'] = req.path_info

        href = Href(req.cgi_location)
        req.hdf['chrome.href'] = href.chrome()
        htdocs_location = self.config.get('trac', 'htdocs_location') or \
                          href.chrome('common')
        req.hdf['htdocs_location'] = htdocs_location.rstrip('/') + '/'

        # HTML <head> links
        add_link(req, 'start', self.env.href.wiki())
        add_link(req, 'search', self.env.href.search())
        add_link(req, 'help', self.env.href.wiki('TracGuide'))
        add_stylesheet(req, 'common/css/trac.css')
        icon = self.config.get('project', 'icon')
        if icon:
            if not icon.startswith('/') and icon.find('://') == -1:
                if '/' in icon:
                    icon = href.chrome(icon)
                else:
                    icon = href.chrome('common', icon)
            mimetype = mimeview.get_mimetype(icon)
            add_link(req, 'icon', icon, mimetype=mimetype)
            add_link(req, 'shortcut icon', icon, mimetype=mimetype)

        # Logo image
        logo_link = self.config.get('header_logo', 'link')
        logo_src = self.config.get('header_logo', 'src')
        if logo_src:
            logo_src_abs = logo_src.startswith('http://') or \
                           logo_src.startswith('https://')
            if not logo_src.startswith('/') and not logo_src_abs:
                if '/' in logo_src:
                    logo_src = href.chrome(logo_src)
                else:
                    logo_src = href.chrome('common', logo_src)
            req.hdf['chrome.logo'] = {
                'link': logo_link, 'src': logo_src, 'src_abs': logo_src_abs,
                'alt': self.config.get('header_logo', 'alt'),
                'width': self.config.get('header_logo', 'width', ''),
                'height': self.config.get('header_logo', 'height', '')
            }
        else:
            req.hdf['chrome.logo.link'] = logo_link

        # Navigation links
        navigation = {}
        active = None
        for contributor in self.navigation_contributors:
            for category, name, text in contributor.get_navigation_items(req):
                navigation.setdefault(category, {})[name] = text
            if contributor is handler:
                active = contributor.get_active_navigation_item(req)

        for category, items in [(k, v.items()) for k, v in navigation.items()]:
            order = [x.strip() for x
                     in self.config.get('trac', category).split(',')]
            def navcmp(x, y):
                if x[0] not in order:
                    return int(y[0] in order)
                if y[0] not in order:
                    return -int(x[0] in order)
                return cmp(order.index(x[0]), order.index(y[0]))
            items.sort(navcmp)

            for name, text in items:
                req.hdf['chrome.nav.%s.%s' % (category, name)] = text
                if name == active:
                    req.hdf['chrome.nav.%s.%s.active' % (category, name)] = 1

