# -*- coding: utf-8 -*-
#
# Copyright (C) 2005 Edgewall Software
# Copyright (C) 2005-2010 Lele Gaifax <lele@metapensiero.it>
#
# 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: Lele Gaifax <lele@metapensiero.it>

from genshi.builder import tag

from trac.config import BoolOption, Option
from trac.core import Component, implements
from trac.db import Column, DatabaseManager, Index, Table
from trac.env import IEnvironmentSetupParticipant
from trac.util.text import shorten_line
from trac.versioncontrol import IRepositoryConnector, NoSuchChangeset
from trac.versioncontrol.web_ui import IPropertyRenderer, RenderedProperty

from trac.wiki import IWikiSyntaxProvider

from tracdarcs.repository import DarcsRepository

class DarcsConnector(Component):

    implements(IRepositoryConnector, IWikiSyntaxProvider)

    dont_escape_8bit = BoolOption('darcs', 'dont_escape_8bit', 'false',
                                  "Avoid darcs automatic escape of non-7bit chars.")

    darcs_command = Option('darcs', 'command', 'darcs',
                           "Name of the external darcs binary.")

    max_concurrent_darcses = Option('darcs', 'max_concurrent_darcses', 'false',
                                    "Max number of concurrent darcses running per repository.")

    possible_encodings = Option('darcs', 'possible_encodings', 'utf-8,iso8859-1',
                                "Specify possible repository encodings.")

    # IRepositoryConnector methods

    def get_supported_types(self):
        """Support the `darcs:` scheme"""
        yield ("darcs", 8)

    def get_repository(self, type, dir, params):
        """Return a `DarcsRepository`"""
        db = self.env.get_db_cnx()
        darcs = self.darcs_command
        if self.dont_escape_8bit:
            darcs = "DARCS_DONT_ESCAPE_8BIT=1 " + darcs
        if self.possible_encodings:
            possible_encodings = [e.strip()
                                  for e in self.possible_encodings.split(',')]

        # Setup the semaphore used to limit the number of concurrent running
        # darcs within a single repository.

        if self.max_concurrent_darcses and int(self.max_concurrent_darcses)>0:
            from command import DarcsCommand
            if DarcsCommand.RUNNING_DARCSES is None:
                from threading import BoundedSemaphore
                DarcsCommand.RUNNING_DARCSES = BoundedSemaphore(value=int(self.max_concurrent_darcses))

        return DarcsRepository(db, dir, self.env.log, darcs, possible_encodings, params)

    # IWikiSyntaxProvider methods

    def get_wiki_syntax(self):
        yield (r'[0-9]{14}-[0-9a-f]{5}-[0-9a-f]{40}(.gz)?',
               lambda formatter, label, match: self._format_link(formatter, 'cset', label, label))

    def get_link_resolvers(self):
        yield ('cset', self._format_link)

    def _format_link(self, formatter, ns, rev, label):

        repos = self.env.get_repository()
        try:
            chgset = repos.get_changeset(rev)
            return tag.a(chgset.rev, class_="changeset",
                         title=shorten_line(chgset.message),
                         href=formatter.href.changeset(chgset.rev))
        except NoSuchChangeset, e:
            return tag.a(label, class_="missing changeset",
                         title="No changeset for %s" % rev, rel="nofollow")

class DarcsSetup(Component):
    """Darcs customizer.

    The db tables required for the darcs backend are created here.
    """

    implements(IEnvironmentSetupParticipant)

    def environment_created(self):
        """After standard environment has been created, add the needed
        tables."""

        db = self.env.get_db_cnx()
        self.upgrade_environment(db)
        db.commit()

    def environment_needs_upgrade(self, db):
        """Check to see if the darcs tables are already there."""

        c = db.cursor()
        try:
            c.execute('SELECT repo_id,rev,hash,name '
                      'FROM darcs_changesets LIMIT 1')
            c.execute('SELECT repo_id,node_id,node_type,add_rev,remove_rev '
                      'FROM darcs_nodes LIMIT 1')
            c.execute('SELECT repo_id,node_id,rev,path,parent_id,change_kind '
                      'FROM darcs_node_changes LIMIT 1')
            c.execute('SELECT repo_id,node_id,rev,content,size '
                      'FROM darcs_cache LIMIT 1')
            c.execute('SELECT repo_id,node_id,rev '
                      'FROM darcs_annotate_cache LIMIT 1')
            return False
        except:
            db.rollback()
            return True

    def upgrade_environment(self, db):
        """Actually add the new db tables."""

        def drop_table(table_name):
            c = db.cursor()
            try:
                c.execute('drop table %s' % table_name)
            except:
                db.rollback()
                pass

        drop_table('darcs_revisions')  # until 0.6
        drop_table('darcs_changesets')
        drop_table('darcs_nodes')
        drop_table('darcs_node_changes')
        drop_table('darcs_cache')
        drop_table('darcs_annotate_cache')

        connector = DatabaseManager(self.env)._get_connector()[0]
        if 'postgres' in [supp[0] for supp in connector.get_supported_schemes()]:
            blobtype = 'bytea'
        else:
            blobtype = 'blob'
        rev_table = Table('darcs_changesets', key=('repo_id','rev'))[
            Column('repo_id',type='int'),
            Column('rev',type='int'),
            Column('hash'),
            Column('name'),
            Index(['hash','repo_id'])]
        node_table = Table('darcs_nodes', key=('repo_id','node_id'))[
            Column('repo_id',type='int'),
            Column('node_id',type='int'),
            Column('node_type',size=1),
            Column('add_rev',type='int'),
            Column('remove_rev',type='int')]
        change_table = Table('darcs_node_changes', key=('repo_id','node_id','rev'))[
            Column('repo_id',type='int'),
            Column('node_id',type='int'),
            Column('rev',type='int'),
            Column('path'),
            Column('parent_id',type='int'),
            Column('change_kind'),
            Index(['path'])]
        cache_table = Table('darcs_cache', key=('repo_id','node_id','rev'))[
            Column('repo_id',type='int'),
            Column('node_id',type='int'),
            Column('rev',type='int'),
            Column('content',type=blobtype),
            Column('size',type='int')]
        ann_cache_table = Table('darcs_annotate_cache', key=('repo_id','node_id','rev','up_to_line'))[
            Column('repo_id',type='int'),
            Column('node_id',type='int'),
            Column('rev',type='int'),
            Column('up_to_line',type='int'),
            Column('blame_rev',type='int')]
        c = db.cursor()
        for t in [rev_table,node_table,change_table,cache_table,ann_cache_table]:
            for stmt in connector.to_sql(t):
                c.execute(stmt)

class EquivalentChangesetsRenderer(Component):
    implements(IPropertyRenderer)

    def match_property(self, name, mode):
        return (mode == 'revprop' and name == 'EqChangesets') and 5 or 0

    def render_property(self, name, mode, context, props):
        eqcsets = props[name]
        eqlinks = [tag.a(repos or '(default)', class_="changeset",
                         title="Equivalent patch %s in repository %s" % (
                             rev, repos or '(default)'),
                         href=context.href.changeset(rev, repos))
                   for repos, rev in eqcsets]
        return RenderedProperty(name='Present in:',
                                name_attributes=[("class", "property")],
                                content=tag([eqlinks]))
