﻿/*
    mangoBasket.js 
    the global js helper object for basket operations.
    after initialisation this is available to all as a well-known object, $mb
*/

// factory method //
var $mb = null;
function initialise(wgid){
    
    if(null == $mb){

        $mb = new mangoBasket(wgid);
        
        $(document).ready(function(){
        
            // load ui prefs
            $mb.styleSettings.loadUIPreferences(wgid, function(){
            
                $mb.buildBasket();
                $mb.decorateHtml();
                $mb.setReady();
                
                // TODO - see case 1677 - not happy with this but clearing cookies messes up loading show.css
                setTimeout("$('#pnlLoading').hide();", 500);
                setTimeout("$('#pnlMain').show();", 500);

                
                
            }); 

            if(true == isUsingProxy()) queryStringToCookies(['rid','rak']);
            
        });
    
    };
    
    return $mb;
};

/// initialise a new instance of mangoBasket
/// + wgid(long) the webgateway id
this.mangoBasket = function(wgid) {

    /* private vars */
    var _rid = getCookie('rid');
    var _rak = getCookie('rak');
    var _productCache = new Object();
    var _readyHandlers = new Array();

    /* basket constants */
    var JSON_ROOT_QS = getRootQueryString(wgid);

    var PNL_BASKET = 'pnlBasket';
    var VW_BASKET = 'vwBasket';
    var VW_BASKET_MESSAGE = 'vwBasketUserMessage';
    var VW_WISHLIST_MESSAGE = 'vwWishListUserMessage';
    var VW_BASKET_BUTTONS = 'vwBasketButtons';
    var VW_BASKET_ITEMS = 'vwBasketItems';
    var VW_BASKET_TOTALS = 'vwBasketTotals';
    var PNL_WISH_LIST = 'pnlWishList';
    var VW_WISH_LIST = 'vwWishList';
    var VW_WISH_LIST_ITEMS = 'vwWishListItems';

    var PLEASE_WAIT = 'Please wait...';

    /* public members */

    // the current web gateway id
    this.webGatewayID = wgid;

    // get a url builder
    this.urlBuilder = createUrlBuilder();

    // panel state controller - init in getPanelStateController()
    this.panelStateController = null;

    // ref to style settings
    this.styleSettings = new styleSettings();

    // json loader
    this.jsonLoader = new jsonObjectLoader(this.webGatewayID);

    /* hooks */

    // hook function that is called when the basket is loaded
    // - passed a single parameter, data (BasketJsonData)
    this.onBasketLoaded;

    // hook function that is called before a basket action: add, delete, move, cost query
    // - passed a single parameter, data (ProductJsonData)
    this.onBeforeAddToBasket;

    // hook function that is called when a basket action is cancelled: add, delete, move, cost query
    // - passed a single parameter, data (ProductJsonData)
    this.onBasketActionCancelled;

    // hook function that is called when an item is updated in the basket / wishlist (including move from one to the other)
    // - passed a single parameter, data (BasketJsonData)
    this.onItemUpdated;

    // hook function that is called when an item is deleted from the basket / wishlist
    // - passed a single parameter, data (BasketJsonData)
    this.onItemDeleted;

    // hook function that is called when an entire order is added to the basket 
    // - passed a single parameter, data (64-bit integer - OrderID)
    this.onOrderAdded;

    // hook function that is called when a cost query completes
    // - passed a single parameter, data (CostData => data.MMID, data.Quantity, data.Cost)
    this.onCostQuery;

    // register a function to be called when the basket is ready
    // + fn (function)
    this.ready = function(fn) {
        _readyHandlers[_readyHandlers.length] = fn;
    };

    // sets the basket as ready and calls any registered handlers
    this.setReady = function() {

        for (var handler in _readyHandlers) {
            if (typeof (_readyHandlers[handler]) == 'function') {
                _readyHandlers[handler]();
            };
        };

    };

    // register buttons and elements for basket calls
    //  + mmid (long) - the MangoMart id
    //  + btnAdd (string) - id of "add to basket" button - can be null
    //  + btnSave (string) - id of "save for later" button - can be null
    //  + btnCost (string) - id of "cost?" button - can be null
    //  + txtQuantity (string) - id of quantity field button - can be null
    //  + returnShoppingUrl (string) - (optional, default current location) override value of the return shopping url
    //      (if null or undefined, the current location is used - leave empty string to ignore)
    //  + isQuantityOverride (bool) - (optional, default false) override the current quantity in the basket? (default false)
    //  + withPriceChange (bool) - (optional, default false) display the current price on the item? (default false)
    this.registerBasketCalls = function(mmid, btnAdd, btnSave, btnCost, txtQuantity, returnShoppingUrl, isQuantityOverride, withPriceChange) {

        isQuantityOverride = typeof (isQuantityOverride) == 'undefined' ? false : isQuantityOverride;
        withPriceChange = typeof (withPriceChange) == 'undefined' ? false : withPriceChange;

        returnShoppingUrl = typeof (returnShoppingUrl) == 'undefined' || returnShoppingUrl == null
                        ? encodeURIComponent(document.location.href)
                        : returnShoppingUrl == '' ? '' : encodeURIComponent(returnShoppingUrl);

        $('#' + btnAdd).click(function() {
            var controller = new basketController($mb.webGatewayID, mmid, getInt(txtQuantity), false, returnShoppingUrl, isQuantityOverride);
            doAddToBasket(controller, withPriceChange);
            return false;
        });

        $('#' + btnSave).click(function() {
            var controller = new basketController($mb.webGatewayID, mmid, getInt(txtQuantity), true, returnShoppingUrl, isQuantityOverride);
            doAddToBasket(controller, withPriceChange);
            return false;
        });

        $('#' + btnCost).click(function() {
            $mb.costQuery(mmid, getInt(txtQuantity));
            return false;
        });

        $('#' + txtQuantity).keypress(function(e) {
            if (getKey(e) == KEY_RETURN) {
                $mb.addProductToBasket(mmid, getInt(txtQuantity), false);
                return true;
            } else {
                return onKeyPressNumericOnly(e);
            };
        });

    };

    // load the basket contents (JSON) and build the basket html
    this.buildBasket = function() {

        var qs = appendBasketID(JSON_ROOT_QS + "Basket");
        var jsonUrl = this.urlBuilder.buildJsonUrl(qs);

        $.getJSON(jsonUrl, function(data) {

            if (true == canHandle(data)) {

                var userMessage = data.IsCatalogueBuildInProcess ? '! Catalogue build in progress !' : data.UserMessage;

                if (true == data.IsSiteClosedForMaintenance) {

                    $('#' + VW_BASKET_BUTTONS).hide();
                    $('#' + VW_BASKET_ITEMS).hide();
                    $('#' + VW_BASKET_TOTALS).hide();
                    $('#' + PNL_WISH_LIST).hide();

                } else {

                    reBuildBasket(data.InnerData);
                    updatePricesAndButtons(data.InnerData);
                    productInfoToCache(data.InnerData);

                    if (typeof ($mb.onBasketLoaded) == 'function')
                        $mb.onBasketLoaded(data);

                    $mb.decorateHtml();

                };

                $mb.setBasketMessage(userMessage, false);


            };
        });
    };

    // queries the current cost of a product in the system
    //  n.b. to hook into the end of the flow, use $mb.onCostQuery
    //  
    //  + mmid (long) - the MangoMart id
    //  + quantity (int) - quantity field 
    //  + isSuppressAlerts (bool) - whether to suppress any alerts (default false)
    this.costQuery = function(mmid, quantity, isSuppressAlerts) {

        var controller = new basketController(this.webGatewayID, mmid, quantity, false, null, false);

        controller.isSuppressAlerts = typeof (isSuppressAlerts) == 'undefined' ? false : isSuppressAlerts; ;
        controller.isCostQuery = true;

        controller.doStockCheck(function() {

            controller.doQuantityCheck(function() {

                if (typeof ($mb.onCostQuery) == 'function') {

                    var data = new Object();

                    var product = $mb.getProductData(mmid);

                    // just create a simple object with our cost query data

                    data.MMID = mmid;
                    data.Quantity = controller.quantity;
                    data.Cost = product.GetPrice(data.Quantity);
                    data.PreDiscountCost = product.GetPreDiscountPrice(data.Quantity);
                    data.CostStringAsHtml = true == product.IsDiscounted
                                                ? '<span class="strikethrough">' + data.PreDiscountCost + '</span> ' + data.Cost
                                                : data.Cost;

                    $mb.onCostQuery(data);
                };

            });
        });

    };

    // add a product to the basket and update the basket html
    // (n.b. client apps should call this function)
    //  + mmid (long) - the MangoMart id
    //  + quantity (int) - quantity field 
    //  + save (bool) - add as a WishList item?
    //  + returnShoppingUrl (string) - return shopping url value (nullable - if null or empty the current document location is used)
    //  + isQuantityOverride (bool) - whether to override the current quantity in the basket (default false)
    this.addProductToBasket = function(mmid, quantity, save, returnShoppingUrl, isQuantityOverride) {

        returnShoppingUrl = (typeof (returnShoppingUrl) == 'undefined' || returnShoppingUrl == null || returnShoppingUrl == '')
                        ? encodeURIComponent(document.location.href)
                        : encodeURIComponent(returnShoppingUrl);

        isQuantityOverride = (typeof (isQuantityOverride) == 'undefined') || isQuantityOverride == null
                        ? false
                        : isQuantityOverride;

        var controller = new basketController(this.webGatewayID, mmid, quantity, save, returnShoppingUrl, isQuantityOverride);
        doAddToBasket(controller, false);

    };

    // add an existing BasketItem to the basket and update the basket html
    //  + mmid (long) - the MangoMart id
    //  + quantity (int) - quantity field 
    //  + save (bool) - add as a WishList item?
    //  + withPriceChange (string) - whether to do price change checks
    //  + isQuantityOverride (bool) - whether to override the current quantity in the basket
    this.addItemToBasket = function(mmid, quantity, save, withPriceChange, isQuantityOverride) {

        // TODO - this could def use func above instead - add withPriceChange as final parameter for app pages only
        var returnShoppingUrl = encodeURIComponent(document.location.href);
        var controller = new basketController(this.webGatewayID, mmid, quantity, save, returnShoppingUrl, isQuantityOverride);
        doAddToBasket(controller, withPriceChange);

    };

    // delete an item from the basket and update the basket html
    //  + biid (long) - the BasketItem id
    //  + fromWishList (bool) - the BasketItem id
    this.deleteBasketItem = function(biid, fromWishList) {

        // TODO - jsonObjectLoader?
        var qs = appendBasketID(JSON_ROOT_QS + 'BasketItemDelete&biid=' + biid);
        var jsonUrl = this.urlBuilder.buildJsonUrl(qs);

        var hook = this.onItemDeleted;

        $.getJSON(jsonUrl, function(data) {

            if (true == canHandle(data)) {
                $('#basketItem_' + biid).fadeOut().remove();
                updatePricesAndButtons(data.InnerData);

                $mb.setBasketMessage(data.UserMessage, fromWishList);

                if (typeof (hook) == 'function')
                    hook(data);
            };

        });

    };


    // move an in the Basket / WishList
    //  + biid (long) - the BasketItem id
    //  + toWishList (bool) - to wish list?
    this.moveBasketItem = function(biid, toWishList) {
        // TODO - jsonObjectLoader?
        var qs = appendBasketID(JSON_ROOT_QS + 'BasketItemMove&biid=' + biid + '&sfl=' + toWishList);
        var jsonUrl = this.urlBuilder.buildJsonUrl(qs);

        var updateHook = this.onItemUpdated;

        $.getJSON(jsonUrl, function(data) {

            if (true == canHandle(data)) {
                $('#basketItem_' + biid).fadeOut().remove();
                addItemHtml(data.InnerData.ActiveItem);
                updatePricesAndButtons(data.InnerData);
            };

            if (typeof (updateHook) == 'function')
                updateHook(data);

        });

    };

    // set the message in one of the basket pods
    // msg (string)
    // inWishList (bool)
    // hide (bool) - hide existing items? default true
    this.setBasketMessage = function(msg, inWishList, hide) {
        if (false != hide) {
            $('#' + VW_BASKET_MESSAGE).hide();
            $('#' + VW_WISHLIST_MESSAGE).hide();
        }
        if (null != msg && msg.length > 0) {
            $mb.setStatus(inWishList ? VW_WISHLIST_MESSAGE : VW_BASKET_MESSAGE, msg);
        };
    };

    /// shows the Basket pod
    this.showPod = function(isWishList) {
        if (isWishList)
            this.showWishListPod();
        else
            this.showBasketPod();
    };

    /// shows the Basket pod
    this.showBasketPod = function() {
        $mb.panelStateController.setBasket(true);
    };

    /// shows the WishList pod
    this.showWishListPod = function() {
        $mb.panelStateController.setWishList(true);
    };

    /// hides the Basket pod
    this.hideBasketPod = function() {
        $mb.panelStateController.setBasket(false);
    };

    /// hides the WishList pod
    this.hideWishListPod = function() {
        $mb.panelStateController.setWishList(false);
    };

    /// sets the visibility of all children of class=midPanel
    /// parentID (string) - the parent element
    /// visible (bool) - visibility of panel after change
    this.setMidPanelStates = function(parentID, visible) {
        $mb.panelStateController.setChildPanels(parentID, ".midPanel", visible);
    };

    // returns true if execution can continue
    // + jsonData - JSON
    this.canHandle = function(jsonData) {
        return canHandleJSON(jsonData, VW_BASKET_MESSAGE);
    };

    // add data to the Product data cache
    // + data (ProductJsonData)
    // + isFullProductInfo (bool)
    this.addProductData = function(data, isFullProductInfo) {
        // prevent lightweights overwriting heavyweights
        if (null != data && typeof (_productCache[data.MMID]) == 'undefined' || true == isFullProductInfo) {
            data.isFullProductInfo = isFullProductInfo;
            _productCache[data.MMID] = data;
        };
    };

    // get data from the Product data cache (ProductJsonData)
    // + mmid (string)
    this.getProductData = function(mmid) {
        return _productCache[mmid];
    };

    // create new Product data (ProductJsonData)
    // new data is automatically added to the cache
    // TODO document fields
    // + mmid   
    // + productCode
    // + name
    // + description
    // + shortDescription
    // + sizeDescription
    // + mainImageUrl
    // + popupImageUrl
    this.createProductData = function(mmid, productCode, name, description, shortDescription, sizeDescription, mainImageUrl, popupImageUrl) {

        // NB this object is not complete... see ProductJsonData.cs, and breakpoints / price functions required too.
        // we are currently adding fields used in:
        // htmlBuilder.buildBasketItemTooltipHtml()
        // htmlBuilder.buildFullProductInfoTooltip()

        var result = new Object();

        result.MMID = mmid;
        result.ProductCode = productCode;
        result.Name = name;
        result.Description = description;
        result.ShortDescription = shortDescription;
        result.SizeDescription = sizeDescription;
        result.MainImageUrl = mainImageUrl;
        result.PopupImageUrl = popupImageUrl;
        result.MainImageHtml = "<img src=\"" + mainImageUrl + "\" width=\"120\" height=\"120\" alt=\"" + name + "\" border=\"0\" />";
        result.PopupImageHtml = "<img src=\"" + popupImageUrl + "\" width=\"300\" height=\"300\" alt=\"" + name + "\" border=\"0\" />";

        $mb.addProductData(result);

        return result;

    };


    // load current Order data as JSON
    // + oid (64-bit int) the order id
    // + callback (function) to which a single parameter is passed ("data" - OrderJsonData)
    this.loadOrder = function(oid, callback) {
        this.jsonLoader.loadOrder(oid, callback);
    };

    // load current User info as JSON
    // + callback (function) to which a single parameter is passed ("data" - UserInfoJsonData)
    this.loadUserInfo = function(callback) {
        this.jsonLoader.loadUserInfo(callback);
    };

    // client logging helper
    // + name (string)
    // + value (string)
    this.log = function(name, value) {
        $().mangoLog(name, value);
    };

    /// run this func after the end of all async .net requests
    this.onDotNetEndRequest = function() {

        // re-init panels
        this.panelStateController.init();

        // re-register pipes and popups etc
        this.decorateHtml();
    };


    // decorateHtml:
    // (re-) register custom tooltips
    // (re-) mandatory fields
    this.decorateHtml = function() {

        // pipe tips
        $('.pipeTip, .infoTip').mangoPipeTip();

        // product popups / tooltips
        $(".productPopup").mangoProductPopup();
        $(".productInfoTip, .basketInfoTip").mangoProductInfoTip();

        // product stickers
        $("img.offer, img.productThumbnail_offer").mangoProductSticker();

        $("input.mandatory, select.mandatory").mangoMandatoryField();

    };

    // register a standard "hover" effect on the anchor with specified id
    // + id (string) - anchor id
    // + img (string) - image url
    // + header (string) - header text
    // + body (string) - body text
    // + imgWidth (int) - image width
    // + imgHeight (int) - image height
    // + imgAlt (int) - image alt tag
    this.registerHover = function(id, img, header, body, imgWidth, imgHeight, imgAlt) {
        $('#' + id).mangoHover(img, header, body, imgWidth, imgHeight, imgAlt);
    };

    // register a standard popup on the anchor with specified id
    // + id (string) - anchor id
    // + img (string) - image url
    // + header (string) - header text
    // + body (string) - body text
    // + imgWidth (int) - image width
    // + imgHeight (int) - image height
    // + imgAlt (int) - image alt tag
    this.registerPopup = function(id, img, header, body, imgWidth, imgHeight, imgAlt) {
        $('#' + id).mangoPopup(img, header, body, imgWidth, imgHeight, imgAlt);
    };

    // adds a css link to the specified file in the <head> element of the document
    // if the link with specified id already exists, the href is updated on the existing link
    // + href (string) 
    // + id (string) - can be null / undefined 
    this.addCss = function(href, id) {
        $(document).ready(function() {
            var link = $('#' + id);
            if (link.length == 0) {
				id = (typeof(id) == 'undefined' || id == null) ? '' : id;
				// creating link with href barfing out SSL in IE - see case 2833. Leave href empty and set below.
				var linkHtml = doStringFormat("<link id=\"{0}\" rel=\"stylesheet\" type=\"text/css\" media=\"screen\" />", [id]);
                link = $(linkHtml);
                $("head").append(link);
            };
            link.attr("href", href);
        });
    };

    // execute the onBeforeAddToBasket handler(s)
    this.doOnBeforeAddToBasket = function(mmid) {
        if (typeof (this.onBeforeAddToBasket) == 'function') {
            this.onBeforeAddToBasket(this.getProductData(mmid));
        };
    };

    // execute the onBeforeAddToBasket handler(s)
    this.doOnBasketActionCancelled = function(mmid) {
        if (typeof (this.onBasketActionCancelled) == 'function') {
            this.onBasketActionCancelled(this.getProductData(mmid));
        };
    };

    // update the product status message (e.g. 100 added. 200 now in basket for £120.00
    // + id(string) - id of DOM element to update
    // + item (BasketItemJsonData) - the item updated
    this.updateProductStatus = function(id, item) {
        var format = "{0}{1} now in {2}{3}{4}";
        var isAdded = null != item.QuantityAdded;
        var args = [isAdded ? item.QuantityAdded + " added. " : "",
                    item.Quantity,
                    item.IsSaveForLater ? "wishlist" : "basket",
                    null == item.CurrentValue ? "" : " for " + item.PriceStringAsHtml,
                    isAdded ? doStringFormat("<br/><span class=\"basketLinks\">Your basket: <a href=\"{0}\" title=\"Click here to view or edit your basket contents\">View / Edit</a>&nbsp;|&nbsp;<a href=\"{1}\" title=\"Click here to checkout\">Checkout</a></span>", [item.EditBasketUrl, item.CheckoutUrl]) : ""];
        var msg = doStringFormat(format, args);
        $mb.setStatus(id, msg);
    };

    // show the initial product status message (e.g. 200 in basket for £120.00, 400 in wishlist for £240.00)
    // + id(string) - id of DOM element to update
    // + item (BasketItemJsonData) - the item
    this.showProductStatus = function(id, item) {
        var el = $("#" + id);
        if (el.length > 0) {
            var format = "{0} in {1}{2}";
            var args = [item.Quantity,
                        item.IsSaveForLater ? "wishlist" : "basket",
                        null == item.CurrentValue ? "" : " for " + item.PriceStringAsHtml];
            var msg = doStringFormat(format, args);
            var html = el.html();
            if (html.length > 0)
                msg = html + ", " + msg;

            $mb.setStatus(id, msg);
        };
    };

    // updates the status text of the specified element
    // + id (string) - id of DOM element to update
    // + value (string) - the status text
    // + cssClass (string) - the css class value for the label
    this.setStatus = function(id, value, cssClass) {
        displayUserMessage(id, value, true, cssClass);
    };

    // initiate a forgotten password call
    // + email (string)
    // + status (string) - id of the status label
    // + error (string) - id of the error label
    this.forgottenPassword = function(email, status, error) {
        $mb.setStatus(status, PLEASE_WAIT);
        this.jsonLoader.forgottenPassword(email, function(data) {
            if (canHandle(data)) {
                $mb.setStatus(status, "");

                // TODO - htmlBuilder for spans
                var format = data.IsValidationError
                                    ? "<span class=\"errorText\">{0}</span>"
                                    : "<span class=\"statusText\">{0}</span>";
                var msg = doStringFormat(format, [data.UserMessage]);
                $mb.setStatus(error, msg);
            };
        });
    };

    // initiate a login
    // + email (string)
    // + pw (string)
    // + status (string) - id of the status label
    // + error (string) - id of the error label
    // + returnUrl (string) - return url - unescaped
    // + dcm (bool) - do customer merge (default false)
    this.login = function(email, pw, status, error, returnUrl, dcm) {
        dcm = (typeof (dcm) == 'undefined') ? false : dcm;
        $mb.setStatus(status, PLEASE_WAIT);
        this.jsonLoader.login(email, pw, returnUrl, dcm, function(data) {
            if (canHandle(data)) {
                $mb.setStatus(status, "");
                // TODO - htmlBuilder for spans
                var format = data.IsValidationError
                                    ? "<span class=\"errorText\">{0}</span>"
                                    : "<span class=\"statusText\">{0}</span>";
                var msg = doStringFormat(format, [data.UserMessage]);
                $mb.setStatus(error, msg);
            };
        });
    };

    // send an invoice to the current authenticated user
    // + orderID (long) - the order to send
    // + anchor (DOM) - trigger anchor
    this.sendInvoice = function(orderID, anchor) {
        $(anchor).html(PLEASE_WAIT).attr("class", "disabled").attr("enabled", "false");
        this.jsonLoader.sendInvoice(orderID, function(data) {
            if (canHandle(data)) {
                var span = $("<span>").attr("class", "statusText").html("Invoice sent");
                $(anchor).fadeOut().replaceWith(span);
            };
        });

    };

    // add a complete order to the basket and update the basket html
    //  + orderID (long) - the Order id
    //  + ctrl (DOM) - trigger control
    this.reOrder = function(orderID, ctrl) {

        var isButton = null != $(ctrl).attr("class") && $(ctrl).attr("class").indexOf("button") != -1;

        if (true == isButton) {
            disableButton(ctrl);
        } else {
            $(ctrl).html(PLEASE_WAIT).attr("class", "disabled").attr("enabled", "false");
        };

        this.jsonLoader.addOrder(orderID, function(data) {

            if (true == canHandle(data)) {
                reBuildBasket(data.InnerData);
                updatePricesAndButtons(data.InnerData);
                $mb.setBasketMessage(data.UserMessage, false);
                $mb.showBasketPod();

                if (typeof ($mb.onOrderAdded) == 'function') {
                    $mb.onOrderAdded(oid);
                };

                if (true == isButton) {
                    $(ctrl).css("color", "green").html(data.UserMessage);
                } else {
                    var span = $("<span>").attr("class", "statusText").html(data.UserMessage);
                    $(ctrl).fadeOut().replaceWith(span);
                };


            };

        });

    };

    // display the print invoice page
    //  + orderID (long) - the Order ID
    this.printInvoice = function(orderID) {
        this.openWindow('print.aspx?oid=' + orderID, 'print');
    };

    // opens a new browser window at the specified url
    this.openWindow = function(url, name) {
        // width: 620
        // height: 600
        // top: 20
        // left: 20
        var features = 'modal=no,scrollbars=yes,directories=0,menubar=0,titlebar=0,toolbar=0,width=620,height=600,top=20,left=20';
        window.open(url, name, features, true);
    };


    // ************************************************************************************************************** //
    // ******************************************** private functions *********************************************** //

    // do the add to basket func
    // + contoller (basketController)
    // + withPriceChange (bool)
    function doAddToBasket(controller, withPriceChange) {

        $mb.doOnBeforeAddToBasket(controller.mmid);

        $mb.setBasketMessage(PLEASE_WAIT, false);

        controller.doStockCheck(function() {

            controller.doQuantityCheck(function() {

                if (controller.quantity > 0) {

                    if (true == withPriceChange) {
                        controller.doPriceChangeChecks(function(data) {
                            controller.doAddToBasket(function(data) {
                                addItemHtml(data.InnerData.ActiveItem);
                                updatePricesAndButtons(data.InnerData);
                                $mb.setBasketMessage(data.UserMessage, controller.save);
                                if (typeof ($mb.onItemUpdated) == 'function') $mb.onItemUpdated(data);
                            });
                        });
                    } else {
                        controller.doAddToBasket(function(data) {
                            addItemHtml(data.InnerData.ActiveItem);
                            updatePricesAndButtons(data.InnerData);
                            $mb.setBasketMessage(data.UserMessage, controller.save);
                            if (typeof ($mb.onItemUpdated) == 'function') $mb.onItemUpdated(data);
                        });
                    };


                } else {

                    // we don't have any data from the server here, create on-the-fly
                    // TODO - do we need anything else?
                    var data = new Object();
                    data.InnerData = new Object();
                    data.InnerData.ActiveItem = new Object();
                    data.InnerData.ActiveItem.Product = $mb.getProductData(controller.mmid);
                    data.InnerData.ActiveItem.QuantityAdded = 0;

                    $mb.setBasketMessage("");
                    if (typeof ($mb.onItemUpdated) == 'function') $mb.onItemUpdated(data);

                };

            });
        });
    };

    function addItemHtml(basketItem) {

        var data = new htmlBuilder().buildBasketItemHtml(basketItem);

        // remove existing...
        $('#basketItem_' + basketItem.ObjectID).remove();

        var selector = basketItem.IsSaveForLater ? VW_WISH_LIST_ITEMS : VW_BASKET_ITEMS;

        $('#' + selector).prepend(data);

        // register tooltips
        $('#basketItemTip1_' + basketItem.ObjectID +
         ',#basketItemTip2_' + basketItem.ObjectID).smallBasketItemTooltip(basketItem);

        $('#basketItemPriceTip_' + basketItem.ObjectID).mangoPipeTip();
        $('#basketItem_' + basketItem.ObjectID).fadeIn();

        $mb.showPod(basketItem.IsSaveForLater);

    };

    /// update the html for prices and buttons
    /// + data (BasketJsonData)
    function updatePricesAndButtons(data) {

        var hb = new htmlBuilder();

        // initialise headers
        var basketHeader;
        var wishListHeader = data.HasSavedForLaterItems
                                ? hb.buildSimpleAnchor(data.EditWishListUrl, "Click here to view or edit your wishlist contents", "Saved for later", "")
                                : "Saved for later";

        if (true == data.HasNotSavedForLaterItems) {

            var totals = hb.buildBasketTotals(data, true);
            $('#' + VW_BASKET_TOTALS).html(totals).show();

            basketHeader = hb.buildSimpleAnchor(data.EditBasketUrl, "Click here to view or edit your basket contents", "Shopping", "");

            $mb.decorateHtml();

        } else {

            $('#' + VW_BASKET_TOTALS).hide();
            basketHeader = hb.buildSimpleAnchor(data.ContinueShoppingUrl, "Click here to go shopping", "Shopping", "");
        };


        if (true == data.CanCheckout) {
            $('#vwBasket_checkoutLink')
                .attr("class", "basketButton")
                .attr("title", "Click here to checkout")
                .attr("href", data.CheckoutUrl)
                .html("Checkout")
                .show();
        } else {
            $('#vwBasket_checkoutLink').hide();
        };

        $('#vwBasket_editLink').attr("href", data.HasNotSavedForLaterItems ? data.EditBasketUrl : data.ContinueShoppingUrl)
                    .attr("class", data.HasNotSavedForLaterItems ? "basketButton" : "basketButtonLarge")
                    .attr("title", data.HasNotSavedForLaterItems ? "Click here to view or edit your basket contents" : "Click here to go shopping")
                    .html(data.HasNotSavedForLaterItems ? "View/Edit" : "Go shopping");

        // update the headers
        $('#' + PNL_BASKET).find(".header").find(".title").html(basketHeader);
        $('#' + PNL_WISH_LIST).find(".header").find(".title").html(wishListHeader);

    };

    // add product info to the cache
    // + data(BasketJsonData)
    function productInfoToCache(data) {
        for (var i in data.Items) {
            if (typeof (data.Items[i].Product) != 'undefined') {
                $mb.addProductData(data.Items[i].Product);
            };
        };
    };

    // append the current basket id (if avail) to the given url
    function appendBasketID(url) {
        // see if we've got the basket id on the current qs - if so, append it
        var pattern = 'bid=[^&]*';
        var re = new RegExp(pattern, 'g');
        var match = re.exec(document.location.href);

        var result = url;

        if (null != match) {
            result = result + '&' + match;
        };

        return result;
    };


    // rebuild the basket
    // + data (BasketJsonData)
    function reBuildBasket(data) {

        // clear existing html
        $('#' + VW_BASKET_ITEMS).html('');
        $('#' + VW_WISH_LIST_ITEMS).html('');

        // buttons?
        $('#' + VW_BASKET_BUTTONS).html("<a id=\"vwBasket_editLink\"></a><a id=\"vwBasket_checkoutLink\"></a>");

        // basket first
        for (var i in data.Items) {
            var item = data.Items[i];
            if (!item.IsSaveForLater) addItemHtml(item);
        };

        // then wishlist
        for (var i in data.Items) {
            var item = data.Items[i];
            if (item.IsSaveForLater) addItemHtml(item);
        };


    };

    // generic handler for JSON data responses - e.g. error handling, redirects etc
    // returns true if execution can continue, pops a waiting message in the basket if redirecting
    // + jsonData - JSON
    function canHandle(jsonData) {
        // TODO - dupe of function this.canHandle, see above
        return canHandleJSON(jsonData, VW_BASKET_MESSAGE);
    };


    // get the root querystring
    function getRootQueryString(wgid) {

        var result = cookiesToQueryString(['rid', 'rak'])
                        + '&wgid=' + wgid
                        + '&urfr=' + escape(document.referrer)
                        + '&cmd=';

        return result;
    };

};


/// factory for panel state controller
function getPanelStateController(data){
    $mb.panelStateController = new panelStateController(data);
    return $mb.panelStateController;
};



// ### deprecated methods ### ///

// use $mb directly
function getStyleSettings(){
    return $mb.styleSettings;
};

