# -*- coding: iso-8859-1 -*-

# ==============================================================================
# COPYRIGHT (C) 1991 - 2003  EDF R&D                  WWW.CODE-ASTER.ORG
# 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 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, WRITE TO EDF R&D CODE_ASTER,
#    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
# ==============================================================================

"""
Give methods to :
- create a new issue in the tracker database,
- ...
Methods are called by an ASTER_RUN object.
"""

__revision__ = "$Id: as_rex.py 2626 2006-12-18 07:50:27Z courtois $"


# ----- differ messages translation
def _(mesg):
   return mesg

import os
import os.path
import re
import time
import getpass

from mystring import convert
import as_maintenance

try:   
   import grp
   import myconnect as db
   from schema    import ReadDB, TYPE, USER, PRODUIT, STATUS, ISSUE, MSG, \
                         ISSUE_MESSAGES, YES, NO, ReadDBError, WriteDBError
   from histor    import InitHistor
   imports_succeed = True
except ImportError:
   imports_succeed = False

std_encoding = 'iso-8859-1'
db_encoding  = 'utf-8'

#-------------------------------------------------------------------------------
def SetParser(run):
   """Configure the command-line parser, add options name to store to the list,
   set actions informations.
   run : ASTER_RUN object which manages the execution
   """
   acts_descr = {
      'create_issue' : {
         'method' : Creer,
         'syntax' : 'issue_file [export_file]',
         'help'   : 'insert a new issue in database and copy attached files '\
                    'if an export file is provided.'
      },
      'extract_histor' : {
         'method' : Histor,
         'syntax' : '[--status=STAT] [--format=FORM] [--all_msg] input_file histor',
         'help'   : 'extract the content of issues listed in `input_file` to `histor`.'
      },
      'close_issue' : {
         'method' : Solder,
         'syntax' : '--version_dev=VERS histor',
         'help'   : 'fill "corrVdev" or "corrVexpl" field (depends on VERS) in issues '\
                    'found in `histor` and eventually close them.'
      },
   }
   opts_descr = {
      'status' : {
         'args'   : ('--status', ),
         'kwargs' : {
            'action'  : 'store',
            'default' : 'all',
            'metavar' : 'STAT',
            'choices' : ('all', 'resolu', 'valide_EDA', 'ferme'),
            'dest'    : 'status',
            'help'    : 'raise an error if issues are not in this status'
         }
      },
      'format' : {
         'args'   : ('--format', ),
         'kwargs' : {
            'action'  : 'store',
            'default' : 'text',
            'metavar' : 'FORM',
            'choices' : ('text', 'html'),
            'dest'    : 'format',
            'help'    : 'format of generated histor file'
         }
      },
      'all_msg' : {
         'args'   : ('--all_msg', ),
         'kwargs' : {
            'action'  : 'store_true',
            'default' : False,
            'dest'    : 'all_msg',
            'help'    : 'retrieve all the messages of issues'
         }
      },
   }
   title = 'Options for issue tracker interface'
   run.SetActions(
         actions_descr=acts_descr,
         actions_order=['create_issue', 'extract_histor', 'close_issue'],
         group_options=True, group_title=title, actions_group_title=False,
         options_descr=opts_descr,
   )

#-------------------------------------------------------------------------------
def Creer(run, *args):
   """Create a new issue in REX database.
   """
   if len(args) < 1 or len(args) > 2:
      run.parser.error(_("'--%s' requires one or two arguments") % run.current_action)

   iret = 0
   on_machref = run.get('rep_agla', 'local') != 'local'
   if not on_machref:
      run.Mess(_('Only available on the AGLA machine'), _('<F>_AGLA_ERROR'))
   
   # 0. check imports
   if not imports_succeed:
      run.Mess(_('Imports of REX interface failed.'), _('<F>_IMPORT_ERROR'))

   # 1. copy export and issue files
   jn = run['num_job']
   ffich = os.path.join(run['tmp_user'], 'rex_fich.'+jn)
   kret = run.Copy(ffich, args[0], niverr=_('<F>_COPYFILE'))
   fichetude = 0
   if len(args) > 1:
      fichetude = 1
      fprof = os.path.join(run['tmp_user'], 'rex_prof.'+jn)
      kret = run.Copy(fprof, args[1], niverr=_('<F>_PROFILE_COPY'))

   # 2. parse issue file
   fiche = open(ffich, 'r').read()
   l_champs = ['NOMUSER', 'MAILUSER', 'UNIUSER',
               'TITRE', 'DATE1', 'DATE2', 'VERSION', 'TYPFIC', 'TEXTE',
               'FICASS']
   d = {}
   for ch in l_champs:
      exp = re.compile('\@%s\@(.*)\@FIN%s\@' % (ch, ch), re.MULTILINE | re.DOTALL)
      mat = exp.search(fiche)
      if mat != None and mat.group(1).strip() != 'Non_défini':
         d[ch] = mat.group(1).strip()
   if run['debug']:
      print 'Dict issue content : ', repr(d)

   # 3. open database connection
   try:
      c = db.CONNECT('REX',
            rcdir = os.path.join(run.astk_serv_root, 'conf'), verbose=run['debug'])
   except Exception, msg:
      run.Mess(msg, _('<F>_DB_ERROR'))
      
   typ = ReadDB(TYPE, c)
   d_typ = {
      'AL'   : typ['anomalie'],
      'EL'   : typ['evolution'],
      'AOM'  : typ['aide utilisation'],
      'AO'   : typ['anomalie'],
      'EO'   : typ['evolution'],
      'ED'   : typ['evolution'],
   }
   if not d['TYPFIC'] in d_typ.keys():
      run.Mess(_('Unknown issue type : %s') % d['TYPFIC'], _('<F>_PARSE_ERROR'))

   # 4. create new issue
   # 4.1. get fields from db
   login = getpass.getuser()
   loginrex = login
   try:
      res = c.exe("""SELECT id, _username FROM _user WHERE _loginaster='%s';""" % login)
      loginrex = res[0][1]
      if len(res) > 1:
         run.Mess(_("More than one user has '%s' as login in REX database, " \
                    "'%s' is taken") % (login, loginrex),
            _('<A>_MULTIPLE_USER'))
   except (IndexError, db.MySQLError), msg:
      run.Mess(str(msg))
      run.Mess(_('User %s unknown in REX database') % login,
            _('<F>_UNKNOWN_USER'))
   user = USER({'_username' : loginrex}, c)
   try:
      user.read()
   except ReadDBError:
      run.Mess(_('User %s unknown in REX database') % login,
            _('<F>_UNKNOWN_USER'))
   prod = PRODUIT({'_name' : 'Code_Aster'}, c)
   try:
      prod.read()
   except ReadDBError:
      run.Mess(_('Code_Aster product not found in database !'), _('<F>_DB_ERROR'))
   emis = STATUS({'_name' : 'emis'}, c)
   try:
      emis.read()
   except ReadDBError:
      run.Mess(_("Status 'emis' not found in database !"), _('<F>_DB_ERROR'))

   # 4.2. get version item
   d_vers = prod.GetLinks()
   vers = d_vers.get(d['VERSION'])
   if vers == None:
      run.Mess(_("Version %s not found in database !") % d['VERSION'])

   # 4.3. fill fields
   date_now = time.strftime('%Y/%m/%d')
   issue = ISSUE({
         '_creator'  : user,
         '_produit'  : prod,
         '_status'   : emis,
         '_title'    : d['TITRE'],
         '_type'     : d_typ[d['TYPFIC']],
         '_version'  : vers,
         '_fichetude' : fichetude,
      }, c)
   descr = MSG({'_author'   : user,
                '_creation' : date_now,
                '_creator'  : user,
                '_date'     : date_now,
                '_summary'  : d['TEXTE'][:255], } ,c)
   lien = ISSUE_MESSAGES({'linkid' : descr, 'nodeid' : issue}, c)
   
   # 4.4. insert issue in database
   try:
      lien.write()
   except WriteDBError, msg:
      run.Mess(_('Insert issue failed'), _('<F>_DB_ERROR'))
   
   # 5. get message and issue id
   numid = issue['id']
   msgid = descr['id']
   print 'INDEX=%s MESSAGE=%s' % (numid, msgid)
   
   # 6. copy message file
   repid = str(int(msgid) / 1000)
   fmsg = os.path.join(run['tmp_user'], 'msg%s' % msgid)
   txtmsg = convert(d['TEXTE'], std_encoding, db_encoding)
   open(fmsg, 'w').write(txtmsg + '\n')
   cmd = []
   cmd.append(os.path.join(run['rep_agla'], 'roundup_cp_uid'))
   cmd.append('put')
   cmd.append(fmsg)
   cmd.append('%s' % repid)
   iret, output = run.Shell(' '.join(cmd))
   if iret != 0:
      run.Mess(_('Error message: %s') % output)
      run.Mess(_('Error occurs during copying message file'), _('<F>_COPY'))
   
   # 7. study files
   if fichetude == 1:
      cmd = []
      cmd.append(os.path.join(run.astk_serv_root, 'bin', 'as_rex_prof'))
      cmd.append(fprof)
      cmd.append('%06d' % numid)
      iret, output = run.Shell(' '.join(cmd))
      if iret != 0:
         run.Mess(_('Error occurs during copying study files'), _('<F>_COPY'))

#-------------------------------------------------------------------------------
def Histor(run, *args):
   """Extract the content of some issues from REX database.
   """
   if len(args) != 2:
      run.parser.error(_("'--%s' requires two arguments") % run.current_action)

   iret = 0
   on_machref = run.get('rep_agla', 'local') != 'local'
   if not on_machref:
      run.Mess(_('Only available on the AGLA machine'), _('<F>_AGLA_ERROR'))
   
   # 0. check imports
   if not imports_succeed:
      run.Mess(_('Imports of REX interface failed.'), _('<F>_IMPORT_ERROR'))
   
   # 1. copy input file
   jn = run['num_job']
   ffich = os.path.join(run['tmp_user'], 'hist_input.'+jn)
   kret = run.Copy(ffich, args[0], niverr=_('<F>_COPYFILE'))
   
   # 2. read input file
   hist_content = open(ffich, 'r').read()
   expr = re.compile('([0-9]+)', re.MULTILINE)
   l_nf = [int(n) for n in expr.findall(hist_content)]
   
   # 3. open database connection
   try:
      c = db.CONNECT('REX',
            rcdir = os.path.join(run.astk_serv_root, 'conf'),
            verbose=run['debug'])
   except Exception, msg:
      run.Mess(msg, _('<F>_DB_ERROR'))
   
   # 4. read issues from database
   prec_user = None
   histor = InitHistor(format=run['format'], url=run['rex_url'])
   for numf in l_nf:
      # 4.1. read the issue
      issue = ISSUE({'id' : numf}, c)
      try:
         issue.read()
      except ReadDBError:
         run.Mess(_('Unable to read issue %s') % numf, _('<E>_UNKNOWN_ISSUE'))
         continue
      
      # 4.2.
      # 4.2.1. check status
      status = issue['_status'].GetPrimValue()
      if status != run['status'] and run['status'] != 'all':
         run.Mess(_("Status of issue %s is not '%s' (%s)") \
               % (numf, run['status'], status), _('<E>_UNEXPECTED_VALUE'))
      
      # 4.2.2. alarm on impactDoc
      if issue['_impactDoc'] != None:
         run.Mess(_('modified documents : %s') % issue['_impactDoc'], \
               _('<A>_DOCUMENTATION'))
      
      # 4.3.1. user header
      if numf == l_nf[0] or prec_user == None:
         prec_user = issue['_assignedto']
         histor.BeginUser(prec_user)
      
      if issue['_assignedto'] != None and prec_user != None \
            and issue['_assignedto']['id'] != prec_user['id']:
         if prec_user != None:
            histor.EndUser(prec_user)
         prec_user = issue['_assignedto']
         histor.BeginUser(prec_user)
      
      # 4.3.2. get message file of answer
      l_rep = []
      d_msg = issue.GetLinks()
      # allow to print all messages
      if run['all_msg']:
         l_msgid = d_msg.keys()
         l_msgid.sort()
         l_msgid.reverse()
      else:
         l_msgid = [max(d_msg.keys())]
      for i, msgid in enumerate(l_msgid):
         repid = str(int(msgid) / 1000)
         msgfile = '%s%smsg%s' % (repid, os.sep, msgid)
         fmsg = os.path.join(run['rep_tmp'], 'msg%s' % msgid)
         cmd = []
         cmd.append(os.path.join(run['rep_agla'], 'roundup_cp_uid'))
         cmd.append('get')
         cmd.append('%s%smsg%s' % (repid, os.sep, msgid))
         cmd.append(run['rep_tmp'])
         iret, output = run.Shell(' '.join(cmd))
         if iret != 0 or not os.path.exists(fmsg):
            run.Mess(_('Error message: %s') % output)
            run.Mess(_('Error occurs during copying message file %s, ' \
                       'only the summary will be printed') % msgid, _('<A>_COPY'))
         else:
            txt = open(fmsg, 'r').read()
            if len(l_msgid) > 1:
               l_rep.append(_('message %d :') % (len(l_msgid) - i))
            txtmsg = convert(txt, db_encoding, std_encoding)
            l_rep.append(txtmsg)
      
      # 4.3.3. add this issue to the histor object
      histor.AddIssue(issue, l_rep)
      
      # 4.3.4. last issue
      if numf == l_nf[-1] and prec_user != None:
         histor.EndUser(prec_user)
   
   # 5. copy histor file
   ffich = os.path.join(run['tmp_user'], 'hist_ouput.'+jn)
   open(ffich, 'w').write(repr(histor))
   kret = run.Copy(args[1], ffich, niverr=_('<F>_COPYFILE'))

#-------------------------------------------------------------------------------
def Solder(run, *args):
   """Fill corrVdev/corrVexpl fields and close issues found in a histor file.
   """
   if len(args) != 1:
      run.parser.error(_("'--%s' requires one argument") % run.current_action)
   
   on_machref = run.get('rep_agla', 'local') != 'local'
   if not on_machref:
      run.Mess(_('Only available on the AGLA machine'), _('<F>_AGLA_ERROR'))
   try:
      is_adm = grp.getgrgid(os.getgid())[0] == run.get('agla_adm_group')
      if not is_adm:
         raise KeyError
   except KeyError, msg:
      run.Mess(_('insufficient privileges to close issues !'), _('<F>_AGLA_ERROR'))
   
   # 0. check imports
   if not imports_succeed:
      run.Mess(_('Imports of REX interface failed.'), _('<F>_IMPORT_ERROR'))
   
   # 1. read histor file
   jn = run['num_job']
   ffich = os.path.join(run['tmp_user'], 'hist_input.'+jn)
   kret = run.Copy(ffich, args[0], niverr=_('<F>_COPYFILE'))
   
   hist_content = open(ffich, 'r').read()
   expr = re.compile('RESTITUTION FICHE +([0-9]+)', re.MULTILINE)
   l_nf = [int(n) for n in expr.findall(hist_content)]
   
   # 2. get version number and database fields to fill
   REPREF = os.path.join(run.aster_root, run['version_dev'])
   run.Mess(_('Close issues for :'))
   iret, l_res = as_maintenance.GetVersion(run, silent=False, REPREF=REPREF)
   tagv = '.'.join(l_res[:3])
   if l_res[4]:
      typv = 'expl'
      autr = 'dev'
   else:
      typv = 'dev'
      autr = 'expl'
   d_champ = {
      'a_corrige' : '_corrV' + typv,
      'v_correct' : '_verCorrV' + typv,
      'a_tester'  : '_corrV' + autr,
      'v_tester'  : '_verCorrV' + autr,
   }
   
   # 3. open database connection
   try:
      c = db.CONNECT('REX',
            rcdir = os.path.join(run.astk_serv_root, 'conf'),
            verbose=run['debug'])
   except Exception, msg:
      run.Mess(msg, _('<F>_DB_ERROR'))
   
   try:
      etat = ReadDB(STATUS, c)
      prod = ReadDB(PRODUIT, c)
   except ReadDBError:
      run.Mess(_('Unable to read status and products'), _('<F>_DB_ERROR'))
   
   # 4. read issues from database
   req_status = 'valide_EDA'
   for numf in l_nf:
      # 4.1. read the issue
      try:
         issue = ISSUE(numf, c)
      except ReadDBError:
         run.Mess(_('Unable to read issue %s') % numf, _('<E>_UNKNOWN_ISSUE'))
         continue
      
      # 4.2. check issue values
      status = issue['_status'].GetPrimValue()
      if status != req_status:
         run.Mess(_("Status of issue %s is not '%s' (%s)") \
               % (numf, req_status, status), _('<A>_UNEXPECTED_VALUE'))
         issue['_status'] = etat[req_status]
      
      if issue['_produit'].GetPrimValue() == 'Code_Aster':
         if not issue[d_champ['a_corrige']] \
            and issue['_type'].GetPrimValue() != 'aide utilisation':
            issue[d_champ['a_corrige']] = YES
            run.Mess(_("issue %s should not been solved in V%s") \
                  % (numf, typv), _('<A>_UNEXPECTED_VALUE'))
      else:
         issue[d_champ['a_corrige']] = NO
         issue[d_champ['a_tester']] = NO
                  
      # 4.3. fill issue fields
      issue[d_champ['v_correct']] = tagv
      
      # 4.4. close issue ?
      if not issue[d_champ['a_tester']] or not issue[d_champ['v_tester']] in ('', None):
         issue['_status'] = etat['ferme']
         run.Mess(_('issue %s is closed') % numf)
      else:
         run.Mess(_('issue %s must be solved in V%s too') % (numf, autr))
      
      # 4.5. write issue in database
      try:
         if not run['debug']:
            issue.write(force=True)
         else:
            issue.repr()
      except WriteDBError, msg:
         run.Mess(_('error occurs during writing issue'), _('<F>_DB_ERROR'))



