# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2007 Nexedi SA and Contributors. All Rights Reserved.
#          Romain Courteaud <romain@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# 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 the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import unittest

import MethodObject

from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Type.Utils import getMessageIdWithContext
from zLOG import LOG

# dependency order
target_business_templates = (
  'erp5_base',
  'erp5_simulation',
  'erp5_trade',

  'erp5_pdf_editor',
  'erp5_pdm',
  'erp5_accounting',
  'erp5_invoicing',

  'erp5_apparel',

##   'erp5_banking_core',
##   'erp5_banking_cash',
##   'erp5_banking_check',
##   'erp5_banking_inventory',

  'erp5_budget',
  'erp5_public_accounting_budget',

  'erp5_consulting',

  'erp5_ingestion',
  'erp5_ingestion_mysql_innodb_catalog',
  'erp5_crm',

  'erp5_web',
  'erp5_dms',

  'erp5_commerce',

  'erp5_forge',

  'erp5_immobilisation',

  'erp5_item',

  'erp5_mrp',

  'erp5_payroll',

  'erp5_project',

  'erp5_calendar',

  'erp5_l10n_fr',
  'erp5_l10n_ja',
  'erp5_l10n_pl_PL',
  'erp5_l10n_pt-BR',
)

class TestWorkflowStateTitleTranslation(ERP5TypeTestCase):
  run_all_test = 1
  domain = 'erp5_ui'
  lang = 'en'

  def getTitle(self):
    return "Translation Test"

  def getBusinessTemplateList(self):
    """  """
    return target_business_templates

  def getTranslation(self, msgid):

    result = self.portal.Localizer.erp5_ui.gettext(
          msgid, default='')
    #result = self.portal.Localizer.translate(
        #domain=self.domain, msgid=msgid, lang=self.lang)
    if (result == msgid) and (self.lang != 'en'):
      #result = None
      result = self.portal.Localizer.erp5_ui.gettext(msgid)
    return result.encode('utf8')

  def logMessage(self, message):
    self.message += '%s\n' % message

  def checkWorkflowStateTitle(self, quiet=0, run=run_all_test):
    """
    Test workflow state title translation.
    Check that using portal_catalog can not return strange results to the user.
    Each translation must be only related to one state ID.
    """
    if not run: return
    self.message = '\n'

    translation_dict = {}

    workflow_tool = self.portal.portal_workflow
    for workflow_id in workflow_tool.objectIds():
      workflow = workflow_tool._getOb(workflow_id)
      class_name = workflow.__class__.__name__
      if class_name == 'DCWorkflowDefinition':
        for state in workflow.states.items():
          state_title = state[1].title
          state_id = state[0]
          msgid = getMessageIdWithContext(state_title, 'state', workflow_id)
          translated_state_title = self.getTranslation(msgid)

          if translated_state_title is not None:
            try:
              translation_dict[translated_state_title].add(state_id)
            except KeyError:
              translation_dict[translated_state_title] = {state_id}


    for key, value in translation_dict.items():
      if len(value) == 1:
        translation_dict.pop(key)

    if translation_dict != {}:
      # State ID has multiple translation associated, and it leads to
      # unexpected results for the user when using portal catalog.
      rejected_key_list = translation_dict.keys()
      result_dict = {x: [] for x in rejected_key_list}
      error_dict =  {x: [] for x in rejected_key_list}
      error = 0

      # Browse all workflows to ease fixing this issue.
      for workflow in self.portal.portal_workflow.objectValues():
        if workflow.__class__.__name__ == 'DCWorkflowDefinition':
          workflow_id = workflow.id
          workflow_dict = {}
          for state_id, state in workflow.states._mapping.items():
            state_title = state.title
            translated_state_title = \
              self.portal.Localizer.erp5_ui.gettext(state_title, lang=self.lang)
            if translated_state_title in rejected_key_list:
              result_dict[translated_state_title].append(
                  (workflow_id, state_id, state_title))

      # XXX To be improved
      not_used_workflow_id_list = []
      for key, item_list in result_dict.items():
        wrong_state_id_list = [x[1] for x in item_list]
        for workflow_id, wrong_state_id, state_title in item_list:
          if workflow_id not in not_used_workflow_id_list:
            workflow = self.portal.portal_workflow._getOb(workflow_id)
            state_id_list = []
            for state_id in workflow.states._mapping.keys():
              if (state_id in wrong_state_id_list) and \
                  (state_id != wrong_state_id):
                state_id_list.append(state_id)

            if len(state_id_list) != 0:
              error_dict[key].append((
                workflow_id, wrong_state_id, state_title, state_id_list))
              error = 1

      if error:
        for key, item_list in error_dict.items():
          if len(item_list) != 0:
            self.logMessage("\n'%s'" % key.encode('utf-8'))
            self.logMessage('\t### Conflicting workflow with common states (ie, what user can see) ###')
            for item in item_list:
              # XXX Improve rendering
              self.logMessage(
                  "\t%r\t%r\t'%s'\t%r" % \
                  item)
            self.logMessage('\n\t### All conflicting workflows (ie, problems asking to happen) ###')
            for item in result_dict[key]:
              # XXX Improve rendering
              self.logMessage(
                  "\t%r\t%r\t'%s'" % \
                  item)

        self.fail(self.message)

  def afterSetUp(self):
    self.portal.Localizer._default_language = 'en'
    self.portal.Localizer.set_languages(['en', 'fr'])

  def test_01_EnglishTranslation(self, quiet=0, run=run_all_test):
    """
    Test English translation
    """
    self.lang = 'en'
    self.checkWorkflowStateTitle(quiet=quiet, run=run)

  def test_02_FrenchTranslation(self, quiet=0, run=run_all_test):
    """
    Test French translation
    """
    self.lang = 'fr'
    self.checkWorkflowStateTitle(quiet=quiet, run=run)

  def test_03_JapaneseTranslation(self, quiet=0, run=run_all_test):
    """
    Test Japanese translation
    """
    self.lang = 'ja'
    self.checkWorkflowStateTitle(quiet=quiet, run=run)

  def test_04_PolishTranslation(self, quiet=0, run=run_all_test):
    """
    Test Polish translation
    """
    self.lang = 'pl'
    self.checkWorkflowStateTitle(quiet=quiet, run=run)

  def test_05_PortugueseTranslation(self, quiet=0, run=run_all_test):
    """
    Test Portuguese translation
    """
    self.lang = 'pt-BR'
    self.checkWorkflowStateTitle(quiet=quiet, run=run)

  def test_06_FrenchTranslationOfMessageWithContext(self, quiet=0,
         run=run_all_test):
    """
    Test French translation
    """
    self.lang = 'fr'

    message_catalog = self.portal.Localizer.erp5_ui
    message_catalog.gettext('Validated', add=1)
    message_catalog.gettext('Draft', add=1)
    message_catalog.gettext(getMessageIdWithContext('Validated',
                                                    'state',
                                                    'item_workflow'),
                            add=1)
    message_catalog.message_edit('Validated', self.lang,
                                 'Validé'.decode('utf8'), '')
    message_catalog.message_edit(getMessageIdWithContext('Validated',
                                                         'state',
                                                         'item_workflow'),
                                 self.lang, "En bon usage", '')
    message_catalog.message_edit('Draft', self.lang, '', '')
    portal_type = 'Organisation'
    organisation_module = self.getPortal().getDefaultModule(portal_type)
    organisation = organisation_module.newContent(
                  portal_type=portal_type,
                  title='My Organisation')
    organisation.validate()
    portal_type = 'Item'
    item_module = self.getPortal().getDefaultModule(portal_type)
    item = item_module.newContent(portal_type=portal_type, title='Lot A')

    self.tic()
    self.portal.Localizer._default_language = 'fr'

    self.assertEqual(item.getTranslatedValidationStateTitle(), 'Draft')
    item.validate()
    self.assertEqual(item.getTranslatedValidationStateTitle(), "En bon usage")
    self.assertEqual(organisation.getTranslatedValidationStateTitle(),
                      'Validé')
    # Now run indexation of translations.
    self.portal.ERP5Site_updateTranslationTable()
    self.tic()
    # Ckeck queries with translated workflow state title generated with
    # getMessageIdWithContext
    self.assertEqual(1, len(self.portal.portal_catalog(
      translated_validation_state_title="En bon usage")))
    self.assertEqual(1, len(self.portal.portal_catalog(
      translated_validation_state_title="En bon usage", portal_type='Item')))
    self.assertEqual(0, len(self.portal.portal_catalog(
      translated_validation_state_title="En bon usage",
      portal_type='Organisation')))

  def test_07_NegatedQueryForTranslation(self, quiet=0, run=run_all_test):
    """
    Check that catalog search with a negated query parameter doesn't return
    unexpected results.
     - get a portal type ans its related workflow
     - add a new context translation for the original state
     - update the catalog tables
     - verify the catalog result
    """
    self.lang = 'en'
    # Get one portal type and its related workflow
    portal_type_id = 'Bug'
    workflow_id = 'bug_workflow'
    assert(workflow_id in \
            self.portal.portal_workflow.getChainFor(portal_type_id))
    bug = self.portal.getDefaultModule(portal_type_id).newContent(
        portal_type=portal_type_id)
    state_title = bug.getSimulationStateTitle()

    # add a new context translation for the original state
    message_catalog = self.portal.Localizer.erp5_ui
    message_catalog.gettext(state_title, add=1)
    message_catalog.gettext(getMessageIdWithContext(state_title,
                                                    'state',
                                                    workflow_id),
                            add=1)
    assert(bug.getTranslatedSimulationStateTitle() == state_title)

    try:
      message_catalog.message_edit(getMessageIdWithContext(state_title,
                                                           'state',
                                                           workflow_id),
                                   self.lang, "%s in context" % state_title, '')
      assert(bug.getTranslatedSimulationStateTitle() == \
                                    "%s in context" % state_title)

      # Update the translation table
      self.portal.ERP5Site_updateTranslationTable()
      self.tic()

      # Check the catalog result
      assert(len(self.portal.portal_catalog(portal_type=portal_type_id)) == 1)
      result = self.portal.portal_catalog(portal_type=portal_type_id,
          translated_simulation_state_title='!="%s in context"' % state_title)
      self.assertEqual(len(result), 0)
    finally:
      # Clean the new context message
      message_catalog.message_del(getMessageIdWithContext(state_title,
                                                      'state',
                                                      workflow_id))
      # Update the translation table
      self.portal.ERP5Site_updateTranslationTable()
      self.tic()

class LanguageGetter(MethodObject.Method):

  def __init__(self, lang):
    self.lang = lang

  def __call__(self, context):
    return self.lang

class TestTranslation(ERP5TypeTestCase):

  lang = 'fr'

  def getTitle(self):
    return 'Test Translation'

  def getBusinessTemplateList(self):
    return ['erp5_base',]

  def _setUpTranslations(self):
    self.portal.Localizer.manage_addLanguage(self.lang)
    erp5_ui = self.portal.Localizer.erp5_ui
    erp5_ui.gettext('Draft', add=1)
    erp5_ui.gettext('Person', add=1)
    erp5_ui.message_edit('Draft', self.lang, 'Brouillon', '')
    erp5_ui.message_edit('Person', self.lang, 'Personne', '')
    self.portal.ERP5Site_updateTranslationTable()

  def _cleanUpTranslations(self):
    erp5_ui = self.portal.Localizer.erp5_ui
    for msgid in ('Person', 'Draft'):
      translations = erp5_ui.get_translations(msgid)
      translations.pop(self.lang, None)
    self.portal.ERP5Site_updateTranslationTable()

  def afterSetUp(self):
    super(TestTranslation, self).afterSetUp()
    self._setUpTranslations()

    # replace Localizer.utils.lang_negotiator in MessageCatalog to return
    # self.lang
    from Products.Localizer import MessageCatalog
    self.old_lang_negotiator = MessageCatalog.lang_negotiator
    def lang_negotiator(avilable_languages, self=self):
      return self.lang
    MessageCatalog.lang_negotiator = lang_negotiator

    # patch get_selected_language, used by portal_catalog queries that use
    # translation
    self.portal.Localizer.get_selected_language = LanguageGetter(self.lang)

    # create the zpt used by self.translate_by_zpt()
    dispatcher = self.portal.manage_addProduct['PageTemplates']
    dispatcher.manage_addPageTemplate('myzpt')
    self.myzpt = self.portal.myzpt
    self.tic()

  def beforeTearDown(self):
    self.abort()
    # unpatch lang_negotiator and get_selected_message
    from Products.Localizer import MessageCatalog
    MessageCatalog.lang_negotiator = self.old_lang_negotiator
    del self.portal.Localizer.get_selected_language

    self._cleanUpTranslations()
    # test clean-up actually worked
    erp5_ui = self.portal.Localizer.erp5_ui
    self.assertEqual(erp5_ui.gettext('Person', lang=self.lang), 'Person')
    self.assertEqual(erp5_ui.gettext('Draft', lang=self.lang), 'Draft')

    # erase created objects
    for module in (self.portal.person_module, self.portal.organisation_module):
      module.manage_delObjects(list(module.objectIds()))
    self.portal.manage_delObjects(['myzpt'])

    self.tic()
    super(TestTranslation, self).beforeTearDown()

  def test_Localizer_translation(self):
    # basically, test afterSetUp worked...
    erp5_ui = self.portal.Localizer.erp5_ui
    self.assertEqual(erp5_ui.gettext('Person', lang=self.lang), 'Personne')

  def translate_by_zpt(self, domain, *words):
    zpt_template = """
    <tal:ommit xmlns:i18n="http://xml.zope.org/namespaces/i18n"
               i18n:domain="%s">
      <tal:ommit repeat="word options/words" content="word"
                 i18n:translate="">Word</tal:ommit>
    </tal:ommit>
    """ % domain
    self.myzpt.pt_edit(zpt_template, 'text/html')
    return self.myzpt(words=words).encode('utf-8').split()

  def test_ZPT_translation(self):
    results = self.translate_by_zpt('erp5_ui', 'Person', 'Draft')
    self.assertEqual(results, ['Personne', 'Brouillon'])

  def test_ZPT_translation_with_domain_alias(self):
    # test with a translation domain alias
    results = self.translate_by_zpt('ui', 'Person', 'Draft')
    self.assertEqual(results, ['Personne', 'Brouillon'])

  def test_portal_type_and_state_title_translation_on_portal_catalog(self):
    # make sure we can search by "translated_validation_state_title" and
    # "translated_portal_type"
    person_1 = self.portal.person_module.newContent(portal_type='Person')
    person_1.validate()
    person_2 = self.portal.person_module.newContent(portal_type='Person')
    organisation = self.portal.organisation_module.newContent(
                            portal_type='Organisation')

    self.tic()
    self.assertEqual({person_1, person_2}, {x.getObject() for x in
          self.portal.portal_catalog(translated_portal_type='Personne')})

    self.assertEqual({person_2, organisation}, {x.getObject() for x in
          self.portal.portal_catalog(translated_validation_state_title='Brouillon',
                                     portal_type=('Person', 'Organisation'))})
    self.assertEqual([person_2],
        [x.getObject() for x in
          self.portal.portal_catalog(translated_validation_state_title='Brouillon',
                                     translated_portal_type='Personne')])

class TestTranslationWithBusinessTemplate(TestTranslation):

  def _setUpTranslations(self):
    self.manuallyInstallBusinessTemplate('erp5_l10n_fr')

  def _cleanUpTranslations(self):
    self.uninstallBusinessTemplate('erp5_l10n_fr')

def test_suite():
  suite = unittest.TestSuite()
  suite.addTest(unittest.makeSuite(TestTranslation))
  suite.addTest(unittest.makeSuite(TestTranslationWithBusinessTemplate))
  suite.addTest(unittest.makeSuite(TestWorkflowStateTitleTranslation))
  return suite