# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2011 Nexedi SA and Contributors. All Rights Reserved.
#                    Nicolas Delaby <nicolas@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 zope.interface
from AccessControl import ClassSecurityInfo
from Acquisition import aq_base
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5.mixin.solver import SolverMixin
from Products.ERP5.mixin.configurable import ConfigurableMixin
from Products.ERP5.MovementCollectionDiff import _getPropertyAndCategoryList

class ItemListSplitSolver(SolverMixin, ConfigurableMixin, XMLObject):
  """Target solver that split the prevision based on aggregated items.

  It creates another prevision movement with the items that were in prevision
  and have been removed in decision.
  """
  meta_type = 'ERP5 Item List Split Solver'
  portal_type = 'Item List Split Solver'
  add_permission = Permissions.AddPortalContent
  isIndexable = 0 # We do not want to fill the catalog with objects on which we need no reporting

  # Declarative security
  security = ClassSecurityInfo()
  security.declareObjectProtected(Permissions.AccessContentsInformation)

  # Default Properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.XMLObject
                    , PropertySheet.CategoryCore
                    , PropertySheet.DublinCore
                    , PropertySheet.Arrow
                    , PropertySheet.TargetSolver
                    )
  # Declarative interfaces
  zope.interface.implements(interfaces.ISolver,
                            interfaces.IConfigurable,
                           )

  def _solve(self, activate_kw=None):
    """This method create new movement based on difference of aggregate sets.
    It supports only removed items.
    Quantity divergence is also solved with sum of aggregated quantities stored
    on each updated movements.
    """
    configuration_dict = self.getConfigurationPropertyDict()
    delivery_dict = {}
    portal = self.getPortalObject()
    for simulation_movement in self.getDeliveryValueList():
      delivery_dict.setdefault(simulation_movement.getDeliveryValue(),
                               []).append(simulation_movement)

    for movement, simulation_movement_list in delivery_dict.iteritems():
      decision_aggregate_set = set(movement.getAggregateList())
      split_list = []
      for simulation_movement in simulation_movement_list:
        simulated_aggregate_set = set(simulation_movement.getAggregateList())
        difference_set = simulated_aggregate_set.difference(decision_aggregate_set)
        mirror_difference_set = decision_aggregate_set.difference(simulated_aggregate_set)
        if difference_set:
          # There is less aggregates in prevision compare to decision
          split_list.append((simulation_movement, difference_set))
        elif mirror_difference_set:
          # There is additional aggregates in decision compare to prevision
          raise NotImplementedError('Additional items detected. This solver'\
                ' does not support such divergence resolution.')
        else:
          # Same set, no divergence
          continue
      # Create split movements
      for (simulation_movement, splitted_aggregate_set) in split_list:
        split_index = 0
        new_id = "%s_split_%s" % (simulation_movement.getId(), split_index)
        applied_rule = simulation_movement.getParentValue()
        while getattr(aq_base(applied_rule), new_id, None) is not None:
          split_index += 1
          new_id = "%s_split_%s" % (simulation_movement.getId(), split_index)
        # Copy at same level
        kw = _getPropertyAndCategoryList(simulation_movement)
        previous_aggregate_list = simulation_movement.getAggregateList()
        new_aggregate_list = list(set(previous_aggregate_list)\
                                .symmetric_difference(splitted_aggregate_set))
        # freeze those properties only if not yet recorded
        # to avoid freezing already recorded value
        if not simulation_movement.isPropertyRecorded('aggregate'):
          simulation_movement.recordProperty('aggregate')

        # edit prevision movement
        simulation_movement.setAggregateList(new_aggregate_list)
        total_quantity = sum(item.getQuantity() for item in\
                                   simulation_movement.getAggregateValueList())
        simulation_movement.setQuantity(total_quantity)

        # create compensation decision movement
        total_quantity = sum([portal.restrictedTraverse(aggregate).getQuantity()\
                              for aggregate in splitted_aggregate_set])
        kw.update({'portal_type': simulation_movement.getPortalType(),
                   'id': new_id,
                   'delivery': None})
        # propagate same recorded properties from original movement
        # to store them in recorded_property
        for frozen_property in ('aggregate', 'start_date', 'stop_date',):
          if simulation_movement.isPropertyRecorded(frozen_property):
            kw[frozen_property] = simulation_movement.getRecordedProperty(frozen_property)

        new_movement = applied_rule.newContent(activate_kw=activate_kw, **kw)
        # freeze aggregate property
        new_movement.recordProperty('aggregate')
        # edit compensation decision movement
        new_movement.setAggregateList(list(splitted_aggregate_set))
        new_movement.setQuantity(total_quantity)

        if activate_kw is not None:
          new_movement.setDefaultActivateParameterDict(activate_kw)
        start_date = configuration_dict.get('start_date', None)
        if start_date is not None:
          new_movement.recordProperty('start_date')
          new_movement.setStartDate(start_date)
        stop_date = configuration_dict.get('stop_date', None)
        if stop_date is not None:
          new_movement.recordProperty('stop_date')
          new_movement.setStopDate(stop_date)

    # Finish solving
    if self.getPortalObject().portal_workflow.isTransitionPossible(
      self, 'succeed'):
      self.succeed()