#--
# Copyright (c) 2007 Robert S. Thau, Smartleaf, Inc.
# 
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
class Order < ActiveRecord::Base

  include Access::Controlled

  owner_attrs_and_validations

  belongs_to :store
  has_many :line_items

  before_validation_on_create do |order|

    # Default owner from current user, as usual --- but owner_firm
    # comes from the store.

    order.owner      = User.current           if order.owner.nil?
    order.owner_firm = order.store.owner_firm if order.owner_firm.nil?
  end

  # Access rights to an order change after it is paid, so note
  # that permissions may dependon the paid state...

  def self.access_control_keys  # :nodoc:
    ['id', 'owner_id', 'paid']
  end

  def self.attribute_block_set_groups
    super + [['paid']]
  end

  # "Update" privilege --- a nonce for the UI; the user must have
  # additional privilege of :edit or :ship_order to actually change
  # something.  

  declare_privilege :update

  validates_length_of :shipping_address, :in => 10..1000,
    :if => Proc.new { |order| order.paid? }
  validates_length_of :payment_authenticator, :in => 3..50,
    :if => Proc.new { |order| order.paid? }

  require_privilege :place,
    :for_action => :create, 
    :to_update_attribute => [:payment_authenticator, :paid]

  require_privilege :edit,      # LineItem also checks this for attr changes
    :to_associate_as  => ['LineItem#order'],
    :to_dissociate_as => ['LineItem#order'],
    :to_update_attribute => [ :shipping_address ]

  require_privilege :administer, :to_update_attribute => :notes
  require_privilege :ship,       :to_update_attribute => :shipped

  never_permit_anyone :at_callback => :before_destroy
  never_permit_anyone :to_update_attribute => [ :store_id ]

  require_eponymous_privilege_to :find

  # Add a new purchase of the given item.  

  def add_item( offer )
    old_item = line_items.find_by_offer_id( offer )
    if old_item.nil?
      LineItem.create! :order => self, :offer => offer, :quantity => 1
    else
      old_item.update_attributes! :quantity => (old_item.quantity + 1)
    end
  end

  # Wrapped version of update_attributes.  In addition to
  # setting attrs on the Order itself, can also tweak quantities
  # on the line items, given a hash with a key/value pair like so:
  #
  #    :line_item => {id => {:quantity => ...}, id => ..., ... }
  # 
  # If any quantity is blank, the corresponding line item is
  # simply deleted (assuming all else is good).  Returns true
  # on success, false if some invalid object could not be saved
  # (in which case nothing happens).

  def update_attributes( new_attrs )

    new_attrs       = new_attrs.clone
    lines_attrs     = new_attrs.delete( :line_item )

    self.attributes = new_attrs

    (lines_attrs || []).each do |id, line_attrs|
      item = line_items.detect { |it| it.id == id.to_i }
      item.attributes = line_attrs
    end

    line_items_ok = line_items.all? do |it|
      it.valid? || it.quantity.blank? || it.quantity == 0
    end

    unless self.valid? && line_items_ok
      return false
    end

    self.save!

    line_items.each do |item|
      if item.quantity.blank? || item.quantity == 0
        item.destroy
      else
        item.save!
      end
    end

    return true

  end

  # Wrapped version of update_attributes!.  Args as for the wrapped
  # update_attributes, but raises RecordNotSaved if something was
  # wrong with the supplied attributes.

  def update_attributes!( new_attrs )
    unless update_attributes( new_attrs )
      raise( RecordNotSaved )
    end
  end

  # Managing payments, or at least mocking it out...

  def pay_with( payment_code )
    write_attribute( :payment_authenticator, payment_code )
    write_attribute( :paid, true )
  end

  def paid=                     # :nodoc:
    raise SecurityError, "You should be calling pay_with"
  end

end

