##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#                    Jean-Paul Smets-Solanes <jp@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.
#
##############################################################################


from Products.CMFCategory.Filter import Filter
from ZODB.POSException import ConflictError
from zLOG import LOG

class Renderer(Filter):
  """
    Produces Item list out of category list

    FIXME: translation
  """

  def __init__(self, spec = None, filter = None, portal_type = None,
                     display_id = None, sort_id = None,
                     display_method = None, sort_method = None, filter_method = None,
                     is_right_display = 0, translate_display = 0, 
                     translatation_domain = None, display_base_category = 0,
                     base_category = None, base = 1,
                     display_none_category = 1, current_category = None,**kw):
    """
    - *display_id*: the id of attribute to "call" to calculate the value to display
                      (getProperty(display_id) -> getDisplayId)

    - *display_method*: a callable method which is used to calculate the value to display

    - *sort_id*: the id of the attribute to "call" to calculate the value used for sorting.
                Sorting is only applied to default ItemList items.

                          self.getProperty(sort_id)
                    foo       3
                    foo1      1
                    foo2      5
          display order will be (foo1, foo, foo2)

    - *sort_method*: a callable method which provides a sort function (?la cmp)

    - *is_right_display*: use the right value in the couple as the display value.

    - *translate_display*: set to 1, we call translation on each item

    - *translatation_domain*: domain to use for translation

    - *display_base_category*: set to 1, display base_category before display
      value

    - *recursive*: browse recursively to build the ItemList

    - *base_category*: the base category to consider (if None, default is used) API

    - *base*: if set to 0, do not include the base category. If set to 1,
              include the base category. If set to a string, use the string as base.

              (implementation trick: if set to string, use that string as the base string
              when recursing) IMPLEMENTATION HACK

    - *base*: if set to 0, do not include the base category. If set to 1,
              include the base category. If set to a string, use the string as base.
              This is useful for creationg multiple base categories sharing the same categories.
              (ex. target_region/region/europe)

    - *is_self_excluded*: allows to exclude this category from the displayed list

    - *current_category*: allows to provide a category which is not part of the
                          default ItemList. Very useful for displaying
                          values in a popup menu which can no longer
                          be selected.

    - *display_none_category*: allows to include an empty value. Very useful
                        to define None values or empty lists through
                        popup widgets. If both has_empty_item and
                        current_category are provided, current_category
                        is displayed first.


    """
    #LOG('Renderer', 0, 'spec = %s, filter = %s, portal_type = %s, display_id = %s, sort_id = %s, display_method = %s, sort_method = %s, is_right_display = %s, translate_display = %s, translatation_domain = %s, base_category = %s, base = %s, display_none_category = %s, current_category = %s' % (repr(spec), repr(filter), repr(portal_type), repr(display_id), repr(sort_id), repr(display_method), repr(sort_method), repr(is_right_display), repr(translate_display), repr(translatation_domain), repr(base_category), repr(base), repr(display_none_category), repr(current_category)))
    Filter.__init__(self, spec=spec, filter=filter,
                    portal_type=portal_type, filter_method=filter_method)
    self.display_id = display_id
    self.sort_id = sort_id
    self.display_method = display_method
    self.sort_method = sort_method
    self.is_right_display = is_right_display
    self.translate_display = translate_display
    self.translatation_domain = translatation_domain
    self.display_base_category = display_base_category
    self.base_category = base_category
    self.base = base
    self.display_none_category = display_none_category
    self.current_category = current_category

  def getObjectList(self, value_list):
    new_value_list = []
    for value in value_list:
      obj = value.getObject()
      if obj is not None:
        new_value_list.append(obj)
    return new_value_list

  def render(self, value_list):
    """
      Returns rendered items
    """
    #LOG('render', 0, repr(self.filter))
    #LOG('render', 10, repr(value_list))
    value_list = self.getObjectList(value_list)
    #LOG('render', 5, repr(value_list))
    value_list = self.filter(value_list)
    #LOG('render', 10, repr(value_list))
    if self.sort_method is not None:
      value_list.sort(self.sort_method)
    elif self.sort_id is not None:
      value_list.sort(lambda x,y: cmp(x.getProperty(self.sort_id), y.getProperty(self.sort_id)))

    # If base=1 but base_category is None, it is necessary to guess the base category
    # by heuristic.
#    if self.base and self.base_category is None:
#      base_category_count_map = {}
#      for value in value_list:
#        if not getattr(value, 'isCategory', 0):
#          continue
#        b = value.getBaseCategoryId()
#        if b in base_category_count_map:
#          base_category_count_map[b] += 1
#        else:
#          base_category_count_map[b] = 1
#      guessed_base_category = None
#      max_count = 0
#      for k,v in base_category_count_map.items():
#        if v > max_count:
#          guessed_base_category = k
#          max_count = v
#      LOG('render', 100, repr(guessed_base_category))

    # Initialize the list of items.
    item_list = []
    if self.current_category:
      if self.is_right_display:
        item = [None, self.current_category]
      else:
        item = [self.current_category, None]
      item_list.append(item)
    if self.display_none_category:
      if self.is_right_display:
        #item = [None, '']
        item = ['', ''] # XXX Formulator prefer '' to None.
      else:
        #item = ['', None]
        item = ['', ''] # XXX Formulator prefer '' to None.
      item_list.append(item)

    for value in value_list:
      #LOG('Renderer', 10, repr(value))
      # Get the label.
      if self.display_method is not None:
        label = self.display_method(value)
      elif self.display_id is not None:
        try:
          label = value.getProperty(self.display_id)
        except ConflictError:
          raise
        except:
          LOG('WARNING: Renderer', 0,
              'Unable to call %s on %s' % (self.display_id, value.getRelativeUrl()))
          label = None
      else:
        label = None
      # Get the url.
      url = value.getRelativeUrl()
      if self.base:
        if self.base_category:
          # Prepend the specified base category to the url.
          url = self.base_category + '/' + url
        else:
          # If the base category of this category does not match the guessed base category,
          # merely ignore this category.
          # This is not the job for a Renderer to automatically remove values if we don not
          # specify a filter
          if not hasattr(value, 'getBaseCategoryId'):
            continue
          # Remove from now, it might be outdated and useless
          #if value.getBaseCategoryId() != guessed_base_category:
          #  continue
      else:
        if self.base_category:
          # Nothing to do.
          pass
        else:
          # Get rid of the base category of this url, only if this is a category.
          if getattr(value, 'isCategory', 0):
            b = value.getBaseCategoryId()
            url = url[len(b)+1:]
      # Add the pair of a label and an url.
      if label is None:
        label = url
      # Add base category in label
      if self.display_base_category:
        if self.base_category:
          bc = value.portal_categories.resolveCategory(self.base_category)
          label = '%s/%s' % (bc.getTitleOrId(), label) 
        else:
          if hasattr(value, 'getBaseCategoryValue'):
            bc = value.getBaseCategoryValue()
            label = '%s/%s' % (bc.getTitleOrId(), label) 

      if self.is_right_display:
        item = [url, label]
      else:
        item = [label, url]
      item_list.append(item)

    return item_list