# Copyright (C) 2005-2012 Aaron Bentley <abentley@panoramicfeedback.com>
#                         Chris Ball <cjb@laptop.org>
#                         Gianluca Montecchi <gian@grys.it>
#                         Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com>
#                         Robert Lehmann <mail@robertlehmann.de>
#                         W. Trevor King <wking@tremily.us>
#
# This file is part of Bugs Everywhere.
#
# Bugs Everywhere 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.
#
# Bugs Everywhere 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
# Bugs Everywhere.  If not, see <http://www.gnu.org/licenses/>.

import itertools
import os
import re

import libbe
import libbe.bug
import libbe.command
import libbe.command.depend
from libbe.command.depend import Filter, parse_status, parse_severity
import libbe.command.tag
import libbe.command.target
import libbe.command.util

# get a list of * for cmp_*() comparing two bugs.
AVAILABLE_CMPS = [fn[4:] for fn in dir(libbe.bug) if fn[:4] == 'cmp_']
AVAILABLE_CMPS.remove('attr') # a cmp_* template.

class List (libbe.command.Command):
    """List bugs

    >>> import sys
    >>> import libbe.bugdir
    >>> bd = libbe.bugdir.SimpleBugDir(memory=False)
    >>> io = libbe.command.StringInputOutput()
    >>> io.stdout = sys.stdout
    >>> ui = libbe.command.UserInterface(io=io)
    >>> ui.storage_callbacks.set_storage(bd.storage)
    >>> cmd = List(ui=ui)

    >>> ret = ui.run(cmd)
    abc/a:om: Bug A
    >>> ret = ui.run(cmd, {'status':'closed'})
    abc/b:cm: Bug B
    >>> ret = ui.run(cmd, {'status':'all', 'sort':'time'})
    abc/a:om: Bug A
    abc/b:cm: Bug B
    >>> bd.storage.writeable
    True
    >>> ui.cleanup()
    >>> bd.cleanup()
    """

    name = 'list'

    def __init__(self, *args, **kwargs):
        libbe.command.Command.__init__(self, *args, **kwargs)
        self.options.extend([
                libbe.command.Option(name='status',
                    help='Only show bugs matching the STATUS specifier',
                    arg=libbe.command.Argument(
                        name='status', metavar='STATUS', default='active',
                        completion_callback=libbe.command.util.complete_status)),
                libbe.command.Option(name='severity',
                    help='Only show bugs matching the SEVERITY specifier',
                    arg=libbe.command.Argument(
                        name='severity', metavar='SEVERITY', default='all',
                        completion_callback=libbe.command.util.complete_severity)),
                libbe.command.Option(name='important',
                    help='List bugs with >= "serious" severity'),
                libbe.command.Option(name='assigned', short_name='a',
                    help='Only show bugs matching ASSIGNED',
                    arg=libbe.command.Argument(
                        name='assigned', metavar='ASSIGNED', default=None,
                        completion_callback=libbe.command.util.complete_assigned)),
                libbe.command.Option(name='mine', short_name='m',
                    help='List bugs assigned to you'),
                libbe.command.Option(name='extra-strings', short_name='e',
                    help='Only show bugs matching STRINGS, e.g. --extra-strings'
                         ' TAG:working,TAG:xml',
                    arg=libbe.command.Argument(
                        name='extra-strings', metavar='STRINGS', default=None,
                        completion_callback=libbe.command.util.complete_extra_strings)),
                libbe.command.Option(name='sort', short_name='S',
                    help='Adjust bug-sort criteria with comma-separated list '
                         'SORT.  e.g. "--sort creator,time".  '
                         'Available criteria: %s' % ','.join(AVAILABLE_CMPS),
                    arg=libbe.command.Argument(
                        name='sort', metavar='SORT', default=None,
                        completion_callback=libbe.command.util.Completer(AVAILABLE_CMPS))),
                libbe.command.Option(name='tags', short_name='t',
                    help='Add TAGS: field to standard listing format.'),
                libbe.command.Option(name='ids', short_name='i',
                    help='Only print the bug IDS'),
                libbe.command.Option(name='xml', short_name='x',
                    help='Dump output in XML format'),
                ])
#    parser.add_option("-S", "--sort", metavar="SORT-BY", dest="sort_by",
#                      help="Adjust bug-sort criteria with comma-separated list SORT-BY.  e.g. \"--sort creator,time\".  Available criteria: %s" % ','.join(AVAILABLE_CMPS), default=None)
#    # boolean options.  All but ids and xml are special cases of long forms
#             ("w", "wishlist", "List bugs with 'wishlist' severity"),
#             ("A", "active", "List all active bugs"),
#             ("U", "unconfirmed", "List unconfirmed bugs"),
#             ("o", "open", "List open bugs"),
#             ("T", "test", "List bugs in testing"),
#    for s in bools:
#        attr = s[1].replace('-','_')
#        short = "-%c" % s[0]
#        long = "--%s" % s[1]
#        help = s[2]
#        parser.add_option(short, long, action="store_true",
#                          dest=attr, help=help, default=False)
#    return parser
#
#                ])

    def _run(self, **params):
        storage = self._get_storage()
        bugdirs = self._get_bugdirs()
        writeable = storage.writeable
        storage.writeable = False
        cmp_list, status, severity, assigned, extra_strings_regexps = \
            self._parse_params(bugdirs, params)
        filter = Filter(status, severity, assigned,
                        extra_strings_regexps=extra_strings_regexps)
        bugs = list(itertools.chain(*list(
                    [bugdir.bug_from_uuid(uuid) for uuid in bugdir.uuids()]
                    for bugdir in bugdirs.values())))
        bugs = [b for b in bugs if filter(bugdirs, b) == True]
        self.result = bugs
        if len(bugs) == 0 and params['xml'] == False:
            print >> self.stdout, 'No matching bugs found'

        # sort bugs
        bugs = self._sort_bugs(bugs, cmp_list)

        # print list of bugs
        if params['ids'] == True:
            for bug in bugs:
                print >> self.stdout, bug.id.user()
        else:
            self._list_bugs(bugs, show_tags=params['tags'], xml=params['xml'])
        storage.writeable = writeable
        return 0

    def _parse_params(self, bugdirs, params):
        cmp_list = []
        if params['sort'] != None:
            for cmp in params['sort'].split(','):
                if cmp not in AVAILABLE_CMPS:
                    raise libbe.command.UserError(
                        'Invalid sort on "%s".\nValid sorts:\n  %s'
                    % (cmp, '\n  '.join(AVAILABLE_CMPS)))
                cmp_list.append(getattr(libbe.bug, 'cmp_%s' % cmp))
        status = parse_status(params['status'])
        severity = parse_severity(params['severity'],
                                  important=params['important'])
        # select assigned
        if params['assigned'] == None:
            if params['mine'] == True:
                assigned = [self._get_user_id()]
            else:
                assigned = 'all'
        else:
            assigned = libbe.command.util.select_values(
                params['assigned'], libbe.command.util.assignees(bugdirs))
        for i in range(len(assigned)):
            if assigned[i] == '-':
                assigned[i] = params['user-id']
        if params['extra-strings'] == None:
            extra_strings_regexps = []
        else:
            extra_strings_regexps = [re.compile(x)
                                     for x in params['extra-strings'].split(',')]
        return (cmp_list, status, severity, assigned, extra_strings_regexps)

    def _sort_bugs(self, bugs, cmp_list=None):
        if cmp_list is None:
            cmp_list = []
        cmp_list.extend(libbe.bug.DEFAULT_CMP_FULL_CMP_LIST)
        cmp_fn = libbe.bug.BugCompoundComparator(cmp_list=cmp_list)
        bugs.sort(cmp_fn)
        return bugs

    def _list_bugs(self, bugs, show_tags=False, xml=False):
        if xml == True:
            print >> self.stdout, \
                '<?xml version="1.0" encoding="%s" ?>' % self.stdout.encoding
            print >> self.stdout, '<be-xml>'
        if len(bugs) > 0:
            for bug in bugs:
                if xml == True:
                    print >> self.stdout, bug.xml(show_comments=True)
                else:
                    bug_string = bug.string(shortlist=True)
                    if show_tags == True:
                        attrs,summary = bug_string.split(' ', 1)
                        bug_string = (
                            '%s%s: %s'
                            % (attrs,
                               ','.join(libbe.command.tag.get_tags(bug)),
                               summary))
                    print >> self.stdout, bug_string
        if xml == True:
            print >> self.stdout, '</be-xml>'

    def _long_help(self):
        return """
This command lists bugs.  Normally it prints a short string like
  bea/576:om:[TAGS:] Allow attachments
Where
  bea/576   the bug id
  o         the bug status is 'open' (first letter)
  m         the bug severity is 'minor' (first letter)
  TAGS      comma-separated list of bug tags (if --tags is set)
  Allo...   the bug summary string

You can optionally (-u) print only the bug ids.

There are several criteria that you can filter by:
  * status
  * severity
  * assigned (who the bug is assigned to)
Allowed values for each criterion may be given in a comma seperated
list.  The special string "all" may be used with any of these options
to match all values of the criterion.  As with the --status and
--severity options for `be depend`, starting the list with a minus
sign makes your selections a blacklist instead of the default
whitelist.

status
  %s
severity
  %s
assigned
  free form, with the string '-' being a shortcut for yourself.

In addition, there are some shortcut options that set boolean flags.
The boolean options are ignored if the matching string option is used.
""" % (','.join(libbe.bug.status_values),
       ','.join(libbe.bug.severity_values))
