/**
 * Unisource v2.0
 *
 * The JavaScript actions for the My Web Order section
 * of My Unisource. This file complements unisource.products.xsl.
 */

MyUnisource.Order = Class.create();
MyUnisource.Order.USE_AJAX = false;

MyUnisource.Order.CLASS_VALID = 'valid';
MyUnisource.Order.CLASS_INVALID = 'invalid';

 

MyUnisource.Order.Text = {
    leaveConfrirmation: '???',
    deliveryChargeWithCutoff: '%charge% %cutoff%',
    toBeDetermined: 'TBD'
    
};

MyUnisource.Order.Policy = Class.create();

MyUnisource.Order.Policy.NONE_REQUIRE_APPROVAL = 'none require approval';
MyUnisource.Order.Policy.ALL_REQUIRE_APPROVAL  = 'all require approval';
MyUnisource.Order.Policy.APPROVAL_ABOVE_THRESHOLD = 'approval above threshold';
MyUnisource.Order.Policy.REJECT_ABOVE_THRESHOLD = 'reject above threshold'; 
MyUnisource.Order.Policy.WARN_ABOVE_THRESHOLD = 'warn above threshold'; 
MyUnisource.Order.Policy.REJECT_ABOVE_THRESHOLD_WITH_APPROVAL = 'reject above threshold with approval'; 

MyUnisource.Order.Policy.prototype = {
    initialize : function(order) {
        this.order = order;
    },
    setOrder : function(order) {
        this.order = order;
    },
    apply : function() {
    },
    
    checkDiscount : function() {
    	var proceed;
    	var rebatePopup = $('rebate-note-popup');
        if(this.order.apply_rebate && rebatePopup && !rebatePopup.done) {
        	rebatePopup.unipopup.show()
        	proceed = false;
        }
        else {
        	proceed = true;
        }    
        return proceed;
    },
    
    accept : function() {
        if (this.checkDiscount()) {
            this.order.submitFinal(true);
        }
    },
    
    requestApproval : function() {                            
    	if (this.checkDiscount()) {
    		this.order.submitForApproval(true);    	
        }
    }
}

MyUnisource.Order.Policy.factory = function(policyName, order) {
    var policy;
    switch (policyName) {
        case MyUnisource.Order.Policy.NONE_REQUIRE_APPROVAL :            
            policy = new MyUnisource.Order.Policy.NoneRequireApproval(order);
            break;
        case MyUnisource.Order.Policy.ALL_REQUIRE_APPROVAL :            
            policy = new MyUnisource.Order.Policy.AllRequireApproval(order);
            break;
        case MyUnisource.Order.Policy.APPROVAL_ABOVE_THRESHOLD :            
            policy = new MyUnisource.Order.Policy.ApprovalAboveThreshold(order);
            break;
        case MyUnisource.Order.Policy.REJECT_ABOVE_THRESHOLD :            
            policy = new MyUnisource.Order.Policy.RejectAboveThreshold(order);
            break;            
        case MyUnisource.Order.Policy.WARN_ABOVE_THRESHOLD :            
            policy = new MyUnisource.Order.Policy.WarnAboveThreshold(order);
            break;            
        case MyUnisource.Order.Policy.REJECT_ABOVE_THRESHOLD_WITH_APPROVAL:
            policy = new MyUnisource.Order.Policy.RejectAboveThresholdWithApproval(order);
            break;
    }
    return policy;
}

MyUnisource.Order.Policy.NoneRequireApproval = Class.create();
MyUnisource.Order.Policy.NoneRequireApproval.prototype = Object.extend(
    new MyUnisource.Order.Policy(),
    {
        apply : function() {
            this.accept();
        }
    }
);

MyUnisource.Order.Policy.AllRequireApproval = Class.create();
MyUnisource.Order.Policy.AllRequireApproval.prototype = Object.extend(
    new MyUnisource.Order.Policy(),
    {
        apply : function() {
            this.requestApproval();
        }
    }
);

MyUnisource.Order.Policy.RejectAboveThreshold = Class.create();
MyUnisource.Order.Policy.RejectAboveThreshold.prototype = Object.extend(
    new MyUnisource.Order.Policy(),
    {
        apply : function() {
            if (order.total >= order.getSpendingLimit()) {
                this.order.showPopup('reject-above-limit');
            } else {
                this.accept();
            }
        }
    }
);


MyUnisource.Order.Policy.ApprovalAboveThreshold = Class.create();
MyUnisource.Order.Policy.ApprovalAboveThreshold.prototype = Object.extend(
    new MyUnisource.Order.Policy(),
    {
        apply : function() {
            if (order.total >= order.getSpendingLimit()) {
                $$('#' + this.order.id + '-approval-above-limit-popup .spending-limit')[0].update(
                    Unisource.formatCurrency(this.order.getSpendingLimit())
                );
                this.order.showPopup('approval-above-limit');                
            } else {
                this.accept();
            }
        }
    }
);

MyUnisource.Order.Policy.WarnAboveThreshold = Class.create();
MyUnisource.Order.Policy.WarnAboveThreshold.prototype = Object.extend(
    new MyUnisource.Order.Policy(),
    {
        apply : function() {
            if (order.total >= order.getSpendingLimit()) {
                $$('#' + this.order.id + '-warn-above-limit-popup .spending-limit')[0].update(
                    Unisource.formatCurrency(this.order.getSpendingLimit())
                );
                this.order.showPopup('warn-above-limit');                
            } else {
                this.accept();
            }
        }
    }
);

MyUnisource.Order.Policy.RejectAboveThresholdWithApproval = Class.create();
MyUnisource.Order.Policy.RejectAboveThresholdWithApproval.prototype = Object.extend(
    new MyUnisource.Order.Policy(),
    {
        apply : function() {
            if (order.total >= order.getSpendingLimit()) {
                this.order.showPopup('reject-above-limit');
            } else {
                this.requestApproval();
            }
            
        }
    }
);



MyUnisource.DeliverySchedule = {
    SAME_DAY : 1,
    NEXT_DAY_AM : 2,
    NEXT_DAY : 3,
    NEXT_DELIVERY_DATE : 4
}

MyUnisource.Charge = Class.create();
MyUnisource.Order.PRINT_WINDOW_WIDTH = 800;
MyUnisource.Order.PRINT_WINDOW_HEIGHT = 600;

MyUnisource.Charge.MINIMUM_ORDER_CHARGE= 1;
MyUnisource.Charge.FUEL_SURCHARGE = 2;
MyUnisource.Charge.FREIGHT = 3;

MyUnisource.Order.PriceType = {
    CURRENT : 'current',
    FUTURE : 'future'
}

MyUnisource.Order.PriceTypes = $H(MyUnisource.Order.PriceType).values();


MyUnisource.Order.prototype = {
    taxRates : {},
    compound: {},
    minimum : 0,
    listingId : null,
    uom : 'UN',
    isOrder: false,
    nextDeliveryDate : null,
    products: {},
    taxes: {},
    taxTypes: {},
    
    checkedProductCodes : {},
    
    isModified : false,
    
    isReadOnly : false,
    
    unloadAuthorized: false,
    
    updateNode: null,
    
    saving: false,
    
    charges : {},
    linePrices : {},
    
    deliveryDays : {},
    
    templates : {},
    
    previousChargeAmounts : {},
    
    oldCharges : {},
    
    originalPrices : {},
    
    
    /**
     * Creates a new instance of the Unisource shopping cart handler
     *
     * @param formId        the ID of the order form
     * @param id            the order ID (from UniOrderCart)
     * @param listingId     the ID of the product listing
     * @param isOrder       whether the handler is being set up
     *                      for an order product listing or a regular one
     *                      (e.g. from My Catalogue or Products)
     * @param products      the initial products (a nested associative array)
     * @param taxRates      the tax rates as an associative array ({tax1: rate1, ...})
     * @param compound      an associative array indictating which taxes should be 
     *                      compounded (e.g. {'GST' : false, 'PST' : true})
     *
     * @param readOnly      indicates whether this order is read-only
     * @param deliverySchedules the delivery schedules available for this order
     */
    initialize: function(formId, id, listingId, isOrder, products, taxRates, compound, readOnly, deliverySchedules, charges, deliveryDays, minimumOrderAmount, minimumOrderChargeAmount)    {
             
        this.minimumOrderAmount = minimumOrderAmount;
        this.minimumOrderChargeAmount = minimumOrderChargeAmount;
        this.minimumAmountMet = null;
        window.order = this; // debug
        
        this.form = $(formId);
        if (!this.form) {
            return;
        }
        
        this.form.order = this;
        
        this.id = id;

        if(!$(id))
            return;
            
        $(id).order = this;
        $(listingId).order = this;
        
        this.updateNode = $(id).parentNode;
        this.listingId = listingId;
        this.products = products;        
        this.buildPricing();
        
        this.isOrder = isOrder;
        if (deliverySchedules) {
            this.deliverySchedules = deliverySchedules;
        } else {
            this.deliverySchedules = [];
        }
        
        if (deliveryDays) {
            this.deliveryDays = deliveryDays;            
        } else {
            this.deliveryDays = {};
        }

        this.requiredDateCalendar = $(this.id + '-required-date-calendar');
        if (charges) {
            this.charges = charges;
        }
        
        if (this.charges[MyUnisource.Charge.FREIGHT]) {
            this.charges[MyUnisource.Charge.FREIGHT].row  = $(this.id + '-freight-charge-amount-row');
            this.charges[MyUnisource.Charge.FREIGHT].field = $(this.id + '-freight-charge-amount-row');
            this.charges[MyUnisource.Charge.FREIGHT].amountField = $(this.id + '-freight-charge-amount');
        }
        
        Event.observe(window, 'load', 
            function() {
                var minimumOrderAmountBlockElement = $(this.id + '-minimum-order-amount-block-message');
                if (minimumOrderAmountBlockElement) {
                    this.templates = {
                        minimumOrderAmountBlock : minimumOrderAmountBlockElement.innerHTML
                    }
                }
            }.bindAsEventListener(this)
        );
        
        this.attachEvents();
        this.taxRates = taxRates || {};
        this.compound = compound || {};        
        this.allTaxes = $H(this.taxRates).keys();
        
        this.attachValidator();
        if ($(this.listingId) && $(this.listingId).listing) {
            this.attachListing();
        } else {
            document.setTimeout(
                this.attachListing.bindAsEventListener(this),
                10
            );
        } 
        
        this.nextDeliveryDate = new Date();
        this.isReadOnly = readOnly;
        
        if (this.isOrder && !this.isReadOnly) {
            this.attachOrderEvents();
        }
        
        var schedules = $H(deliverySchedules).values();
        if (schedules.length == 1) {
            this.setDeliveryScheduleId(schedules[0].id);
        } 
                
    },
    
    /**
    * Builds the internal data structures for pricing calculations
    */
    buildPricing : function() {
        this.pricing = {};
        var priceTypes = ['current', 'future'];
        $H(this.products).each(
            function(entry) {
                var productId = entry.key;
                var product = entry.value;
                this.pricing[productId] = {};
                $A(priceTypes).each(
                    function(priceType) {                        
                        if (product.pricing[priceType]) {
                            this.pricing[productId][priceType] = 
                                new MyUnisource.ProductPrice(product.pricing[priceType]);
                        }
                    }.bind(this)
                );
            }.bind(this)
        );
    },
    
    /**
    * Retains original price values
    */
    retainOriginalPrices : function() {
        $A(MyUnisource.Order.PriceTypes).each(
            function(priceType) {
                $H(this.products).keys().each(
                    function(productId) {
                        if (!this.originalPrices[productId]) {
                            this.originalPrices[productId] = {};
                        }
                        var element = this.getProductPriceElement(productId, priceType);
                        if (element) {                            
                            this.originalPrices[productId] = element.value;
                        }
                    }.bind(this)
                );                
            }
        );
    },
    
    /**
    * Restores the original price (the one pre-filled in the listing) 
    * for the specified product
    * 
    * @param productId the product ID
    */
    restoreOriginalPrice : function(productId) {
        $A(MyUnisource.Order.PriceTypes).each(
            function(priceType) {
                var element = this.getProductPriceElement(productId, priceType);
                if (element && this.originalPrices) {                            
                    element.value = this.originalPrices[productId];
                }
            }
        );
        
    },
    
    /**
     * Attaches event handlers to the underlying product 
     * listing
     */
    attachListing: function() {
        this.listingAttached = true;        
        var listingElement = $(this.listingId);
        this.listing = listingElement.listing;
        this.listing.order = this;
        if (listingElement) {
            if (!(listingElement.listing)) {
                throw new EmptyElementException('$(' + this.listingId + ').listing');
            }
            
            listingElement.listing.onRefresh = function(url) {
                this.performAction(url, true);
            }.bind(this);
        }            
    },
    
    
    /**
     * Attaches an IgnitionWeb form validator to the order form
     */
    attachValidator: function() {
        this.form.validator = new Validator(
            this.form, 
            Paths.www + '/myunisource/uni_orders/validateForm'
        );
        this.form.validator.method = function (errors) {
            if (typeof errors["data[UniOrder][po_number]"] != 'undefined') {
                $('missing-po').unipopup.show();
            } else {
                var message =
                    $H(errors).values().inject(
                        '',
                        function(accumulator, value) {
                            return accumulator + "- " + value + "\n";
                        }
                    );   
               alert(message);                 
            }                        
        };             
    },
    
    
    /**
     * Returns the name of the ordering policy that is currently 
     * in place
     */
    getOrderingPolicy : function() {
        return (this.orderingPolicy) ? this.orderingPolicy : null;
    },
    
    /**
     * Sets the name of the ordering policy to be used
     *
     * @param orderingPolicy the new ordering policy
     */
    setOrderingPolicy : function(orderingPolicy) {
        this.orderingPolicy = orderingPolicy
    },
    
    /**
     * Returns the spending limit for the current order (usually
     * obtained from the user's profile
     *
     * @return Number
     */
    getSpendingLimit: function() {
        return (this.spendingLimit) ? this.spendingLimit : null;
    },

    /**
     * Sets the maximum order total above which orders
     * will not be automatically accepted 
     *
     * @param spendingLimit the new limit (null  means no limit)
     */    
    setSpendingLimit : function(spendingLimit) {
        this.spendingLimit = spendingLimit;
    },
    
    
    /**
     * Returns the next delivery date
     *
     * @return Date
     */
    getNextDeliveryDate : function () {
        var deliveryScheduleId = this.getDeliveryScheduleId();
        var deliverySchedule = this.deliverySchedules[deliveryScheduleId];
        var deliveryDate;
        if (deliverySchedule.fixedDate) {
            nextDeliveryDate = null;
        } else if (deliveryScheduleId == MyUnisource.DeliverySchedule.NEXT_DELIVERY_DATE && !this.nextDeliveryDate.isAfterTomorrow()) {
            deliveryDate = new Date(this.nextDeliveryDate);
            deliveryDate.addDays(1);
        } else {
            deliveryDate = this.nextDeliveryDate;
        }
        
        return deliveryDate;
    },
    
    
    /**
     * Sets the next delivery date for this order
     *
     * @param nextDeliveryDate the next delivery date as a Date
     *          instance or an ISO date
     */
    setNextDeliveryDate : function(nextDeliveryDate) {
        if (nextDeliveryDate instanceof Date) {
            this.nextDeliveryDate = nextDeliveryDate;
        } else {
            this.nextDeliveryDate = new Date();
            this.nextDeliveryDate.setISODate(nextDeliveryDate);
        }
    },
    
    
    getUpdateNode : function() {
        return this.updateNode;
    },
    
    setUpdateNode : function(updateNode) {
        this.updateNode = updateNode;        
    },
    
    getQuantityField : function(productId) {
        return $(this.listingId + '-product-' + productId + '-quantity');
    },
    
    getQuantity : function(productId) {
        var field = this.getQuantityField(productId);
        var quantity;
        if (!field) {
            quantity = field;
        } else if (field.value) {
            quantity = field.value;
        } else {
            quantity = '';
        }
        return quantity;
    },
    
    getProductTotalElement : function(productId) {
        return $(this.listingId + '-product-' + productId + '-total');
    },
    getProductPriceElement : function(productId, priceType) {
        if (!priceType) {
            priceType = MyUnisource.Order.PriceType.CURRENT;
        }
        return $(this.listingId + '-product-' + productId + '-' + priceType + '-price');
    },
    getProductUnitOfMeasureElement: function(productId, priceType) {
        if (!priceType) {
            priceType = MyUnisource.Order.PriceType.CURRENT;
        }
        
        return $(this.listingId + '-product-' + productId + '-' + priceType + '-uom');
    },
    
    hasProductPriceElement : function(productId, priceType) {
        if (!priceType) {
            priceType = MyUnisource.Order.PriceType.CURRENT;
        }
        return $(this.listingId + '-product-' + productId + '-' + priceType + '-price')
    },
    
    getMinimumQuantity : function(productId) {
        var minimumQuantity = NaN;
        if (
            (this.products[productId])
            &&
            (this.products[productId].pricing.current.breaks)
            &&
            (this.products[productId].pricing.current.breaks.length > 0)
        ) {
            minimumQuantity = this.products[productId].pricing.current.breaks[0].minimum_quantity;
        }
        return minimumQuantity;
    },
    
    getPrice : function(productId, quantity, type) {
        if (!quantity) {
        	quantity = Number(this.getQuantity(productId));
		}
               
        if (!type) {
            type = 'current';
        }
                
        var result = null;
        if (this.pricing[productId][type]) {
            result = this.pricing[productId][type].getPriceAt(
                quantity, 
                this.products[productId].pricing[type].uom['quantity']
            );
        } 
        return result;
    },
    
    
    /**
    * Returns the line item total for a specific product 
    * (e.g. the amount to pay for 100 sheets of paper)
    *
    * @param Integer productId the ID of the product in the line item
    *
    * @return Number
    */
    getProductTotal : function(productId, quantity, priceType) {
        if (!priceType) {
            priceType = MyUnisource.Order.PriceType.CURRENT;            
        }
    	if (quantity === undefined) {
    		quantity = Number(this.getQuantity(productId));
		}
    	var price = this.getPrice(productId, quantity, priceType);
        
    	return (price === null) ? 0.0 : price.unit * quantity;    
    },
    
    /**
    * Recalculates and updates the amount for a specific product and
    * returns the amounts to be used in the form of an anonymous
    * object, formatted as follows:
    *
    * {
    *	 id		   : [the value of productId]
    *    price 	   : [the price of the product, effective at the new quantity - for display purposes]
	*    lineTotal : [the new line total - unit price * quantity]
	* 	 delta     : [the change in the sub-total caused by the update]
    * }
    *
    * @param Integer productId the ID of the product for which the amount 
    *                      is being updated
    * @param Integer previousQuantity the quantity before the update
    * @param Integer newQuantity      the quantity after the update
    * @param String  priceType        the price type to be recalculated (MyUnisource.Order.PriceType.*)
    *
    * @return Numeric the change in the sub-total
    */
    recalculateLine : function(productId, previousQuantity, newQuantity, priceType) {
        if (!priceType) {
            priceType = MyUnisource.Order.PriceType.CURRENT;
        }
        
        var previousLineTotal = 
        	this.getProductTotal(productId, previousQuantity, priceType);
            
        if (isNaN(previousLineTotal)) {
            previousLineTotal = 0.0;
        }
        
        var newPrice	  = 
        	this.getPrice(productId, newQuantity, priceType);
        	
        var newLineTotal = (newPrice === null) ? 0.0 :
        	newPrice.unit * this.getQuantity(productId);
        
        var updatedPrice = {
        	id: productId,
        	price: newPrice,
        	lineTotal: newLineTotal,
        	delta: newLineTotal - previousLineTotal
        };        
        updatedPrice.priceType = priceType;
        
        return updatedPrice;
    },
    
    /**
    * Updates the display of a product line
    *
    * @param Object lineUpdate  the return value of recalculate() or a similarly
    * 			formatted object
    */
    updateLineDisplay: function(lineUpdate) {
    	if (lineUpdate.price !== null) {
	        var priceElement = this.getProductPriceElement(lineUpdate.id, lineUpdate.priceType);

	        if (priceElement !== null) {
        		priceElement.update(
        			Unisource.formatCurrency(
        				lineUpdate.price.bracket.price
        			)
        		);
	        }                
	        
	        var unitOfMeasureElement = this.getProductUnitOfMeasureElement(lineUpdate.id, lineUpdate.priceType);        
	        if (unitOfMeasureElement) {
        		var formattedUnit = 
        			lineUpdate.price.bracket.priceQuantity + ' ' + lineUpdate.price.bracket.unitOfMeasure.price;
	            
	            unitOfMeasureElement.update(
            		formattedUnit
	            );
	        }    	
	        
            if (!lineUpdate.priceType || lineUpdate.priceType == MyUnisource.Order.PriceType.CURRENT) {
                
                // Only the current price has anything to do with the order
                var productTotalElement = this.getProductTotalElement(lineUpdate.id, lineUpdate.priceType);
	        if (productTotalElement) {
        		productTotalElement.update(
        			Unisource.formatCurrency(lineUpdate.lineTotal)
        		);
	        }
                
            }
		}        
    },
    
    /**
    * Applies the line delta to the sub-total and
    * recalculates other totals
    *
    * @param Object lineUpdate  the return value of recalculate() or a similarly
    * 			formatted object    
    */
    applyLineDelta: function (lineUpdate) {
    	this.subtotal += lineUpdate.delta;
        this.addTaxes(
            this.products[lineUpdate.id].taxes,
            lineUpdate.delta
        );
        if (this.isOrder) {
            this.calculateTotals();
        }
    },
    
    /**
    * Updates the total for a line item, along with the corresponding
    * UI elements, and refreshes the totals at the bottom
    *
    * @param Integer productId the ID of the product in the line to be
    *		updated
    * @param Integer previousQuantity the quantity before the update
    * @param Integer newQuantity      the quantity after the update
    */
    refreshLine: function(productId, previousQuantity, newQuantity) {
        $A(MyUnisource.Order.PriceTypes).each(
            function(priceType) {
                if (this.hasProductPriceElement(productId, priceType)) {
                    var lineUpdate = this.recalculateLine(productId, previousQuantity, newQuantity, priceType);
                    this.applyLineDelta(lineUpdate);
                    this.updateLineDisplay(lineUpdate);
                }                
            }.bind(this)
        )
    	//if (this.isOrder) {
    		this.showTotals();
		//}
    },
    
    
    
    
    /**
    * Determines the quantity or price unit of measure for a
    * specific quantity
    *
    * @param Integer productId the ID of the product to consider
    * @param String  uomType   the unit of measure to get ('price' or 'quantity')
    *
    * @return String
    */
    getUnitOfMeasure: function(productId, uomType) {
        return this.products[productId].pricing.current.uom[uomType];
    },
    
    
    /**
    * Refreshes all product lines
    */
    refreshAllLines : function() {
        if (Unisource.hasPrivilege('access.prices')) {
            $H(this.products).each(
                function(entry) {
                    var quantity = this.getQuantity(entry.key);
                    this.refreshLine(
                        entry.key,
                        quantity,
                        quantity
                    );
                }.bind(this)
            );
        }
    },
    
    
    /**
    * Recalculates all line prices, line totals, order totals and taxes 
    * for an order
    */
    updateAllAmounts: function() {    
        this.subtotal = 0.0;
        this.total = 0.0;
        this.total_before_rebate = 0.0;
        
        this.resetTaxes();

		this.linePrices = {};
        $H(this.products).each(        
            function(entry) {
            	var lineUpdate = this.recalculateLine(
            		entry.key, 
            		0,
            		this.getQuantity(entry.key)
            	);
				if (entry.value.taxes) {                	
                	this.addTaxes(entry.value.taxes, lineUpdate.lineTotal);
                }
                this.linePrices[lineUpdate.id] = {
                	id: lineUpdate.id,
                	price: lineUpdate.price,
                	lineTotal : lineUpdate.lineTotal
                };
            	this.subtotal += lineUpdate.delta;
            	
            }.bind(this)
        );
        
        if (this.isOrder) {
            this.calculateTotals(); 
        }            
    },
    
    /**
    * Calculates all total fileds based on the subtotal
    */
    calculateTotals : function() {
        this.total = this.subtotal;
        
        var charges = $H(this.charges).values();
                
        var minimumAmountMet = (this.minimumOrderAmount <= this.subtotal);

        var minimumCharge = $A(charges).find( function(charge) {return charge.type_code == 'minimum_order_charge'} );
        
        if (this.minimumOrderChargeAmount !== undefined && minimumCharge) {
            this.charges[MyUnisource.Charge.MINIMUM_ORDER_CHARGE].amount = 
                minimumAmountMet ? 0.0 : this.minimumOrderChargeAmount; 
        } 
        
        this.minimumAmountMet = minimumAmountMet;
        

        // Add the fuel surcharge
        if(this.fuel_surcharge) {
            this.charges[MyUnisource.Charge.FUEL_SURCHARGE].amount = this.fuel_surcharge;
        }
        
        
        // "Frameworked" charges...         
        this.total += $A(charges).inject(
            0.0,
            function(acc, charge) { return acc + charge.amount; }
        );
        
        var chargeDeltas = {};
        $H(this.charges).each(
            function(entry) {
                chargeDeltas[entry.key] = entry.value.amount - 
                    (this.oldCharges[entry.key] ? this.oldCharges[entry.key] :  0.0);
                this.oldCharges[entry.key] = entry.value.amount;
            }.bind(this)
        );
        
        $A(charges).each(
            function(charge) {
                if (charge.taxes) {
                    this.addTaxes(
                        charge.taxes, 
                        chargeDeltas[charge.id]
                    );
                } 
            }.bind(this)
        );        
        
        
        // Add taxes
        $H(this.taxes).each(
            function(entry) {
                if (entry.value) {
                    this.total += entry.value;
                }
            }.bind(this)
        );
        
        
        this.total_before_rebate = this.total;
        // Add the rebate amount 
        if(this.rebate_amount && (this.minimum_amount_for_rebate <= this.subtotal)) {
            this.total -= this.rebate_amount;
            this.apply_rebate = true;
        } else  {
            this.apply_rebate = false;
        }
        
        // Add percentage rebate amount 
        if(this.percentage_rebate_amount && (this.minimum_amount_for_percentage_rebate <= this.subtotal)) {
            this.total -= this.subtotal * this.percentage_rebate_amount/100;
            this.apply_percentage_rebate = true;
        }
        else {
            this.apply_percentage_rebate = false;
        }
        
    },
    
    /**
    * Determines whether or not there is at least one
    * charge that applies to this order
    *
    * @return Boolean
    */
    hasCharges : function() {
        var result = (
            ! this.minimumAmountMet 
            || this.fuel_surcharge
            || 
            $H(this.charges).values().find(
                function(charge) {
                    return charge.amount > 0.0;
                }
            )
        );        
        
        return result;
    },
    

	/**
	* Adds the specified taxes on a specific amounts to the total 
	* tax amount on this order. 
	*
	* Sample Use Case: Add the GST on $12.95. Ignore the PST.
	* 
	* @param Array  taxes  the taxes to add
	* @param Number amount the amount on which taxes are being added
	*/    
    addTaxes : function(taxes, amount) {    	
    	for (var i=0;i<taxes.length;i++) {
            var currentTax = taxes[i];
            if (!this.taxes[currentTax]) {
                this.taxes[currentTax] = 0.0;
            }
            
    		this.taxes[currentTax] += this.getTaxAmount(currentTax, amount);    		
    	}
    },
    
    /**
    * Calculates the tax value for a specific
    * tax
    *
    * @param String	tax 	a tax identifier
    * @param Number amount  the number on which the tax is calculated
    */
    getTaxAmount : function(tax, amount) {
    	var taxableAmount = amount;
    	if (this.compound[tax]) {
    		for (var i=0;(i<this.allTaxes.length) && (this.allTaxes[i] != tax) ;i++) {
    			taxableAmount += this.getTaxAmount(this.allTaxes[i], amount);
    		}
    	}
    	return taxableAmount * this.taxRates[tax];
    },
    
    /**
    * Resets all taxes to 0
    */
    resetTaxes: function() {
    	for (var i=0;i<this.allTaxes.length;i++) {
    		this.taxes[this.allTaxes[i]] = 0;
    	}
    },
    
    
    /**
    * Calculates the tax amount for the specified tax (e.g. 'GST' or 'PST')
    * on the specified amount before tax.
    *
    * @param string tax 			the tax to calculate
    * @param Number amountBeforeTax the amount on which the tax will be calculated
    */
    calculateTaxAmount: function (tax, amountBeforeTax) {
    	var taxRate = this.taxRates[tax];
    	if (this.compound[tax]) {
    		var taxes = $H(this.taxRates).keys();
    		
    		var taxIndex = taxes.indexOf(tax);                      
    		var compoundedTaxes = taxes.slice(
    			0,
    	        taxIndex
    		);
    		
    		if (compoundedTaxes.length > 0) {
    			$A(compoundedTaxes).each(
    				function(compoundedTax) {
    					amountBeforeTax += 
    						this.calculateTaxAmount(compoundedTax, amountBeforeTax);
    				}.bind(this)
    			);
			}    		
    	}
    	return taxRate * amountBeforeTax;    		
    	
    },
    
    /**
    * Ensures that each product quantity meets the corresponding
    * product's minimum quantity and multiple requirements and
    * returns an object with arrays of errors if errors are found 
    * or an empty error array with a count of zero if the check
    * is successful
    *
    * @return Object
    */
    checkMinimumQuantity : function() {
        var errors = {
            belowMinimum : [], 
            notInMultiples : [],
            count : 0
        };

        $H(this.products).each(
            function(entry) {
                var quantity = this.getQuantity(entry.key);
                var minimumQuantity = this.getMinimumQuantity(entry.key);
                //console.log(entry.key + ' min qty = ' + minimumQuantity);
                if (!isNaN(minimumQuantity) && quantity != 0) {
                    if (quantity < minimumQuantity) {
                        errors.belowMinimum.push(entry.key);
                        errors.count++;
                    } 
                    if ((quantity % minimumQuantity) != 0) {
                        errors.notInMultiples.push(entry.key);
                        errors.count++;
                    }
                }
            }.bind(this)
        );
        
        return errors;        
    },
    
    getSubTotalWithCharges : function () {
        var subtotal2 = this.subtotal;
        
        var charges = $H(this.charges).values();
        subtotal2 += $A(charges).inject(
            0.0,
            function(acc, charge) { return acc + charge.amount; }
        );        
        return subtotal2;
    },
    
    /**
	* Determines whether this order contains the 
	* product ID specified
	*
	* @param Number productId a product ID
	*/
    hasProduct : function(productId) {
    	return this.products[productId];
    },
    
    
    /**
    * Updates the totals for all items
    */
    showTotals: function() {
    	
        
        // If not an order, check if the tbody#{formID}subtotal is set
        if (!this.isOrder) {
            
            var subtotal_field = $(this.id + '-subtotal-field');
            
            if(subtotal_field)
                subtotal_field.update(Unisource.formatCurrency(this.subtotal));
            else
                return;
            
		} else if ($(this.id + '-sub-total')) { // No subtotal = no prices
            var subtotal = Unisource.formatCurrency(this.subtotal);
            $(this.id + '-sub-total').update(subtotal);
            if ($('order-status-current-total')) {
                $('order-status-current-total').update(subtotal)
            }
            if ($('order-status-current-count')) {
                $('order-status-current-count').update($H(this.products).keys().length);
            }
            
            $H(this.taxRates).each(
                function(entry) {
                    var tax = this.taxes[entry.key];
                    var taxField = $(this.id + '-tax-' + entry.key);
                    taxField.update(Unisource.formatCurrency(tax));
                    // Hide empty tax rows
                    $(taxField.parentNode)[tax == 0 ? 'hide' : 'show'](); 
                }.bind(this)
            );        
            $(this.id + '-total').update(Unisource.formatCurrency(this.total));                
            $(this.id + '-total-before-rebate').update(Unisource.formatCurrency(this.total_before_rebate));
            
            // show the minimum charge amount (only shown if subtotal is less than the minimum order amount)
            var charge_amount = Unisource.formatCurrency(this.minimumOrderChargeAmount);
            $(this.id + '-charge-amount').update(charge_amount);
            
            // show the rebate amount (only shown if subtotal is more than, or equal to, the minimum order amount for rebate)
            charge_amount = Unisource.formatCurrency(-this.rebate_amount);
            $(this.id + '-rebate-amount').update(charge_amount);
            
            // show percentage rebate amount (only shown if subtotal is more than, or equal to, the minimum order amount for rebate)
            charge_amount = Unisource.formatCurrency(-(this.subtotal * this.percentage_rebate_amount/100));
            $(this.id + '-percentage-rebate-amount').update(charge_amount);
            
            // show the fuel surcharge amount, if applicable
            charge_amount = (this.fuel_surcharge != undefined)? Unisource.formatCurrency(this.fuel_surcharge): Unisource.formatCurrency(0);
            $(this.id + '-fuel-surcharge-amount').update(charge_amount);
            
            // show the subtotal with charge (subtotal2)
            var subtotal_with_charge = Unisource.formatCurrency(this.getSubTotalWithCharges());
            $(this.id + '-sub-total-with-charge').update(subtotal_with_charge);
            
            if(this.minimumAmountMet) {
                $(this.id + '-charge-amount-row').hide();
            } 
            else {
                $(this.id + '-charge-amount-row').show();
            }
            
            // Show/hide rebate
            if(!this.apply_rebate) {
                $(this.id + '-rebate-amount-row').hide();
            } 
            else {
                $(this.id + '-rebate-amount-row').show();
            }
            
            // Show/hide percentage rebate
            if(!this.apply_percentage_rebate) {
                $(this.id + '-percentage-rebate-amount-row').hide();
            }
            else {
                $(this.id + '-percentage-rebate-amount-row').show();
            }
            
            // Show/hide total before rebate
            if(!this.apply_rebate && !this.apply_percentage_rebate) {
                $(this.id + '-total-before-rebate-row').hide();
            }
            else {
                $(this.id + '-total-before-rebate-row').show();
            }

            
            if(this.fuel_surcharge == undefined) {
                $(this.id + '-fuel-surcharge-amount-row').hide();
            } 
            else {
                $(this.id + '-fuel-surcharge-amount-row').show();
            }
            
            var subtotal_label = $(this.id + '-sub-total-label');
            if(this.hasCharges()) {
                $(this.id + '-sub-total-with-charge-row').show();
                subtotal_label.update(this.original_subtotal_label + ' 1');
            } else {
                $(this.id + '-sub-total-with-charge-row').hide();
                subtotal_label.update(this.original_subtotal_label);
            }
        }
        
        
    },
    
    /**
    * Re-calculates the totals and updates the total amounts
    */
    refreshAllAmounts : function() {
        this.updateAllAmounts();
        this.showAllPrices();
        
        if ($(this.id + '-sub-total')) {
            this.showTotals();
        } 
    },
    
    /**
    * Updates the UI elements for all prices and line totals    
    */
    showAllPrices : function() {
    	$H(this.linePrices).each(
    		function(entry) {
    			this.updateLineDisplay(entry.value);
    		}.bind(this)
    	);
    },       
    
    blockLink : function(e, destination) {
        if (this.isModified) {
            Event.stop(e);
            this.showPopup('leave-confirm');
            this.destination = destination;
        }
    },
    
    handleUnload : function(e) {
        if (this.isModified && !this.unloadAuthorized) {
            var result = confirm(MyUnisource.Order.Text.leaveConfirmation);
            if (!result) {
                try {
                    this.saveOrderContents(null, true);
                } catch (e) {             
                    alert(e.message);
                }
            } 
        }
    },
    
    leave : function(saveQuantities) {
        if (!this.saving) {
            if (saveQuantities) {
                this.saving = true;
                this.saveOrderContents(this.navigateToDestination);
            } else {
                this.navigateToDestination();
            }        
        }
    },
    
    navigateToDestination : function() {
        this.unloadAuthorized = true;
        window.unloadAuthorized = true;
        location.href = this.destination;
    },    
    
    freeze : function() {
        this.frozen = true;
    },
    
    unfreeze : function() {
        this.frozen = false;
    },
        
    submit : function(e) {
        if (this.frozen && e || this.submitted) {
            return;
        }
        
        this.updateAllAmounts();

        
        if ($H(this.products).keys().size() < 1) {
            this.showPopup('empty-order');
        	this.unfreeze();                            
        } else if (this.checkRequiredDate()) {
            var minimumQuantityErrors = this.checkMinimumQuantity();
            if (minimumQuantityErrors.count > 0) {
                var popup = 
                    new MyUnisource.Order.MinimumQuantityPopup(
                        this, 
                        minimumQuantityErrors
                    );
                popup.show();
        		this.unfreeze();                
            } else if (this.subtotal < this.minimumOrderAmount) {
                if (this.minimumOrderChargeAmount == 0) {
                    $(this.id + '-minimum-order-amount-block-message').update(
                        this.templates.minimumOrderAmountBlock.replace('%minimum_order_amount%', Unisource.formatCurrency(this.minimumOrderAmount))
                    );
                    
                    this.showPopup('minimum-order-amount-block');
                } else {
                    this.showPopup('mincharge-confirm');
                }
        		this.unfreeze();                
            } else if (Unisource.hasPrivilege('orders.approve')) {
                this.submitFinal(true);
            } else {
            	this.submitWithPolicy(true);
            }
        }
    },
    
    /**
    * Submits the order, applying the current ordering policy
    */
    submitWithPolicy : function(force) {
        if (!force && this.frozen) {
            return;
        }
        var policy = MyUnisource.Order.Policy.factory(
            this.getOrderingPolicy(), 
            this
        );
        policy.apply();
    },
    
    
    submitForApproval : function(force) {
        if (!force && this.frozen) {
            return;
        }                                
                
        this.freeze();
        new Ajax.Request(Paths.www + '/myunisource/uni_orders/submitForApproval',
            {
                onSuccess: function(transport) {
                    var response = eval(transport.responseText);
                    if (response.result == 'success') {
                        this.showPopup('submit-done');
                    } else {
                        this.showPopup('submit-failed');
                    }
                    this.unfreeze();

                }.bindAsEventListener(this),
                
                parameters: Form.serialize(this.form)
            }
        );
    },
    
    
    submitFinal: function(force) {
        if (!force && this.frozen) {
            return;
        }
        this.freeze();
        if (this.form.validator.validate(null, {submit : false})) {
            
            this.unloadAuthorized = true;
            
            try {
                new Ajax.Request(
                    Paths.www + '/myunisource/uni_orders/submit',
                    {
                        parameters: Form.serialize(this.form),
                        onSuccess : function(transport) {                            
                            var result = transport.responseJSON;
                            if (result.success == true || (this.paymentOptions.selectedOption != 'credit-card' && !result.updated)) {
                                location.href = Paths.www + '/myunisource/uni_orders/completeOrder';
                            } else if (result.updated) {
                                this.getPopup('updated').onclose = function() {
                                    location.href = Paths.www + '/myunisource/uni_orders/createOrder';                                    
                                };
                                this.showPopup('updated');
                            } else {
                                this.showPopup('invalid-cc');    
                                this.unfreeze();
                            }
                        }.bind(this),
                        onFailure : function() {
                            this.unfreeze();
                        }.bind(this),
                        onException : function() {
                            this.unfreeze();
                        }.bind(this)
                        
                    }
                );
            } catch(e) {
                alert(e.message);
            }
        } else {
            this.unfreeze();
        } 
    },
        
    getPopup : function(name) {
        return $(this.id + '-' + name + '-popup').unipopup;
    },
    
    showPopup : function(name) {
        $(this.id + '-' + name + '-popup').unipopup.show();
    },
    
    hidePopup : function(name) {
        $(this.id + '-' + name + '-popup').unipopup.hide();
    },
    
    
    
    refresh : function() {
        this.unloadAuthorized = true;
        location.reload();
    },
    
    quickAdd : function() {
        this.freeze();
        
        var productCode = $(this.id + '-quick-add-product-code').value;
        var quantity = $(this.id + '-quick-add-quantity').value;

        if (!productCode.match(/^\w+$/)) {
            $(this.id + '-invalid-product-code-popup').unipopup.show();
            this.unfreeze();
            return;
        }
        
        if (!quantity.match(/^\d+$/)) {
            $(this.id + '-invalid-quantity-popup').unipopup.show();
            this.unfreeze();
            return;
        }                
        
        new Ajax.Request(
            Paths.www + '/uni_products/checkProductCode/' + productCode + '/true/' + quantity,
            {
                onSuccess: this.quickAddCheckResult.bindAsEventListener(this)
            }
        );
    },
    
    quickAddCheckResult : function(transport) {
            
        var response = transport.responseJSON;
        if ($A(response.replacements).any()) {            
            this.unfreeze();
            this.listing.replacementPopup.show(response.replacements);
        } else if (response.result == 'success') {
            try{
                this.unfreeze();
                this.quickAddComplete();
            } catch(e) {                     
                alert(e.message);
                this.unfreeze();
            }            
        } else {
            $(this.id + '-invalid-product-code-popup').unipopup.show();
            this.unfreeze();
        }
    },
    
    /**
     * Performs a quick check of the product code, highlights
     * it in red if it is invalid and fetches the UOM if it is
     * valid
     *
     * @param productCode the product code to check
     */
    quickCheckProductCode : function(productCode) {
        if (this.checkedProductCodes[productCode]) {
            this.handleProductCodeCheck(this.checkedProductCodes[productCode]);    
        } else {
            new Ajax.Request(
                Paths.www + '/uni_products/checkProductCode/' 
                    + productCode + '/true',
                {
                    onSuccess: function(transport) {
                        var response = eval(transport.responseText);
                        this.checkedProductCodes[productCode] = response;
                        this.handleProductCodeCheck(response);
                    }.bindAsEventListener(this)
                }
            );
        }
    },
    
    /**
     * Handles the result of a product code check. This
     * method can be used to process both cached and newly obtained
     * responses
     *
     * @param response the complete response obtained from
     *          checkProductCode, as a JavaScript object
     */
    handleProductCodeCheck : function(response) {
        if (response.result == 'success') {
            this.setQuickAddProductCodeValid(true);
            this.setQuickAddUnitOfMeasure(response.pricing.unitOfMeasure);
        } else {
            this.clearQuickAddUnitOfMeasure();
            this.setQuickAddProductCodeValid(false);
        }
    },
    
    /**
     * Resets Quick Add to its initial state
     */
    clearQuickAdd : function() {
        this.clearQuickAddUnitOfMeasure();
        this.setQuickAddProductCodeValid(true);        
    },
    
    /**
     * Sets the Unit of Measure (UOM) code for the
     * Quick Add field
     *
     * @param uom the new Unit of Measure
     */
    setQuickAddUnitOfMeasure : function(uom) {
        $(this.id + '-quick-add-uom').update(uom);
    },
    
    
    /**
     * Clears the Unit Of Measure code in the
     * Quick Add field by resetting it to an empty string
     */
    clearQuickAddUnitOfMeasure : function() {
        $(this.id + '-quick-add-uom').update('');
    },
    
    
    /**
     * Informs the ordering component of whether
     * the Quick Add product code is valid and causes
     * it to update the UI accordingly
     *
     * @param isValid true if the code is valid, false otherwise
     */
    setQuickAddProductCodeValid : function(isValid) {
        var productCodeElement = $(this.id + '-quick-add-product-code');
        if (isValid) {
            productCodeElement.removeClassName(
                MyUnisource.Order.CLASS_INVALID);
            productCodeElement.addClassName(
                MyUnisource.Order.CLASS_VALID);            
        } else {
            productCodeElement.removeClassName(
                MyUnisource.Order.CLASS_VALID);
            productCodeElement.addClassName(
                MyUnisource.Order.CLASS_INVALID);            
        }
    },
     
    performAction : function(url, serialize) {
        if (this.frozen) {
            return;
        }
        
        this.form.action = url;
        this.form.submit();
    },
    
    quickAddComplete: function() {
        var url = Paths.www + '/myunisource/uni_orders/quickAdd/'  
                + $(this.id + '-quick-add-product-code').value 
                + '/'
                + $(this.id + '-quick-add-quantity').value;
                
        this.performAction(url, true);
    },
    
    clear : function() {
        $(this.id + '-clear-confirm-popup').unipopup.show();
    },
    
    confirmClear : function() {                                         
        var successFn = function() {window.location.reload()}
        var req = new Ajax.Request(Paths.www + '/myunisource/uni_orders/clear', {onSuccess: successFn});
    },
    
    checkRequiredDate : function() {
        var valid = false;
        
        if (!this.calendar && $('UniOrder/required_date')) {
            this.calendar = $('UniOrder/required_date').unicalendar;
        }
        
        if (this.calendar) {
            if (!this.calendar.isDateValid()) {
                this.showPopup('required-date-impossible');
            } else {
                var requiredDate = this.calendar.getDate();
                var nextDeliveryDate = this.getNextDeliveryDate();
                var valid;
                if (!nextDeliveryDate || requiredDate.isAfter(nextDeliveryDate)) {
                    valid = true; 
                } else {                
                    $(this.id + '-required-date-popup-next-delivery-date')
                        .update(
                            nextDeliveryDate.toISODate()
                        );
                    this.showPopup('required-date');
                }
            }
        } else {
            valid = true; 
        }
        
        return valid;
    },
        
    saveOrderContents : function(callback, synchronous, mode) {                
        if (!mode) {
            mode = 'quick';
        }
        var requestParameters = {
            parameters: Form.serialize(this.form)
        };                                                 
        
        if (callback) {
            requestParameters.onSuccess = callback.bindAsEventListener(this);
        }  
        
        if (synchronous) {
            requestParameters.asynchronous = false;            
        }
        
        new Ajax.Request(Paths.www + '/myunisource/uni_orders/save/' + mode,
            requestParameters
        );
    },
    
    save : function() {     
        if (this.frozen) {
            return;
        }
        this.freeze();
        this.saveOrderContents(
            function(transport) {
                    var response = eval(transport.responseText);
                    if (response.result == 'success') {
                        this.showPopup('save-done');
                    } else {
                        this.showPopup('save-failed');
                    }
                    this.unfreeze();
                    this.isModified = false;
            }
        );           
    },
    
    /**
     * Silently saves the order and opens a window with a print-friendly
     * version. If the parameters 'redirect' is set to 1 (or any
     * version of true), the user is redirected instead of a new pop-up.
     */
    printFriendly : function() {
        if (this.frozen) {
            return;
        }
        this.freeze();
        this.saveOrderContents(
            function(transport) {
                var response = eval(transport.responseText);
                if (response.result == 'success') {                    
                    var destination = Paths.www + '/myunisource/uni_orders/printCurrentDetails/';
                    destination += this.id;
                    
                    var handler=iw_open_window(
                        destination, 
                        'ignitionweb_print_order_window', 
                        MyUnisource.Order.PRINT_WINDOW_WIDTH, 
                        MyUnisource.Order.PRINT_WINDOW_HEIGHT, 
                        'yes', 
                        'yes'
                    );
                    
                } else {
                    this.showPopup('save-failed');
                }
                this.unfreeze();
                this.isModified = false;
            }, 
            true,
            'full'
        );           
    },
    
    
    Quantity : {
        enter : function(e) {            
            var element = Event.element(e);
            element.previousValue = element.value;
            
            if (element.value == '0') {
                element.value = '';
            }
        },
        update : function(e, productId) {
        	var element = Event.element(e);        
            
            if(element.value != 0) {
                this.isModified = true;
            }
            
            if (!element.value.match(/^\d*$/)) {      
                element.value = (element.previousValue === undefined) ? '' : element.previousValue;          
            } else {
                this.refreshLine(productId, element.previousValue, element.value);
                element.previousValue = element.value;
            }
        },
        exit: function(e) {
            var element = Event.element(e);        
            
            if (element.value == '') {
                element.previousValue = null;     
            } else if (element.previousValue !== undefined) {
                if (!element.value.match(/^\d*$/)) {
                    element.value = element.previousValue;
                    this.refreshAllAmounts(e);
                }
                
                element.previousValue = null;
            }
        },
        beforeUpdate : function(e) {
            var element = Event.element(e);
            // Prevent form submission
            if(element.value == 0 && e.keyCode == 13) {
                Event.stop(e);
            }
        }       
    },    
    
    attachEvents : function() {
        $H(this.products).each(
            function(entry) {
                var quantityField =this.getQuantityField(entry.key);
                if (quantityField) {
                    Event.observe(
                        quantityField,
                        'change',
                        this.Quantity.update.bindAsEventListener(this, entry.key)
                    );
                    Event.observe(
                        quantityField,
                        'keydown',
                        this.Quantity.beforeUpdate.bindAsEventListener(this)
                    );
                    Event.observe(
                        quantityField,
                        'focus',
                        this.Quantity.enter.bindAsEventListener(this)
                    );
                    Event.observe(
                        quantityField,
                        'blur',
                        this.Quantity.exit.bindAsEventListener(this)
                    );
                    
                }
            
            }.bind(this)
        );
        
        if (this.isOrder) {
            document.observe(
                'dom:loaded',
                function(e) {
                    if (this.form.elements['data[Customer][shipping_address]']) {
                        Event.observe(
                            this.form.elements['data[Customer][shipping_address]'],
                            'keyup',
                            function() {
                                this.form.elements['data[Customer][shipping_address_option]'][1].checked = true;
                            }
                        );                        
                    }
                    if (this.form.elements['data[Customer][address]']) {
                        Event.observe(
                            this.form.elements['data[Customer][address]'],
                            'keyup',
                            function() {
                                this.form.elements['data[Customer][billing_address_option]'][1].checked = true;
                            }
                        );
                    }
                }.bind(this)
            );
        }
        
    },
    
    /**
    * Registers event handlers pertaining to freight
    * charges
    *
    * @access private 
    */
    attachFreightEvents : function() {        
        var schedules = $H(this.deliverySchedules).values();
        schedules.each(
            function(schedule) {
                var button = $(this.id + '-delivery-schedule-option-' + schedule.id);
                if (button) {
                    if (button.checked) {
                        this.setDeliveryScheduleId(schedule.id);
                    }
                    this.observe(
                        button,
                        'click',
                        function(e) {
                            var button = Event.element(e);
                            if (button && button.checked) {
                                this.setDeliveryScheduleId(button.value);
                                this.refreshAllAmounts();
                            }
                            
                        }.bindAsEventListener(this)
                    )
                }
            }.bind(this)
        );  
        
        
        if (schedules.length == 1) {
            this.setDeliveryScheduleId(schedules[0].id);
        }
        
    },
    
    attachedEvents : [],
    
    /**
    * Registers an event with the cart in a way that makes it
    * possibleto undregister it
    *
    * @param HTMLElement aElement       an HTML element
    * @param string      aEventName     the name of the event
    * @param function    aHandler       the handler function
    */
    observe: function(aElement, aEventName, aHandler) {
        var observer = {
            element: aElement,
            eventName: aEventName,
            handler: aHandler
        };
        this.attachedEvents.push(observer);
        Event.observe(observer.element, observer.eventName, observer.handler);
    },
    
    unobserveAll : function() {
        $A(this.attachedEvents).each(
            function(observer) {
                Event.stopObserving(
                    observer.element, 
                    observer.eventName, 
                    observer.handler
                );
            }
        );
    },
    
    setDeliveryScheduleId : function(scheduleId) {
        this.deliveryScheduleId = scheduleId;
        var schedule = this.deliverySchedules[scheduleId];
        
        if (schedule.fixedDate) {
            this.requiredDateCalendar.hide();
        } else {
            this.requiredDateCalendar.show();
        }
        if (this.charges[MyUnisource.Charge.FREIGHT]) {
            with(this.charges[MyUnisource.Charge.FREIGHT]) {
                if (schedule.charge && schedule.charge > 0.00 || schedule.charge === null) {
                    if (row) {
                        row.show();                    
                    }
                    var amountValue;
                    if (amountField) {
                        if (schedule.charge === null) {
                            amountValue = MyUnisource.Order.Text.toBeDetermined;
                        } else {
                            amountValue = Unisource.formatCurrency(schedule.charge);
                        }
                        amountField.update(amountValue);
                    }
                    amount = schedule.charge;
                } else {
                    if (row) {
                        row.hide();
                    }
                    amount = 0.0;
                }
            }
        }
    },
    
    /**
    * Returns the schedule ID that is currently selected
    */
    getDeliveryScheduleId : function() {
        return this.deliveryScheduleId;
    },
    
    /**
    * Formats the note displayed in parentheses next to a delivery
    * schedule.
    *
    * @param object schedule a delivery schedule
    */
    formatDeliveryScheduleNote: function(schedule) {
        var note = null;
        if (schedule.charge && schedule.cutoff) {
            note = MyUnisource.Order.Text.deliveryChargeWithCutoff.replace(
                '%charge%', 
                Unisource.formatCurrency(schedule.charge)
            ).replace(
                '%cutoff%',
                this.formatDeliveryCutoff(schedule.cutoff)
            );
        } else if (schedule.charge) {
            note = Unisource.formatCurrency(schedule.charge);
        }
        
        return note;
    },
    
    /**
    * Formats the delivery cut-off time as HH[AM|PM] in English
    * (e.g. 11AM, 5PM) or HHh (e.g. 11h, 17h) in French. Note
    * the time format difference
    *
    * @param string value the ISO-style time value to format 
    *           (HH:MM, 24-hour format)
    */
    formatDeliveryCutoff : function(value) {
        var language = (Paths) ? Paths.lang : 'en';
        var hours = parseInt(value.substring(0,2));
        var formattedTime;
        
        
        if (language == 'en') {
            if (hours < 12) {
                formattedTime = hours + 'AM';                            
            } else if (hours == 12) {
                formattedTime = hours + 'PM';
            } else {
                formattedTime = (hours - 12) + 'PM';
            } 
        } else {
            formattedTime = hours + 'h';
        }
        
        return formattedTime;
    },
    
    attachOrderEvents: function() {
        this.observe(
            this.form,
            'submit',
            this.submitForm.bindAsEventListener(this)
        );        
        
        // Give the Quick Add Product Code field the focus
        // Also results in scrolling down to that field on window reload!
        Event.observe(
            window,
            'load',
            function() {
                Field.focus($(this.id + '-quick-add-product-code'));
            }.bindAsEventListener(this)
        );
        
        if (!window.__orderLinkHandlers) {
            var links = document.getElementsByTagName('A');
            for (var i=0;i<links.length;i++) {
                window.__orderLinkHandlers = true;
                if (links[i].rel != 'javascript' && links[i].rel != 'ajax' && links[i].className != 'action') {
                    Event.observe(
                        links[i],
                        'click',
                        function(e, formId, link) {
                            $(formId).order.blockLink(e, link);
                        }.bindAsEventListener(this, this.form.id, links[i].href)
                    );
                }
            }
        }
        
        var productCodeElement = $(this.id + '-quick-add-product-code');
        if (productCodeElement) {
            this.observe(
                productCodeElement,
                'blur',
                function() {
                    var productCode = productCodeElement.value;
                    if (productCode == '') {
                        this.clearQuickAdd();
                    } else {
                        this.quickCheckProductCode(productCode);
                    }
                }.bindAsEventListener(this)
            );
        }
        
        if (!window.__orderUnload) {
            window.__orderUnload = true;
            Event.observe(
                window,
                'unload',
                function(e, formId){
                    $(formId).order.handleUnload(e);
                }.bindAsEventListener(this, this.form.id)
            );
        }
        
        this.attachFreightEvents();
    },
    
     submitForm : function(e) {  
        Event.stop(e);
        if (this.frozen) {
            return;
        } 
        this.freeze();

        try {
            var productCode = $(this.id + '-quick-add-product-code').value;
            var quantity = $(this.id + '-quick-add-quantity').value;

            if(productCode.match(/[0-9a-zA-Z]+/) || quantity.match(/[0-9a-zA-Z]+/)) {
                this.quickAdd();
            } else {
                $(this.id + '-submit-confirm-popup').unipopup.show();
                this.unfreeze();
                return false;
                //this.submit();
            }
        } catch(e) {       
            alert(e.message);
        }
        
    }
}

MyUnisource.Order.MinimumQuantityPopup = Class.create();

MyUnisource.Order.MinimumQuantityPopup.prototype = {
    initialize : function (order, errors) {
        this.order = order;
        this.errors = errors;
    },
    
    show : function() {
        this.showBelowMinimum();        
        this.showMultiples();        
        this.order.showPopup('minimum-quantity');
    },
    
    showBelowMinimum : function() {
        if (this.errors.belowMinimum.length > 0) {
            this.buildBelowMinimum();
            $(this.order.id + '-minimum-quantity-below').show();
        } else {
            this.buildMultiples();
            $(this.order.id + '-minimum-quantity-below').hide();
        }
    },
    
    showMultiples : function() {
        if (this.errors.notInMultiples.length > 0) {
            this.buildMultiples();
            $(this.order.id + '-minimum-quantity-multiples').show();
        } else {
            $(this.order.id + '-minimum-quantity-multiples').hide();
        }
    },
    
    buildBelowMinimum : function() {
        var list = $(this.order.id + '-minimum-quantity-product-code-list');
        
        list.removeAllChildren();
                
        $A(this.errors.belowMinimum).each(
            function(productId) {
                var item = document.createElement('li');
                item.appendChild(
                    document.createTextNode(this.order.products[productId].code)
                );
                list.appendChild(item);
            }.bind(this)
        );
    },
    
    buildMultiples : function() {
        var listBody = $(this.order.id + '-minimum-quantity-multiples-product-list-body');
        listBody.deleteAllRows();
        
        $A(this.errors.notInMultiples).each(
            function(productId) {
                with(this.order.products[productId]) {
                    listBody.addRow(
                        [
                            code,
                            this.order.getMinimumQuantity(productId),
                            pricing.current.uom.quantity
                         ]
                    );
                }
            }.bind(this)
        );        
    }
}; 

MyUnisource.Order.OptionSelector = Class.create();

MyUnisource.Order.OptionSelector.prototype = {
    selectedOption: null,

    initialize : function(id, paymentOptions, selected) {
        this.id = id;
        this.paymentOptions = paymentOptions;
        this.attachEvents();
        if (!selected) {
            selected = paymentOptions[0];
        }
        Event.observe(
            window,
            'load',
            function() {
                this.getOptionButton(selected).checked = true;
                this.selectOption(selected);
            }.bindAsEventListener(this)
        );
    },
    
    getOptionNote : function(option) {
        return $(this.id + '-payment-option-' + option + '-note');
    },
    getOptionButton : function(option) {
       return $(this.id + '-payment-option-' + option + '-button');
    },
    
    attachEvents : function() {
        $A(this.paymentOptions).each(
            function(option) {
                var optionElement = this.getOptionButton(option);
                Event.observe(
                    optionElement,
                    'click',
                    function(e, option) {
                        this.selectOption(option);
                    }.bindAsEventListener(this, option)
                );
            }.bind(this)
        );
    },
    
    fireEvent: function(option, event) {
        if (option && this.options[option] && this.options[option][event]) {
            var eventFunction = this.options[option][event].bind(this);
            eventFunction();
        }
    },
    
    selectOption : function(option) {
        if (this.selectedOption != option) {
            this.fireEvent(this.selectedOption, 'deselect');
            this.selectedOption = option;
            this.hideAllNotes();
            this.getOptionNote(option).expand();        
            this.fireEvent(this.selectedOption, 'select');
        }
    },     
    
    hideAllNotes : function() {
        $A(this.paymentOptions).each(
            function(option) {
                this.getOptionNote(option).hide();
            }.bind(this)
        );
    },
    
    options : {
        'on-account' : {
            select: function(){},
            deselect: function(){}            
        },
        'credit-card' : {
            select: function() {
                $(this.id + '-step-credit-card-info').expand();
            },
            deselect: function() {
                $(this.id + '-step-credit-card-info').collapse();
            }
            
        }
    }
}



MyUnisource.OrderListing = Class.create();

MyUnisource.OrderListing.prototype = Object.extend(new Listing(),
    {
        // Implementation to follow
    }
);


