///<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/UI/effects.js" />
///<reference path="/Web/Js/UI/position.js" />
///<reference path="/Web/Js/system/validation.js" />
///<reference path="/Web/Js/system/XMLHTTP.js" />
///<reference path="/Web/Js/system/ajax.js" />
///<reference path="../siteTracker.js" />
///<reference path="dialogue.js" />
///<reference path="../locale.js" />
///<reference path="../locale.en.GB.js" />
///<reference path="entities.js" />
///<reference path="basket.row.js" />
///<reference path="basket.shelf.js" />

TESCO.UI.Basket = (function() {

    //	constants
    //#region
    var NODE = TESCO.system.DOM.node;
    var EVENT = TESCO.system.event;
    var CONNECTION = TESCO.system.connection;
    var ENTITIES = TESCO.UI.entities;
    var STATE = TESCO.system.BitState;
    var SHELF;
    var DEFAULTSHELF;
    //#endregion

    //  private static
    //#region
    var _views = ["mini", "midi", "maxi"];
    var _emptyDialogue;

    //events
    //#region
    EVENT.attach(ENTITIES.Product.event, "quantityupdatestart",
    //#region
        function(product) {
            var _basket = product.basket;
            var _row = _basket.rows["tr-" + product.getId()];
            if ((product.action === ENTITIES.Product.actions.move ||
                 product.action === ENTITIES.Product.actions.add) && !_row) {
                _row = _basket.createRow(null, product); //	optimistic
                _basket.updateStart(_row, product);
            }
            var _emptyRow = _basket.rows[_basket.emptyRowId];
            if ((product.action === ENTITIES.Product.actions.add ||
                 product.action === ENTITIES.Product.actions.include ||
                 product.action === ENTITIES.Product.actions.move) && _emptyRow) {
                _emptyRow.hide();
            }
            _row.start();
        }
    //#endregion
    );

    EVENT.attach(ENTITIES.Product.event, "quantityupdatesend",
    //#region        
        function(o) { //	post the update request
            for (var i = 0, L = o.baskets.length; i < L; i++) {
                _setEmptyControls.call(o.baskets[i], true); //	temporarily disable user's idle thumbs
                switch (o.baskets[i].action) {
                    case _constructor.actions.empty:
                        _beginEmpty.call(o.baskets[i], o);
                        break;
                    case _constructor.actions.refresh:
                        _beginRefresh.call(o.baskets[i], o);
                        break;
                    case _constructor.actions.select:
                        o.baskets[i].beginSelect(o);
                        break;
                    case _constructor.actions.create:
                        o.baskets[i].beginCreate(o);
                        break;
                    default:
                        for (var ii = 0, LL = o.baskets[i].products.length; ii < LL; ii++) {
                            if (o.baskets[i].products[ii].action === ENTITIES.Product.actions.update) {
                                o.baskets[i].rows["tr-" + o.baskets[i].products[ii].getId()].lock();
                            }
                        }
                        _updateQuantity.call(o.baskets[i], o);
                        break;
                }
            }
        }
    //#endregion
    );

    EVENT.attach(ENTITIES.Product.event, "quantityupdateend",
    //#region
        function(product) {
            var _basket = product.basket;
            var _row = _basket.rows["tr-" + product.getId()];
            if (_row) {
                switch (product.action) {
                    case ENTITIES.Product.actions.refresh:
                        break;
                    case ENTITIES.Product.actions.remove:
                        _basket.deleteRow(_basket.rows["tr-" + product.getId()].tr);
                        break;
                    case ENTITIES.Product.actions.exclude:
                        _row.warn();
                        break;
                    default:
                        _row.confirm();
                        break;
                }
            }
        }
    //#endregion
    );

    EVENT.attach(ENTITIES.Product.event, "quantityupdateerror",
    //#region
        function(product) {
            var _basket = product.basket;
            var _row = _basket.rows["tr-" + product.getId()];
            _row.warn();
            if (product.action === ENTITIES.Product.actions.add ||
                product.action === ENTITIES.Product.actions.include ||
                product.action === ENTITIES.Product.actions.move) {
                EVENT.attach(_row.colour, "stop",
					function() {
					    _basket.deleteRow(_row.tr); //	postpone until after colour blend
					    _row.colour.removeEventListener("stop", arguments.callee);
					    var _emptyRow = _basket.rows[_basket.emptyRowId];
					    if (_emptyRow) {
					        _emptyRow.show();
					    }
					}
				);
            }
        }
    //#endregion
    );
    //#endregion
    //#endregion 

    //  private instance
    //#region    
    function _beginEmpty(o) {   //	empty basket
        //#region
        _emptyDialogue.hide();
        var _basket = this;
        this.collapseShelves(
			function() {
			    _endEmpty.call(_basket, o);
			}
        );
        //#endregion
    }

    function _setPromotionalPoints(points) {	//	promotional reward points
        //#region
        var _promotionalPoints = NODE.getElementsByClassAndTagName(this.container, "basketPromotionalPoints", "dd");
        if (_promotionalPoints != null) NODE.setTextValue(_promotionalPoints[0], points.nodeValue);
        //#endregion
    }

    function _setGreenPoints(points) {	//	green reward points
        //#region
        var _greenPoints = NODE.getElementsByClassAndTagName(this.container, "basketGreenPoints", "dd");
        if (_greenPoints != null) NODE.setTextValue(_greenPoints[0], points.nodeValue);
        //#endregion
    }

    function _setRewardPoints(points) {	//	standard reward points
        //#region
        var _nodes = NODE.getElementsByClassName(this.container, "basketStandardPoints");
        if (_nodes) {
            for (var i = 0, l = _nodes.length; i < l; i++) {
                NODE.setTextValue(_nodes[i], points.nodeValue);
            }
        }
        //#endregion
    }

    function _setTotalItems(items) {	//	number of items
        //#region
        var _totalItems = NODE.getElementsByClassAndTagName(this.container, "subTotalItems", "span");
        if (_totalItems) {
            for (var i = 0, l = _totalItems.length; i < l; i++) {
                if (items.nodeValue == 1) {
                    NODE.setTextValue(_totalItems[i], TESCO.locale.basket.totalItemValue.format(items.nodeValue));
                } else {
                    NODE.setTextValue(_totalItems[i], TESCO.locale.basket.totalItemsValue.format(items.nodeValue));
                }
            }
        }
        //#endregion
    }

    function _setEmptyControls(disable) {
        //#region
        var _nodes = NODE.getElementsByClassName(this.container, "emptyBasket");
        if (_nodes) {
            var _a, _txt, L = _nodes.length;
            if (disable) {
                for (var i = 0; i < L; i++) {
                    _a = _nodes[i].getElementsByTagName("a");
                    if (_a.length === 0) {	//	it's already disabled
                        continue;
                    }
                    NODE.addClassName(_nodes[i], "disabledLink");
                    _txt = _a[0].firstChild;
                    _nodes[i].removeChild(_a[0]);
                    _nodes[i].appendChild(_txt);
                }
            } else {
                var _shelf = TESCO.UI.Basket.Shelf.getByType(this.defaultShelfType); //	get the default shelf
                if (this.rows[this.emptyRowId] && _shelf.tbody().getElementsByTagName("tr").length > 1) {	//	is the empty table row the only row?
                    //	delete empty basket table row
                    this.deleteRow(this.rows[this.emptyRowId].tr);
                }
                for (var i = 0; i < L; i++) {
                    if (_nodes[i].getElementsByTagName("a").length > 0) {	//	already enabled
                        continue;
                    }
                    NODE.removeClassName(_nodes[i], "disabledLink");
                    _a = document.createElement("a");
                    _a.setAttribute("href", TESCO.locale.basket.emptyBasketHref);
                    _a.appendChild(_nodes[i].removeChild(_nodes[i].firstChild));
                    NODE.addClassName(_a, "emptyBasketLink");
                    _nodes[i].appendChild(_a);
                }
            }
        }
        //#endregion
    }

    function _updateQuantity(o) {
        //#region
        var _basket = this;
        this.post(
		    function(e) {
		        //  render from response		        
		        _basket.update(o.complete, e.response.response, !ENTITIES.Product.getProductsInBasket(_basket.id).length);
		    },
		    TESCO.sites.Configuration.application.path + TESCO.locale.basket.updateQuantityHref,
		    o.request,
		    function() {	//	error
		        o.error();
		        _setEmptyControls.call(_basket, false);
		    }
	    );
        //#endregion
    }

    function _endEmpty(o) {
        //#region
        var _basket = this;
        this.post(	//	send basket
            function(e) {
                _basket.products = [];
                _basket.update(o.complete, e.response.response, true);
                _basket.expandShelves();
            }, TESCO.sites.Configuration.application.path + "/Ajax/Dialogue.aspx",
            new TESCO.sites.UI.entities.Basket.Empty(_basket),
            function() {	//	error
                o.error();
                _setEmptyControls.call(_basket, false);
                _basket.expandShelves();
            }
        );
        //#endregion
    }

    function _endRefresh(o) {
        //#region
        var _basket = this;
        this.post(	//	send basket
            function(e) {
                var _response = e.response.response;
                var _html = _response.baskets.nodeValue;
                var _trs = _html.getElementsByTagName("tr");
                var _oldIds = [];
                //	delete all the old trs
                for (var i = 0, L = _basket.shelves.length; i < L; i++) {
                    _basket.iterateOverTrs(_basket.shelves[i].tbody(),
						function(tr) {
						    _oldIds.push(tr.id);
						    _basket.deleteRow(tr);
						}
					);
                }
                //	does the new html contain the empty row message, and there's only 1 row?
                var _empty = ((_trs[0].id === _basket.emptyRowId) && (_trs.length === 1));
                _basket.update(o.complete, _response, _empty);
                _basket.refreshProductObjects(_oldIds, _basket.emptyRowId);
                _basket.expandShelves();
            }, TESCO.sites.Configuration.application.path + "/ajax/getBasket.aspx",
            o.request,
            function() {	//	error
                o.error();
                _setEmptyControls.call(_basket, !ENTITIES.Product.getProductsInBasket(_basket.id).length);
                _basket.expandShelves();
            }
        );
        //#endregion
    }

    function _beginRefresh(o) {   //	refresh basket
        //#region
        var _basket = this;
        this.collapseShelves(
			function() {
			    _endRefresh.call(_basket, o);
			}
        );
        //#endregion
    }

    function _confirmEmpty(control) {	//	Launch Dialogue
        //#region
        _emptyDialogue = new TESCO.sites.retail.UI.Dialogue.Ajax(new TESCO.sites.retail.UI.Dialogue.Request("EmptyBasket"));
        _emptyDialogue.addEventListener("confirm", __confirmed);
        _emptyDialogue.addEventListener("cancel", __clean);
        _emptyDialogue.addEventListener("error", __clean);
        var _basket = this;
        function __confirmed() {
            __clean();
            ENTITIES.Product.empty(_basket, { "name": "basket" });
        }
        function __clean() { // remove event listeners to stop other dialogues firing confirm event
            _emptyDialogue.removeEventListener("confirm", __confirmed);
            _emptyDialogue.removeEventListener("cancel", __clean);
            _emptyDialogue.removeEventListener("error", __clean);
        }
        _emptyDialogue.show();
        //#endregion
    }

    function _getClickAction(target) {
        //#region
        var _tr = this.getTr(target);
        var _control = { "target": target };
        var _targetClass = target.className;
        _control.action = NODE.getAction(_constructor.actionTable, target);
        if (_control.action === "quantityAdjust") {// ||_control.action === "quantityRemove" ) {         
            _control.product = this.rows[_tr.id].product;
        }
        return _control;
        //#endregion
    }
    //#endregion 

    //	public static
    //#region
    _constructor.actionTable = [{ val: "locked", className: "locked", depth: 3 },
						        { val: "quantityAdjust", className: "bi" },
					            { val: "quantityAdjust", className: "bd" },
					            { val: "quantityAdjust", className: "br" },
					            { val: "emptyBasket", className: "emptyBasketLink"}];

    _constructor.emptyRowId = "tr-empty-{0}";

    _constructor.productIdFormat = /^tr-([0-9]{1,10})$/;

    _constructor.views = {
        "mini": 0,
        "midi": 1,
        "maxi": 2
    }

    _constructor.actions = {
        "none": 0,
        "empty": 1,
        "refresh": 2,
        "select": 3,
        "create": 4,
        "update": 5
    }

    _constructor.viewString = function(view) {
        return _views[view];
    }

    _constructor.viewLoading = (function() {
        var _viewLoading = false;
        this.getValue = function() {
            return _viewLoading;
        }
        this.setValue = function(value) {
            _viewLoading = !!value;
        }
        return this;
    })();
    //#endregion

    //  constructor
    //#region
    function _constructor(containerId, view) {
        if (!SHELF) {
            SHELF = TESCO.sites.UI.Basket.Shelf;
        }
        DEFAULTSHELF = TESCO.UI.Basket.Shelf.Default;
        this.id = containerId.substr(containerId.lastIndexOf("-") + 1);
        this.container = document.getElementById(containerId);
        this.view = view;
        this.createContainers();
        var _basket = this;
        EVENT.attach(this.container.parentNode, "click",
			function(e) {
			    _basket.clickHandler(e);
			}
		);
        return this;
    }
    //#endregion 

    //  public instance
    //#region 
    _constructor.prototype.NAME = "TESCO.UI.Basket";

    _constructor.prototype.setTotalPoints = function(totalPoints) {	//	reward points
        //#region
        var _nodes = NODE.getElementsByClassName(this.container, "basketTotalPoints");
        if (_nodes) {
            for (var i = 0, l = _nodes.length; i < l; i++) {
                NODE.setTextValue(_nodes[i], totalPoints.nodeValue);
            }
        }
        //#endregion
    }

    _constructor.prototype.setTotalSavings = function(savings) {	//	total savings points
        //#region
        var _nodes = NODE.getElementsByClassName(this.container, "savings");
        if (_nodes) {
            for (var i = 0, l = _nodes.length; i < l; i++) {
                NODE.setTextValue(_nodes[i], savings.nodeValue);
            }
        }
        //#endregion
    }

    _constructor.prototype.setTotalPrice = function(price) {	//	total price
        //#region
        var _nodes = NODE.getElementsByClassName(this.container, "tprice");
        if (_nodes) {
            for (var i = 0, l = _nodes.length; i < l; i++) {
                NODE.setTextValue(_nodes[i], price.nodeValue);
            }
        }
        //#endregion
    }

    _constructor.prototype.update = function(callback, response, empty) {
        //#region
        var _basket = NODE.getElementsByClassAndTagName(response.baskets.nodeValue, this.container.id, "div");
        if (_basket) {
            _basket = _basket[0];
            this.render(_basket); //  the html in the response
            callback(); //	complete the request in entities (_confirmChange, dispatches quantityupdateend)
            this.totalise(_basket.attributes); //  set the new total price, clubcard points etc
            this.toggleControls(empty, _basket.attributes);
        }
        //#endregion
    }

    _constructor.prototype.toggleControls = function(empty, attributes) {
        //#region      
        _setEmptyControls.call(this, empty);  //  is the basket empty?
        this.setCheckoutLinks(empty, attributes);
        //#endregion
    }

    _constructor.prototype.totalise = function(attributes) { //  update clubcard points, price etc.
        //#region
        //	savings
        var _savings = attributes["savings"];
        if (_savings) {
            this.setTotalSavings(_savings);
        }
        //	reward points
        var _standardPoints = attributes["standardPoints"];
        if (_standardPoints) {
            _setRewardPoints.call(this, _standardPoints);
        }
        var _totalPoints = attributes["totalPoints"];
        if (_totalPoints) {
            this.setTotalPoints(_totalPoints);
        }
        //	promotional reward points
        var _promotionalPoints = attributes["promotionalPoints"];
        if (_promotionalPoints) {
            _setPromotionalPoints.call(this, _promotionalPoints);
        }
        //	green reward points
        var _greenPoints = attributes["greenPoints"];
        if (_greenPoints) {
            _setGreenPoints.call(this, _greenPoints);
        }
        //	number of items
        var _items = attributes["itemCount"];
        if (_items) {
            _setTotalItems.call(this, _items);
        }
        //	sub total price
        var _price = attributes["subTotal"];
        if (_price) {
            this.setTotalPrice(_price);
        }
        //#endregion
    }

    _constructor.prototype.createContainers = function() {
        //#region
        this.emptyRowId = _constructor.emptyRowId.format(this.id);
        var _shelves = NODE.getElementsByClassName(this.container, "shelf");
        //  reset shelves from any previous baskets
        TESCO.UI.Basket.Shelf.removeAllTypes();
        //  store instances of shelf class
        this.shelves = [];
        //  store instances of row class
        this.rows = [];

        for (var i = 0, L = _shelves.length; i < L; i++) {
            var _type = TESCO.UI.Basket.Shelf.getTypeFromNode(this, _shelves[i]);
            var _tbody = _shelves[i].getElementsByTagName("tbody");
            //  tbody might be collapsible container
            _tbody = _tbody.length ? _tbody[0] : _shelves[i];

            var _collapsed = NODE.hasClassName(_shelves[i], "collapsed");
            if (DEFAULTSHELF.type.compare(this, _type)) {	//	is it the default shelf?
                this.shelves.push(new DEFAULTSHELF(this, _shelves[i], _tbody, _collapsed))
            } else {
                this.shelves.push(new SHELF(_type, _shelves[i], _tbody, _collapsed));
            }
            this.iterateOverTrs(_tbody, this.createRow);
        }
        //#endregion
    }

    _constructor.prototype.getTr = function(node) {
        return NODE.getAncestorByAttributeRegExp(node, "id", _constructor.productIdFormat);
    }

    _constructor.prototype.getProductId = function(node) {
        var _id = _constructor.productIdFormat.exec(node.getAttribute("id"));
        return _id ? _id[1] : null;
    }

    _constructor.prototype.iterateOverTrs = function(container, callback) {
        //#region        
        var _trs = container.getElementsByTagName("tr");
        if (_trs) { //  length affected if tr is updated in callback so iterate backwards
            for (var i = _trs.length - 1; i >= 0; i--) {
                callback.call(this, _trs[i]);
            }
        }
        //#endregion
    }

    _constructor.prototype.getRowById = function(id) {	//	return instance of row class
        return this.rows["tr-" + id];
    }

    _constructor.prototype.getRowByProduct = function(product) {	//	return instance of row class
        return this.getRowById(product.getId());
    }

    _constructor.prototype.getTrByProduct = function(product) {	//	return tr node
        var _row = this.getRowByProduct(product);
        return _row ? _row.tr : null;
    }

    _constructor.prototype.deleteRow = function(tr) {	//	delete instance of row class
        this.rows[tr.id].remove();
        delete this.rows[tr.id];
    }

    _constructor.prototype.createRow = function(tr, entity) {	//	create instance of row class
        //#region
        var _row, _rows = this.rows;
        if (tr) {
            var _id = this.getProductId(tr);
            if (_id) {	//	it's a product, update object
                _row = _rows[tr.id] = new TESCO.sites.UI.Basket.Row.Product(this, ENTITIES.Product.getProductById(_id), tr);
            } else {	//	empty basket message
                _row = _rows[tr.id] = new TESCO.sites.retail.UI.Basket.Row(this, tr);
            }
        } else {	//	optimistic add
            _row = _rows["tr-" + entity.getId()] = new TESCO.sites.UI.Basket.Row.Product(this, entity);
        }
        return _row;
        //#endregion
    }

    _constructor.prototype.render = function(html) {
        //#region
        var _basket = this;
        var _shelves = NODE.getElementsByClassAndTagName(html, "shelf", "tbody");
        for (var i = 0, L = _shelves.length; i < L; i++) {
            //  todo: grocery, tbody may not exist, create
            var _shelfType = TESCO.UI.Basket.Shelf.getTypeFromNode(this, _shelves[i]);
            var _shelf = TESCO.UI.Basket.Shelf.getByType(_shelfType);
            this.iterateOverTrs(_shelves[i], //  iterate over tr's in response
			    function(tr) {
			        _basket.renderTr(tr, _shelf);
			    }
		    );
        }
        //#endregion
    }

    _constructor.prototype.renderTr = function(tr, shelf) {
        var _row = this.rows[tr.id];
        if (_row) {
            //	row exists in this basket
            _row.update(shelf, tr, ENTITIES.Product.getProductById(this.getProductId(tr)), _row.tr.parentNode !== shelf.tbody());
        } else {
            //  tr doesn't exist, insert it
            this.createRow(tr).insert(shelf.tbody()); //  create object, insert html
        }
    }

    _constructor.prototype.updateStart = function(row, product) {
        //#region
        row.insert(TESCO.UI.Basket.Shelf.getByType(this.defaultShelfType).tbody());
        //#endregion
    }

    _constructor.prototype.clickHandler = function(e) {
        //#region        
        var _target = e.target;
        var _control = _getClickAction.call(this, _target);
        var _stopEvent = true;
        if (_control.action == "locked") {
            //  the row is updating, take no action
        } else if (_control.action == "quantityAdjust") {
            var _step = NODE.hasClassName(_target, "bd") ? ENTITIES.Product.steps.decrement : ENTITIES.Product.steps.increment;
            _step = NODE.hasClassName(_target, "br") ? ENTITIES.Product.steps.remove : _step;
            this.updateBasketQuantity(_control.product, _step);
        } else if (_control.action == "emptyBasket") {
            _confirmEmpty.call(this);
        } else {
            _stopEvent = false;
        }
        if (_stopEvent) {
            e.prevent();
        }
        return _stopEvent;
        //#endregion
    }

    _constructor.prototype.updateBasketQuantity = function(product, step) {
        product.updateBasketQuantity(step, this, { "name": "basket&view=" + this.viewStr });
    }

    _constructor.prototype.setCheckoutLinks = function(disable, attributes, container, callback) {   // set disable argument to false to enable
        //#region

        var _nodes = NODE.getElementsByClassAndTagName(container, "checkoutBtn", "a") ||
						NODE.getElementsByClassAndTagName(container, "checkoutBtnDisabled", "a") ||
						NODE.getElementsByClassAndTagName(container, "amendCheckoutBtn", "a");
        if (_nodes) {
            for (var i = 0, L = _nodes.length; i < L; i++) {
                if (disable) {
                    NODE.removeClassName(_nodes[i], "checkoutBtn");
                    NODE.addClassName(_nodes[i], "checkoutBtnDisabled");
                    _nodes[i].removeAttribute("href");
                    _nodes[i].removeAttribute("title");
                } else {
                    NODE.removeClassName(_nodes[i], "checkoutBtnDisabled");
                    NODE.addClassName(_nodes[i], "checkoutBtn");
                    _nodes[i].setAttribute("href", TESCO.locale.basket.checkout.href);
                }
                if (callback)
                    callback.call(this, disable, _nodes[i], attributes);
            }
        }
        //#endregion
    }

    _constructor.prototype.collapseShelves = function(callback) {   //	refresh 
        //#region
        _constructor.viewLoading.setValue(true);
        for (var i = 0, L = this.shelves.length; i < L; i++) {
            this.shelves[i].collapse(callback); //	callback only run in default shelf
        }
        //#endregion
    }

    _constructor.prototype.expandShelves = function() {
        //#region
        _constructor.viewLoading.setValue(false);
        for (var i = 0, L = this.shelves.length; i < L; i++) {
            if (!this.shelves[i].isEmpty()) {
                this.shelves[i].expand();
            }
        }
        //#endregion
    }

    _constructor.prototype.post = function(callback, url, entity, errorCallback) {
        //#region
        var _connection = new CONNECTION.ajax(false);
        CONNECTION.eggTimer.show(document.body);
        EVENT.attach(_connection, "complete",
			function(e) {
			    CONNECTION.eggTimer.hide(document.body);
			    callback(e);
			}
		);
        EVENT.attach(_connection, "error", //	XMLHTTP errors
			function(e) {
			    CONNECTION.eggTimer.hide(document.body);
			    if (errorCallback) {
			        errorCallback(e);
			    }
			    e.exception.dialogue.show(e.exception);
			}
		);
        _connection.request(url, entity, CONNECTION.XMLHTTP.METHOD.post, CONNECTION.XMLHTTP.MIMETYPE.xml);
        //#endregion
    }

    _constructor.prototype.refreshProductObjects = function(oldProductIds, emptyRowId) {
        //#region
        var _basket = this;
        for (var i = 0, L = oldProductIds.length; i < L; i++) {	//	was it removed from basket during the refresh?
            if (oldProductIds[i] !== emptyRowId) {
                var _row = this.rows[oldProductIds[i]];
                var _productId = _constructor.productIdFormat.exec(oldProductIds[i]);
                if (_productId) {
                    var _product = ENTITIES.Product.getProductById(_productId[1]);
                    if (_product) {
                        if (!_row) {
                            //  removed from basket after this page loaded
                            _product.staleDelete(_basket.id);
                        }
                    }
                }
            }
        }
        for (var i = 0, L = this.shelves.length; i < L; i++) {
            this.iterateOverTrs(this.shelves[i].tbody(),
				function(tr) {
				    var _row = _basket.rows[tr.id];
				    if (_row) {
				        var _id = _constructor.productIdFormat.exec(tr.id);
				        if (_id) {
				            var _product = ENTITIES.Product.getProductById(_id[1]);
				            if (_product) {
				                //  update product object in row after new script block has been inserted in page 
				                _row.product = _product;
				            }
				        }
				    }
				}
			);
        }
        //#endregion
    }
    //#endregion

    //  return constructor as function pointer
    return _constructor;
})();
