# Copyright (C) 1999 Milan Zamazal
#
# COPYRIGHT NOTICE
#
# 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, 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; see the file COPYING.  If not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.


"""Problem handling.

This module defines class 'Problem' as a basic representation of a problem.  It
is independent on both GNATS backend (format of storage) and WWW frontend
(presentation form).  To handle a problem, create 'Problem' instance and use
its methods.
"""

__author__  = "Milan Zamazal <pdm@freesoft.cz>"
__version__ = "$Id: problem.py,v 1.10 2000/01/09 19:38:07 pdm Exp $"
__copyright__ = "GNU General Public License, version 2"


import copy
import string

from gnats2w import config
from gnats2w.i18n import L


class Problem:
    """Class corresponding to a problem report.

    Problem is represented by a dictionary with tags and their values.  All
    tags are represented by lower case strings.
    
    The following tags are allowed:

      - Enumerated: confidential, class, severity, state, priority, number
      - Text: submitter-id, originator, email, synopsis, category, release,
        reponsible, arrival-date
      - Multitext: organization, environment, description, how-to-repeat, fix,
        audit-trail, unformatted

    The class provides the following methods:
    
      - Problem information: 'id', 'responsible', 'synopsis', 'confidential',
        'items', 'changes'
      - Problem checking: 'ok', 'errors'
      - Changing the problem: 'ok', 'errors', 'change', 'undo', 'update'
    """

    def __init__ (self, database, items, id=''):
        """Arguments:

          database -- database accessor, e.g. 'Gnats' instance
          items -- dictionary of item tags and values
          id -- problem id
        """
        self._database = database
        self._items = {}
        for k, v in items.items ():
            if v:
                self._items[k] = string.strip (v)
            else:
                self._items[k] = ''
        self._changes = {}
        self._errors = ''
        self._changer = ''
        self._reason_responsible = self._reason_state = ''
        self._id = id
        

    def ok (self):
        """Return whether problem tags and values are OK.

        This is currently tested partially by database instance, since database
        is the entity, which must accept the message.

        If everything is OK, return true value, otherwise return false and set
        the private attribute '_errors' appropriately.  This attribute can be
        later retrieved by any of the 'print_error' functions.
        """
        items = self.items ()
        result = ''
        # Basic tests
        for k in 'synopsis', 'email':
            if not items.has_key (k):
                result = result + L("Required item `%s' not available.\n" % k)
            elif not string.strip (items[k]):
                result = result + L("Required item `%s' empty.\n" % k)
        # Test the latest changes
        if self._changes.has_key ('responsible') and \
           not self._reason_responsible and \
           not self._changes.has_key ('category'):
            result = result + \
                     L('Reason not given for change of responsible.\n')
        if self._changes.has_key ('state') and not self._reason_state:
            result = result + L('Reason not given for change of state.\n')
        # Test in database
        result = result + self._database.check_problem (self)
        # Return result
        self._errors = result
        return not result


    def errors (self):
        """Return the value of 'self._errors', as a string.
        """
        return self._errors


    def change (self, items, changer, reason_responsible, reason_state):
        """Update field values from the 'items' dictionary.

        Previous changes (if any) are applied permanently before this action.

        'reason_responsible' and 'reason_state' are reasons for changing of
        the appropriate fields (if changed).
        'changer' is identification of the person making the change.
        """
        self._items = self.items ()
        self._changes = {}
        for k, v in items.items ():
            v = string.strip (v)
            if not v:
                v = ''                
            if self._items.has_key (k):
                if k in ('description', 'how-to-repeat', 'fix', 'environment'):
                    if v:
                        self._changes[k] = self._items[k] + '\n\n' + v
                elif self._items[k] != v:
                    self._changes[k] = v
            else:
                self._changes[k] = v
        self._changer = changer
        audit = self._database.update_trail (self._items, self._changes,
                                             changer, reason_responsible,
                                             reason_state)
        if audit != self._items['audit-trail']:
            self._changes['audit-trail'] = audit
        self._reason_responsible = reason_responsible
        self._reason_state = reason_state


    def update (self):
        """Insert changes into database (and make them permanent).

        On success, return an empty string, otherwise return error message.
        """
        self.change ({}, '', '', '')
        return self._database.update_problem (self)


    def undo (self):
        """Undo last changes.

        Multiple undos are not possible.
        """
        self._changes = {}


    def changes (self):
        """Return dictionary of last real changes.
        """
        return self._changes
    

    def id (self):
        """Return problem id.
        """
        return self._id
    

    def responsible (self):
        """Return (NAME, EMAIL) of the person responsible for the problem.

        What cannot be found, it is an empty string.
        """
        items = self.items ()
        try:
            responsible = string.strip (items['responsible'])
        except KeyError:
            return '', ''
        return self._database.id2email (responsible)


    def originator (self):
        """Return (NAME, EMAIL) of the originator of the problem.

        What cannot be found, it is an empty string.
        """
        items = self.items ()
        try:
            originator = string.strip (items['originator'])
        except KeyError:
            originator = ''
        try:
            email = string.split (items['email'])
        except KeyError:
            email = ''
        return originator, email


    def synopsis (self):
        """Return subject of the problem.
        """
        items = self.items ()
        return items['synopsis']


    def confidential (self):
        """Return whether the problem is confidential.
        """
        items = self.items ()
        return not items.has_key ('confidential') or \
               items['confidential'] != 'no'


    def items (self):
        """Return the dictionary of tags and values.
        """
        items = copy.copy (self._items)
        items.update (self._changes)
        return items
