# Adjustments represent a change to the +item_total+ of an Order.  Each adjustment has an +amount+ that be either
# positive or negative.  Adjustments have two useful boolean flags
#
# +mandatory+
#
# If this flag is set to true then it means the the charge is required and will not be removed from the
# order, even if the amount is zero.  In other words a record will be created even if the amount is zero.
# This is  useful for representing things such as shipping and tax charges where you may want to make it explicitly
# clear that no charge was made for such things.
#
# +locked+
#
# The charge is never to be udpated.  Typically you would want to freeze certain adjustments after checkout.
# One use case for this is if you want to lock a shipping adjustment so that its value does not change
# in the future when making other trivial edits to the order (like an email change).
#
# +eligible?+
#
#  This boolean attributes stores whether this adjustment is currently eligible for its order. Only eligible
#  adjustments count towards the order's adjustment total. This allows an adjustment to be preserved if it
#  becomes ineligible so it might be reinstated.
#
module Spree
  class Adjustment < ActiveRecord::Base
    attr_accessible :amount, :label

    belongs_to :adjustable, :polymorphic => true
    belongs_to :source, :polymorphic => true
    belongs_to :originator, :polymorphic => true

    validates :label, :presence => true
    validates :amount, :numericality => true

    scope :tax, lambda { where(:originator_type => 'Spree::TaxRate', :adjustable_type => 'Spree::Order') }
    scope :price, lambda { where(:adjustable_type => 'Spree::LineItem') }
    scope :shipping, lambda { where(:label => I18n.t(:shipping)) }
    scope :optional, where(:mandatory => false)
    scope :eligible, where(:eligible => true)
    scope :charge, where("amount >= 0")
    scope :credit, where("amount < 0")

    after_save :update_adjustable
    after_destroy :update_adjustable

    # Update the boolean _eligible_ attribute which deterimes which adjustments count towards the order's
    # adjustment_total.
    def set_eligibility
      update_attribute_without_callbacks(:eligible,
                                         mandatory ||
                                         (amount != 0 && eligible_for_originator?))
    end

    # Allow originator of the adjustment to perform an additional eligibility of the adjustment
    # Should return _true_ if originator is absent or doesn't implement _eligible?_
    def eligible_for_originator?
      return true if originator.nil?
      !originator.respond_to?(:eligible?) || originator.eligible?(source)
    end

    # Update both the eligibility and amount of the adjustment. Adjustments delegate updating of amount to their Originator
    # when present, but only if +locked+ is false.  Adjustments that are +locked+ will never change their amount.
    # The new adjustment amount will be set by by the +originator+ and is not automatically saved.  This makes it save
    # to use this method in an after_save hook for other models without causing an infinite recursion problem.
    #
    # order#update_adjustments passes self as the src, this is so calculations can be performed on the
    # current values. If we used source it would load the old record from db for the association
    def update!(src = nil)
      src ||= source
      return if locked?
      if originator.present?
        originator.update_adjustment(self, src)
      end
      set_eligibility
    end

    private

      def update_adjustable
        adjustable.update! if adjustable.is_a? Order
      end

  end
end
