﻿///<reference path="/Web/Js/TESCO.js" />
///<reference path="/Web/Js/system/event.js" />
///<reference path="/Web/Js/system/event.manager.js" />
///<reference path="/Web/Js/system/exception.js" />
///<reference path="/Web/Js/system/DOM.node.js" />
///<reference path="/Web/Js/system/connection/XMLHTTP.js" />
///<reference path="/Web/Js/system/connection/ajax.js" />
///<reference path="/Web/Js/system/serialization.js" />

TESCO.$("UI.entities").Product = (function() {

    //  private static
    //#region
    var _products = {}
    var _entities = []; //  array of instances
    var _inProgress = false;
    var _sendTimer = null;
    var _trace = {}
    var _basketIds = TESCO.sites.Configuration.baskets;
	var _emptyProduct;

    function _completeChange(ok) {	//	on completion of a basket ajax call, update the screen objects
        //#region              
        var idx, _basket;
        for (idx = 0; idx < _constructor.queue.length; idx++) {
            var _update = _constructor.queue[idx];
            if (_update.inProgress) {
                var f = _products[_update.id];
				if (ok) {
					if (f) {	//	there might be no products in the request, eg new basket
						if (f.pendingQuantityDelta) {
							// only update if successful
							f["basket-" + _update.product.basket.id].quantity += f.pendingQuantityDelta; 
							f.pendingQuantityDelta = null;
						}
						if (_update.product.action === _constructor.actions.move) {  
							//  it's been moved, reset source basket quantity
							f["basket-" + _update.product.sourceBasket.id].quantity = 0;
						}
					}
					_update.product.trace = _update.trace;
					_constructor.event.dispatchEvent("quantityupdateend", _update.product);
				} else {
					if (f) {	//	there might be no products in the request, eg new basket
						if (_update.product.action === _constructor.actions.move) {  
							//  it's been moved, reset source basket quantity
							f["basket-" + _update.product.basket.id].quantity = 0;
						}
						f.pendingQuantityDelta = null;
					}
					_constructor.event.dispatchEvent("quantityupdateerror", _update.product);
				}
                _constructor.queue.splice(idx, 1);  // remove the element and adjust pointer;
                idx--;
            }
        }
        _inProgress = false;
        if (_constructor.queue.length > 0)
            _constructor.sendUpdate();  // start another ajax request if queued
        //#endregion
    }

    function _confirmChange() {
        //  following a successful basket ajax - update the screen objects
        _completeChange(true);
    }

    function _failureChange() {
        //  the basket update has failed - roll back
        _completeChange(false);
    }

    function _getNewBasketLineQuantity(f, inc, basketId, action) {
        //#region
        //  Return the new basket line quantity for a basket update - 
        //  increments the basket line with 'increment' * step (for basket buttons), 
        //  or quantity (for product 'add' button) if step == 0.
        f.quantity = f.increment; //	return product quantity to original value
        var _delta;
        if (action === _constructor.actions.include || action === _constructor.actions.move) {
			_delta = inc;
        } else {
			_delta = Math.min((f.pendingQuantityDelta || 0) + inc, f.maxQuantity - (f.pendingQuantityDelta ? f.pendingQuantityDelta + f["basket-" + basketId].quantity : f["basket-" + basketId].quantity));
        }
        _delta = Math.max(_delta, -f["basket-" + basketId].quantity);
		return (_delta == 0) ? null : _delta;
		//#endregion
    }
    //#endregion

    //	public static
    //#region
    _constructor.queue = [];
    
    _constructor.sendUpdate = function() {	// Takes all the updates from the queue, builds an update request and sends it
        //#region
        var idx;
        var _baskets = [];
        var _break = false;
        for (idx = 0; idx < _constructor.queue.length; idx++) {
            var _update = _constructor.queue[idx];
            var ii = 0;
            var LL = _baskets.length;
            var _found = false;
            for (; ii < LL; ii++) {
                if (_baskets[ii].id === _update.product.basket.id) {
                    _found = true;
                    break;
                }
            }
            if (!_found) {
                _baskets[LL] = _update.product.basket;
                _baskets[LL].products = [];
                ii = LL;
            }
            _baskets[ii].action = _update.action;
            if (_baskets[ii].action !== TESCO.UI.Basket.actions.none &&
                _baskets[ii].action !== TESCO.UI.Basket.actions.update) {
                _break = true;
            } else if (_break && (_update.action === TESCO.UI.Basket.actions.none)) {
                //  this update is not part of an empty, select, new or refresh request which is being sent
                //	finish the basket action before processing more requests
                break;
            }
            if (!_update.inProgress) {
                _update.inProgress = true;
                if (!_break) {
                    var _fields = _products[_update.id];
                    var _pidx = _baskets[ii].products.length;
                    _baskets[ii].products[_pidx] = _entities[_fields.index];
                }
            }
        }
        if (_break || _baskets.length > 0) {
            _inProgress = true;            
            _constructor.event.dispatchEvent("quantityupdatesend", {
                "baskets": _baskets,
                "request": new TESCO.sites.UI.entities.Baskets.Request(_baskets, _baskets[0].action === TESCO.UI.Basket.actions.empty),
                "complete": _confirmChange,
                "error": _failureChange
            });
        }
        //#endregion
    }

    _constructor.update = function(basket, products) {
        //#region
        var _basketId = basket.id;
        for (var i = 0, L = products.length; i < L; i++) {
            var _f = _products[products[i].id()];
            if (products[i].newQuantity) {
                products[i].newQuantity = _f.pendingQuantityDelta = _getNewBasketLineQuantity(_f, products[i].newQuantity, _basketId);
            } else {
                products[i].newQuantity = 0;
            }
            products[i].action = _constructor.actions.update;   //  product action
            products[i].basket = basket;
            TESCO.UI.entities.Product.queue.push({
                "id": products[i].id(),
                "product": products[i],
                "action": TESCO.UI.Basket.actions.update        //  basket action
            });
        }
        TESCO.UI.entities.Product.sendUpdate();
        //#endregion
    }

    _constructor.actions = {
        //#region
        "add": 0,
        "update": 1,
        "remove": 2,
        "move": "MoveToActiveBasketFromPreviousBasket",
        "include": "MoveToActiveBasketFromExcluded",
        "exclude": 5,
        "refresh": 6
        //#endregion
    }
    
    _constructor.steps = {
        //#region
        "add": "i",
        "remove": "r",
        "move": "m",
        "increment": "+",
        "decrement": "-"
        //#endregion
    }

    _constructor.getProductById = function(id) {
        //#region
        var _p = _products["p-" + id];
        return _p ? _entities[_p.index] : null;
        //#endregion
    }

    _constructor.createBasket = function(basketId) {
        //#region
        
        var _bIds = new Array();
       
        for(var n = 0; n < _basketIds.length; n++)
        {
            _bIds[n] = _basketIds[n];
        }
        _bIds[_basketIds.length] = "basket-" + basketId;
        _basketIds = _bIds;        
        
        for (var i = 0; i < _entities.length; i++) {
            _products[_entities[i].id()]["basket-" + basketId] = {
                "quantity" : 0
            }
        }
        //#endregion
    }
    
    _constructor.getProductsInBasket = function(basketId) {
        //#region
        var _productsInBasket = [];
        for (var i = 0; i < _entities.length; i++) {
            if (_entities[i].isInBasket(basketId)) {
                _productsInBasket.push(_entities[i]);
            }
        }
        return _productsInBasket;
        //#endregion
    }

    _constructor.empty = function(basket, trace) {
        //#region
        var _basketId = basket.id;
        var _p = _constructor.getProductsInBasket(_basketId);
        for (var i = 0, L = _p.length; i < L; i++) {
            var _f = _products[_p[i].id()];
            var _inc = _entities[_f.index].getBasketLineInc(_f, _constructor.steps.remove, _basketId);
            _p[i].newQuantity = _f.pendingQuantityDelta = _getNewBasketLineQuantity(_f, _inc, _basketId);
            _p[i].action = _constructor.actions.remove;
            _p[i].basket = basket;
            _constructor.queue.push({
                "id": _p[i].id(),
                "product": _p[i],
                "action": TESCO.UI.Basket.actions.empty,
                "trace": trace
            });
        }
        _constructor.sendUpdate();
        //#endregion
    }

    _constructor.refresh = function(basket, action) {
        //#region
        var _basketId = basket.id;
        if (_entities.length > 0) {
			for (var i = 0, L = _entities.length; i < L; i++) {
				_entities[i].action = _constructor.actions.refresh;
				_entities[i].basket = basket;
				_constructor.queue.push({
					"id": _entities[i].id(),
					"product": _entities[i],
					"action": action
				});
			}
		} 
        _constructor.sendUpdate();
        //#endregion
    }

    TESCO.system.event.manager.call(_constructor.event = {}, "update", "updateerror", "quantityupdatestart", "quantityupdatesend", "quantityupdateend", "quantityupdateerror")
    //#endregion

    function _constructor(obj) {
        //#region
        var _p = _products["p-" + obj.productId];
        var _fields = {
            "id": obj.productId,
            "baseProductId": obj.baseProductId,
            "name": obj.name,
            "price": obj.price,
            "quantity": obj.quantity || 0,              //  the quantity to be added on an <add> click
            "maxQuantity": obj.maxQuantity,             //  maximum allowed in basket
            "imgURL": obj.imageURL,
            "pendingQuantityDelta": null,
            "index": _p ? _p.index : _entities.length, //	entity already exists? overwrite
            "increment": obj.increment
        }
        if (_basketIds) {
            for (var i = 0, L = _basketIds.length; i < L; i++) {
                _fields[_basketIds[i]] = {
                    "quantity": 0   //  set default quantity for this product in all baskets
                }
            }
        }
        if (obj.basket) {  //  overwrite basket quantity for this product 
            _fields[obj.basket.id] = {
                "quantity": obj.basket.quantity
            }
        }
        var _id = "p-" + obj.productId;
        _products[_id] = _fields;
        this.id = function() {      //  instance function to hold the id
            return _id;
        }       
        return _entities[_fields.index] = this;
        //#endregion
    }

     function _processRequest(idx, id, self, trace) {        
            _constructor.queue[idx] = {
                "id": id,
                "product": self,
                "trace": trace,
                "action": TESCO.UI.Basket.actions.none
            }
            _constructor.event.dispatchEvent("quantityupdatestart", self);
            if (!_inProgress) {
                if (_sendTimer) {
                    clearTimeout(_sendTimer);
                }
                _sendTimer = setTimeout(_constructor.sendUpdate, TESCO.sites.Configuration.entities.product.updateWait);
            }
        }


    //#public instance
    //#region
    _constructor.prototype = {
        NAME: "TESCO.UI.entities.Product",
        
        "getName": function() {
            return _products[this.id()].name;
        },

        "getImgURL": function() {
            return _products[this.id()].imgURL;
        },

        "getBasketLineQuantity": function(basketId) {
            var _fields = _products[this.id()];
            return _fields.pendingQuantityDelta ? (_fields.pendingQuantityDelta + _fields["basket-" + basketId].quantity) : _fields["basket-" + basketId].quantity;
        },

        "isInBasket": function(basketId) {
            return this.getBasketLineQuantity(basketId) > 0;
        },
        
        "getUnitPrice" : function() {
			return _products[this.id()].price;
        },

        "getBasketPrice": function(basketId) {
            return this.getBasketLineQuantity(basketId) * this.getUnitPrice();
        },

        "getFormattedBasketPrice": function(basketId) {
            return TESCO.locale.entities.currencySymbol.format(this.getBasketPrice(basketId).toDecimal(2).toFixed(2));
        },

        "atMaxQty": function(basketId) {
            var _fields = _products[this.id()];
            return _fields.quantity >= (_fields.maxQuantity - _fields["basket-" + basketId].quantity) || _fields.zeroIncrement;
        },

		"getBaseProductId" : function() {
			return _products[this.id()].baseProductId;
		},

        "atCurrentMaxQty": function(basketId) {
            var _fields = _products[this.id()];
            return _fields["basket-" + basketId].quantity >= _fields.maxQuantity;
        },

        "atMinQty": function() {
            var _fields = _products[this.id()];
            return _fields.quantity <= _fields.increment || _fields.zeroIncrement;
        },

        "getId": function() {
            return _products[this.id()].id;
        },

        "getQuantity": function() {
            return _products[this.id()].quantity;
        },

        "getIncrement": function() {
            return _products[this.id()].increment;
        },
         "getMaxQuantity": function() {
            return _products[this.id()].maxQuantity;
        },

        "staleDelete": function(basketId) {
            _products[this.id()]["basket-" + basketId].quantity = 0;
            _constructor.event.dispatchEvent("quantityupdateend", this);
        },

        "getFormattedQty": function(basketId) {
            var dp = _products[this.id()].increment.decimalPlaces();
            return this.getBasketLineQuantity(basketId).toDecimal(dp).toFixed(dp);
        },

        "setQuantity": function(newQuantity, basketId, input) {
            //#region
            var _fields = _products[this.id()];            
			if (!isNaN(newQuantity) && !(newQuantity.toString().trim() == '') && Math.floor(newQuantity) >= 0) {              
                newQuantity = parseFloat(newQuantity, 10);
            } else {
                _constructor.event.dispatchEvent("updateerror", input);
                newQuantity = _fields.increment;
            }
            
            _fields.quantity = newQuantity.range(_fields.increment, _fields.maxQuantity - _fields["basket-" + basketId].quantity);
            _constructor.event.dispatchEvent("update", this);
            //#endregion
        },
        
        "setPrice": function(price) {
            _products[this.id()].price = price;
        },

        "getNextQtyStep": function(startQty, step) {
            //#region
            //  ensure quantity is a valid step of the product increment
            var _fields = _products[this.id()];
            startQty = Math.isNull(startQty, _fields.quantity);
            if (_fields.increment == 0) {
                TESCO.system.Exception.handler.raise(new TESCO.system.Exception("Zero increment in meta-data - product " + f.id, "entities.js"));
            } else {
                startQty = Math.round(_fields.quantity / _fields.increment) * _fields.increment;
            }
            var newQty = startQty + (_fields.increment * step);
            newQty = newQty.range(0, _fields.maxQuantity);
            return newQty.toDecimal(_fields.increment.decimalPlaces());
            //#endregion
        },

        "incDecQty": function(shift, basketId) {
            //#region
            //  Increase or decease add quantity by shift
            var f = _products[this.id()];          
            var q = this.getNextQtyStep(null, shift);
            var m = f.maxQuantity - f["basket-" + basketId].quantity;
            q = q.range(f.increment, m);
            f.quantity = q;
            _constructor.event.dispatchEvent("update", this);
            //#endregion
        },

        "setBasketQuantity": function(basketId, newQuantity) {
            var _fields = _products[this.id()];
            if (isNaN(newQuantity)) {
                throw new Error(this.NAME + ".setBasketQuantity: newQuantity must be a number");
            } else {
                _fields["basket-" + basketId].quantity = newQuantity;
            }
        },

        "updateBasketQuantity": function(step, basket, trace, sourceBasket, isChristmasProduct) {
            //#region
            //  call to start a basket update
            //  step : '+' or '-' to increment or decrement (buttons on basket)
            // 	'i' to increment by quantity on product list i.e. the 'add to basket' button
            //	'r' to remove from basket i.e. reduce quantity to 0.
            //	'm' to move to basket
            var _id = this.id();
            var idx;
            var _f = _products[this.id()];
            for (idx = 0; idx < _constructor.queue.length; idx++) {
                if (_constructor.queue[idx].id == _id)	//	find it in the current queue
                    break;
            }
            if (_constructor.queue[idx]) {    //  it's queued
                if (_constructor.queue[idx].inProgress) { //  it's being requested
                    return;
                }
            }
            var _basketId = basket.id;
            var _inc = this.getBasketLineInc(_f, step, sourceBasket ? sourceBasket.id : _basketId);
            var _action;
            if (sourceBasket && basket !== sourceBasket) {
                _action = _constructor.actions.move;
            } else if (sourceBasket && basket === sourceBasket) {
                _action = _constructor.actions.include;
            }
            var _newQuantity = _getNewBasketLineQuantity(_f, _inc, sourceBasket ? sourceBasket.id : _basketId, _action);
            if (_newQuantity != null) {  // if newQty is null, there is no change
				if (_action) {
					this.action = _action;
					if (_action === _constructor.actions.move) {
						_f["basket-" + _basketId].quantity = _newQuantity;
					}
				} else {
					_f.pendingQuantityDelta = _newQuantity;
					if (_f["basket-" + _basketId].quantity === 0) {
						this.action = _constructor.actions.add;
		            } else if ((_f["basket-" + _basketId].quantity + _f.pendingQuantityDelta).isFloatEqualTo(0)) {
						this.action = _constructor.actions.remove;
					} else {
						this.action = _constructor.actions.update;
					}
				}
                this.newQuantity = _newQuantity;
                this.basket = basket;
                this.sourceBasket = sourceBasket || null;                                                
                //if (isChristmasProduct && _f["basket-" + _basketId].quantity + _newQuantity <= 0.1) { //CHRISTMAS CHANGE
                //    var _self = this;
                //    var _removeDialogue = new TESCO.sites.retail.UI.Dialogue.Ajax(new TESCO.sites.retail.UI.Dialogue.Request("ChristmasProductRemove"));
                //    _removeDialogue.show();
                //    _removeDialogue.addEventListener("confirm",
                //            function(e) {
                //                e.prevent();
                //                _processRequest(idx, _id, _self, trace);
                //                _removeDialogue.removeEventListener("confirm", arguments.callee);
                //                _removeDialogue.hide();
                //            }
                //        );
                //} else {                    
                //    _processRequest(idx, _id, this, trace);
                //}
                _processRequest(idx, _id, this, trace);
            }
            //#endregion
        },

        "getBasketLineInc": function(f, step, basketId) {
            //#region
            var inc;
            switch (step) {
                case _constructor.steps.decrement: // basket increment/ decrement
                case _constructor.steps.increment:
                    inc = (step == _constructor.steps.increment ? 1 : -1) * f.increment;
                    break;
                case _constructor.steps.add: // product add
                    inc = f.quantity;
                    break;
                case _constructor.steps.remove: // basket remove
                    inc = -(f.pendingQuantityDelta ? f.pendingQuantityDelta + f["basket-" + basketId].quantity : f["basket-" + basketId].quantity);
                    break;
                case _constructor.steps.move: // basket move
                    inc = (f.pendingQuantityDelta ? f.pendingQuantityDelta + f["basket-" + basketId].quantity : f["basket-" + basketId].quantity);
                    break;
            }
            return inc;
            //#endregion
        }
    }
    //#endregion

	_emptyProduct = new _constructor({});
	
    return _constructor;
})();
