﻿/*
    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){
        var link = $('#' + id); 
        if(link.length == 0){
            var html = new htmlBuilder().buildCssLink(id, href);
            $("head").append(html);
        }else{
            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}";
        var args = [null == item.QuantityAdded ? "" : item.QuantityAdded + " added. ",
                    item.Quantity, 
                    item.IsSaveForLater ? "wishlist" : "basket", 
                    null == item.CurrentValue ? "" : " for " + item.PriceStringAsHtml];
        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(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 
                        + '&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;
};

