# -*- coding: UTF-8 -*-
# Copyright (C) 2007 Luis Arturo Belmar-Letelier <luis@itaapy.com>
# Copyright (C) 2007 Sylvain Taverne <sylvain@itaapy.com>
# Copyright (C) 2007-2008 Henry Obein <henry@itaapy.com>
# Copyright (C) 2007-2008 Hervé Cauwelier <herve@itaapy.com>
# Copyright (C) 2007-2008 Juan David Ibáñez Palomar <jdavid@itaapy.com>
# Copyright (C) 2007-2008 Nicolas Deram <nicolas@itaapy.com>
#
# 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 3 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, see <http://www.gnu.org/licenses/>.

# Import from itools
from itools.core import merge_dicts, thingy_lazy_property
from itools.csv import CSVFile, Property
from itools.datatypes import Boolean, Integer, String, Unicode
from itools.gettext import MSG
from itools.handlers.utils import transmap
from itools.stl import stl
from itools.uri import encode_query, Reference
from itools.web import BaseView, BaseForm, STLForm, FormError, INFO, ERROR
from itools.web.views import process_form

# Import from ikaaro
from ikaaro.access import Roles_Datatype
from ikaaro.autoform import HiddenWidget, SelectWidget, TextWidget
from ikaaro.buttons import BrowseButton
from ikaaro import messages
from ikaaro.views import BrowseForm, SearchForm as BaseSearchForm, ContextMenu
from ikaaro.views_new import NewInstance
from ikaaro.registry import get_resource_class
from ikaaro.resource_views import DBResource_Edit

# Import from ikaaro.tracker
from issue import Issue
from datatypes import get_issue_fields, TrackerList, ProductInfoList
from datatypes import Tracker_UsersList
from stored import StoredSearch


columns = [
    ('id', MSG(u'Id')),
    ('title', MSG(u'Title')),
    ('product', MSG(u'Product')),
    ('module', MSG(u'Module')),
    ('version', MSG(u'Version')),
    ('type', MSG(u'Type')),
    ('state', MSG(u'State')),
    ('priority', MSG(u'Priority')),
    ('assigned_to', MSG(u'Assigned To')),
    ('mtime', MSG(u'Modified'))]



###########################################################################
# Menus
###########################################################################
class GoToIssueMenu(ContextMenu):

    title = MSG(u'Go To Issue')
    template = '/ui/tracker/menu_goto.xml'


    def path_to_tracker(self):
        return '..' if isinstance(self.resource, Issue) else '.'



class StoreSearchMenu(ContextMenu):
    """Form to store a search.
    """
    title = MSG(u'Remember this search')
    template = '/ui/tracker/menu_remember.xml'

    def get_query_schema(self):
        resource = self.resource
        stored_search_class = resource.stored_search_class
        return merge_dicts(stored_search_class.class_handler.schema,
                           search_name=String,
                           search_title=Unicode)


    @thingy_lazy_property
    def search_name(self):
        return self.context.get_query_value('search_name')


    @thingy_lazy_property
    def search(self):
        search_name = self.search_name
        if search_name:
            return self.resource.get_resource(search_name, soft=True)
        return None


    def search_title(self):
        search = self.search
        return search.get_title() if search else None


    def search_fields(self):
        search = self.search
        if search:
            get = search.get_values
            stored_search_class = search
        else:
            # Warning, a menu is not the default view!
            query = process_form(self.context.get_query_value,
                                 self.get_query_schema())
            get = query.get

            resource = self.resource
            stored_search_class = resource.stored_search_class

        # Fill the fields
        fields = []
        for name, type in stored_search_class.class_handler.schema.iteritems():
            value = get(name)
            if isinstance(value, list):
                for x in value:
                    fields.append({'name': name, 'value': type.encode(x)})
            elif value is not None:
                fields.append({'name': name, 'value': type.encode(value)})

        # Ok
        return fields



class StoredSearchesMenu(ContextMenu):
    """Provides links to every stored search.
    """

    title = MSG(u'Stored Searches')

    def get_items(self):
        context = self.context
        resource = self.resource
        root = context.root

        # If called from a child
        if isinstance(resource, Issue):
            resource = resource.parent

        # Namespace
        search_name = context.get_query_value('search_name')
        base = '%s/;view' % context.get_link(resource)
        items = []
        ac = resource.get_access_control()
        for item in resource.search_resources(cls=StoredSearch):
            if not ac.is_allowed_to_view(context.user, item):
                continue
            # Make the title
            get_value = item.handler.get_value
            query = resource.get_search_query(get_value)
            issues_nb = len(root.search(query))
            kw = {'search_title': item.get_property('title'),
                  'issues_nb': issues_nb}
            title = MSG(u'{search_title} ({issues_nb})')
            title = title.gettext(**kw)

            # Namespace
            items.append({'title': title,
                          'href': '%s?search_name=%s' % (base, item.name),
                          'class': 'nav-active' if (item.name == search_name)
                                                else None})
        items.sort(lambda x, y: cmp(x['title'], y['title']))

        return items



class TrackerViewMenu(ContextMenu):

    title = MSG(u'Advanced')

    def get_items(self):
        # Keep the query parameters
        schema = self.context.view.get_query_schema()
        params = encode_query(self.context.query, schema)
        return [
            {'title': MSG(u'Edit this search'),
             'href': ';search?%s' % params},
            {'title': MSG(u'Change Several Issues'),
             'href': ';change_several_bugs?%s' % params},
            {'title': MSG(u'Export to Text'),
             'href': ';export_to_text?%s' % params},
            {'title': MSG(u'Export to CSV'),
             'href': ';export_to_csv_form?%s' % params}]



###########################################################################
# Views
###########################################################################
class Tracker_NewInstance(NewInstance):

    schema = merge_dicts(NewInstance.schema, product=Unicode(mandatory=True))
    widgets = NewInstance.widgets + [
        TextWidget('product', title=MSG(u'Give the title of one Product'))]


    def action(self, resource, context, form):
        # Get the container
        container = form['container']
        # Make the resource
        name = form['name']
        class_id = context.query['type']
        cls = get_resource_class(class_id)
        child = container.make_resource(name, cls)
        # The metadata
        language = container.get_edit_languages(context)[0]
        title = Property(form['title'], lang=language)
        child.metadata.set_property('title', title)
        # Add the initial product
        product = form['product']
        table = container.get_resource('%s/product' % name).get_handler()
        product = Property(product, language='en')
        table.add_record({'title': product})
        # Ok
        goto = str(resource.get_pathto(child))
        return context.come_back(messages.MSG_NEW_RESOURCE, goto=goto)



class Tracker_Edit(DBResource_Edit):

    widgets = (DBResource_Edit.widgets + [
        SelectWidget('included_roles',
            title=MSG(u"Authorized roles for 'Assigned to' and 'CC' fields"),
            has_empty_option=False)])


    def _get_schema(self, resource, context):
        roles = Roles_Datatype(resource=resource, multiple=True,
                               mandatory=True)
        return merge_dicts(DBResource_Edit.schema, included_roles=roles)


    def get_value(self, resource, context, name, datatype):
        if name == 'included_roles':
            return list(resource.get_property('included_roles'))

        proxy = super(Tracker_Edit, self)
        return proxy.get_value(resource, context, name, datatype)


    def set_value(self, resource, context, name, form):
        if name == 'included_roles':
            value = form['included_roles']
            resource.set_property('included_roles', tuple(value))
            return False

        proxy = super(Tracker_Edit, self)
        return proxy.set_value(resource, context, name, form)



class Tracker_AddIssue(STLForm):

    access = 'is_allowed_to_edit'
    title = MSG(u'Add')
    icon = 'new.png'
    template = '/ui/tracker/add_issue.xml'
    styles = ['/ui/tracker/style.css']
    scripts = ['/ui/tracker/tracker.js']


    def get_schema(self, resource, context):
        schema = get_issue_fields(resource)
        schema['comment'] = Unicode(mandatory=True)
        return schema


    def get_value(self, resource, context, name, datatype):
        if getattr(datatype, 'mandatory', False):
            datatype = datatype(mandatory=False)
        value = context.get_query_value(name, type=datatype)
        # By default, set cc_list to the current user
        if name == 'cc_list' and not value:
            return [context.user.name]
        return value


    def get_namespace(self, resource, context):
        namespace = STLForm.get_namespace(self, resource, context)
        namespace['list_products'] = resource.get_list_products_namespace()
        return namespace


    def action(self, resource, context, form):
        # Add
        id = resource.get_new_id()
        issue_cls = resource.issue_class
        issue = resource.make_resource(id, issue_cls)
        issue.add_comment(context, form, new=True)

        # Ok
        message = INFO(u'New issue added.')
        goto = './%s/' % id
        return context.come_back(message, goto=goto)



class Tracker_View(BrowseForm):

    access = 'is_allowed_to_view'
    title = MSG(u'View')
    icon = 'view.png'
    styles = ['/ui/tracker/style.css']


    schema = {
        'ids': String(multiple=True, mandatory=True)}

    tracker_schema = {
        # Do not batch
        'batch_size': Integer(default=0),
        # search_fields
        'search_name': String(),
        'mtime': Integer(default=0),
        'product': Integer(multiple=True),
        'module': Integer(multiple=True),
        'version': Integer(multiple=True),
        'type': Integer(multiple=True),
        'state': Integer(multiple=True),
        'priority': Integer(multiple=True),
        'assigned_to': String(multiple=True),
        # Specific fields
        'search_field': String,
        'search_term': Unicode,
        # BrowseForm fields
        'sort_by': String,
        'reverse': Boolean(default=None),
    }

    context_menus = [StoreSearchMenu(), TrackerViewMenu()]


    def get_query_schema(self):
        return merge_dicts(BrowseForm.get_query_schema(self),
                           self.tracker_schema)


    def on_query_error(self, resource, context):
        query = encode_query(context.uri.query)
        return context.come_back(None, goto=';search?%s' % query)


    def get_page_title(self, resource, context):
        query = getattr(context, 'query', {})
        search_name = query.get('search_name')
        if search_name:
            search = context.resource.get_resource(search_name, soft=True)
            if search:
                search = search.get_title()
                template = MSG(u'{title} - {search}')
                title = self.title.gettext()
                return template.gettext(title=title, search=search)

        return self.title


    def GET(self, resource, context):
        # Check stored search
        search_name = context.query['search_name']
        if search_name:
            search = resource.get_resource(search_name, soft=True)
            if search is None:
                msg = MSG(u'Unknown stored search "{sname}".')
                goto = ';search'
                return context.come_back(msg, goto=goto, sname=search_name)
        # Ok
        return BrowseForm.GET(self, resource, context)


    def get_namespace(self, resource, context):
        # Default table namespace
        namespace = BrowseForm.get_namespace(self, resource, context)

        # Keep the search_parameters, clean different actions
        schema = self.get_query_schema()
        namespace['search_parameters'] = encode_query(context.query, schema)

        return namespace


    def get_items(self, resource, context):
        return resource.get_search_results(context)


    def sort_and_batch(self, resource, context, results):
        query = context.query
        # Stored search, or default
        search_name = query['search_name']
        if search_name:
            search = resource.get_resource(search_name).handler
            sort_by = search.get_value('sort_by')
            reverse = search.get_value('reverse')
        else:
            sort_by = 'title'
            reverse = False
        # Query takes precedence
        if query['sort_by'] is not None:
            sort_by = query['sort_by']
        if query['reverse'] is not None:
            reverse = query['reverse']

        # Case 1: title
        if sort_by == 'title':
            items = results.get_documents()
            key = lambda x: x.title.lower().translate(transmap)
            items.sort(key=key, reverse=reverse)
            return items

        # Case 2: ordered table
        if sort_by in ('product', 'module', 'version', 'type', 'state',
                       'priority'):
            # Make the key function
            table_handler = resource.get_resource(sort_by).handler
            sorted_ids = list(table_handler.get_record_ids_in_order())

            def key(x):
                x_value = getattr(x, sort_by)
                try:
                    return sorted_ids.index(x_value)
                except ValueError:
                    return None

            # Sort
            items = results.get_documents()
            items.sort(key=key, reverse=reverse)

            # Return the result
            return items

        # Case 3: something else
        return results.get_documents(sort_by=sort_by, reverse=reverse)


    def get_item_value(self, resource, context, item, column):
        if column == 'checkbox':
            datatype = String(multiple=True)
            selected_issues = context.get_form_value('ids', type=datatype)
            return item.name, item.name in selected_issues
        if column == 'id':
            id = item.name
            return id, '%s/;edit' % id

        value = getattr(item, column)
        if value is None:
            return None
        if column == 'title':
            return value, '%s/;edit' % item.name
        # Assigned to
        if column == 'assigned_to':
            users = resource.get_resource('/users')
            user = users.get_resource(value, soft=True)
            if user is None:
                return None
            return user.get_title()
        # Mtime
        if column == 'mtime':
            return context.format_datetime(value)

        # Tables
        table = resource.get_resource(column).handler
        table_record = table.get_record(value)
        if table_record is None:
            return None
        return table.get_record_value(table_record, 'title')


    table_actions = []


    def get_table_columns(self, resource, context):
        table_columns = columns[:]
        table_columns.insert(0, ('checkbox', None))
        return table_columns



class Tracker_Search(BaseSearchForm, Tracker_View):

    access = 'is_allowed_to_view'
    title = MSG(u'Search')
    icon = 'search.png'
    styles = ['/ui/tracker/style.css']
    scripts = ['/ui/tracker/tracker.js']


    # Search Form
    search_template = '/ui/tracker/search.xml'
    search_schema = {
        'search_name': String(),
        'search_title': Unicode(),
        'text': Unicode(),
        'mtime': Integer(),
        'product': Integer(multiple=True),
        'module': Integer(multiple=True),
        'version': Integer(multiple=True),
        'type': Integer(multiple=True),
        'state': Integer(multiple=True),
        'priority': Integer(multiple=True),
        'assigned_to': String(multiple=True)
        }

    context_menus = []


    def get_query(self, context):
        try:
            return BaseSearchForm.get_query(self, context)
        except FormError:
            schema = self.get_query_schema()
            query = {}
            for name in schema:
                default = schema[name].get_default()
                query[name] = context.uri.query.get(name, default)
            return query


    on_query_error = BaseSearchForm.on_query_error


    def get_search_namespace(self, resource, context):
        # Search Form
        get_resource = resource.get_resource
        query = context.query
        search_name = query['search_name']
        if search_name:
            search = get_resource(search_name)
            get_value = search.handler.get_value
            get_values = search.get_values
            search_title = search.get_property('title')
        else:
            get_value = query.get
            get_values = query.get
            search_name = None
            search_title = query['search_title']

        # Build the namespace
        product = get_values('product')
        module = get_values('module')
        version = get_values('version')
        type = get_values('type')
        state = get_values('state')
        priority = get_values('priority')
        assigned_to = get_values('assigned_to')

        # is_admin
        ac = resource.get_access_control()
        pathto_website = resource.get_pathto(resource.get_site_root())

        return  {
           'search_name': search_name,
           'search_title': search_title,
           'text': get_value('text'),
           'mtime': get_value('mtime'),
           'is_admin': ac.is_admin(context.user, resource),
           'manage_assigned': '%s/;browse_users' % pathto_website,
           'products': TrackerList(element='product',
                                   tracker=resource).get_namespace(product),
           'modules': ProductInfoList(element='module',
                                      tracker=resource).get_namespace(module),
           'versions': ProductInfoList(element='version',
                                     tracker=resource).get_namespace(version),
           'types': TrackerList(element='type',
                                tracker=resource).get_namespace(type),
           'states': TrackerList(element='state',
                                 tracker=resource).get_namespace(state),
           'priorities': TrackerList(element='priority',
                                 tracker=resource).get_namespace(priority),
           'assigned_to': Tracker_UsersList(
                              resource=resource).get_namespace(assigned_to),
           'list_products': resource.get_list_products_namespace()}


    def get_namespace(self, resource, context):
        search_template = resource.get_resource(self.search_template)
        search_namespace = self.get_search_namespace(resource, context)
        return {
            'batch': None,
            'table': None,
            'search': stl(search_template, search_namespace)}



class Tracker_RememberSearch(BaseForm):

    access = 'is_allowed_to_edit'

    def get_schema(self, resource, context):
        return merge_dicts(resource.stored_search_class.class_handler.schema,
                           search_name=String,
                           search_title=Unicode(mandatory=True))

    def GET(self, resource, context):
        # Required for when the form fails the automatic checks
        return context.come_back(message=context.message)


    def action(self, resource, context, form):
        search_name = form.get('search_name')
        title = form['search_title']

        # Already a search name ?

        # No
        if search_name is None:
            # Search for a search with the same title
            if isinstance(resource, Issue):
                resource = resource.parent
            searches = resource.search_resources(cls=StoredSearch)
            for search in searches:
                # Found !
                if title == search.get_property('title'):
                    search_name = search.name
                    message = MSG(u'The search has been modified.')
                    break
            else:
                # Not found => so we make a new search resource
                search_name = resource.get_new_id('s')
                search = resource.make_resource(search_name, StoredSearch)
                message = MSG(u'The search has been stored.')
        # Yes
        else:
            search = resource.get_resource(search_name)
            message = MSG(u'The search title has been changed.')

        # Reset the search
        search.handler.load_state_from_string('')

        # Set title
        language = resource.get_edit_languages(context)[0]
        search.set_property('title', title, language=language)

        # Save the value
        for name, type in search.class_handler.schema.iteritems():
            value = form.get(name, None)
            if value:
                search.set_values(name, value, type)

        # Go
        return context.come_back(message, goto=';view?search_name=%s' %
                                          search_name)



class Tracker_ForgetSearch(BaseForm):

    access = 'is_allowed_to_edit'
    schema = {
        'search_name': String(mandatory=True)}


    def action(self, resource, context, form):
        name = form['search_name']
        resource.del_resource(name)
        # Ok
        message = MSG(u'The search has been removed.')
        return context.come_back(message, goto=';search')



class Tracker_GoToIssue(BaseView):

    access = 'is_allowed_to_view'

    def GET(self, resource, context):
        issue_name = context.get_form_value('issue_name')
        if not issue_name:
            return context.come_back(messages.MSG_NAME_MISSING)

        issue = resource.get_resource(issue_name, soft=True)
        if issue is None or not isinstance(issue, Issue):
            return context.come_back(ERROR(u'Issue not found.'))

        return context.uri.resolve2('../%s' % issue_name)



class Tracker_ExportToCSVForm(Tracker_View):

    template = '/ui/tracker/export_to_csv.xml'
    external_form = True

    def get_query_schema(self):
        schema = Tracker_View.get_query_schema(self)
        schema['ids'] = String(multiple=True, default=[])
        return schema


    def get_namespace(self, resource, context):
        namespace = Tracker_View.get_namespace(self, resource, context)
        query = context.query

        # Insert query parameters as hidden input fields
        parameters = []
        schema = Tracker_View.get_query_schema(self)
        for name in schema:
            if name in namespace:
                continue
            value = query[name]
            datatype = schema[name]
            if datatype.multiple is True:
                for value in value:
                    value = datatype.encode(value)
                    parameters.append({'name': name, 'value': value})
            else:
                default = datatype.get_default()
                if value != default:
                    value = datatype.encode(value)
                    parameters.append({'name': name, 'value': value})
        namespace['hidden_fields'] = parameters

        return namespace



class Tracker_ExportToCSV(BaseView):

    access = 'is_allowed_to_view'
    title = MSG(u'Export to CSV')
    query_schema = {
        'editor': String(default='excel'),
        'ids': String(multiple=True),
    }


    def GET(self, resource, context):
        # Get search results
        results = resource.get_search_results(context)
        if isinstance(results, Reference):
            return results

        # Selected issues
        issues = results.get_documents()
        selected_issues = context.query['ids']
        if selected_issues:
            issues = [ x for x in issues if x.name in selected_issues ]

        if len(issues) == 0:
            context.message = ERROR(u"No data to export.")
            return

        # Get CSV encoding and separator (OpenOffice or Excel)
        editor = context.query['editor']
        if editor == 'oo':
            separator = ','
            encoding = 'utf-8'
        else:
            separator = ';'
            encoding = 'cp1252'

        # Create the CSV
        csv = CSVFile()
        for issue in issues:
            issue = get_issue_informations(resource, issue, context)
            row = []
            for name, label in columns:
                value = issue[name]
                if isinstance(value, unicode):
                    value = value.encode(encoding)
                else:
                    value = str(value)
                row.append(value)
            csv.add_row(row)

        # Ok
        context.set_content_type('text/comma-separated-values')
        context.set_content_disposition('attachment', 'export.csv')
        return csv.to_str(separator=separator)



class Tracker_ExportToText(Tracker_ExportToCSVForm):

    template = '/ui/tracker/export_to_text.xml'

    def get_query_schema(self):
        schema = Tracker_ExportToCSVForm.get_query_schema(self)
        schema['column_selection'] = String(multiple=True, default=['title'])
        return schema


    def get_namespace(self, resource, context):
        namespace = Tracker_ExportToCSVForm.get_namespace(self, resource,
                                                          context)
        query = context.query

        # Column Selector
        selection = query['column_selection']
        export_columns = columns[2:] + [columns[1]]
        namespace['columns'] = [
            {'name': name, 'title': title, 'checked': name in selection}
            for name, title in export_columns ]

        # Text
        items = self.get_items(resource, context)
        items = self.sort_and_batch(resource, context, items)
        selected_items = query['ids']
        if selected_items:
            items = [ x for x in items if x.name in selected_items ]
        items = [ get_issue_informations(resource, x, context) for x in items ]
        # Create the text
        lines = []
        for item in items:
            name = item['name']
            line = [u'#%s' % name]
            for x in selection:
                value = item[x]
                if type(value) is unicode:
                    pass
                elif type(value) is str:
                    value = unicode(value, 'utf-8')
                else:
                    value = unicode(value)
                line.append(value)
            line = u'\t'.join(line)
            lines.append(line)
        namespace['text'] = u'\n'.join(lines)

        # Ok
        return namespace



class Tracker_ChangeSeveralBugs(Tracker_View):

    access = 'is_allowed_to_view'
    title = MSG(u'Change Several Issues')
    template = '/ui/tracker/change_bugs.xml'
    scripts = ['/ui/tracker/tracker.js']
    schema = {
        'comment': Unicode,
        'ids': String(multiple=True),
        'change_product': Integer,
        'change_module': Integer,
        'change_version': Integer,
        'change_type': Integer,
        'change_priority': Integer,
        'change_assigned_to': String,
        'change_state': Integer,
    }

    external_form = True

    table_actions = [
        BrowseButton(name='change_several_bugs', title=MSG(u'Edit issues'))]


    def get_namespace(self, resource, context):
        namespace = Tracker_View.get_namespace(self, resource, context)
        # Edit several bugs at once
        get_resource = resource.get_resource
        namespace['products'] = get_resource('product').get_options()
        namespace['modules'] = get_resource('module').get_options()
        namespace['versions'] = get_resource('version').get_options()
        namespace['priorities'] = get_resource('priority').get_options()
        namespace['types'] = get_resource('type').get_options()
        namespace['states'] = get_resource('state').get_options()
        namespace['assigned_to'] = Tracker_UsersList(
                                      resource=resource).get_namespace('')
        namespace['list_products'] = resource.get_list_products_namespace()

        # Ok
        return namespace


    def action(self, resource, context, form):
        # Get search results
        results = resource.get_search_results(context)
        if isinstance(results, Reference):
            return results

        # Selected issues
        issues = results.get_documents()
        selected_issues = form['ids']
        issues = [ x for x in issues if x.name in selected_issues ]

        if len(issues) == 0:
            context.message = ERROR(u"No issue selected.")
            return

        # Modify all issues selected
        names = ['product', 'module', 'version', 'type', 'priority', 'state']
        comment = form['comment']
        user = context.user
        username = user and user.name or ''
        users_issues = {}
        for issue in issues:
            issue = resource.get_resource(issue.name)
            old_metadata = issue.metadata.clone()
            # Assign-To
            assigned_to = issue.get_property('assigned_to')
            new_assigned_to = form['change_assigned_to']
            if new_assigned_to == 'do-not-change':
                issue.set_property('assigned_to', assigned_to)
            else:
                issue.set_property('assigned_to', new_assigned_to)
            # Integer Fields
            for name in names:
                new_value = form['change_%s' % name]
                if new_value == -1:
                    issue.set_property(name, issue.get_property(name))
                else:
                    issue.set_property(name, new_value)
            # Comment
            p_comment = Property(comment, author=username,
                                 date=context.timestamp)
            modifications = issue.get_diff_with(old_metadata, context)
            if modifications:
                title = MSG(u'Modifications:').gettext()
                p_comment.value += u'\n\n%s\n\n%s' % (title, modifications)
            issue.metadata.set_property('comment', p_comment)

            # Mail (create a dict with a list of issues for each user)
            info = {'href': context.uri.resolve(issue.name),
                    'name': issue.name,
                    'title': issue.get_title()}
            if new_assigned_to and new_assigned_to != 'do-not-change':
                users_issues.setdefault(new_assigned_to, []).append(info)
            if assigned_to and assigned_to != new_assigned_to:
                users_issues.setdefault(assigned_to, []).append(info)
            # Change
            context.database.change_resource(issue)

        # Send mails
        root = context.root
        if user is None:
            user_title = MSG(u'ANONYMOUS').gettext()
        else:
            user_title = user.get_title()
        template = MSG(
            u'--- Comment from: {user} ---\n\n{comment}\n\n{issues}')
        tracker_title = resource.get_property('title') or 'Tracker Issue'
        subject = u'[%s]' % tracker_title
        for user_id in users_issues:
            user_issues = [
                u'#%s - %s - %s' % (x['href'], x['name'], x['title'])
                for x in users_issues[user_id]
            ]
            user_issues = '\n'.join(user_issues)
            body = template.gettext(user=user_title, comment=comment,
                                    issues=user_issues)
            to_addr = root.get_user(user_id).get_property('email')
            root.send_email(to_addr, subject, text=body)

        context.message = messages.MSG_CHANGES_SAVED



def get_issue_informations(resource, item, context):
    """Construct a dict with issue informations.  This dict is used to
    construct a line for a table.
    """
    # Build the namespace
    infos = {
        'name': item.name,
        'id': item.id,
        'title': item.title}

    # Select Tables
    get_resource = resource.get_resource
    tables = ['product', 'module', 'version', 'type', 'state', 'priority']
    for name in tables:
        infos[name] = None
        value = getattr(item, name)
        if value is None:
            continue
        table = get_resource(name).handler
        table_record = table.get_record(value)
        if table_record is None:
            continue
        infos[name] = table.get_record_value(table_record, 'title')

    # Assigned-To
    assigned_to = getattr(item, 'assigned_to')
    infos['assigned_to'] = ''
    if assigned_to:
        users = resource.get_resource('/users')
        user = users.get_resource(assigned_to, soft=True)
        if user is not None:
            infos['assigned_to'] = user.get_title()

    # Modification Time
    infos['mtime'] = context.format_datetime(item.mtime)

    return infos
