# -*- coding: utf-8 -*-
#
# Copyright (C) 2005-2009 Edgewall Software
# All rights reserved.
#
# 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://trac.edgewall.org/.

from datetime import datetime

from trac.admin import IAdminPanelProvider
from trac.core import *
from trac.perm import PermissionSystem
from trac.resource import ResourceNotFound
from trac.ticket import model
from trac.util.datefmt import utc, parse_date, get_date_format_hint, \
                              get_datetime_format_hint
from trac.util.text import exception_to_unicode
from trac.util.translation import _
from trac.web.chrome import add_link, add_notice, add_script, add_warning


class TicketAdminPanel(Component):

    implements(IAdminPanelProvider)

    abstract = True

    # IAdminPanelProvider methods

    def get_admin_panels(self, req):
        if 'TICKET_ADMIN' in req.perm:
            yield ('ticket', _('Ticket System'), self._type, self._label[1])

    def render_admin_panel(self, req, cat, page, version):
        req.perm.require('TICKET_ADMIN')
        # Trap AssertionErrors and convert them to TracErrors
        try:
            return self._render_admin_panel(req, cat, page, version)
        except AssertionError, e:
            raise TracError(e)


def _save_config(config, req, log):
    """Try to save the config, and display either a success notice or a
    failure warning.
    """
    try:
        config.save()
        add_notice(req, _('Your changes have been saved.'))
    except Exception, e:
        log.error('Error writing to trac.ini: %s', exception_to_unicode(e))
        add_warning(req, _('Error writing to trac.ini, make sure it is '
                           'writable by the web server. Your changes have not '
                           'been saved.'))


class ComponentAdminPanel(TicketAdminPanel):

    _type = 'components'
    _label = (_('Component'), _('Components'))

    # TicketAdminPanel methods

    def _render_admin_panel(self, req, cat, page, component):
        # Detail view?
        if component:
            comp = model.Component(self.env, component)
            if req.method == 'POST':
                if req.args.get('save'):
                    comp.name = req.args.get('name')
                    comp.owner = req.args.get('owner')
                    comp.description = req.args.get('description')
                    comp.update()
                    add_notice(req, _('Your changes have been saved.'))
                    req.redirect(req.href.admin(cat, page))
                elif req.args.get('cancel'):
                    req.redirect(req.href.admin(cat, page))

            add_script(req, 'common/js/wikitoolbar.js')
            data = {'view': 'detail', 'component': comp}

        else:
            default = self.config.get('ticket', 'default_component')
            if req.method == 'POST':
                # Add Component
                if req.args.get('add') and req.args.get('name'):
                    name = req.args.get('name')
                    try:
                        model.Component(self.env, name=name)
                    except ResourceNotFound:
                        comp = model.Component(self.env)
                        comp.name = name
                        if req.args.get('owner'):
                            comp.owner = req.args.get('owner')
                        comp.insert()
                        add_notice(req, _('The component "%(name)s" has been '
                                          'added.', name=name))
                        req.redirect(req.href.admin(cat, page))
                    else:
                        raise TracError(_('Component %(name)s already exists.',
                                          name=name))

                # Remove components
                elif req.args.get('remove'):
                    sel = req.args.get('sel')
                    if not sel:
                        raise TracError(_('No component selected'))
                    if not isinstance(sel, list):
                        sel = [sel]
                    db = self.env.get_db_cnx()
                    for name in sel:
                        comp = model.Component(self.env, name, db=db)
                        comp.delete(db=db)
                    db.commit()
                    add_notice(req, _('The selected components have been '
                                      'removed.'))
                    req.redirect(req.href.admin(cat, page))

                # Set default component
                elif req.args.get('apply'):
                    name = req.args.get('default')
                    if name and name != default:
                        self.log.info('Setting default component to %s', name)
                        self.config.set('ticket', 'default_component', name)
                        _save_config(self.config, req, self.log)
                        req.redirect(req.href.admin(cat, page))

            data = {'view': 'list',
                    'components': model.Component.select(self.env),
                    'default': default}

        if self.config.getbool('ticket', 'restrict_owner'):
            perm = PermissionSystem(self.env)
            def valid_owner(username):
                return perm.get_user_permissions(username).get('TICKET_MODIFY')
            data['owners'] = [username for username, name, email
                              in self.env.get_known_users()
                              if valid_owner(username)]
            data['owners'].insert(0, '')
            data['owners'].sort()
        else:
            data['owners'] = None

        return 'admin_components.html', data


class MilestoneAdminPanel(TicketAdminPanel):

    _type = 'milestones'
    _label = (_('Milestone'), _('Milestones'))

    # IAdminPanelProvider methods

    def get_admin_panels(self, req):
        if 'MILESTONE_VIEW' in req.perm:
            return TicketAdminPanel.get_admin_panels(self, req)
        return iter([])

    # TicketAdminPanel methods

    def _render_admin_panel(self, req, cat, page, milestone):
        req.perm.require('MILESTONE_VIEW')
        
        # Detail view?
        if milestone:
            mil = model.Milestone(self.env, milestone)
            if req.method == 'POST':
                if req.args.get('save'):
                    req.perm.require('MILESTONE_MODIFY')
                    mil.name = req.args.get('name')
                    mil.due = mil.completed = None
                    due = req.args.get('duedate', '')
                    if due:
                        mil.due = parse_date(due, req.tz)
                    if req.args.get('completed', False):
                        completed = req.args.get('completeddate', '')
                        mil.completed = parse_date(completed, req.tz)
                        if mil.completed > datetime.now(utc):
                            raise TracError(_('Completion date may not be in '
                                              'the future'),
                                            _('Invalid Completion Date'))
                    mil.description = req.args.get('description', '')
                    mil.update()
                    add_notice(req, _('Your changes have been saved.'))
                    req.redirect(req.href.admin(cat, page))
                elif req.args.get('cancel'):
                    req.redirect(req.href.admin(cat, page))

            add_script(req, 'common/js/wikitoolbar.js')
            data = {'view': 'detail', 'milestone': mil}

        else:
            default = self.config.get('ticket', 'default_milestone')
            if req.method == 'POST':
                # Add Milestone
                if req.args.get('add') and req.args.get('name'):
                    req.perm.require('MILESTONE_CREATE')
                    name = req.args.get('name')
                    try:
                        model.Milestone(self.env, name=name)
                    except ResourceNotFound:
                        mil = model.Milestone(self.env)
                        mil.name = name
                        if req.args.get('duedate'):
                            mil.due = parse_date(req.args.get('duedate'),
                                                 req.tz)
                        mil.insert()
                        add_notice(req, _('The milestone "%(name)s" has been '
                                          'added.', name=name))
                        req.redirect(req.href.admin(cat, page))
                    else:
                        raise TracError(_('Milestone %(name)s already exists.',
                                          name=name))

                # Remove milestone
                elif req.args.get('remove'):
                    req.perm.require('MILESTONE_DELETE')
                    sel = req.args.get('sel')
                    if not sel:
                        raise TracError(_('No milestone selected'))
                    if not isinstance(sel, list):
                        sel = [sel]
                    db = self.env.get_db_cnx()
                    for name in sel:
                        mil = model.Milestone(self.env, name, db=db)
                        mil.delete(db=db, author=req.authname)
                    db.commit()
                    add_notice(req, _('The selected milestones have been '
                                      'removed.'))
                    req.redirect(req.href.admin(cat, page))

                # Set default milestone
                elif req.args.get('apply'):
                    name = req.args.get('default')
                    if name and name != default:
                        self.log.info('Setting default milestone to %s', name)
                        self.config.set('ticket', 'default_milestone', name)
                        _save_config(self.config, req, self.log)
                        req.redirect(req.href.admin(cat, page))

            # Get ticket count
            db = self.env.get_db_cnx()
            cursor = db.cursor()
            milestones = []
            for milestone in model.Milestone.select(self.env, db=db):
                cursor.execute("SELECT COUNT(*) FROM ticket "
                               "WHERE milestone=%s", (milestone.name, ))
                milestones.append((milestone, cursor.fetchone()[0]))
            
            data = {'view': 'list',
                    'milestones': milestones,
                    'default': default}

        data.update({
            'date_hint': get_date_format_hint(),
            'datetime_hint': get_datetime_format_hint()
        })
        return 'admin_milestones.html', data


class VersionAdminPanel(TicketAdminPanel):

    _type = 'versions'
    _label = (_('Version'), _('Versions'))

    # TicketAdminPanel methods

    def _render_admin_panel(self, req, cat, page, version):
        # Detail view?
        if version:
            ver = model.Version(self.env, version)
            if req.method == 'POST':
                if req.args.get('save'):
                    ver.name = req.args.get('name')
                    if req.args.get('time'):
                        ver.time = parse_date(req.args.get('time'), req.tz)
                    else:
                        ver.time = None # unset
                    ver.description = req.args.get('description')
                    ver.update()
                    add_notice(req, _('Your changes have been saved.'))
                    req.redirect(req.href.admin(cat, page))
                elif req.args.get('cancel'):
                    req.redirect(req.href.admin(cat, page))

            add_script(req, 'common/js/wikitoolbar.js')
            data = {'view': 'detail', 'version': ver}

        else:
            default = self.config.get('ticket', 'default_version')
            if req.method == 'POST':
                # Add Version
                if req.args.get('add') and req.args.get('name'):
                    name = req.args.get('name')
                    try:
                        model.Version(self.env, name=name)
                    except ResourceNotFound:
                        ver = model.Version(self.env)
                        ver.name = name
                        if req.args.get('time'):
                            ver.time = parse_date(req.args.get('time'),
                                                  req.tz)
                        ver.insert()
                        add_notice(req, _('The version "%(name)s" has been '
                                          'added.', name=name))
                        req.redirect(req.href.admin(cat, page))
                    else:
                        raise TracError(_('Version %(name)s already exists.',
                                          name=name))
                         
                # Remove versions
                elif req.args.get('remove'):
                    sel = req.args.get('sel')
                    if not sel:
                        raise TracError(_('No version selected'))
                    if not isinstance(sel, list):
                        sel = [sel]
                    db = self.env.get_db_cnx()
                    for name in sel:
                        ver = model.Version(self.env, name, db=db)
                        ver.delete(db=db)
                    db.commit()
                    add_notice(req, _('The selected versions have been '
                                      'removed.'))
                    req.redirect(req.href.admin(cat, page))

                # Set default version
                elif req.args.get('apply'):
                    name = req.args.get('default')
                    if name and name != default:
                        self.log.info('Setting default version to %s', name)
                        self.config.set('ticket', 'default_version', name)
                        _save_config(self.config, req, self.log)
                        req.redirect(req.href.admin(cat, page))

            data = {'view': 'list',
                    'versions': model.Version.select(self.env),
                    'default': default}

        data.update({
            'datetime_hint': get_datetime_format_hint()
        })
        return 'admin_versions.html', data


class AbstractEnumAdminPanel(TicketAdminPanel):
    implements(IAdminPanelProvider)
    abstract = True

    _type = 'unknown'
    _enum_cls = None
    _label = (_('(Undefined)'), _('(Undefined)'))

    # TicketAdminPanel methods

    def _render_admin_panel(self, req, cat, page, path_info):
        data = {'label_singular': self._label[0],
                'label_plural': self._label[1]}

        # Detail view?
        if path_info:
            enum = self._enum_cls(self.env, path_info)
            if req.method == 'POST':
                if req.args.get('save'):
                    enum.name = req.args.get('name')
                    enum.update()
                    add_notice(req, _('Your changes have been saved.'))
                    req.redirect(req.href.admin(cat, page))
                elif req.args.get('cancel'):
                    req.redirect(req.href.admin(cat, page))
            data.update({'view': 'detail', 'enum': enum})

        else:
            default = self.config.get('ticket', 'default_%s' % self._type)
            if req.method == 'POST':
                # Add enum
                if req.args.get('add') and req.args.get('name'):
                    name = req.args.get('name')
                    try:
                        self._enum_cls(self.env, name=name)
                    except:
                        enum = self._enum_cls(self.env)
                        enum.name = name
                        enum.insert()
                        add_notice(req, _('The %(field)s "%(name)s" has been '
                                          'added.',
                                          field=self._label[0].lower(),
                                          name=name))
                        req.redirect(req.href.admin(cat, page))
                    else:
                        raise TracError(_('%(type)s %(name)s already exists',
                                          type=self._label[0], name=name))
                         
                # Remove enums
                elif req.args.get('remove'):
                    sel = req.args.get('sel')
                    if not sel:
                        raise TracError(_('No %s selected') % self._type)
                    if not isinstance(sel, list):
                        sel = [sel]
                    db = self.env.get_db_cnx()
                    for name in sel:
                        enum = self._enum_cls(self.env, name, db=db)
                        enum.delete(db=db)
                    db.commit()
                    add_notice(req, _('The selected %(fields)s have been '
                                      'removed.',
                                      fields=self._label[1].lower()))
                    req.redirect(req.href.admin(cat, page))

                # Appy changes
                elif req.args.get('apply'):
                    changed = False
                    
                    # Set default value
                    name = req.args.get('default')
                    if name and name != default:
                        self.log.info('Setting default %s to %s',
                                      self._type, name)
                        self.config.set('ticket', 'default_%s' % self._type,
                                        name)
                        try:
                            self.config.save()
                            changed = True
                        except Exception, e:
                            self.log.error('Error writing to trac.ini: %s',
                                           exception_to_unicode(e))
                            add_warning(req,
                                        _('Error writing to trac.ini, make '
                                          'sure it is writable by the web '
                                          'server. The default value has not '
                                          'been saved.'))

                    # Change enum values
                    order = dict([(str(int(key[6:])), 
                                   str(int(req.args.get(key)))) for key
                                  in req.args.keys()
                                  if key.startswith('value_')])
                    values = dict([(val, True) for val in order.values()])
                    if len(order) != len(values):
                        raise TracError(_('Order numbers must be unique'))
                    db = self.env.get_db_cnx()
                    for enum in self._enum_cls.select(self.env, db=db):
                        new_value = order[enum.value]
                        if new_value != enum.value:
                            enum.value = new_value
                            enum.update(db=db)
                            changed = True
                    db.commit()

                    if changed:
                        add_notice(req, _('Your changes have been saved.'))
                    req.redirect(req.href.admin(cat, page))

            data.update(dict(enums=list(self._enum_cls.select(self.env)),
                             default=default, view='list'))
        return 'admin_enums.html', data


class PriorityAdminPanel(AbstractEnumAdminPanel):
    _type = 'priority'
    _enum_cls = model.Priority
    _label = (_('Priority'), _('Priorities'))


class ResolutionAdminPanel(AbstractEnumAdminPanel):
    _type = 'resolution'
    _enum_cls = model.Resolution
    _label = (_('Resolution'), _('Resolutions'))


class SeverityAdminPanel(AbstractEnumAdminPanel):
    _type = 'severity'
    _enum_cls = model.Severity
    _label = (_('Severity'), _('Severities'))


class TicketTypeAdminPanel(AbstractEnumAdminPanel):
    _type = 'type'
    _enum_cls = model.Type
    _label = (_('Ticket Type'), _('Ticket Types'))
