# Written by Alexander Belchenko
# based on Robert Collins code
#
#    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

"""Show all 'heads' in a repository"""


import time

import bzrlib
from bzrlib.commands import Command, display_command
from bzrlib import errors
from bzrlib.option import Option
from bzrlib.urlutils import unescape_for_display


class cmd_heads(Command):
    """Show all revisions in a repository not having descendants.
    """
    takes_options = [Option('by-date',
                     help='Sort heads by date (descending).'),
                     Option('all', help='Show all heads (dead and alive).'),
                     Option('dead-only', help='Show only dead heads.'),
                     Option('tips', help='Show tips of all branches.'),
                     Option('debug-time',
                     help='Enable debug print of operations times.'),
                    ]

    encoding_type = "replace"
    takes_args = ['location?']

    @display_command
    def run(self, by_date=False, all=False, dead_only=False, tips=False,
            debug_time=False, location='.'):
        import bzrlib.branch
        from bzrlib.osutils import format_date
        import bzrlib.repository

        self._init_elapsed_time(debug_time)

        to_file = self.outf

        branch = None
        try:
            branch = bzrlib.branch.Branch.open_containing(location)[0]
            repo = branch.repository
        except errors.NotBranchError:
            try:
                repo = bzrlib.repository.Repository.open(location)
            except errors.NotBranchError:
                print >>to_file, \
                      ("You need to run this command "
                       "either from the root of a shared repository,\n"
                       "or from a branch.")
                return 3
        repo.lock_read()
        try:
            possible_heads = set(repo.all_revision_ids())
            g = repo.get_graph().get_parent_map(possible_heads)
            not_heads = set()
            for parents in g.values():
                not_heads.update(set(parents))

            self.heads = possible_heads.difference(not_heads)

            # TODO: use different sorting schemes instead of alphabetical sort
            self.heads = list(self.heads)

            self._print_elapsed_time('get heads:')

            ## mark heads as dead or alive
            # mark all heads as dead
            self.head_mark = {}
            self.tips = {}
            for head in self.heads:
                self.head_mark[head] = 'dead'
            # give the list of live branches in repository or current branch
            # tip
            self._iter_branches_update_marks(repo, self.outf.encoding)
            self._print_elapsed_time('make head marks:')

            if tips:
                heads_tips = set(self.heads)
                heads_tips.update(set(self.tips.keys()))
                self.heads = list(heads_tips)
            head_revisions = dict(zip(self.heads,
                                  repo.get_revisions(self.heads)))

            # sorting by date
            if by_date:
                dates = {}
                for head in self.heads:
                    rev = head_revisions[head]
                    timestamp = rev.timestamp
                    dates[timestamp] = head
                keys = dates.keys()
                keys.sort()
                keys.reverse()
                self.heads = []
                for k in keys:
                    self.heads.append(dates[k])
                self._print_elapsed_time('sort by date:')


            # show time
            indent = ' '*2
            show_timezone = 'original'

            for head in self.heads:

                mark = self.head_mark[head]
                if not all:
                    if dead_only:
                        if mark != 'dead':
                            continue
                    else:
                        if mark != 'alive':
                            if not tips or mark != 'tip':
                                continue

                # tips
                if mark in ('alive', 'tip'):
                    t = self.tips[head]
                    if len(t) > 1:
                        print >>to_file, 'TIP of branches:',
                        print >>to_file, '[', ', '.join(t), ']'
                    else:
                        print >>to_file, 'TIP of branch:', t[0]

                if mark in ('alive', 'dead'):
                    print >>to_file, 'HEAD:',

                print >>to_file, "revision-id:", head,
                if mark == 'dead':  print >>to_file, '(dead)'
                else:               print >>to_file

                rev = head_revisions[head]
                # borrowed from LongLogFormatter
                print >>to_file,  indent+'committer:', rev.committer
                try:
                    print >>to_file, indent+'branch nick: %s' % \
                        rev.properties['branch-nick']
                except KeyError:
                    pass
                date_str = format_date(rev.timestamp,
                                       rev.timezone or 0,
                                       show_timezone)
                print >>to_file,  indent+'timestamp: %s' % date_str

                print >>to_file,  indent+'message:'
                if not rev.message:
                    print >>to_file,  indent+'  (no message)'
                else:
                    message = rev.message.rstrip('\r\n')
                    for l in message.split('\n'):
                        print >>to_file,  indent+'  ' + l
                self._print_elapsed_time('print head:')
                print

            if not self.heads:
                print >>to_file, 'No heads found'
                return 1
        finally:
            repo.unlock()

    def _iter_branches_update_marks(self, repo, encoding):
        for b in repo.find_branches(using=True):
            last_revid = b.last_revision()
            if last_revid in self.heads:
                self.head_mark[last_revid] = 'alive'
            else:
                self.head_mark[last_revid] = 'tip'
            escaped = unescape_for_display(b.base, encoding)
            self.tips.setdefault(last_revid, []).append(escaped)

    def _init_elapsed_time(self, debug_time=False):
        self.debug_time = debug_time
        if debug_time:
            self._time = time.time()

    def _print_elapsed_time(self, msg):
        if self.debug_time:
            last_time = time.time()
            print msg, last_time - self._time
            self._time = last_time
#/class cmd_heads
