# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility 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
# guarantees 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################

from Products.ERP5.MovementCollectionDiff import _getPropertyAndCategoryList

class MovementGeneratorMixin:
  """
  This class provides a generic implementation of IMovementGenerator.
  It is used by rules to generate a collection of movements from
  the context of an applied rule.

  TODO:
    Deliveries ? Movements ? Items ?
    Does it depend by default on IAmountGeneratorMixin
  """
  # Default values
  _applied_rule = None
  _rule = None
  _trade_phase_list = None

  def __init__(self, applied_rule=None, rule=None, trade_phase_list=None):
    self._trade_phase_list = trade_phase_list # XXX
    self._applied_rule = applied_rule
    if rule is None and applied_rule is not None:
      self._rule = applied_rule.getSpecialiseValue()
    else:
      self._rule = rule # for rule specific stuff

  # Implementation of IMovementGenerator
  def getGeneratedMovementList(self, movement_list=None, rounding=False):
    """
    Returns an IMovementList generated by a model applied to the context

    context - an IMovementCollection, an IMovementList or an IMovement

    movement_list - optional IMovementList which can be passed explicitely
                    whenever context is an IMovementCollection and whenever
                    we want to filter context.getMovementList

    rounding - boolean argument, which controls if rounding shall be applied on
               generated movements or not

    NOTE:
      - implement rounding appropriately (True or False seems
        simplistic)
    """
    # Default implementation bellow can be overriden by subclasses
    # however it should be generic enough not to be overriden
    # by most classes
    # Results will be appended to result, objects created inside folder
    from Products.ERP5Type.Document import newTempMovement
    result = []
    if self._applied_rule is None:
      folder = self # Really useful ?
    else:
      folder = self._applied_rule
    # Build a list of movement and business path
    id_index = 0
    for input_movement, business_path in self \
            ._getInputMovementAndPathTupleList(movement_list=movement_list, 
                                               rounding=rounding):
      # Merge movement and business path properties (core implementation)
      kw = self._getPropertyAndCategoryDict(input_movement, business_path)
      # Update movement properties (class overridable)
      kw.update(self._getUpdatePropertyDict(input_movement))
      # And build temp movement of appropriate type
      simulation_movement = newTempMovement(folder,
                                          'generated_movement_%s' % id_index)
      simulation_movement._edit(**kw)
      result.append(simulation_movement)
      id_index += 1
    return result

  def _getUpdatePropertyDict(self, input_movement):
    # Default implementation bellow can be overriden by subclasses
    return {'delivery': input_movement.getRelativeUrl(), # XXX-JPS empty is better
            }

  def _getTradePhaseList(self, input_movement, business_process):
    if self._trade_phase_list:
      return self._trade_phase_list
    if self._rule:
      self._rule.getTradePhaseList()
    return business_process.getTradePhaseList()

  def _getInputMovementAndPathTupleList(self, movement_list=None, rounding=None):
    """
    Returns list of tuples (movement, business_path)
    """
    # Init result
    result = []
    # First generate a list of movements with any appropriate method
    input_movement_list = self._getInputMovementList(movement_list=movement_list, rounding=rounding)
    # For each input movement
    for input_movement in input_movement_list:
      # Find its business process, if any
      business_process = input_movement.asComposedDocument() # This produces a business process ideally      
      # Initialise trade phase list for a movement and business process
      trade_phase_list = self._getTradePhaseList(input_movement, business_process)

      if business_process is None or len(trade_phase_list) == 0:
        result.append((input_movement, None))
      else:
        for business_path in business_process.getPathValueList(
                          trade_phase_list,
                          input_movement) or [None]:
          result.append((input_movement, business_path))

    return result

  def _getInputMovementList(self, movement_list=None, rounding=None):
    raise NotImplementedError
    # Default implementation takes amounts ?
    # Use TradeModelRuleMovementGenerator._getInputMovementList as default implementation
    # and potentially use trade phase for that.... as a way to filter out

  def _getPropertyAndCategoryDict(self, movement, business_path):
    """
    Merge a movement and a business_path and return a dict of
    properties and categories whch can be used to create a new movement.

    movement -- an IMovement instance
 
    business_path -- an IBusinessPath instance

    rule -- optional rule parameter which can be used to 
            narrow down properties to be copied
    """
    rule = self._rule
    if rule is None:
      property_dict = _getPropertyAndCategoryList(movement)
    else:
      property_dict = {}
      for tester in rule._getUpdatingTesterList(exclude_quantity=False):
        property_dict.update(tester.getUpdatablePropertyDict(
          movement, None))

    if business_path is None:
      return property_dict

    # Arrow
    for base_category, category_url_list in \
            business_path.getArrowCategoryDict(context=movement).iteritems():
      property_dict[base_category] = category_url_list

    # Amount
    if business_path.getQuantity():
      property_dict['quantity'] = business_path.getQuantity()
    elif business_path.getEfficiency():
      property_dict['quantity'] = movement.getQuantity() *\
        business_path.getEfficiency()
    else:
      property_dict['quantity'] = movement.getQuantity()

    movement_start_date = movement.getStartDate()
    movement_stop_date = movement.getStopDate()
    if movement_start_date == movement_stop_date:
      property_dict['start_date'] = business_path.getExpectedStartDate(
          movement)
      property_dict['stop_date'] = business_path.getExpectedStopDate(movement)
      # in case of not fully working BPM get dates from movement
      # XXX: as soon as BPM will be fully operational this hack will not be
      #      needed anymore
      if property_dict['start_date'] is None:
        property_dict['start_date'] = movement_start_date
      if property_dict['stop_date'] is None:
        property_dict['stop_date'] = movement_stop_date
    else: # XXX shall not be used, but business_path.getExpectedStart/StopDate
          # do not works on second path...
      property_dict['start_date'] = movement_start_date
      property_dict['stop_date'] = movement_stop_date

    # save a relation to business path
    property_dict['causality'] = [business_path.getRelativeUrl()]

    return property_dict
