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

from TargetSolver import TargetSolver

class CopyToTarget(TargetSolver):
  """
    This solver calculates the ratio between the new (delivery) and old
    (simulation) quantity and applies this ratio to the simulation movement
    and to its parent, until a stable one is found

    XXX: This solver's name is not good, and it tries too many things.
    Once the new isDivergent engine is implemented, this solver can be
    splitted in smaller ones (one for profit and loss, one for backtracking)
    
    Backtracking alone is not enough to solve completely, it must be used with
    another solver (profit and loss, or creating a compensation branch ...)
  """
  def _generateValueDeltaDict(self, simulation_movement):
    """
      Get interesting values
      XXX: better description is possible. But is it needed ?
    """
    # Get interesting value
    old_quantity = simulation_movement.getQuantity()
    old_start_date = simulation_movement.getStartDate()
    old_stop_date = simulation_movement.getStopDate()
    new_quantity = simulation_movement.getDeliveryQuantity() * \
                   simulation_movement.getDeliveryRatio()
    new_start_date = simulation_movement.getDeliveryStartDateList()[0]
    new_stop_date = simulation_movement.getDeliveryStopDateList()[0]
    # Calculate delta
    quantity_ratio = 0
    if old_quantity not in (None,0.0): # XXX: What if quantity happens to be an integer ?
      quantity_ratio = new_quantity / old_quantity
    start_date_delta = 0
    stop_date_delta = 0
    if new_start_date is not None and old_start_date is not None:
      start_date_delta = new_start_date - old_start_date
    if new_stop_date is not None and old_stop_date is not None:
      stop_date_delta = new_stop_date - old_stop_date
    return {
      'quantity_ratio': quantity_ratio,
      'start_date_delta': start_date_delta,
      'stop_date_delta': stop_date_delta,
    }

  def solve(self, simulation_movement):
    """
      Adopt values as new target
    """
    value_dict = self._generateValueDeltaDict(simulation_movement)
    # Modify recursively simulation movement
    self._recursivelySolve(simulation_movement, **value_dict)

  def _generateValueDict(self, simulation_movement, quantity_ratio=1, 
                         start_date_delta=0, stop_date_delta=0,
                         **value_delta_dict):
    """
      Generate values to save on simulation movement.
    """
    value_dict = {}
    # Modify quantity, start_date, stop_date
    start_date = simulation_movement.getStartDate()
    if start_date is not None:
      value_dict['start_date'] = start_date + start_date_delta
    stop_date = simulation_movement.getStopDate()
    if stop_date is not None:
      value_dict['stop_date'] = stop_date + stop_date_delta
    value_dict['quantity'] = simulation_movement.getQuantity() * quantity_ratio
    return value_dict

  def _getParentParameters(self, simulation_movement, 
                           **value_delta_dict):
    """
      Get parent movement, and its value delta dict.
    """
    #XXX max_allowed_delta is the maximum number of days we want not to
    # account as a divergence. It should be configurable through a Rule
    max_allowed_delta = 15

    applied_rule = simulation_movement.getParentValue()
    parent_movement = applied_rule.getParentValue()
    if parent_movement.getPortalType() != "Simulation Movement":
      parent_movement = None

    for date_delta in ('start_date_delta', 'stop_date_delta'):
      if date_delta in value_delta_dict.keys():
        if abs(value_delta_dict[date_delta]) <= \
            applied_rule.getProperty('max_allowed_delta', max_allowed_delta):
          value_delta_dict.pop(date_delta)
        
    return parent_movement, value_delta_dict

  def _recursivelySolve(self, simulation_movement, is_last_movement=1, **value_delta_dict):
    """
      Update value of the current simulation movement, and update
      his parent movement.
    """
    value_dict = self._generateValueDict(simulation_movement, **value_delta_dict)

    parent_movement, parent_value_delta_dict = \
                self._getParentParameters(simulation_movement, **value_delta_dict)
    
    #if parent is not None and parent_movement.isFrozen():
      # If backtraxcking is not possible, we have to make sure that the
      # divergence is solved locally by using profit and loss
      # sm_quantity = simulation_movement.getQuantity()
      # delivery_quantity = \
      #      simulation_movement.getDeliveryValue().getQuantity()
      #  simulation_movement.edit(
      #    profit_quantity=sm_quantity - delivery_quantity)
    #else:
    if is_last_movement:
        delivery_quantity = \
            simulation_movement.getDeliveryValue().getQuantity()
        simulation_movement.setDeliveryError(delivery_quantity -
            value_dict['quantity'])
    
    delivery = simulation_movement.getDeliveryValue()
    
    # XXX Hardcoded Set 
    simulation_movement.setDestination(delivery.getDestination())
    simulation_movement.setSource(delivery.getSource())
    simulation_movement.setDestinationSection(delivery.getDestinationSection())
    simulation_movement.setSourceSection(delivery.getSourceSection())
		
    simulation_movement.edit(**value_dict)
      
    if parent_movement is not None and not parent_movement.isFrozen():
        # backtrack to the parent movement only if it is not frozen
        self._recursivelySolve(parent_movement, is_last_movement=0,
            **parent_value_delta_dict)