# -*- coding: utf-8 -*-
#   Copyright 2008 Agile42 GmbH, Berlin (Germany)
#   Copyright 2007 Andrea Tomasini <andrea.tomasini__at__agile42.com>
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#   
#    Authors: 
#        Jonas von Poser <jonas.vonposer__at__agile42.com>
#        Andrea Tomasini <andrea.tomasini__at__agile42.com>

from trac.core import TracError
from trac.util.translation import _
from trac.web.chrome import add_script, add_warning
from trac.util.translation import _

from agilo.api.admin import AgiloAdminPanel
from agilo.charts.chart_generator import ChartGenerator
from agilo.scrum.backlog.model import Backlog
from agilo.ticket.links.model import LinksConfiguration
from agilo.utils import config, Key, ajax, BacklogType
from agilo.utils.config import get_label, AgiloConfig
from agilo.utils.sorting import By, Column, SortOrder


class BacklogAdminPanel(AgiloAdminPanel):
    """Administration panel for backlogs."""
    
    _type = 'backlogs'
    _label = ('Backlogs', _('Backlogs'))

    def __init__(self):
        self.agilo_config = config.AgiloConfig(self.env)
        # create list of ticket types and fields
        #self.types = self.agilo_config.TYPES.keys()
        # Get the calculated properties if available
        self.calculated = LinksConfiguration(self.env).get_calculated() or []
        
    def _get_fields(self, ticket_types=[]):
        """Get all possible fields. The ones that are allowed in the selected 
        ticket_types have the attribute 'disabled' set to False. Returns a list 
        of dictionary containing the field name, label and a flag disabled."""
        result = []
        for ticket_type in self.agilo_config.TYPES:
            for f_name in self.agilo_config.TYPES[ticket_type]:
                if f_name in (Key.ID, Key.TYPE, Key.SUMMARY):
                    # don't show these fields, no matter what
                    continue
                for f in result:
                    # loop over fields so far to avoid duplicates, we 
                    # need to include all the fields which are allowed 
                    # for at least one of the types. There is the 
                    # alternative option for the others
                    if f['name'] == f_name:
                        if f['disabled']:
                            f['disabled'] = ticket_type not in \
                                                ticket_types
                        break
                else:
                    result.append({
                        'name': f_name,
                        'label': self.agilo_config.LABELS.get(f_name, 
                                                              get_label(f_name)),
                        'disabled' : ticket_type not in ticket_types,
                    })
                    
        # add calculated fields
        result += [{
            'name': f_name,
            'label': self.agilo_config.LABELS.get(f_name, 
                                                  get_label(f_name)),
            'disabled' : False,
        } for f_name in self.calculated]
        # Sort the fields by label, is nicer for the UI
        result.sort(By(Column(Key.LABEL), SortOrder.ASCENDING))
        
        return result

    def _get_backlog(self, name):
        for b in self._get_backlogs():
            if b.name == name:
                return b
        
    def _get_backlogs(self):
        return Backlog.select(self.env)
    
    def _parse_boolean_option(self, req, key, value, conf_fields):
        assert value in ['True', 'False']
        value = (value == 'True')
        # All properties with True or False starts with a 4 letter_ now
        prop = key[5:]
        if key.startswith('sort_'):
            # One property enable the sorting, let's get the sorting key
            # value
            if value == True:
                if prop not in conf_fields:
                    conf_fields[prop] = {}
                # if value is set and part of the sorting_key fiasco, set the
                # sorting_key value to the rest of the name, e.g
                # "sorting_key_status" -> "status"
                # default sort order is descending (option missing or no 
                # option selected)
                sorting = req.args.get('sorting_key_%s' % prop, None) or 'True'
                sort_descendingly = (sorting == 'True')
                conf_fields[prop]['sorting'] = sort_descendingly
        elif key.startswith('show_'):
            # It is a show field, so add show to the fields_config dictionary
            # Sorting out properties for show_ and edit_ the length of the
            # strings is the same
            if not conf_fields.has_key(prop):
                conf_fields[prop] = {}
            conf_fields[prop]['show'] = value
        elif key.startswith('edit_'):
            # It is an edit property so set edit to the value
            if not conf_fields.has_key(prop):
                conf_fields[prop] = {}
            conf_fields[prop]['editable'] = value
    
    
    def detail_save_view(self, req, cat, page, name):
        backlog = self._get_backlog(name)
        if not backlog:
            return req.redirect(req.href.admin(cat, page))

        # update values from request
        backlog.description = req.args.get('description')
        backlog.b_type = int(req.args.get('scope'))
        backlog.b_strict = bool(req.args.get('strict'))
        # don't save as unicode, cpickle/sqlite don't like it
        # FIXME: What if there are unicode characters in the ticket 
        # type? It may be a problem, but we need to use the Alias to
        # make the name nice, not the ticket type, or Pickle won't 
        # always work...
        backlog.ticket_types = [str(t) for t in \
                                req.args.getlist('ticket_types')]

        if 'preview' in req.args:
            return self.detail_view(req, cat, page, name, backlog)
        
        conf_fields = {}
        
        for key, value in req.args.items():
            # loop over all POST values
            if value in ['True', 'False']:
                self._parse_boolean_option(req, key, value, conf_fields)
            elif key.startswith(Key.ALTERNATIVE + '_'):
                # These are the alternative fields to show, it is one per type
                prop = key[len(Key.ALTERNATIVE + '_'):]
                if value not in [None, '']:
                    if not conf_fields.has_key(prop):
                        conf_fields[prop] = {}
                    conf_fields[prop][Key.ALTERNATIVE] = value
            elif key.startswith(Key.ORDER + '_'):
                prop = key[len(Key.ORDER + '_'):]
                if isinstance(value, list):
                    # add warning and set the first value
                    value = value[0]
                    add_warning(req, _("Please make sure orders are unique, " \
                                       "there is more than one property with " \
                                       "order: %s" % value[0]))
                if value not in [None, ''] and value.isdigit():
                    if not conf_fields.has_key(prop):
                        conf_fields[prop] = {}
                    conf_fields[prop][Key.ORDER] = int(value)
        # Now sort the conf_fields according to the order first
        conf_sorted = sorted(conf_fields.iteritems(), 
                             cmp=By(Column(Key.ORDER)), 
                             key=lambda(t):t[1], 
                             reverse=True)
        columns = []
        sorting_keys = []
        for col, props in conf_sorted:
            if props.get('show'):
                if props.get('sorting') is not None:
                    sorting_keys.append((col, props['sorting']))
                if props.get('editable'):
                    col += u':editable'
                alt = props.get(Key.ALTERNATIVE)
                if alt is not None:
                    col += u'|%s' % alt
                columns.append(col)

        # Now save the changes to the config file and to the Database
        backlog.sorting_keys = sorting_keys
        backlog.save(author=req.authname, config_only=True)
        
        # if there is no exception than we save on the config file
        backlog_config_name = self.agilo_config.backlog_conf_by_name.get(backlog.name)
        agilo_backlogs = self.agilo_config.get_section(AgiloConfig.AGILO_BACKLOGS)
        if backlog_config_name is None:
            # It is probably a new backlog, so let's create the name
            backlog_config_name = backlog.name.lower().replace(' ', '_')
            agilo_backlogs.change_option('%s.name' % backlog_config_name,
                                         backlog.name)
        # Writing to config file
        agilo_backlogs.change_option('%s.columns' % backlog_config_name, 
                                     ', '.join(columns))
        # If there is at least a chart, save it
        charts = req.args.getlist('backlog_charts')
        if charts is not None and len(charts) > 0:
            agilo_backlogs.change_option('%s.charts' % backlog_config_name, 
                                         ', '.join(charts))
        else:
            agilo_backlogs.remove_option('%s.charts' % backlog_config_name)
            # Remove the charts from local cache
            if self.agilo_config.backlog_charts.has_key(backlog.name):
                self.agilo_config.backlog_charts.pop(backlog.name)
        # save the changes made to the config file
        agilo_backlogs.save()
        # Invalidate cache
        if req.chrome['warnings']:
            return self.detail_view(req, cat, page, name, backlog)
        req.redirect(req.href.admin(cat, page))

    def detail_view(self, req, cat, page, name, backlog=None):
        if ajax.is_ajax(req):
            # we got called by an Ajax request -> get available fields
            ticket_types = req.args.getlist('ticket_types')
            items = []
            if ticket_types:
                items = [f['name'] for f in self._get_fields(ticket_types)
                         if not f['disabled']]

            return 'ajax_response.xml', {'items': items}
        
        if backlog:
            # we got a preview request without ajax, get available 
            # ticket types
            backlog_fields = self._get_fields(req.args.getlist('ticket_types'))
        else:
            # no backlog, load from name
            backlog = self._get_backlog(name)
            if not backlog:
                return req.redirect(req.href.admin(cat, page))

            # loads the information from the config file for this backlog
            backlog_fields = self.agilo_config.backlog_columns.get(name)
            
            if backlog_fields is None:
                backlog_fields = []
            else:
                # Copy the list or we will mess up the AgiloConfig
                backlog_fields = list(backlog_fields)
            
            field_names = [f['name'] for f in backlog_fields]
            for f in self._get_fields(backlog.ticket_types):
                if f['name'] not in field_names:
                    backlog_fields.append(f)
            # Sort the backlog fields by label, so the user can find them faster.
            backlog_fields = sorted(backlog_fields, cmp=By(Column(Key.LABEL), 
                                                           SortOrder.ASCENDING))

        # Available charts
        available_charts = []
        for chart, scope in ChartGenerator(self.env).available_charts.items():
            available_charts.append( (chart, ' '.join(["scope_%s" % s for s in scope])) )

        data = {
            'view': 'detail',
            'backlog': backlog,
            'backlog_types' : BacklogType.LABELS,
            't_types' : [(t_type, 
                          t_type in backlog.ticket_types,
                          self.agilo_config.ALIASES.get(t_type, 
                                                        t_type))
                         for t_type in self.agilo_config.TYPES.keys()],
            'fields' : backlog_fields,
            'sort_values' : [(None, ''), 
                             (True, 'Descending'), 
                             (False, 'Ascending')],
            'sorting_keys' : dict(backlog.sorting_keys),
            'available_charts': available_charts,
            'backlog_charts' : [chart_name for chart_name in \
                                self.agilo_config.backlog_charts.get(name, [])],
        }
        add_script(req, 'agilo/js/backlog_admin.js')
        add_script(req, 'common/js/wikitoolbar.js')
        template = 'agilo_admin_backlog.html'

        return template, data
    
    def list_view(self, req, cat, page):
        data = {
            'view': 'list',
            'backlogs': self._get_backlogs(),
            'backlog_types' : BacklogType.LABELS,
        }
        return 'agilo_admin_backlog.html', data
    
    def list_save_view(self, req, cat, page):
        # TODO: better sanity checks for input, show error in form
        name = req.args.get('name')
            
        if req.args.get('add') and name:
            if self._get_backlog(name):
                #  backlog already exists, redirect to it
                return req.redirect(req.href.admin(cat, page, name))

            backlog = Backlog(self.env, name)
            backlog.save(author=req.authname, config_only=True)
            req.redirect(req.href.admin(cat, page, name))

        # Remove components
        if req.args.get('remove'):
            sel = req.args.get('sel')
            if not sel:
                raise TracError(_('No backlog selected'))
            if not isinstance(sel, list):
                sel = [sel]
            for name in sel:
                backlog = Backlog(self.env, name, load=False)
                backlog.delete()
               
        return req.redirect(req.href.admin(cat, page))