##############################################################################
#
# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
#                    Romain Courteaud <romain@nexedi.com>
#               2015 Wenjie ZHENG <wenjie.zheng@tiolive.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 AccessControl import ClassSecurityInfo
from Acquisition import aq_inner, aq_parent
from Persistence import PersistentMapping
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.id_as_reference import IdAsReferenceMixin
from Products.ERP5Type.XMLMatrix import XMLMatrix
from Products.ERP5Type.XMLObject import XMLObject
from zLOG import LOG, ERROR, DEBUG, WARNING

class StateError(Exception):
  """
  Must call only an available transition
  """
  pass

class State(IdAsReferenceMixin("state_", "prefix"), XMLObject, XMLMatrix):
  """
  A ERP5 State.
  """
  meta_type = 'ERP5 State'
  portal_type = 'State'
  add_permission = Permissions.AddPortalContent
  isPortalContent = 1
  isRADContent = 1
  erp5_permission_roles = {} # { permission: [role] or (role,) }
  default_reference = ''
  # Declarative security
  security = ClassSecurityInfo()
  security.declareObjectProtected(Permissions.AccessContentsInformation)

  # Declarative properties
  property_sheets = (
             PropertySheet.Base,
             PropertySheet.XMLObject,
             PropertySheet.CategoryCore,
             PropertySheet.DublinCore,
             PropertySheet.Reference,
             PropertySheet.State,)

  def getAvailableTransitionList(self, document):
    transition_list = self.getDestinationValueList(portal_type = 'Transition')
    result_list = []
    for transition in transition_list:
      value = transition._checkPermission(document)
      if value:
        result_list.append(transition)
    return result_list

  def executeTransition(self, transition, document, form_kw=None):
    if transition not in self.getAvailableTransitionList(document):
      raise StateError
    else:
      transition.execute(document, form_kw=form_kw)
      self.getParent().updateRoleMappingsFor(document)

  def undoTransition(self, document):
    wh = self.getWorkflowHistory(document, remove_undo=1)
    status_dict = wh[-2]
    # Update workflow state
    state_var = self.getParentValue().getStateVariable()
    document.setCategoryMembership(state_var, status_dict[state_var])
    # Update workflow history
    status_dict['undo'] = 1
    self.getParentValue()._updateWorkflowHistory(document, status_dict)
    # XXX
    LOG("State, undo", ERROR, "Variable (like DateTime) need to be updated!")

  def getWorkflowHistory(self, document, remove_undo=0, remove_not_displayed=0):
    """
    Return history tuple
    """
    wh = document.workflow_history[self.getParentValue()._generateHistoryKey()]
    result = []
    # Remove undo
    if not remove_undo:
      result = [x.copy() for x in wh]
    else:
      result = []
      for x in wh:
        if x.has_key('undo') and x['undo'] == 1:
          result.pop()
        else:
          result.append(x.copy())
    return result

  def getVariableValue(self, document, variable_name):
    """
    Get current value of the variable from the object
    """
    status_dict = self.getParentValue().getCurrentStatusDict(document)
    return status_dict[variable_name]

  # Multiple inheritance definition
  updateRelatedContent = XMLMatrix.updateRelatedContent
  security.declareProtected(Permissions.AccessContentsInformation,
                              'hasCellContent')
  def hasCellContent(self, base_id='movement'):
    """Return true if the object contains cells.
    """
    # Do not use XMLMatrix.hasCellContent, because it can generate
    # inconsistency in catalog
    # Exemple: define a line and set the matrix cell range, but do not create
    # cell.
    # Line was in this case consider like a movement, and was catalogued.
    # But, getVariationText of the line was not empty.
    # So, in ZODB, resource as without variation, but in catalog, this was
    # the contrary...
    cell_range = XMLMatrix.getCellRange(self, base_id=base_id)
    return (cell_range is not None and len(cell_range) > 0)
    # DeliveryLine can be a movement when it does not content any cell and
    # matrix cell range is not empty.
    # Better implementation is needed.
    # We want to define a line without cell, defining a variated resource.
    # If we modify the cell range, we need to move the quantity to a new
    # cell, which define the same variated resource.
#       return XMLMatrix.hasCellContent(self, base_id=base_id)

  security.declareProtected( Permissions.AccessContentsInformation, 'getCell' )
  def getCell(self, *kw , **kwd):
    """
        This method can be overriden
    """
    if 'base_id' not in kwd:
      kwd['base_id'] = 'movement'
    return XMLMatrix.getCell(self, *kw, **kwd)

  security.declareProtected( Permissions.ModifyPortalContent, 'newCell' )
  def newCell(self, *kw, **kwd):
    """
        This method creates a new cell
    """
    if 'base_id' not in kwd:
      kwd['base_id'] = 'movement'
    return XMLMatrix.newCell(self, *kw, **kwd)

  def setPermission(self, permission, acquired, roles, REQUEST=None):
      """Set a permission for this State."""
      pr = self.erp5_permission_roles
      if pr is None:
          self.erp5_permission_roles = pr = PersistentMapping()
      if acquired:
          roles = list(roles)
      else:
          roles = tuple(roles)
      pr[permission] = roles

  def getPermissionRoleList(self):
    return self.erp5_permission_roles

  def getWorkflow(self):
    return aq_parent(aq_inner(aq_parent(aq_inner(self))))

  def getDestinationReferenceList(self):
    ref_list = []
    for tr in self.getDestinationValueList():
      ref_list.append(tr.getReference())
    return ref_list

  def setGroups(self, REQUEST, RESPONSE=None):
    """Set the group to role mappings in REQUEST for this State.
    """
    map = self.group_roles
    if map is None:
      self.group_roles = map = PersistentMapping()
    map.clear()
    all_roles = self.getWorkflow().getRoles()
    for group in self.getWorkflow().getGroups():
      roles = []
      for role in all_roles:
        if REQUEST.get('%s|%s' % (group, role), 0):
          roles.append(role)
      roles.sort()
      roles = tuple(roles)
      map[group] = roles
    if RESPONSE is not None:
      RESPONSE.redirect(
            "%s/manage_groups?manage_tabs_message=Groups+changed."
            % self.absolute_url())