/*
    http://www.JSON.org/json2.js
    2009-08-17
    Public Domain.
    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
    See http://www.JSON.org/js.html
    This file creates a global JSON object containing two methods: stringify
    and parse.
        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.
            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.
            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.
            This method produces a JSON text from a JavaScript value.
            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the value
            For example, this would serialize Dates as ISO strings.
                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        
                        return n < 10 ? '0' + n : n;
                    }
                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };
            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.
            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.
            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.
            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.
            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.
            Example:
            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            
            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            
            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            
        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.
            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.
            Example:
            
            
            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });
            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });
    This is a reference implementation. You are free to copy, modify, or
    redistribute.
    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html
    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.
*/
/*jslint evil: true */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/
"use strict";


if (!this.JSON) {
    this.JSON = {};
}
(function () {
    function f(n) {
        
        return n < 10 ? '0' + n : n;
    }
    if (typeof Date.prototype.toJSON !== 'function') {
        Date.prototype.toJSON = function (key) {
            return isFinite(this.valueOf()) ?
                   this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z' : null;
        };
        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }
    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;
    function quote(string) {




        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }
    function str(key, holder) {

        var i,          
            k,          
            v,          
            length,
            mind = gap,
            partial,
            value = holder[key];

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }


        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

        switch (typeof value) {
        case 'string':
            return quote(value);
        case 'number':

            return isFinite(value) ? String(value) : 'null';
        case 'boolean':
        case 'null':



            return String(value);


        case 'object':


            if (!value) {
                return 'null';
            }

            gap += indent;
            partial = [];

            if (Object.prototype.toString.apply(value) === '[object Array]') {


                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }


                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }


            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {





            var i;
            gap = '';
            indent = '';


            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

            } else if (typeof space === 'string') {
                indent = space;
            }


            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }


            return str('', {'': value});
        };
    }

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {


            var j;
            function walk(holder, key) {


                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }



            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }











            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {




                j = eval('(' + text + ')');


                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

            throw new SyntaxError('JSON.parse');
        };
    }
}());
if (!window.Deal) {
(function(){
window.Deal = {};
Deal.availableStr  = "Available";
Deal.inCartStr     = "InCart";
Deal.claimedStr    = "Claimed";
Deal.expiredStr    = "Expired";
Deal.waitInLineStr = "WaitInLine";
Deal.pendingAtcStr = "PendingAddToCart";
Deal.log = function(msg) {
    msg = new Date() + ": "+msg;
    if (window.console) {
        window.console.log(msg);
    } else {
        var div = document.getElementById('Deal.log');
        if (div) {
            div.appendChild(document.createElement('br'));
            div.appendChild(document.createTextNode(msg));
        }
    }
};
Deal.getDealStatus = function(deal) {
    
    
    var status = gbResources.getString('csld-available_status');
    if ( !deal.status.started ) {
	status = gbResources.getString('coming-soon_1565');
    } else if ((deal.asins[0] && deal.asins[0].offerServiceSoldOut) ||
	        deal.status.percentSoldOut >= 100 ) {
	status = gbResources.getString('csld-cat-sold-out');
    } else if (deal.status.ended || deal.status.expired) {
	status = gbResources.getString('csld-expired');
    }
    return status;
};
Deal.min = function(a) {
    var min = arguments[0];
    for (var i=1; i<arguments.length; i++) {
        if (arguments[i] < min) {
            min = arguments[i];
        }
    }
    return min;
};
Deal.max = function(a) {
    var max = arguments[0];
    for (var i=1; i<arguments.length; i++) {
        if (arguments[i] > max) {
            max = arguments[i];
        }
    }
    return max;
};

Deal.sortByAsinTimes = function(a, b) {
	var x = a.status.purchaseStatus.expiresDate;
	var y = b.status.purchaseStatus.expiresDate;
	return x-y;
};


Deal.filterAsinsByState = function (dealAsins, states) {
    var filteredAsins = [];
    for (var i = 0; i < dealAsins.length; i++) {
        var dealAsin = dealAsins[i];
        for (var x = 0; x < states.length; x++) {
            var state = states[x];
            if (dealAsin.status.purchaseStatus.state == state) {
                filteredAsins.push (dealAsin);
                break;
            }
        }
    }
    return filteredAsins;
};
Deal.commify =  function(num, decimals) {
    num = parseFloat(num);
    if (decimals !== undefined) {
        num = num.toFixed(decimals);
    } else {
        num = num.toString();
    }
    var parts = num.split('.');
    var left = parts[0];
    for (var i=left.length-3; i>0; i-=3) {
        left = left.substr(0,i)+','+left.substr(i,left.length-i);
    }
    if (parts.length == 2) {
        return left+'.'+parts[1];
    } else {
        return left;
    }
};

Deal.stateAlertsEnum = {
    ATC_EXPIRES_SOON: 0,
    ATC_EXPIRED: 1,
    WL_PATC: 2,
    WL_PATC_EXPIRED: 3,
    WL_SOLD_OUT: 4,
    WL_DEAL_ENDED: 5
};


Deal.pStateTypeEnum = {
    CART: 0,
    WAITLIST: 1,
    EITHER: 2,
    NO_ACTION: 3
};
Deal.pStateTypeMap = {};
Deal.pStateTypeMap[Deal.availableStr]  = Deal.pStateTypeEnum.NO_ACTION;
Deal.pStateTypeMap[Deal.inCartStr]     = Deal.pStateTypeEnum.CART;
Deal.pStateTypeMap[Deal.claimedStr]    = Deal.pStateTypeEnum.NO_ACTION;
Deal.pStateTypeMap[Deal.expiredStr]    = Deal.pStateTypeEnum.EITHER;
Deal.pStateTypeMap[Deal.waitInLineStr] = Deal.pStateTypeEnum.WAITLIST;
Deal.pStateTypeMap[Deal.pendingAtcStr] = Deal.pStateTypeEnum.WAITLIST;


Deal.findDealAsin = function (asin, deal) {
    var self = this;
    var dealAsin = null;
    
    if (deal.asins == null) {
        return dealAsin;
    }
    for (var i = 0; i < deal.asins.length; i++) {
        dealAsin = deal.asins[i];
        if (dealAsin.asin == asin) {
            break;
        }
    }
    return dealAsin;
};
Deal.getPurchaseState = function(deal, asin) {
    
    var purchaseState = 'Available';
    var decisionAsin = asin;
    
    
    
    if (decisionAsin == null) {
        if (deal != null &&
            deal.asins != null) {
            if (deal.asins.length > 1) {
                
                
                
                
                var purchaseState = 'Available';
                var allPurchaseStates = {};
                
                
                for (var i = 0; i < deal.asins.length; i++) {
                    var tmpAsin = deal.asins[i];
		    var pState = Deal.getPurchaseState(deal, tmpAsin);
                    if (allPurchaseStates[pState]) {
                        allPurchaseStates[pState] += 1;
                    } else {
                        allPurchaseStates[pState] = 1;
                    }
                }
                
                
                
                for (var pState in allPurchaseStates) {
                    var pStateCount = allPurchaseStates[pState];
                    if (deal.asins.length == pStateCount) {
                        purchaseState = pState;
                        break;
                    }
                }
                return purchaseState;
            } else if ( deal.asins[0] != null ) {
                
                decisionAsin = deal.asins[0];
            }
        } else {
            
            
            
            
        }
    }
    
    
    
    if (gbResources.features["timed-checkout"] &&
        decisionAsin && decisionAsin.status &&
        decisionAsin.status.purchaseStatus) {
        purchaseState = decisionAsin.status.purchaseStatus.state;
    } else {
        
        
        
        if (decisionAsin && decisionAsin.isCustomerClaimed) {
            purchaseState = 'Claimed';
        } else {
            purchaseState = 'Available';
        }
    }
    return purchaseState;
};

Deal.isClaimed = function(deal, asin) {
    return Deal.getPurchaseState(deal, asin) == Deal.claimedStr;
};

Deal.inState = function(deal, asin, state) {
    return Deal.getPurchaseState(deal, asin) == state;
};

Deal.isSoldOut = function(deal) {
    var self = this;
    if (!deal.asins || deal.asins.length === 0) {
	return false;
    }
    
    
    
    if ( !gbResources.features.waitlist &&
	 deal.status.percentClaimed >= 100 ) {
	return true;
    }
    
    
    for (var i=0; i<deal.asins.length; i++) {
	if ( !deal.asins[i].offerServiceSoldOut ) {
	    if ( !gbResources.features.waitlist ) {
		return false;
	    } else if ( deal.asins[i].status.percentSoldOut < 100 ) {
		return false;
	    }
	}
    }
    return true;
};


Deal.isPrimeEligible = function(deal) {
    if ( deal.asins ) {
	for ( var i=0; i<deal.asins.length; i++ ) {
	    if (deal.asins[i].isPrimeEligible) {
		return true;
	    }
	}
    }
    return false;
}


Deal.resizeImage = function(url, size) {
    url = url.replace(/\.((_.*_)\.)?(gif|jpg)$/, '.$2_AA' + size + '_.$3');
    return url;
};



Deal.checkAndSetSSLImageUrl = function (url) {
    if (url == null) {
        return url;
    }
    
    if (window.location.protocol == 'https:') {
        url = url.replace (/(http:\/\/[^\/]*\/)(.*$)/,
          'https://images-na.ssl-images-amazon.com/$2');
    }
    return url;
};
Deal.create = function(baseObject, typeOverrides) {
    if (typeof Object.create !== 'function') {
	Object.create = function(object) {
	    function F() {}
	    F.prototype = object;
	    return new F();
	};
    }
    var newObject = Object.create(baseObject);
    for (var name in typeOverrides) {
	
	newObject[name] = typeOverrides[name];
    }
    return newObject;
};
Deal.Class = function(base, attrs) {
    var prototype = base;
    if (attrs === undefined) {
        for (var attr in attrs) {
            prototype[attr] = attrs[attr];
        }
    }
    var constructor = (prototype && prototype.__init__) || function() { };
    constructor.prototype = prototype;
    return constructor;
};
Deal.dealTitle = function(deal) {
    var title = deal.detail.title || gbResources.getString('csld-no_title');
    return title;
};
Deal.dealImage = function(deal, size) {
    
    var src = deal.detail.imageAsin;
    if (!src) {
	src = gbResources.getImage('no_image');
    }
    src = Deal.resizeImage(src, size);
    return src;
};
Deal.dealUrl = function(deal) { 
    if (deal.detail.url) {
    	return Deal.appendUrlParameter(deal.detail.url, 'smid', deal.merchantID);
    } else if (deal.asins.length == 1) {
    	return Deal.appendUrlParameter('/gp/product/'+deal.asins[0].asin, 'smid', deal.merchantID);
    } else if (deal.asins.length > 1 && deal.parentAsin) { 
    	return Deal.appendUrlParameter('/gp/product/'+deal.parentAsin, 'smid', deal.merchantID);
    } else {
        Deal.log("fudging title href");
        return '/gp/goldbox';
    }
};
Deal.appendUrlParameter = function(url, parameterKey, parameterValue) { 
	var operator = '?';
	if(url.indexOf("?") != -1) { operator = '&'; }
	return url + operator + parameterKey + "=" + parameterValue;
};
Deal.Signal = {
    
    methods: ["connect", "disconnectAll", "signal"],
    /** Initialize an object to use signals.
     * The object should have a "signals" attribute that is an Array of
     * strings. Each string represents the signal name.
     *
     * This sets the __signals__ attribute which is a map from signal name to
     * a map of connection ID to connection object.
     *
     * This adds the connect, disconnectAll, and signal methods that behave as
     * described below.
     */
    init: function(src) {
        var self = this;
        var i;
        var method;
        if (src.__signals__) {
            throw new Error("already registered");
        }
        if (src.signals === undefined) {
            throw new Error("expected 'signals' attribute");
        }
        for (var i=0; i<this.methods.length; i++) {
            method = this.methods[i];
            if (src[method] !== undefined) {
                throw new Error("method "+method+" is already defined");
            }
        }
        
        src.__signals__ = {};
        for (var i=0; i<src.signals.length; i++) {
            src.__signals__[src.signals[i]] = {};
        }
        for (var i=0; i<this.methods.length; i++) {
            method = this.methods[i];
            src[method] = this[method];
        }
    },
    
    next_id: 100,
    /* Connect a signal to an object's method. Returns a connection object.
     *     - this:     The source object. It should already have been
     *                 registered with the register method above.
     *     - signal:   The signal name. It should be one of the signals
     *                 registered.
     * For the 'obj_or_func' and 'method' parameters, there are two cases:
     *   1. You want to register a function. Pass it in as 'obj_or_func' and
     *      leave 'method' blank.
     *   2. You want to register the method of an object. Pass the object as
     *      'obj_or_func' and the method name as 'method'.
     *
     * Returns a connection object. This object has the 'disconnect' signal
     * which is signalled when the connection is disconnected. It also has the
     * 'disconnect' and 'trigger' methods. 'disconnect' will disconnect the
     * connection. 'trigger' triggers the call as if it were signalled with
     * the parameters passed in.
     */
    connect: function(signal, obj_or_func, method) {
        
        var self = this;
        if (self.__signals__[signal] === undefined) {
            throw new Error("no such signal "+signal);
        }
        var id = Deal.Signal.next_id++;
        var connection = {
            signals: ['disconnect'],
            disconnect: function() {
                delete self.__signals__[signal][id];
                this.signal("disconnect");
                this.disconnect = function() { };
            }
        };
        if (obj_or_func instanceof Function) {
            connection.trigger = obj_or_func;
        } else {
            if (obj_or_func[method] === undefined) {
                throw new Error("method '"+method+
                    "' for obj "+obj_or_func+
                    " doesn't exist");
            }
            if (!obj_or_func[method] instanceof Function) {
                throw new Error("method '"+method+
                    "' for obj "+obj_or_func+
                    " isn't a Function");
            }
            connection.trigger = function() {
                obj_or_func[method].apply(obj_or_func, arguments);
            };
        }
        self.__signals__[signal][id] = connection;
        Deal.Signal.init(connection);
        return connection;
    },
    /* disconnects all the connections.
     * 'signal': Which signal to disconnect. If undefined, then all signals
     * are disconnected.
     */
    disconnectAll: function(signal) {
        
        var self = this;
        var sig;
        if (signal) {
            if (self.__signals__[signal] === undefined) {
                throw new Error("no such signal "+signal);
            }
            var id;
            for (var id in this.__signals__[signal]) {
                this.__signals__[signal][id].disconnect();
            }
            return;
        }
        for (var sig in this.__signals__) {
            this.disconnectAll(sig);
        }
    },
    /* Signals an event. Pass the signal name and the parameters.
     */
    signal: function(signal /* additional parameters accepted */) {
        
        var self = this;
        if (self.__signals__[signal] === undefined) {
            throw new Error("no such signal "+signal);
        }
        
        var args = [];
	var i = 1;
	if ( arguments.length == 1 ) {
	    i = 0;
	}
        for (var i; i<arguments.length; i++) {
            args.push(arguments[i]);
        }
        var errors = [];
        for (var id in this.__signals__[signal]) {
            var conn = this.__signals__[signal][id];
            try {
                conn.trigger.apply(conn, args);
            } catch (e) {
                errors.push(e);
            }
        }
        if (errors.length > 1) {
            var error = new Error("multiple errors. See 'errors'");
            error.errors = errors;
            throw error;
        } else if (errors.length == 1) {
            throw errors[0];
        }
    }
};
Deal.Service = Deal.Class({
    __init__: function(p) {
        var self = this;
        if (!p.marketplace_id) {
            throw new Error("must specify 'marketplace_id'");
        }
        self.marketplace_id = p.marketplace_id;
        self.customer_id = p.customer_id;
        self.session_id = p.session_id;
        self.timeout = p.timeout || 5*60*1000;
        self.includeVariations = p.includeVariations;
        self.widgetReftag = p.widgetReftag;
    },
    
    
    ajax_with_retries: function(params) {
        var self = this;
        var start = new Date();
        var retries = 0; 
	var retryInterval = params.retryInterval ||
            Deal.Service.base_retry_interval ||
            60000;
        delete params.retryInterval;
        var error = params.error;
        delete params.error;
        var retry = function () {
            
            if (!Deal.Service.continue_requests) {
                error(new Error(
                    "continue_requests is false:"+
                    " no more requests should be made."));
                return;
            }
            
            if (params.url.indexOf('?') == -1) {
                params.url = params.url+'?nocache='+new Date().getTime();
            } else {
                params.url = params.url+'&nocache='+new Date().getTime();
            }
            params.error = function(xhr) {
                var retry_after = retryInterval*
                    (1 + Math.pow(2, retries++)*Math.random());
                if (retry_after + new Date().getTime() - start.getTime() >
                        self.timeout)
                {
                    error(new Error("timed out"));
                } else {
                    Deal.log("retrying after "+retry_after+"ms");
                    window.setTimeout(retry, retry_after);
                }
            };
            jQuery.ajax(params);
        };
        retry();
    },
    
    
    
    
    
    call: function(url, data, success, error, retryInterval) {
        var self = this;
	var params = {
            success: function(result) {
                if (result.responseMetadata) {
                    Deal.Service.base_retry_interval =
                        result.responseMetadata.baseRetryInterval;
                    Deal.Service.continue_requests =
                        result.responseMetadata.continueRequests;
                }
                success(result);
            },
            error: error,
            url: url,
            type: "POST",
            data: JSON.stringify(data),
            dataType: 'json' };
	if ( retryInterval !== undefined ) {
	    params.retryInterval = retryInterval;
	}
	 self.ajax_with_retries(params);
    },
    get_deal_metadata: function(request, onsuccess, onerror) {
        var self = this;
        self.call(
            '/xa/goldbox/GetDealMetadata',
            {
                requestMetadata: {
                    marketplaceID: self.marketplace_id},
                filters: request.filters,
                orderings: request.orderings,
                includeVariations: self.includeVariations
            }, onsuccess, onerror);
    },
    _deal_ids_from_callbacks: function(deal_ids_to_callbacks) {
        var deal_ids = [];
        var deal_id;
        for (deal_id in deal_ids_to_callbacks) {
            deal_ids.push(deal_id);
        }
        return deal_ids;
    },
    get_deals: function(deal_ids_to_callbacks, filter) {
        var self = this;
        var deal_ids = self._deal_ids_from_callbacks(deal_ids_to_callbacks);
        var i;
        if (filter == null) {
            filter = Deal.Service.DealIDDealFilter(deal_ids);
        }
        self.call(
            '/xa/goldbox/GetDeals',
            {
                requestMetadata: {
                    marketplaceID: self.marketplace_id},
                customerID: self.customer_id,
                filter: filter,
                ordering: [],
                page: 1,
                resultsPerPage: deal_ids.length,
                includeVariations: self.includeVariations
            },
            function(result) {
                var seen={};
                for (var i=0; i<result.deals.length; i++) {
                    var deal = result.deals[i];
                    deal_ids_to_callbacks[deal.dealID][0](deal);
                    seen[deal.dealID] = true;
                }
                for (var i=0; i<deal_ids.length; i++) {
                    var deal_id = deal_ids[i];
                    if (!seen[deal_id]) {
                        deal_ids_to_callbacks[deal_id][1](
                            new Error("No deal returned for dealID "+deal_id));
                    }
                }
            },
            function(error) {
                for (var i=0; i<deal_ids.length; i++) {
                    var deal_id = deal_ids[i];
                    deal_ids_to_callbacks[deal_id][1](error);
                }
            });
    },
    get_deal_statuses: function(deal_ids_to_callbacks) {
        var self = this;
        var deal_ids = self._deal_ids_from_callbacks(deal_ids_to_callbacks);
        self.call(
            '/xa/goldbox/GetDealStatus',
            {
                requestMetadata: {
                    marketplaceID: self.marketplace_id},
                dealIDs: deal_ids},
            function(result) {
                var seen={};
                var deal_id;
                var deal_status;
                var i;
                for (var deal_id in result.dealStatus) {
                    deal_status = result.dealStatus[deal_id];
                    deal_ids_to_callbacks[deal_status.dealID][0](deal_status);
                    seen[deal_status.dealID] = true;
                }
                for (var i=0; i<deal_ids.length; i++) {
                    deal_id = deal_ids[i];
                    if (!seen[deal_id]) {
                        deal_ids_to_callbacks[deal_id][1](
                            new Error("No status returned for dealID "+deal_id));
                    }
                }
            },
            function(error) {
                var i;
                var deal_id;
                for (var i=0; i<deal_ids.length; i++) {
                    deal_id = deal_ids[i];
                    deal_ids_to_callbacks[deal_id][1](error);
                }
            });
    },
    get_deal_asin_statuses: function(deal_ids_to_callbacks) {
        var self = this;
        var deal_ids = self._deal_ids_from_callbacks(deal_ids_to_callbacks);
        Deal.log ("Calling GetDealAsinStatus: " + deal_ids);
        self.call(
            '/xa/goldbox/GetDealAsinStatus',
            {
                requestMetadata: {marketplaceID: self.marketplace_id},
                customerID: self.customer_id,
                filter: Deal.Service.DealIDDealFilter(deal_ids)
            },
            function(result) {
                var asinStatuses = result.dealAsinStatuses;
                var asinStatusMap = {};
                var dealsList = [];
                var seenDealIdMap = {};
                
                
                
                for (var i = 0; i < asinStatuses.length; i++) {
                    var asinStatus = asinStatuses[i];
                    asinStatusMap[asinStatus.asin] = asinStatus;
                    seenDealIdMap[asinStatus.dealID] = 1;
                }
                for (var tmpDealId in seenDealIdMap) {
                    deal_ids_to_callbacks[tmpDealId][0](asinStatusMap);
                }
            },
            function(error) {
                for (var i = 0; i < deal_ids.length; i++) {
                    var deal_id = deal_ids[i];
                    deal_ids_to_callbacks[deal_id][1](error);
                }
            }
        );
    },
    get_deal_asin_status: function(deal_id, success, error) {
        var self = this;
        self.call(
            '/xa/goldbox/GetDealAsinStatus',
            {
                requestMetadata: {
                    marketplaceID: self.marketplace_id},
                customerID: self.customer_id,
                dealID: deal_id},
            success,
            error);
    },
    
    
    
    get_deal_asin_status2: function(deal_id, success, error) {
        var self = this;
        return self._add_request('get_deal_asin_status', deal_id, success, error);
    },
    redeem_deal: function(deal_id, asin, success, error) {
        var self = this;
        var reftagString = '';
        
        if (self.widgetReftag) {
            reftagString = '&ref=' + self.widgetReftag + '_'
                           + deal_id + '_' + asin;
        }
        self.call(
            '/gp/deal/ajax/redeemDeal.html'+
                '?marketplaceID='+self.marketplace_id+
                '&dealID='+deal_id+
                '&asin='+asin+
                '&sessionID='+self.session_id+
                reftagString,
            {},
            success,
            error,
	    Deal.Service.base_retry_interval/4);
    },
    
    _pending: {
        get_deal: {
            current: false,
            timeout: undefined,
            deal_ids: {
                
            }
        },
        get_deal_status: {
            current: false,
            timeout: undefined,
            deal_ids: {
                
            }
        },
        get_deal_asin_status: {
            current: false,
            timeout: undefined,
            deal_ids: {
            }
        }
    },
    next_id: 100,
    get_deal: function(deal_id, success, error) {
        var self = this;
        return self._add_request('get_deal', deal_id, success, error);
    },
    get_deal_status: function(deal_id, success, error) {
        var self = this;
        return self._add_request('get_deal_status', deal_id, success, error);
    },
    _add_request: function(type, deal_id, success, error) {
        var self = this;
        var p = self._pending[type];
        
        if (p.deal_ids[deal_id] === undefined) {
            p.deal_ids[deal_id] = {};
        }
        var d = p.deal_ids[deal_id];
        
        var id = self.next_id ++;
        
        var request = {
            cancel: function() {
                delete d[id];
                this.cancel = function() {
                    throw new Error("already cancelled");
                };
            },
            success: success,
            error: error
        };
        
        d[id] = request;
        self._start_request_timer(type);
        return request;
    },
    _start_request_timer: function(type) {
        var self = this;
        var p = self._pending[type];
        
        if (!p.current) {
            
            if (p.timeout) {
                window.clearTimeout(p.timeout);
            }
            p.timeout = window.setTimeout(function() {
                p.timeout = undefined;
                self._start_request(type);
            }, 100);
        }
    },
    _start_request: function(type) {
        var self = this;
        var p = self._pending[type];
        
        if (p.timeout) {
            window.clearTimeout(p.timeout);
            p.timeout = undefined;
        }
        
        if (p.current) {
            return;
        }
        
        var deal_ids = [];
        var deal_ids_to_callbacks = {};
        for (var deal_id in p.deal_ids) {
            
            if (deal_ids.length >= 10) {
                break;
            }
            
            var requests = false;
            for (var id in p.deal_ids[deal_id]) {
                requests = true;
                break;
            }
            if (!requests) {
                delete p.deal_ids[deal_id];
                break;
            }
            if (p.deal_ids[deal_id].current) {
                continue;
            }
            p.deal_ids[deal_id].current = true;
            deal_ids.push(deal_id);
            (function(deal_id) {
                deal_ids_to_callbacks[deal_id] = [
                    function(deal) {
                        self._request_success(type, deal_id, deal);
                    }, function(error) {
                        self._request_error(type, deal_id, error);
                    }];
            })(deal_id);
        }
        if (deal_ids.length === 0) {
            return;
        }
        if (type == 'get_deal') {
            self.get_deals(deal_ids_to_callbacks);
        } else if (type == 'get_deal_status') {
            self.get_deal_statuses(deal_ids_to_callbacks);
        } else if (type == 'get_deal_asin_status') {
            self.get_deal_asin_statuses(deal_ids_to_callbacks);
        }
    },
    _request_success: function(type, deal_id, deal) {
        var self = this;
        var p = self._pending[type];
        p.current = false;
        var d = p.deal_ids[deal_id];
        if (d !== undefined) {
            delete p.deal_ids[deal_id];
            delete d.current;
            for (var id in d) {
                d[id].success(deal);
            }
        }
        self._start_request_timer(type);
    },
    _request_error: function(type, deal_id, error) {
        var self = this;
        var p = self._pending[type];
        p.current = false;
        var d = p.deal_ids[deal_id];
        if (d !== undefined) {
            delete p.deal_ids[deal_id];
            delete d.current;
            for (var id in d) {
                d[id].error(error);
            }
        }
        self._start_request_timer(type);
    }
});
Deal.Service.base_url = "http://internal.amazon.com/coral/com.amazon.DealService.model/";
Deal.Service.AndDealFilter = function(children) {
    return {
        __type: "AndDealFilter:"+Deal.Service.base_url,
        children: children
    };
};
Deal.Service.OrDealFilter = function(children) {
    return {
        __type: "OrDealFilter:"+Deal.Service.base_url,
        children: children
    };
};
Deal.Service.DealIDDealFilter = function(dealIDs) {
    return {
        __type: "DealIDDealFilter:"+Deal.Service.base_url,
        dealIDs: dealIDs
    };
};
Deal.Service.SlotDealFilter = function(slots) {
    return {
        __type: "SlotDealFilter:"+Deal.Service.base_url,
        slots: slots
    };
};
Deal.Service.AsinDealFilter = function(asins) {
    return {
        __type: "AsinDealFilter:"+Deal.Service.base_url,
        asins: asins
    };
};
Deal.Service.AvailableDealFilter = function() {
    return {
        __type: "AvailableDealFilter:"+Deal.Service.base_url
    };
};
Deal.Service.StartDateDealFilter = function(from, to) {
    return {
        __type: "StartDateDealFilter:"+Deal.Service.base_url,
        from: (from instanceof Date ? from.valueOf() : from),
        to: (to instanceof Date ? to.valueOf() : to)
    };
};
Deal.Service.EndDateDealFilter = function(from, to) {
    return {
        __type: "EndDateDealFilter:"+Deal.Service.base_url,
        from: (from instanceof Date ? from.valueOf() : from),
        to: (to instanceof Date ? to.valueOf() : to)
    };
};
Deal.Service.SoldOutDealFilter = function(soldOut) {
    return {
        __type: "SoldOutDealFilter:"+Deal.Service.base_url,
        soldOut: soldOut
    };
};
Deal.Service.ClaimedDealFilter = function(claimed) {
    return {
        __type: "ClaimedDealFilter:"+Deal.Service.base_url,
        claimed: claimed
    };
};
Deal.Service.StartDateDealOrder = function() {
    return {
        __type: "StartDateDealOrder:"+Deal.Service.base_url
    };
};
Deal.Service.NextAvailableDealOrder = function() {
    return {
        __type: "NextAvailableDealOrder:"+Deal.Service.base_url
    };
};
Deal.Service.BucketDealOrder = function(slots) {
    return {
        __type: "BucketDealOrder:"+Deal.Service.base_url,
        slots: slots
    };
};
Deal.Model = {};
Deal.Model.Metadata = Deal.Class({
    __init__: function(filters, orderings) {
        var self = this;
        
        self.filters = filters;
        
        for (var filter in self.filters) {
            var f = self.filters[filter];
            self.filters[filter] = {};
            for (var i=0; i<f.length; i++) {
                self.filters[filter][f[i]] = true;
            }
        }
        
        self.orderings = orderings;
    },
    
    get_deal_ids: function(filter, ordering) {
        var self = this;
        
        filter = self.filters[filter];
        if (ordering) {
            ordering = self.orderings[ordering];
        } else {
            ordering = [];
        }
        var deal_ids = [];
        var deals_seen = {};
        var i;
        var deal_id;
        for (var i=0; i<ordering.length; i++) {
            deal_id = ordering[i];
            deals_seen[deal_id] = true;
            if (filter[deal_id]) {
                deal_ids.push(deal_id);
            }
        }
        for (var deal_id in filter) {
            if (!deals_seen[deal_id]) {
                deal_ids.push(deal_id);
            }
        }
        return deal_ids;
    }
});
/* A deal database. Returns the Deal.Model.Deal objects. */
Deal.Model.Deals = Deal.Class({
    __init__: function(deals) {
        var self = this;
        self.deals = {};
        for (var i=0; i<deals.length; i++) {
            var deal = deals[i];
            var new_deal = self.get_deal(deal.dealID);
            new_deal.load_from_deal(deal);
        }
    },
    
    get_deal: function(deal_id) {
        if (this.deals[deal_id] === undefined) {
            this.deals[deal_id] = new Deal.Model.Deal(deal_id);
        }
        return this.deals[deal_id];
    }
});
Deal.Model.Deal = Deal.Class({
    /* Attributes are:
     *  marketplaceID
     *  merchantID
     *  dealID
     *  startDate: Date (Use only for display)
     *  endDate: Date (Use only for display)
     *  limitedQuantity: boolean
     *  parentAsin: The parent Asin, if any, for variational deals only
     *  msToCacheExpires: How long until it expires.
     *  cacheExpiresDate: When the deal expires.
     *  expired: Whether or not it has already expired.
     *  loading: Whether it is loading
     *  status: {
     *      marketplaceID:
     *      dealID:
     *      percentClaimed:
     *      msToStart:
     *      startDate: now + msToStart (Use for calculations)
     *      started: Whether it has started
     *      msToEnd:
     *      endDate: now + msToEnd (Use for calculations)
     *      ended: Whether it has ended
     *      msToCacheExpires:
     *      cacheExpiresDate: When the cache expires.
     *      expired: Whether the cache has expired.
     *  },
     *  detail: {
     *      marketplaceID:
     *      dealID:
     *      title:
     *      description:
     *      imageAsin:
     *      url:
     *      buyBoxUrl:
     *  },
     *  teaser: { (optional)
     *      marketplaceID:
     *      dealID:
     *      category:
     *      teaser:
     *  },
     *  asins: [ { (list of objects)
     *      marketplaceID:
     *      dealID:
     *      asin:
     *      listPrice:
     *      ourPrice:
     *      dealPrice:
     *      offerServiceSoldOut:
     *      status: {
     *          marketplaceID
     *          dealID:
     *          asin:
     *          percentClaimed
     *          offerServiceSoldOut:
     *      }
     *  }],
     *  customer: { (optional)
     *      marketplaceID:
     *      dealID:
     *      customerID:
     *      claimed:
     *  }
     */
    signals: [
        
        'change',
        
        'expire',
        
        'status_expire',
        
        
        
        'pstatus_expires_soon',
        
        
        'pstatus_expire'
        ],
    __init__: function(deal_id) {
        var self = this;
        Deal.Signal.init(self);
        self.dealID = deal_id;
        self.timeouts = {};
        self.loading = true;
        self.expired = true;
        self.status = {expired: true};
        self.detail = {};
        self.asins = [];
        
        self.asinExpiresSoonStack = [];
        self.asinExpiredStack     = [];
        
        self.purchaseStatusWarningThreshold = 2 * 60 * 1000;
        if (gbResources.features["notifier-waitlist"] &&
            gbResources.features["notifier-waitlist"] != 'C') {
            self.purchaseStatusWarningThreshold =
              parseInt (gbResources.features["notifier-waitlist"], 10) * 60 * 1000;
        }
    },
    _init_status: function(now) {
        var self = this;
        self.status.cacheExpiresDate = new Date(
            now.getTime() + parseInt(self.status.msToCacheExpires, 10));
        self.status.expired = false;
        self.status.startDate = new Date(
            now.getTime() + parseInt(self.status.msToStart, 10));
        self.status.started = self.status.msToStart <= 0;
        self.status.endDate = new Date(
            now.getTime() + parseInt(self.status.msToEnd, 10));
        self.status.ended = self.status.msToEnd <= 0;
        if (self.timeouts.start_timeout) {
            window.clearTimeout(self.timeouts.start_timeout);
        }
        if (!self.status.started) {
            self.timeouts.start_timeout = window.setTimeout(
                function () {
                    self.status.started = true;
                    self.signal("change", self);
                },
                self.status.startDate.getTime() - new Date().getTime());
        }
        if (self.timeouts.end_timeout) {
            window.clearTimeout(self.timeouts.end_timeout);
        }
        if (!self.status.ended) {
            self.timeouts.end_timeout = window.setTimeout(
                function () {
                    self.status.ended = true;
                    self.signal("change", self);
                },
                self.status.endDate.getTime() - new Date().getTime());
        }
        if (self.timeouts.status_expire_timeout) {
            window.clearTimeout(self.timeouts.status_expire_timeout);
        }
        self.timeouts.status_expire_timeout = window.setTimeout(
            function () {
                self.status.expired = true;
                self.signal("status_expire", self);
            },
            self.status.cacheExpiresDate.getTime() - new Date().getTime());
    },
    _init_asin_statuses: function() {
        var self = this;
        
        if (!self.asins) {
            Deal.log ("No Asins, not initializing statuses.");
            return;
        }
        var now = new Date();
        
        
        var filteredAsins =
          Deal.filterAsinsByState (self.asins, 
            [Deal.inCartStr, Deal.expiredStr,
             Deal.waitInLineStr, Deal.pendingAtcStr]);
        
        
        for (var i = 0; i < filteredAsins.length; i++) {
            var asin = filteredAsins[i];
            if (self.purchaseStatusWarningThreshold) {
                
                asin.status.purchaseStatus.expiresDate =
                  new Date(now.getTime() +
                           parseInt(asin.status.purchaseStatus.msToExpiry, 10));
            }
        }
        
        filteredAsins.sort (Deal.sortByAsinTimes);
        
        self.asinExpiresSoonStack = [];
        self.asinExpiredStack     = [];
        
        
        for (var i = 0; i < filteredAsins.length; i++) {
            var asin = filteredAsins[i];
            
            if (self.timeouts.pstatus_exp_soon[asin.asin]) {
                window.clearTimeout
                  (self.timeouts.pstatus_exp_soon[asin.asin]);
            }
            var pStatusTimeout =
              asin.status.purchaseStatus.expiresDate.getTime() - new Date().getTime();
            var endsSoonTimeout = pStatusTimeout - self.purchaseStatusWarningThreshold;
            if (endsSoonTimeout < 0) {
                endsSoonTimeout = 0;
            }
            
            
            
            
            var staticAsin = "" + asin.asin;
            
            
            
            if (pStatusTimeout > 0 &&
                asin.status.purchaseStatus.state != Deal.waitInLineStr &&
                asin.status.purchaseStatus.state != Deal.pendingAtcStr) {
                
                self.asinExpiresSoonStack.push (staticAsin);
                
                self.timeouts.pstatus_exp_soon[staticAsin] =
                 window.setTimeout(
                   function() {
                       
                       var tmpAsin = self.asinExpiresSoonStack.shift();
                       self.signal("pstatus_expires_soon", self, tmpAsin);
                   },
                   endsSoonTimeout
                 );
            }
            
            if (self.timeouts.pstatus_expire[asin.asin]) {
                window.clearTimeout
                  (self.timeouts.pstatus_expire[asin.asin]);
            }
            
            self.asinExpiredStack.push (staticAsin);
            self.timeouts.pstatus_expire[asin.asin] =
             window.setTimeout(
               function() {
                   
                   var tmpAsin = self.asinExpiredStack.shift();
                   self.signal("pstatus_expire", self, tmpAsin);
                   self.signal("change", self);
               },
               pStatusTimeout
             );
        }
    },
    
    load_from_deal: function(deal) {
        var self = this;
        var now = new Date();
        var attr;
        for (var attr in deal) {
            self[attr] = deal[attr];
        }
        self.loading = false;
        self.startDate = new Date(self.startDate*1000);
        self.endDate = new Date(self.endDate*1000);
        self.cacheExpiresDate = new Date(
            now.getTime() + parseInt(self.msToCacheExpires, 10));
        self.expired = false;
	if (self.timeouts.expire_timeout) {
            window.clearTimeout(self.timeouts.expire_timeout);
        }
        self.timeouts.expire_timeout = window.setTimeout(
            function () {
                self.expired = true;
                self.signal("expire", self);
            },
            self.cacheExpiresDate.getTime() - new Date().getTime());
        self.limitedQuantity = self.limitedQuantity == '1';
        if (self.customer) {
            self.customer.claimed = self.customer.claimed == '1';
        }
        if (self.asins) {
            if (!self.timeouts.pstatus_exp_soon) {
                self.timeouts.pstatus_exp_soon = {};
            }
            if (!self.timeouts.pstatus_expire) {
                self.timeouts.pstatus_expire = {};
            }
            for (var i=0; i<self.asins.length; i++) {
                self.asins[i].offerServiceSoldOut =
                    self.asins[i].offerServiceSoldOut == "1";
            }
        }
        self._init_status(now);
        self._init_asin_statuses();
        self.signal('change', self);
    },
    
    load_from_status: function(status) {
        var self = this;
        self.status = status;
        self._init_status(new Date());
        self.signal('change', self);
    },
    load_from_asin_status: function(dealAsinStatus) {
        var self = this;
        if (self.asins == null) {
            return;
        }
        if (dealAsinStatus == null) {
            return;
        }
        var updatedAsin = dealAsinStatus.asin;
        for (var i = 0; i < self.asins.length; i++) {
            var asin = self.asins[i];
            if (asin.asin == updatedAsin) {
                self.asins[i].status = dealAsinStatus;
                break;
            }
        }
        self._init_asin_statuses();
        self.signal('change', self);
    },
    
    
    setPurchaseStatusWarningThreshold: function(thresholdMs) {
        if (thresholdMs > 0) {
            self.purchaseStatusWarningThreshold = thresholdMs;
        } else {
            self.purchaseStatusWarningThreshold = 2 * 60 * 1000;
        }
    },
    getPurchaseStatusWarningThreshold: function() {
        var self = this;
        return self.purchaseStatusWarningThreshold;
    }
});
Deal.Price = {
    currencies: {
        'USD':{
            decimals: 2,
            format: function(price) {
                return '$'+price;
            }
        }
    },
    make_price: function(currency, price) {
        return {currency:currency, price:price};
    },
    test_same_currency: function(a, b) {
        if (a.currency != b.currency) {
            throw new Error("Currencies don't match: "+
                a.currency+" and "+b.currency);
        }
    },
    minus: function(a, b) {
        if (!a || !b) {
            return undefined;
        }
        this.test_same_currency(a, b);
        return this.make_price(a.currency, a.price-b.price);
    },
    percent_off: function(higher, lower) {
        if (!higher || !lower) {
            return undefined;
        }
	this.test_same_currency(higher, lower);
	var discount = higher.price - lower.price;
	return discount * 100.0 / higher.price;
    },
    format: function(price, if_null) {
        if (!price) {
            return if_null;
        }
        var desc = this.currencies[price.currency];
        if (!desc) {
            desc = {
                decimals: 2,
                format: function(price) {
                    return price +' '+currency;
                }
            };
        }
        return desc.format(Deal.commify(price.price, desc.decimals));
    }
};
//RW Environment
//Separate controller for the Detail Page
//No Page concept, nor does it call getDeals or getDealMetadata
//TODO: We may want to move the controller to another file - since this is 
//exclusively for the DetailPageBuyBox
Deal.DPController = Deal.Class({
  signals: [
      
      'cell_change', 
  ],
  __init__: function(p) {
      var self = this;
      Deal.Signal.init(self);
      
      Deal.Service.base_retry_interval = p.base_retry_interval || 60000;
      Deal.Service.continue_requests = p.continue_requests || true;
      self.login_uri = p.login_uri;
      self.images    = p.images;
      self.widgetReftag = p.widgetReftag;
      self.service = new Deal.Service({
          marketplace_id: p.marketplace_id,
          customer_id: p.customer_id,
          session_id: p.session_id,
          timeout: p.timeout,
          includeVariations: p.includeVariations,
          widgetReftag: self.widgetReftag
      });
      
      self.buying = {};
      
      self.deals = new Deal.Model.Deals(p.deals);
      
      self.statuses = {
          available:Deal.Service.AndDealFilter([
              Deal.Service.EndDateDealFilter("now", null),
              Deal.Service.SoldOutDealFilter(false)
          ]),
          upcoming: Deal.Service.StartDateDealFilter("now", null),
          sold_out: Deal.Service.SoldOutDealFilter(true),
          expired: Deal.Service.EndDateDealFilter(null, "now")
      };
      
      self.varPopCloseFunction = null;
      
      
      self.connections = [ ];
      
      self.cells = 1;
      
      self.cell_to_deal = {};
      
      self.deal_id_to_cell = {};
      
      self.page = 1;
      
      self.pages = 1;
      
      self.filter = undefined;
      
      self.order = undefined;
      
      
      self.deal_ids = [];
      if (p.deals && p.deals.length > 0) {
      	//There should only be one deal ID
      	self.deal_ids.push(p.deals[0].dealID);
      }
      self.set_page(1);
  },
  closeVarPopover: function() {
      var self = this;
      if ( self.varPopCloseFunction ) {
	  self.varPopCloseFunction();
      }
  },
  setVarPopCloseFunction: function(funcPointer) {
      var self = this;
      self.closeVarPopover();
      self.varPopCloseFunction = funcPointer;
  },
  set_page: function(page) {
      var self = this;
      var cell = 0;
      var deal;
      var deal_id;
      self.disconnect_all();
      self.cell_to_deal = [];
      self.deal_id_to_cell = {};
      deal_id = self.deal_ids[0];
      self.deal_id_to_cell[deal_id] = cell;
      deal = self.deals.get_deal(deal_id);
      self.cell_to_deal[cell] = deal;
      self.connect_deal_change(cell, deal);
      self.connect_deal_expire(deal);
      self.connect_deal_status_expire(deal);
      self.signal('cell_change', cell, deal);
  },
  
  
  connect_deal_change: function(cell, deal) {
      var self = this;
      var conn = deal.connect('change', function(deal) {
          self.signal('cell_change', cell, deal);
      });
      self.connections.push(conn);
  },
  
  connect_deal_expire: function(deal) {
      var self = this;
      var conn = deal.connect('expire', function(deal) {
          var conn2;
          var req = self.service.get_deal(deal.dealID,
              function(data) {
                  conn2.disconnect();
                  deal.load_from_deal(data);
              }, function(error) {
                  Deal.log("Error getting deal: "+error+
                      " stack: "+error.stack);
              });
          conn2  = conn.connect('disconnect', function() {
              req.cancel();
          });
      });
      self.connections.push(conn);
      if (deal.expired || deal.loading) {
          conn.trigger(deal);
      }
  },
  
  connect_deal_status_expire: function(deal) {
      var self = this;
      var conn = deal.connect('status_expire', function(deal) {
          
          var conn2;
          var req = self.service.get_deal_status(deal.dealID,
              function(status) {
                  conn2.disconnect();
                  deal.load_from_status(status);
              }, function(error) {
                  Deal.log("Error getting status: "+error+
                      " stack: "+error.stack);
              });
          conn2  = conn.connect('disconnect', function() {
              req.cancel();
          });
          
          var conn3;
          var req2 = self.service.get_deal_asin_status2(deal.dealID,
              function(asinStatusMap) {
                  conn3.disconnect();
                  
                  
                  
                  
                  var asins = deal.asins;
                  for (var i=0; i<asins.length; i++) {
                      var asin = asins[i];
                      if (asinStatusMap[asin.asin]) {
                          asin.status = asinStatusMap[asin.asin];
                          asin.status.purchaseStatus.expiresDate =
                            new Date (new Date().getTime() +
                               parseInt (asin.status.purchaseStatus.msToExpiry, 10));
                      }
                  }
                  deal._init_asin_statuses();
                  
                  deal.signal('change', deal);
              },
              function(error) {
                Deal.log("error with GetDealAsinStatus("+
                  deal.dealID+"):"+error);  
              }
          );
          conn3  = conn.connect('disconnect', function() {
              req2.cancel();
          });
      });
      self.connections.push(conn);
      if (deal.status.expired && !(deal.expired || deal.loading)) {
          conn.trigger(deal);
      }
  },
  disconnect_all: function() {
      var self = this;
      
      for (var i=0; i < self.connections.length; i++) {
          self.connections[i].disconnect();
      }
      self.connections = [];
  },
  
  buy: function(deal, asin, waitlist) {
      var self = this;
      var resultDealAsin;
      
      
      if (!self.service.customer_id) {
          var uri = self.login_uri;
          uri = uri.replace(/%257BdealID%257D/,
              deal.dealID);
          uri = uri.replace(/%257Basin%257D/,
              asin);
          uri = uri.replace(/%257BmarketplaceID%257D/,
              self.service.marketplace_id);
          uri = uri.replace(/%257Bcategory%257D/,
              self.filter);
          window.location = uri;
          return;
      }
      
      if (!self.buying[deal.dealID]) {
          self.buying[deal.dealID] = true;
          self.service.redeem_deal(deal.dealID, asin,
              function(result) {
                  delete self.buying[deal.dealID];
                  
                  var resultDeal = result.deal;
                  if (asin != undefined) {
                      var dealAsins = resultDeal.asins;
                      
                      
                      for (var i = 0; i < dealAsins.length; i++) {
                          var tmpAsin = dealAsins[i];
                          if (tmpAsin.asin == asin) {
                              resultDealAsin = tmpAsin;
                              break;
                          }
                      }
                  } else {
                      
                      
                      
                      resultDealAsin = resultDeal.asins[0];
                  }
                  var resultDealAsinPurchaseStatus =
                    resultDealAsin.status.purchaseStatus;
                  var resultPurchaseState =
                    resultDealAsinPurchaseStatus.state;
                  
                  
                  //
                  if ( result.redeemed == 1 &&
			 (resultPurchaseState == Deal.waitInLineStr ||
			  resultPurchaseState == Deal.inCartStr) ) {
			deal.cartP =
			    new Deal.CartPopover( resultDeal, resultDealAsin, self.images,
						  waitlist, resultPurchaseState );
							self.closeVarPopover();
                      deal.load_from_deal(result.deal);
                      if (result.redeemed == 1 && window.dealNotifier) {
                          Deal.log ("Calling dealNotifier.registerDeal(" + resultDealAsin.asin + ", " + resultDeal.dealID + ")");
                          dealNotifier.registerDeal(resultDealAsin.asin, deal);
                      }
                  } else {
                      deal.load_from_deal(result.deal);
                  }
              }, function (error) {
                  delete self.buying[deal.dealID];
                  Deal.log("Error redeeming deal: "+error);
                  if (asin != null) {
                      var asins = deal.asins;
                      if (asins == null) {
                          deal.status.percentClaimed = 100;
                      } else {
                          for (var i = 0; i < asins.length; i++) {
                              var tmpAsin = asins[i];
                              if (tmpAsin.asin == asin) {
                                  tmpAsin.status.percentClaimed = 100;
                                  break;
                              }
                          }
                      }
                  } else {
                      deal.status.percentClaimed = 100;
                  }
                  deal.signal('change', deal);
              });
      }
  }
});
Deal.Controller = Deal.Class({
    signals: [
        
        'cell_change', 
        
        'page_change', 
        
        
        'metadata_change' 
    ],
    __init__: function(p) {
        var self = this;
        Deal.Signal.init(self);
        
        Deal.Service.base_retry_interval = p.base_retry_interval || 60000;
        Deal.Service.continue_requests = p.continue_requests || true;
        self.login_uri = p.login_uri;
        self.images    = p.images;
        self.service = new Deal.Service({
            marketplace_id: p.marketplace_id,
            customer_id: p.customer_id,
            session_id: p.session_id,
            timeout: p.timeout,
            includeVariations: p.includeVariations
        });
        
        self.buying = {};
        
        self.deals = new Deal.Model.Deals(p.deals);
        
        self.metadata = new Deal.Model.Metadata(
            p.filters,
            p.orderings);
        self.browseNodes = p.browseNodes;
        self.ordering = p.ordering;
        
        self.statuses = {
            available:Deal.Service.AndDealFilter([
                Deal.Service.EndDateDealFilter("now", null),
                Deal.Service.SoldOutDealFilter(false)
            ]),
            upcoming: Deal.Service.StartDateDealFilter("now", null),
            sold_out: Deal.Service.SoldOutDealFilter(true),
            expired: Deal.Service.EndDateDealFilter(null, "now")
        };
	
	self.varPopCloseFunction = null;
        
        
        self.connections = [ ];
        
        self.cells = 1;
        
        self.cell_to_deal = {};
        
        self.deal_id_to_cell = {};
        
        self.page = 1;
        
        self.pages = 1;
        
        self.filter = undefined;
        
        self.order = undefined;
        
        
        self.deal_ids = [];
    },
    closeVarPopover: function() {
	var self = this;
	if ( self.varPopCloseFunction ) {
	    self.varPopCloseFunction();
	}
    },
    setVarPopCloseFunction: function(funcPointer) {
	var self = this;
	self.closeVarPopover();
	self.varPopCloseFunction = funcPointer;
    },
    
    
    _calc_deal_ids: function() {
        var self = this;
        self.deal_ids = self.metadata.get_deal_ids(
            self.filter, self.order);
        self._calc_pages();
    },
    
    
    _calc_pages: function() {
        var self = this;
        self.pages = Math.ceil(self.deal_ids.length/self.cells);
    },
    
    set_status: function(status) {
        var self = this;
        if (!self.statuses[status]) {
            throw new Error("No such status '"+status+"'");
        }
        self.status = status;
        var status_filter = self.statuses[status];
        var filters = {
            all: Deal.Service.AndDealFilter([
                status_filter,
                Deal.Service.SlotDealFilter(["LD:ALL"])
            ]),
            gbld: Deal.Service.AndDealFilter([
                status_filter,
                Deal.Service.SlotDealFilter(["GBLD:ALL"])
            ])
        };
        for (var browseNodeID in self.browseNodes) {
            filters[browseNodeID] = Deal.Service.AndDealFilter([
                status_filter,
                self.browseNodes[browseNodeID]
            ]);
        }
        
        self.service.get_deal_metadata({
                filters: filters,
                orderings: { start: self.ordering }
            },
            function(result) {
                self.metadata = new Deal.Model.Metadata(
                    result.filters, result.orderings);
                self.signal('metadata_change');
            },
            function(error) {
                Deal.log("error="+error);
            });
    },
    
    set_filter: function(filter) {
        var self = this;
        if (!self.metadata.filters[filter]) {
            throw new Error("Invalid filter: "+filter);
        }
        self.filter = filter;
        self._calc_deal_ids();
    },
    
    set_ordering: function(ordering) {
        var self = this;
        if (!self.metadata.orderings[ordering]) {
            throw new Error("Invalid ordering: "+ordering);
        }
        self.order = ordering;
        if (self.filter) {
            self._calc_deal_ids();
        }
    },
    
    /* example:
        var deal_index = (cx.page-1) * cx.cells;
        cx.set_cells(5);
        cx.set_page(Math.floor(deal_index/cx.cells));
        You could also use set_page_to_show_deal somehow.
    */
    set_cells: function(cells) {
        var self = this;
        if (cells < 1) {
            throw new Error("Invalid number of cells: "+cells);
        }
        self.cells = cells;
        self._calc_pages();
    },
    
    /* Note on animation:
      If you want to animate, you have to keep the cell_change signal from
      changing the contents immediately. What you can do is this:
      - The function that updates the cell checks to see if we are animating.
        If so, don't update the cell, just remember the new deal. Otherwise,
        call the "real_update" function.
      - When the user presses "next" or whatever, set "animating=true" and
        then call "next_page".
      - After "next_page" or whatever has been called, your widget should know
        what deals go where because the cell_change signal has been emitted
        for each cell.
      - Initiate the animation by doing the "real_update" in whatever order
        and frequency you want.
    */
    prev_page: function(wrap) {
        var self = this;
        if (self.page == 1) {
            if (wrap) {
                self.set_page(self.pages);
            } else {
                throw new Error("already at first page");
            }
        } else {
            self.set_page(self.page-1);
        }
    },
    
    next_page: function(wrap) {
        var self = this;
        if (self.page < self.pages) {
            self.set_page(self.page+1);
        } else {
            if (wrap) {
                self.set_page(1);
            } else {
                throw new Error("already at last page");
            }
        }
    },
    
    set_page_to_show_deal: function(deal_id) {
        var self = this;
        if (!deal_id) {
            self.set_page(1);
            return;
        }
        
        var i;
        for (var i=0; i<self.deal_ids.length; i++) {
            if (self.deal_ids[i] == deal_id) {
                break;
            }
        }
        if (i == self.deal_ids.length) {
            Deal.log("couldn't find deal id "+deal_id);
            self.set_page(1);
            return;
        }
        self.set_page(Math.floor(i/self.cells) + 1);
    },
    set_page: function(page) {
        var self = this;
        var cell;
        var i;
        var deal;
        var deal_id;
        if (self.pages === 0) {
            for (var cell=0; cell<self.cells; cell++) {
                self.signal('cell_change', cell, null);
            }
            self.signal('page_change', 0, 0, 0, 0, 0);
            return;
        }
        if (page < 1 || Math.floor(page) != page) {
            throw new Error("invalid page: "+page);
        }
        if (page > self.pages) {
            throw new Error("page ("+page+")"+
                " exceeds pages ("+self.pages+")");
        }
        self.disconnect_all();
        self.page = page;
        self.cell_to_deal = [];
        self.deal_id_to_cell = {};
        self.signal("page_change",
            self.page,
            self.pages,
            (self.page-1)*self.cells+1,
            Deal.min(self.page*self.cells, self.deal_ids.length),
            self.deal_ids.length);
        
        for (var cell=0; cell < self.cells; cell++) {
            i = (self.page-1)*self.cells + cell;
            if (i < self.deal_ids.length) {
                deal_id = self.deal_ids[i];
                self.deal_id_to_cell[deal_id] = cell;
                deal = self.deals.get_deal(deal_id);
                self.cell_to_deal[cell] = deal;
                self.connect_deal_change(cell, deal);
                self.connect_deal_expire(deal);
                self.connect_deal_status_expire(deal);
                self.signal('cell_change', cell, deal);
            } else {
                
                self.cell_to_deal[cell] = null;
                self.signal('cell_change', cell, null);
            }
        }
        
        
        for (var i = self.page*self.cells;
                i < (self.page+1)*self.cells && i < self.deal_ids.length;
                i++
        ) {
            self.connect_deal_expire(self.deals.get_deal(self.deal_ids[i]));
            self.connect_deal_status_expire(self.deals.get_deal(self.deal_ids[i]));
        }
    },
    
    
    connect_deal_change: function(cell, deal) {
        var self = this;
        var conn = deal.connect('change', function(deal) {
            self.signal('cell_change', cell, deal);
        });
        self.connections.push(conn);
    },
    
    connect_deal_expire: function(deal) {
        var self = this;
        var conn = deal.connect('expire', function(deal) {
            var conn2;
            var req = self.service.get_deal(deal.dealID,
                function(data) {
                    conn2.disconnect();
                    deal.load_from_deal(data);
                }, function(error) {
                    Deal.log("Error getting deal: "+error+
                        " stack: "+error.stack);
                });
            conn2  = conn.connect('disconnect', function() {
                req.cancel();
            });
        });
        self.connections.push(conn);
        if (deal.expired || deal.loading) {
            conn.trigger(deal);
        }
    },
    
    connect_deal_status_expire: function(deal) {
        var self = this;
        var conn = deal.connect('status_expire', function(deal) {
            
            var conn2;
            var req = self.service.get_deal_status(deal.dealID,
                function(status) {
                    conn2.disconnect();
                    deal.load_from_status(status);
                }, function(error) {
                    Deal.log("Error getting status: "+error+
                        " stack: "+error.stack);
                });
            conn2  = conn.connect('disconnect', function() {
                req.cancel();
            });
            
            var conn3;
            var req2 = self.service.get_deal_asin_status2(deal.dealID,
                function(asinStatusMap) {
                    conn3.disconnect();
                    
                    
                    
                    
                    var asins = deal.asins;
                    for (var i=0; i<asins.length; i++) {
                        var asin = asins[i];
                        if (asinStatusMap[asin.asin]) {
                            asin.status = asinStatusMap[asin.asin];
                            asin.status.purchaseStatus.expiresDate =
                              new Date (new Date().getTime() +
                                 parseInt (asin.status.purchaseStatus.msToExpiry, 10));
                        }
                    }
                    deal._init_asin_statuses();
                    
                    deal.signal('change', deal);
                },
                function(error) {
                  Deal.log("error with GetDealAsinStatus("+
                    deal.dealID+"):"+error);  
                }
            );
            conn3  = conn.connect('disconnect', function() {
                req2.cancel();
            });
        });
        self.connections.push(conn);
        if (deal.status.expired && !(deal.expired || deal.loading)) {
            conn.trigger(deal);
        }
    },
    disconnect_all: function() {
        var self = this;
        
        for (var i=0; i < self.connections.length; i++) {
            self.connections[i].disconnect();
        }
        self.connections = [];
    },
    
    buy: function(deal, asin, waitlist) {
        var self = this;
	var resultDealAsin;
        
        
        if (!self.service.customer_id) {
            var uri = self.login_uri;
            uri = uri.replace(/%257BdealID%257D/,
                deal.dealID);
            uri = uri.replace(/%257Basin%257D/,
                asin);
            uri = uri.replace(/%257BmarketplaceID%257D/,
                self.service.marketplace_id);
            uri = uri.replace(/%257Bcategory%257D/,
                self.filter);
            window.location = uri;
            return;
        }
        
        if (!self.buying[deal.dealID]) {
            self.buying[deal.dealID] = true;
            self.service.redeem_deal(deal.dealID, asin,
                function(result) {
                    delete self.buying[deal.dealID];
                    
                    var resultDeal = result.deal;
                    if (asin != undefined) {
                        var dealAsins = resultDeal.asins;
                        
                        
                        for (var i = 0; i < dealAsins.length; i++) {
                            var tmpAsin = dealAsins[i];
                            if (tmpAsin.asin == asin) {
                                resultDealAsin = tmpAsin;
                                break;
                            }
                        }
                    } else {
                        
                        
                        
                        resultDealAsin = resultDeal.asins[0];
                    }
                    var resultDealAsinPurchaseStatus =
                      resultDealAsin.status.purchaseStatus;
                    var resultPurchaseState =
                      resultDealAsinPurchaseStatus.state;
                    
                    
                    //
                    if ( result.redeemed == 1 &&
			 (resultPurchaseState == Deal.waitInLineStr ||
			  resultPurchaseState == Deal.inCartStr) ) {
			deal.cartP =
			    new Deal.CartPopover( resultDeal, resultDealAsin, self.images,
						  waitlist, resultPurchaseState );
			self.closeVarPopover();
                        deal.load_from_deal(result.deal);
                        if (result.redeemed == 1 && window.dealNotifier) {
                            Deal.log ("Calling dealNotifier.registerDeal(" + resultDealAsin.asin + ", " + resultDeal.dealID + ")");
                            dealNotifier.registerDeal(resultDealAsin.asin, deal);
                        }
                    } else {
                        deal.load_from_deal(result.deal);
                    }
                }, function (error) {
                    delete self.buying[deal.dealID];
                    Deal.log("Error redeeming deal: "+error);
                    if (asin != null) {
                        var asins = deal.asins;
                        if (asins == null) {
                            deal.status.percentClaimed = 100;
                        } else {
                            for (var i = 0; i < asins.length; i++) {
                                var tmpAsin = asins[i];
                                if (tmpAsin.asin == asin) {
                                    tmpAsin.status.percentClaimed = 100;
                                    break;
                                }
                            }
                        }
                    } else {
                        deal.status.percentClaimed = 100;
                    }
                    deal.signal('change', deal);
                });
        }
    }
});
Deal.DOM = {
    set_attributes: function(el, attrs) {
        for (var attr in attrs) {
            if (attr == 'style') {
                el.style.cssText = attrs[attr];
            } else {
                var new_attr = {
                    "class": "className",
                    "checked": "defaultChecked",
                    "usemap": "useMap",
                    "for": "htmlFor",
                    "readonly": "readOnly",
                    "colspan": "colSpan",
                    "bgcolor": "bgColor",
                    "cellspacing": "cellSpacing",
                    "cellpadding": "cellPadding",
                    "valign":"vAlign",
                    "nowrap":"noWrap"
                }[attr] || attr;
                el[new_attr] = attrs[attr];
            }
        }
    },
    el: function(type, attrs, children) {
        var el = document.createElement(type);
        if (attrs) {
            Deal.DOM.set_attributes(el, attrs);
        }
        if (children) {
            this.appendChildren(el, children);
        }
        return el;
    },
    img: function(attrs) {
        attrs = attrs || {};
        attrs.border = attrs.border || 0;
        return this.el('img', attrs);
    },
    div: function(attrs, children) {
        return this.el('div', attrs, children);
    },
    span: function(attrs, children) {
        return this.el('span', attrs, children);
    },
    p: function(attrs, children) {
        return this.el('p', attrs, children);
    },
    a: function(attrs, children) {
        return this.el('a', attrs, children);
    },
    table: function(attrs, children) {
        if (!attrs) {
            attrs = {};
        }
        attrs.cellpadding = attrs.cellpadding || 0;
        attrs.cellspacing = attrs.cellspacing || 0;
        attrs.border = attrs.border || 0;
        return this.el('table', attrs, [this.el('tbody', null, children)]);
    },
    tr: function(attrs, children) {
        return this.el('tr', attrs, children);
    },
    td: function(attrs, children) {
        return this.el('td', attrs, children);
    },
    td_nowrap: function(attrs, children) {
        return this.el('td', attrs, [this.span({style:'white-space:nowrap'}, children)]);
    },
    br: function(attrs, children) {
        return this.el('br', attrs, children);
    },
    hr: function(attrs) {
        return this.el('hr');
    },
    select: function(attrs, children) {
        return this.el('select', attrs, children);
    },
    option: function(attrs, children) {
        return this.el('option', attrs, children);
    },
    appendChildren: function(el, children) {
        for (var i=0; i<children.length; i++) {
            var child = children[i];
            if (typeof child == "string" || typeof child == "number") {
                child = this.text(child);
            } else if (child instanceof Array) {
                this.appendChildren(el, child);
                continue;
            } else if (child === null || child === undefined) {
                continue;
            }
            el.appendChild(child);
        }
    },
    clearChildren: function(el) {
        while (el.firstChild) {
            el.removeChild(el.firstChild);
        }
    },
    replaceChildren: function(el, children) {
        this.clearChildren(el);
        this.appendChildren.call(this, el, children);
    },
    text: function(text) {
        return document.createTextNode(text);
    }
};
Deal.Widget = {};
Deal.Widget.preload_img = function(src) {
    if (!Deal.Widget.preload_img.div) {
        Deal.Widget.preload_img.div = Deal.DOM.div({'style':'display:none'});
    }
    Deal.Widget.preload_img.div.appendChild(Deal.DOM.img({src:src}));
};
Deal.clock = {
    signals: ['tick']
};
Deal.Signal.init(Deal.clock);
Deal.clock.tick = function() {
    Deal.clock.signal('tick');
};
window.setInterval(Deal.clock.tick, 250);
Deal.JQTimer = function(deal, noHours) {
    var self = {};
    if ( !deal.status.started || !deal.status.endDate ) {
	var now = new Date().getTime();
	deal.status.endDate =
	    self.t = new Date(now + parseInt(deal.status.msToEnd, 10));
	deal.status.started = deal.status.msToStart <= 0;
    }
    if (deal.status.started) {
    	self.t = deal.status.endDate;
    	self.span = jQuery("#gbd_time_remaining_id-" + deal.dealID);
	if (!self.span.length) {
	    self.span = jQuery("#lightningDealTimeRemaining");
	}
    } else {
    	self.t = deal.status.startDate;
    	self.span = jQuery("#gbd_starts_in_id-" + deal.dealID);
    }
    self.noHours = noHours;
    self.deal = deal;
    var attributes = {};
    self.onTimeoutFunction = null;
    
    self.setOnTimeoutFunction = function (timeoutFunction) {
		self.onTimeoutFunction = timeoutFunction;
    };
    self.update = function() {
	var msToT = Deal.max(
			self.t.getTime() - new Date().getTime(),
			0);
        if (msToT <= 0) {
		    self.disconnect();
		    
		    
		    
		    if (self.onTimeoutFunction != null) {
				self.onTimeoutFunction();
		    }
        }
        var h = Math.floor(msToT/(60*60*1000));
        if (h<10) { h = '0'+h; }
        var m = Math.floor(msToT/(60*1000)%60);
        if (m<10 && !self.noHours) { m = '0'+m; }
        var s = Math.floor(msToT/(1000)%60);
        if (s<10) { s = '0'+s; }
        var formatted = '';
		if ( self.noHours ) {
		    formatted = m+':'+s;
		} else {
		    formatted = h+':'+m+':'+s;
		}
	    if (self.deal.status.started) {
	    	self.span.each(function() { 
	    		jQuery(this).text(gbResources.getString("csld-time_remaining", {time: formatted}));
	    	});
	    } else {
	    	self.span.each(function() {
		        jQuery(this).text(gbResources.getString("deal_starts_in", {time_left: formatted}));
	        });
	    }
    };
    self.disconnect = function() {
        self.cx.disconnect();
    };
    self.cx = Deal.clock.connect('tick', self.update);
    self.update();
    return self;
};
Deal.InCart = {};
Deal.InCart.JQTimer = function(expiryTime, spanID, noHours) {
    var self = {};
    self.span = jQuery(spanID);
    self.t = expiryTime;
    self.noHours = noHours;    
    self.onTimeoutFunction = null;
    
    self.setOnTimeoutFunction = function (timeoutFunction) {
		self.onTimeoutFunction = timeoutFunction;
    };
    self.update = function() {
    	var msToT = Deal.max(self.t.getTime() - new Date().getTime(),0);
        if (msToT <= 0) {
		    self.disconnect();
		    
		    
		    
		    if (self.onTimeoutFunction != null) {
				self.onTimeoutFunction();
		    }
        }
        var h = Math.floor(msToT/(60*60*1000));
        if (h<10) { h = '0'+h; }
        var m = Math.floor(msToT/(60*1000)%60);
        if (m<10 && !self.noHours) { m = '0'+m; }
        var s = Math.floor(msToT/(1000)%60);
        if (s<10) { s = '0'+s; }
        var formatted = '';
	if ( self.noHours ) {
	    formatted = m+':'+s;
	} else {
	    formatted = h+':'+m+':'+s;
	}
	self.span.each(function() { 
	    jQuery(this).text(formatted);
	});
    };
    self.disconnect = function() {
        self.cx.disconnect();
    };
    self.cx = Deal.clock.connect('tick', self.update);
    self.update();
    return self;
};
Deal.Timer = function(t, spanAttributes, noHours) {
    var self = {};
    self.t = t;
    self.noHours = noHours;
    var attributes = {};
    self.span = Deal.DOM.span (attributes);
    self.onTimeoutFunction = null;
    
    self.setOnTimeoutFunction = function (timeoutFunction) {
	self.onTimeoutFunction = timeoutFunction;
    };
    self.update = function() {
	var msToT = Deal.max(
			self.t.getTime() - new Date().getTime(),
			0);
        if (msToT <= 0) {
	    self.disconnect();
	    
	    
	    
	    if (self.onTimeoutFunction != null) {
		self.onTimeoutFunction();
	    }
        }
        var h = Math.floor(msToT/(60*60*1000));
        if (h<10) { h = '0'+h; }
        var m = Math.floor(msToT/(60*1000)%60);
        if (m<10 && !self.noHours) { m = '0'+m; }
        var s = Math.floor(msToT/(1000)%60);
        if (s<10) { s = '0'+s; }
	if ( self.noHours ) {
	    self.span.innerHTML = m+':'+s;
	} else {
	    self.span.innerHTML = h+':'+m+':'+s;
	}
    };
    self.disconnect = function() {
        self.cx.disconnect();
    };
    self.cx = Deal.clock.connect('tick', self.update);
    self.update();
    return self;
};
Deal.CheckoutTimer = function(t, useColons) {
    var self = {};
    self.span = Deal.DOM.span();
    self.t = t;    
    self.onTimeoutFunction = null;
    
    self.setOnTimeoutFunction = function (timeoutFunction) {
        self.onTimeoutFunction = timeoutFunction;
    };
    self.update = function() {
	var msToT = Deal.max(
			     self.t.getTime() - new Date().getTime(),
			     0);
    if (msToT <= 0) {
        self.disconnect();
        
        
        
        if (self.onTimeoutFunction != null) {
            self.onTimeoutFunction();
        }
        return;
    }
    
	var minutes = Math.floor(msToT/(60*1000)%60);
	if (minutes < 10 && useColons) { minutes = '0' + minutes; }
	var seconds = Math.floor(msToT/(1000)%60);
    if (seconds < 10 && useColons) { seconds = '0' + seconds; }
    
    var minutesString = ' minutes ';
    var secondsString = ' seconds';
    
    if (useColons) {
        minutesString = '<span class="timeleft-large">:</span>';
        secondsString = '';
    }
	var timeleft = '<span class="timeleft-large">'+minutes+'</span>'+
	               minutesString+
	               '<span class="timeleft-large">'+seconds+'</span>'+
	               secondsString;
        self.span.innerHTML  = timeleft;
    };
    self.disconnect = function() {
        self.cx.disconnect();
    };
    self.cx = Deal.clock.connect('tick', self.update);
    self.update();
    return self;
};
Deal.truncate_text = function(el) {
    if (el.scrollHeight > el.clientHeight ||
            el.scrollWidth > el.clientWidth
    ) {
        el.title = el.innerHTML;
    }
    while (el.scrollHeight > el.clientHeight ||
            el.scrollWidth > el.clientWidth
    ) {
        el.innerHTML = el.innerHTML.replace(
            /\s*\S{0,10}$/, '...');
    }
};
Deal.truncate_description = function(el, len) {
    var truncated = '';
    var text = el.innerHTML;
    if (text == null) {
	
	
	if ( typeof(el) == 'string' ) {
	    text = el;
	} else {
	    return truncated;
	}
    }
    if (text.length > len) {
        var str = text.substr(0, len);
        var words = str.split(' ');
        words[words.length-1] = '';
        truncated = words.join(' ');
        truncated = truncated.replace(/[^\w\d]*$/, '');
        truncated = truncated.substr(0, truncated.length - 1) + '...';
        el.title = el.innerHTML;
        el.innerHTML = truncated;
    }
    return truncated;
};
Deal.help = function(link, isText) {
    
    
    
    var divSpan;
    var trigger;
    if ( isText ) {
	divSpan = Deal.DOM.span(null, [
	   trigger = Deal.DOM.a({href: '#', id:'waitlist_help'}, [ link ])
	]);
    } else {
	divSpan = Deal.DOM.div(null, [
            trigger = Deal.DOM.img({src: link})
	]);
    }
    amznJQ.available('popover', function() {
        jQuery(trigger).amazonPopoverTrigger({
            showOnHover: true,
            showCloseButton: false,
            location: ['left', 'bottom'],
            width: 300,
            literalContent: "Lightning Deals are discount promotions that are limited in both time and quantity. To buy a Lightning Deal item, you must be signed in to Amazon.com and then add the item to your cart from the Lightning Deals box."
        });
    });
    return divSpan;
};
Deal.CombinationGenerator = Deal.Class({
    __init__: function (n, r) {
        var self = this;
        self.a = new Array();
        self.n = null;
        self.r = null;
        if (r > n) {
          Deal.log("ERROR: r > n");
          return;
        }
        if (n < 1) {
          Deal.log("ERROR: n < 1");
          return;
        }
        self.n = n;
        self.r = r;
        self.a = new Array(r);
        var nFact = self.getFactorial (n);
        var rFact = self.getFactorial (r);
        var nminusrFact = self.getFactorial (n - r);
        self.total = nFact / (rFact * nminusrFact);
        self.reset ();
    },
    reset: function () {
      var self = this;
      for (var i = 0; i < self.a.length; i++) {
        self.a[i] = i;
      }
      self.numLeft = self.total;
    },
    getNumLeft: function () {
      var self = this;
      return self.numLeft;
    },
    hasMore: function() {
      var self = this;
      if (self.numLeft == 0) {
          return false;
      }
      return true;
    },
    getTotal: function () {
      var self = this;
      return self.total;
    },
    getFactorial: function (n) {
      var self = this;
      var fact = 1;
      for (var i = n; i > 1; i--) {
        fact *= i;
      }
      return fact;
    },
    getNext: function () {
       var self = this;
      if (self.numLeft == self.total) {
        self.numLeft -= 1;
        return self.a;
      }
      var i = self.r - 1;
      while (self.a[i] == self.n - self.r + i) {
        i--;
      }
      self.a[i] = self.a[i] + 1;
      for (var j = i + 1; j < self.r; j++) {
        self.a[j] = self.a[i] + j - i;
      }
      self.numLeft -= 1;
      return self.a;
    }
});
Deal.PStatus = Deal.Class({
    /* Class used for timed checkout functionality */
    __init__: function(deal, images) {
	var self = this;
	self.deal = deal;
	self.widget = deal.widget;
	self.images = deal.images || images;
	self.popover = deal.popover;
	self.stateAsins = {};
	
        for (var i = 0; i < self.deal.asins.length; i++) {
	    var tmpAsin = self.deal.asins[i];
	    var pState = Deal.getPurchaseState(deal, tmpAsin);
	    if ( self.stateAsins[pState] == undefined ) {
		self.stateAsins[pState] = [];
	    }	    
	    self.stateAsins[pState].push(tmpAsin);
        }
	
	if ( self.stateAsins[Deal.inCartStr] ) {
	    self.stateAsins[Deal.inCartStr].sort(self.sortByAsinTimes);
	}
	
	if ( self.stateAsins[Deal.pendingAtcStr] ) { 
	    self.stateAsins[Deal.pendingAtcStr].sort(self.sortByAsinTimes); 
	} 
    },
    sortByAsinTimes: function(a, b) {
	
	
	var x = a.status.purchaseStatus.expiresDate;
	var y = b.status.purchaseStatus.expiresDate;
	return x-y;
    },
    getCount: function(pState, onlyValids) {
	
	
	var self = this;
	var now  = new Date();
	var length = 0;
	if ( self.stateAsins[pState] && self.stateAsins[pState].length ) {
	    for (var i=0; i < self.stateAsins[pState].length; i++) {
		var expDate =
		    self.stateAsins[pState][i].status.purchaseStatus.expiresDate;
		if ( !expDate ) {
		    self.stateAsins[pState][i].status.purchaseStatus.expiresDate =
			new Date (new Date().getTime() +
				  parseInt(self.stateAsins[pState][i].status.purchaseStatus.msToExpiry, 10));
		    expDate = self.stateAsins[pState][i].status.purchaseStatus.expiresDate;
		} 
		if ( onlyValids && expDate.getTime() > now.getTime() ) {
		    length++;
		} else if ( !onlyValids ) {
		    length++;
		}
	    }
	}
	return length;
    },
    getTime: function(asinString, state) {
	
	
	
	
        var self = this;
	var now = new Date();
        
        
	
	
	
	
	if ( self.stateAsins[state] ) { 
	    for ( var i=0; i < self.stateAsins[state].length; i++ ) { 
		
		var expDate = 
		    self.stateAsins[state][i].status.purchaseStatus.expiresDate; 
		if ( !expDate ) {
		    self.stateAsins[state][i].status.purchaseStatus.expiresDate =
			new Date (new Date().getTime() +
				  parseInt (self.stateAsins[state][i].status.purchaseStatus.msToExpiry, 10));
		    expDate = self.stateAsins[state][i].status.purchaseStatus.expiresDate;
		}
		if ( asinString != undefined ) { 
		    if ( self.stateAsins[state][i].asin == asinString ) { 
			return expDate;
		    } 
		} else { 
		    
		    if ( expDate.getTime() > now.getTime() ) { 
			self.asin = self.stateAsins[state][i]; 
			return expDate;
		    } 
		} 
	    } 
	}
        return null;
    },
    getTimerMessage: function(asinString, num, pending) {
	
	
	
	
	
	var self = this;
	var timerMsg = null;
	if ( asinString != undefined && asinString != null ) {
	    self.asinString = asinString;
	}
	var state = Deal.inCartStr;
	if ( pending ) {
	    state = Deal.pendingAtcStr;
	}
	var expiresDate = self.getTime(self.asinString, state);
	var timer = Deal.Timer(expiresDate, null, true);
	if ( pending ) { 
	    timerMsg = Deal.DOM.div({'class':'status_msg_content', id: 'gbd_status_inner_msg'},
			    ['You have ', 
			     Deal.DOM.span({'class':'status_timer'}, 
				  [timer.span]), 
			     ' to add this deal to your Cart.']); 
	} else { 
	    var ending = ' to receive this discount.';
	    if ( num != null && num > 1 ) {
		ending = ' to receive these discounts.';
	    }
	    timerMsg = Deal.DOM.div({'class':'status_msg_content', id: 'gbd_status_inner_msg'},
			   [Deal.DOM.a({href: '/gp/cart/view.html/ref=gno_cart'}, ['Check out']),
			   ' within ',
			   Deal.DOM.span({'class':'status_timer'},
			   [timer.span]),
			   ending
		       ]);
	}
	return timerMsg;
    },
    getItemMsg: function(num, type) {
	var self = this;
	
	var singleMsg = 'This deal is in your Cart.'; 
	var allMsg = 'All options are in your Cart.'; 
	if ( type == 'wait' ) { 
	    singleMsg = 'You are on the waitlist.'; 
	    allMsg = 'You are on the waitlist for all options.'; 
	} 
	var itemMsg = Deal.DOM.span({'class':'item_msg'},
				    [singleMsg]);
	
	if ( self.deal.asins.length > 1 ) {
	    var finalMsg = null;
	    if ( num > 1 && num == self.deal.asins.length ) { 
		
		finalMsg = allMsg;
	    } else if ( num == 1 ) { 
		
		finalMsg = '1 option is in your Cart'; 
		if ( type == 'wait' ) { 
		    finalMsg = 'You are on the waitlist for 1 option.'; 
		} 
	    } else if ( num > 1 ) { 
		
		finalMsg = num + ' options are in your Cart'; 
		if ( type == 'wait' ) { 
		    finalMsg = 'You are on the waitlist for ' 
			+ num + ' options.'; 
		} 
	    }
	    jQuery(itemMsg).html(finalMsg);
	}
	return itemMsg;
    },
    getMixedItemMsg: function(cartNum, waitNum) { 
	var self = this; 
	var itemMsg = Deal.DOM.span({'class':'item_msg_normal'}, []); 
	 
	 
	var inCart = Deal.DOM.span({}, [ 
			  Deal.DOM.span({style:'font-weight:bold;'}, 
			       [cartNum]), 
			  ' in Cart. ']); 
	var inWaitlist = Deal.DOM.span({}, [ 
			      Deal.DOM.span({style:'font-weight:bold;'}, 
				   [waitNum]), 
			      ' on Waitlist.']); 
	var minTimeLeft = self.getTime (null, Deal.inCartStr); 
	var timer = Deal.Timer(minTimeLeft, null, true); 
	var timerMsg = Deal.DOM.span({'class':'timer'}, [ 
			    Deal.DOM.span({style:'font-color:#666666 !important;'}, 
				 ['Check out in ']), 
			    Deal.DOM.span({'class':'status_timer'}, 
				 [timer.span])]); 
	Deal.DOM.appendChildren(itemMsg,
	     [inCart, 
	      timerMsg, 
	      Deal.DOM.br(), 
	      inWaitlist]); 
	return itemMsg; 
    }, 
    showBuying: function() { 
	var self = this; 
	Deal.DOM.replaceChildren(self.buyButtonContainer, [ 
	     Deal.DOM.img({ 
		  id:'buying-'+self.deal.dealID, 
		  alt:'Checking deal status...', 
		  src:self.images.spinner 
	     }),  
	     Deal.DOM.span({ 
		  style:'color: #E68221;'+ 
			'font-weight: bold;'+ 
			    'font-size: 10px;' },
		  ["Checking Deal Status"]) 
	     ]); 
	self.buy_button.onclick = null; 
    },
    getContent: function(showMinMessaging) {
	
	
	
	var self = this;
	var numInCart         = self.getCount(Deal.inCartStr, false);
	var validNumInCart    = self.getCount(Deal.inCartStr, true);
	var numInWaitlist     = self.getCount(Deal.waitInLineStr, false); 
	var numPending        = self.getCount(Deal.pendingAtcStr, true); 
	var invalidNumPending = self.getCount(Deal.pendingAtcStr, false); 
	if ( numPending > 0 ) { 
	    
	    
	    self.buy_button = Deal.DOM.img({ 
                                 style:'cursor:pointer;', 
				 src:self.images.add_to_cart}); 
	    self.buyButtonContainer = Deal.DOM.div({style:'opacity:1.0;'+ 
							 'filter:"alpha(opacity=100)"'+ 
							 'cursor:pointer'}, 
					  [ self.buy_button ]); 
	    self.buy_button.onclick =  function(event) { 
		if (event) { 
		    event.stopPropagation(); 
		    event.preventDefault(); 
		}
		self.widget.cx.buy(self.deal, self.asin.asin, true); 
		self.showBuying(); 
		return false; 
	    }; 
	    var timerMsg = self.getTimerMessage(null, numPending, true);
	    if ( showMinMessaging ) {
		return timerMsg;
	    }
	    if ( Deal.isPrimeEligible(self.deal) ) { 
		self.deal.prime = Deal.DOM.span({style:'display:block; position:absolute; left: 115px;'},
		            [ Deal.DOM.img({ 
                                    alt:'Prime Eligible', 
				    id:'prime-badge-'+self.deal.dealID,
				    src:self.images.prime}) 
		]);
	    } 
	    return Deal.DOM.div({style:'position:relative;'}, [
		        self.deal.prime, 
		        Deal.DOM.div({style:'position:relative;'},
			     [self.buyButtonContainer, 
			      timerMsg]) ]);
	} else if ( numInCart == 0 && numInWaitlist == 0 &&
                invalidNumPending == 0) {
	    
	    
	    return null;
	} else if ( validNumInCart == 0 && numInWaitlist == 0) {
	    
	    
	    if ( showMinMessaging ) {
		return Deal.DOM.div({'class':'status_content bb_status_content', id:'gbd_status_msg_content'},
		       [Deal.DOM.img({
			    style:'padding:5px;',
			    alt:'...',
			    src:self.images.spinner
		        })
		]);
	    }
	    return Deal.DOM.div({'class':'status_content', id: 'gbd_status_msg_content'},
		       [Deal.DOM.img({
			    id:'buying-'+self.deal.dealID,
			    alt:'Checking deal status...',
			    src:self.images.spinner
		        }), 
		        Deal.DOM.span({
		    	    style:'color: #E68221;'+
			          'font-weight: bold;'+
				  'font-size: 10px;'
			}, ["Checking Deal Status"])
	           ]);
	}
	
	
	
	var itemMsg  = null; 
	var timerMsg = null; 
	if ( validNumInCart > 0 && numInWaitlist == 0 ) { 
	    
	    itemMsg = self.getItemMsg(validNumInCart, 'cart'); 
	    timerMsg = self.getTimerMessage(null, validNumInCart, false); 
	} else if ( numInWaitlist > 0 && validNumInCart == 0) {
	    
	    itemMsg = self.getItemMsg(numInWaitlist, 'wait'); 
	} else { 
	    
	    
	    itemMsg = self.getMixedItemMsg(validNumInCart, numInWaitlist); 
	} 
	
	
	
	if ( self.deal.asins.length > 1 && !self.deal.status.ended ) { 
	    var optionsLink = null;
	    optionsLink = Deal.DOM.span({'class':'options_link'},
			      [ 'Select more options' ]);
	    if ( !self.popOver ) {
		self.popOver = new Deal.VariationPopover(optionsLink, self.deal, self.deal.images,
				       self.widget.cx);
	    }
	}
	return Deal.DOM.div({'class':'status_content', id: 'gbd_status_msg_content'},
		   [ itemMsg,
		     Deal.DOM.br(),
		     timerMsg,
		     optionsLink]);
    }
});
Deal.CartPopover = Deal.Class({
    
    
    
    __init__: function(deal, asin, images, waitlist, pState) {
        var self      = this;
        self.deal     = deal;
        self.asin     = asin;
        self.images   = images;
	self.waitlist = waitlist; 
	self.state    = pState;
	self.popover  = self.getPopoverDiv(); 
	var width = 835; 
	var height = 250; 
	var modal = true; 
	var closeText = 'Close and Continue Shopping'; 
	if ( self.state == Deal.waitInLineStr ) { 
	    width = 410; 
	    closeText = null; 
	    modal = false; 
	}
	
	amznJQ.available('popover', function() {
	    self.pop =
		jQuery.AmazonPopover.displayPopover({
		    showCloseButton: true,
		    width: width,
		    height: height,
		    modal: modal,
		    closeText: closeText,
		    closeEventInclude: 'CLICK_OUTSIDE',
		    location: 'centered',
		    locationAlign: 'middle',
		    literalContent: self.popover,
		    onHide: function() {
			self.onHide();
		    }
	    });
	});
	
	
	if (self.timer) {
	    self.timer.setOnTimeoutFunction (function() {
		if (self.pop) {
		    self.pop.close();
		}
	    });
	}
    },
    getPopoverDiv: function() {
	
	var self  = this;
	var imgStyle = 'background:url('+self.images.sprite_site_wide +') -30px -189px;'; 
	var header = '1 deal added to Cart'; 
	var labelStyle = null; 
	if ( self.state == Deal.waitInLineStr ) { 
	    header = 'You joined this waitlist'; 
	    labelStyle = 'font-size: 15px; top:-3px; left:23px;'; 
	    imgStyle = 'background:url('+self.images.sprite_site_wide+ 
		       ') -139px -189px; height: 20px; width: 20px;'; 
	}
	var space = Deal.DOM.br();
	var timerBox = null;
	var msgBox = null;
	var colWidth = 'width: 280px;'; 
	var cartPopClass = 'image_title_price'; 
	if ( self.state == Deal.waitInLineStr ) { 
	    cartPopClass = 'image_title_price_waitlist'; 
	    msgBox = self.get_inner_message(); 
	    colWidth = 'width: 410px;'; 
	    space = null;
	} else { 
	    timerBox = self.get_timer_atc_table(); 
	} 
	return Deal.DOM.div({}, [
		   Deal.DOM.div({style:'position:relative;'}, [
		       Deal.DOM.div({'class':'greencheck_img',
			             style:imgStyle}, [
                            Deal.DOM.span({'class':'cart-popover-label',
				           style:labelStyle},
					  [header])
			   ])
		   ]),
		   space,
		   Deal.DOM.table({'class':'cart-popover-table'}, [
		       Deal.DOM.tr({}, [
			   Deal.DOM.td({'class':cartPopClass,
					style:colWidth}, [
				self.get_image_title_price_table(),
			   ]),
			   timerBox
		       ]),
		       msgBox
		    ])
		]);
    },
    get_position_chance: function() {
	
	
	var self = this;
	var div = null;
	
	if ( self.asin.status.purchaseStatus.waitListStatus == undefined ||
	     self.asin.status.purchaseStatus.waitListStatus == null ) {
	    return null;
	} else {
	    var wlStatus  = self.asin.status.purchaseStatus.waitListStatus;
	    var indicator = wlStatus.inCartIndicator;
	    var position  = wlStatus.position;
	    if ( indicator == undefined || indicator == null ||
		 position == undefined || position == null ) {
		return null;
	    }
	}
	if ( self.asin.status.purchaseStatus.waitListStatus != undefined ) {
	    var inCartIndicator =
		self.asin.status.purchaseStatus.waitListStatus.inCartIndicator;
	    var position =
		self.asin.status.purchaseStatus.waitListStatus.position;
	    if ( position > 500 ) {
		position = '500+';
	    } else {
		position = '#' + position;
	    }
	    div = Deal.DOM.div({'class':'waitlist_position_chance'}, [
	      Deal.DOM.br(),
              Deal.DOM.table({}, [
		 Deal.DOM.tr({}, [
                      Deal.DOM.td({'class':'pc_value'}, [
			   position
                      ]),
		      Deal.DOM.td(null, ['\xA0']),
                      Deal.DOM.td({'class':'pc_label'},
			   ['Your place on the waitlist']) ]),
		 Deal.DOM.tr({}, [
                      Deal.DOM.td({'class':'pc_value'}, [
			   inCartIndicator
                      ]),
		      Deal.DOM.td(null, ['\xA0']),
                      Deal.DOM.td({'class':'pc_label'},
			   ['Your chance of getting this deal']) ])
            ]) ]);
	}
	return div;
    },
    get_inner_message: function() { 
	
	
	var self = this; 
	var message = Deal.DOM.span({'class':'waitlist_message'}, [ 
			   'You can continue shopping and we will alert ' + 
			   'you when this deal becomes available.']); 
	
	if ( self.state == Deal.waitInLineStr && !self.waitlist) { 
	    jQuery(message).html('We added you the waitlist because the deal has ' + 
				 'reached capacity. We will alert you when this ' + 
				 'deal becomes available.'); 
	} 
	var bubbleStyle = 'background:url('+self.images.sprite_site_wide+') -117px -189px;'; 
	var bubble = Deal.DOM.div({'class':'bubble', style:bubbleStyle}, [' ']); 
	var continueShopping = Deal.DOM.a({ 
                                    href:'#', 
				    'class':'ap_custom_close'}, [ 
				    Deal.DOM.img({ 
                                         alt:'Continue shopping', 
					 src:self.images.continue_shopping 
				    }) 
			       ]); 
	return Deal.DOM.tr({}, [ Deal.DOM.td({}, [ 
		    Deal.DOM.table({'class':'inner_message_waitlist'}, [ 
			 Deal.DOM.tr({}, [ 
                              Deal.DOM.td({style:'padding: 8px 0 8px 8px;'}, 
                                   [ bubble ]), 
                              Deal.DOM.td({style:'padding: 8px;'}, [ message ]) 
                         ]), 
                         Deal.DOM.tr({}, [ 
			      Deal.DOM.td({style:'padding: 0 8px 8px;', colSpan:2}, 
                                   [ continueShopping ]) 
                         ]) 
               ])])]); 
    }, 
    get_timer_atc_table: function() {
	
	
	var self = this;
	return Deal.DOM.td({}, [
		 Deal.DOM.table({'class':'inner-cart-popover-table'}, [
		   Deal.DOM.tr({}, [
		       Deal.DOM.td({style:'text-align:right; padding:10px;'}, [
			   Deal.DOM.a({
			     href:'/gp/cart/view.html/ref=gno_cart'}, [
			       Deal.DOM.img({
				   alt:'View Cart and proceed to Checkout',
				   src:self.images.view_cart_proceed
			       })
			   ]),
 			   Deal.DOM.br(),
			   self.get_timer_message_table()
		       ])
		   ])
	       ])
	   ]);
    },
    get_timer_message_table: function() {
	
	
	var self = this;
	var message = null;
	if (gbResources.features["notifier-waitlist"] &&
	    gbResources.features["notifier-waitlist"] != 'C') {
	    message =
		'If you choose not to check out now, we will alert you again ' +
		gbResources.features["notifier-waitlist"] +
		' minutes before you need to complete checkout.';
	}
	return Deal.DOM.table({'class':'inner-msg'}, [
		   Deal.DOM.tr({}, [
		       Deal.DOM.td({style:'padding: 8px 8px 0px;'}, [
			   Deal.DOM.div({'class':'alert',
			       style:'background:url('+self.images.sprite_site_wide+');' }, [])
			   ]),
			   Deal.DOM.td({'class':'msgtitle'}, [
			       'To receive the Lightning Deal price, complete checkout within:'
			   ])
		       ]),
		   Deal.DOM.tr({}, [
		       Deal.DOM.td(null, []),
		       Deal.DOM.td({'class':'timeleft'}, [
			   self.timeRemaining()
		       ])
		   ]),
		   Deal.DOM.tr({}, [
		       Deal.DOM.td(null, []),
		       Deal.DOM.td({'class':'smallprint', style:'width:100%;'}, [
                           message
		       ])
		   ])
	       ]);
    },
    get_image_title_price_table: function() {
	
	
	var self = this;
	var asinImage = self.asin.imageURL;
	asinImage = asinImage = Deal.resizeImage(asinImage, 85);
	var asinTitle = null;
	if ( self.deal.detail.title ) {
	    asinTitle =
		Deal.truncate_description(self.deal.detail.title, 65);
	}
	var prevPrice = null;
	if ( self.asin.listPrice ) {
	    prevPrice = Deal.Price.format(self.asin.listPrice);
	} else if ( self.asin.ourPrice) {
	    prevPrice = Deal.Price.format(self.asin.ourPrice);
	}
	var spacing = Deal.DOM.br(); 
	var dpLabel = 'Deal price: '; 
	var positionChance = null; 
	if ( self.state == Deal.waitInLineStr ) { 
	    spacing = ' '; 
	    dpLabel = null; 
	    positionChance = self.get_position_chance();
	} 
	return Deal.DOM.table({style:'padding-top:10px;'}, [
	    Deal.DOM.tr({}, [
		Deal.DOM.td({style:'padding-right: 10px; vertical-align: top;'}, [
		    Deal.DOM.img({
			alt:'Deal Image',
			src:asinImage
		    })
		]),
		Deal.DOM.td({style:'vertical-align: top;'}, [
		    Deal.DOM.span({'class':'cart-popover-asintitle'}, [
			asinTitle
		    ]),
		    Deal.DOM.p(null, []),
		    Deal.DOM.span({'class':'cart-popover-prevprice'}, [
			prevPrice
		    ]),
		    spacing,
		    Deal.DOM.span({'class':'cart-popover-dealprice'}, [
			dpLabel,
			Deal.Price.format(self.asin.dealPrice)
		    ]),
		    positionChance
		])
            ])
	]);
    },
    timeRemaining: function() {
        var self = this;
	self.timer_div = Deal.DOM.div({
	    'class':'timeleft'
        }, []);
        self.updateTimeRemaining();
        return self.timer_div;
    },
    updateTimeRemaining: function() {
        var self = this;
        var deal = self.deal;
	var asin = self.asin;
        if ( self.timer ) {
            self.timer.disconnect();
        }
        if (deal.status.ended) {
            self.timer = null;
        } else {
	    var msToExpiry = parseInt(asin.status.purchaseStatus.msToExpiry, 10);
	    var countDown = new Date();
	    countDown.setTime(countDown.getTime() + msToExpiry);
	    self.timer = Deal.CheckoutTimer(countDown, false);
            Deal.DOM.replaceChildren(self.timer_div, [
                self.timer.span
            ]); 
        }
    },
    onHide: function() {
        var self = this;
	if ( self.timer ) {
	    self.timer.disconnect();
	}
    }
});
Deal.VariationPopover = Deal.Class({
    __init__: function(element, deal, images, cx) {
        var self = this;
        self.deal = deal;
        self.images = images;
        self.cx = cx;
        self.dropDownOptions = {};
        self.currentSelections = {};
	var x = 0;
	var y = 0;
	
	if (self.cx.name == 'GBLD_WIDGET') {
	    x = -137;
	    y = -156;
	} else if (self.cx.cells == 1) {
	    
	    
	    x = -174;
	    y = -197;
	} else {
	    
	    x = -79;
	    y = -221;
	}
        amznJQ.available('popover', function() {
            jQuery(element).amazonPopoverTrigger({
                showOnHover: false,
                locationAlign: "middle",
                location: 'auto',
		locationOffset: [x, y],
                literalContent: ' ',
		closeEventInclude: 'CLICK_OUTSIDE',
                onShow: function(popover) {
                    self.onShow(popover);
                },
                onHide: function() {
                    self.onHide();
                },
                title: '<span style="color: #E47911;">Lightning Deal Options</span>',
                closeText: "Close"
            });
        });
    },
    onShow: function(popover) {
        var self = this;
	self.cx.setVarPopCloseFunction(popover.close);
        self.selection = {};
        self.selectors = {};
        self.selection_span = {};
        self.selectionDropDownElements = {};
        self.asinsForSelection();
        self.allValidCombinations = {};
        self.getAllCombinations();
        
        
        var container = popover.find('.ap_sub_content');
        if (container.length == 0) {
            container = popover.find('.ap_content');
        }
	var progressBar = self.progressBar();
        container.empty().append(
            Deal.DOM.table({width:'430'}, [
                Deal.DOM.tr({valign:'top'}, [
                    Deal.DOM.td({width:'120'}, [
                        self.image(),
			progressBar,
                        self.timeRemaining()
                    ]),
                    Deal.DOM.td({width:'10'}),
                    Deal.DOM.td({}, [
                        self.title(),
                        self.selector(),
                        self.price(),
                        Deal.DOM.div({
                          "style": "margin-top:10px; position: relative;",
                          "height": "22"
                         }, [
                            self.buyButton(),
                            self.statusBox(),
			    Deal.DOM.br(),
			    Deal.DOM.div({style:'position:relative; left:0px; font-family: verdana;'},
			        ["Discount applied at checkout. One per customer for each option."
			    ])
                        ])
                    ])
                ])
            ])
        );
        self.container = container;
        self.change_cx = self.deal.connect(
            'change',
            self, 'deal_change');
    },
    getSelectionCombinations: function (elements, depth) {
        var indices = new Array();
        if (depth == 0) {
            elements.length;
        }
        var x = new Deal.CombinationGenerator (elements.length, depth);
        var combination = "";
        var selectionCombinations = [];
        while (x.hasMore()) {
           combination = "";
           indices = x.getNext();
           var tmpArr = [];
           for (var i = 0; i < indices.length; i++) {
              tmpArr.push (elements[indices[i]]);
           }
           combination = tmpArr.join (";");
           selectionCombinations.push (combination);
        }
        return selectionCombinations;
    },
    onHide: function() {
        var self = this;
        self.timer.cx.disconnect();
        self.change_cx.disconnect();
    },
    deal_change: function() {
        var self = this;
        self.asinsForSelection();
        self.update();
    },
    update: function() {
        var self = this;
        self.updateImage();
        self.updatePrice();
        self.updateSelectionBoxes();
        self.updateBuyButton();
        self.updateStatusBox();
        self.updateProgressBar();
        self.updateTimeRemaining();
    },
    image: function() {
        var self = this;
        self.img = Deal.DOM.img();
        self.updateImage();
        return Deal.DOM.div(null, [self.img]);
    },
    updateImage: function() {
        var self = this;
        var src = self.imageURLForCurrentSelection();
        if (!src) {
            src = gbResources.getImage('no_image');
        } else {
            src = src.replace(/\.((_.*_)\.)?(gif|jpg)$/, '.$2_AA120_.$3');
        }
        self.img.src = src;
    },
    imageURLForCurrentSelection: function() {
        var self = this;
        var i;
        if (self.selectedAsin) {
            return self.selectedAsin.imageURL;
        }
        
        var imageURL = null;
        for (var i=0; i<self.selectedAsins.length; i++) {
            if (imageURL) {
                if (imageURL != self.selectedAsins[i].imageURL) {
                    imageURL = null;
                    break;
                }
            } else {
                imageURL = self.selectedAsins[i].imageURL;
            }
        }
        if (imageURL) {
            return imageURL;
        }
        
        for (var k in self.selection) {
            var v = self.selection[k];
            imageURL = self.imageURLForSelection(k,v);
            if (imageURL) {
                return imageURL;
            }
        }
        return  self.deal.detail.imageAsin;
    },
    imageURLForSelection: function(k, v) {
        var self = this;
        var imageURL;
        for (var i=0; i<self.deal.asins.length; i++) {
            var asin = self.deal.asins[i];
            if (asin.variationData[k] != v) {
                continue;
            }
            if (imageURL) {
                if (imageURL != asin.imageURL) {
                    return null;
                }
            } else {
                imageURL = asin.imageURL;
            }
        }
        return imageURL;
    },
    progressBar: function() {
        var self = this;
        self.progressBarInner = Deal.DOM.div({
            style:'position:absolute;'+
                'background-color:#C9D7E4;'+
                'height:19px;'
        });
        self.progressBarText = Deal.DOM.div({
            style:'font-size:11px;'+
                'overflow:hidden;'+
                'position:absolute;'+
                'padding:4px;'+
                'text-align:center;'+
                'vertical-align:middle;'
        });
        self.progressBarOuter = Deal.DOM.div({
            style:'border:1px solid #C9D7E4;'+
                'height: 19px;'+
                'position: relative;'+
                'margin-top:10px;'+
                'width: 118px;'
        }, [self.progressBarInner, self.progressBarText]);
        self.updateProgressBar();
        return self.progressBarOuter;
    },
    updateProgressBar: function() {
        var self = this;
        if (self.deal.limitedQuantity) {
            if (self.selectedAsins.length == self.deal.asins.length) {
                self.showPercentClaimed(self.deal.status.percentClaimed);
            } else if (self.selectedAsin) {
                self.showPercentClaimed(
                    self.selectedAsin.status.percentClaimed,
                    (self.selectedAsin.status.offerServiceSoldOut ? 100 : 0));
            } else {
                self.showPercentClaimed(null);
            }
        } else {
            self.showPercentClaimed(null);
        }
    },
    showPercentClaimed: function(percentClaimed) {
        var self = this;
        percentClaimed = Math.round(percentClaimed);
        if (percentClaimed != null) {
            self.progressBarOuter.style.visibility = '';
            self.progressBarInner.style.width = percentClaimed+'%';
            self.progressBarText.innerHTML = percentClaimed+'% now claimed';
        } else {
            self.progressBarOuter.style.visibility = 'hidden';
        }
    },
    timeRemaining: function() {
        var self = this;
        self.timer_div = Deal.DOM.div({
            style:'font-size:11px;'+
                'text-align:center; margin-top: 5px;'
        });
        self.updateTimeRemaining();
        return self.timer_div;
    },
    updateTimeRemaining: function() {
        var self = this;
        if (self.timer) {
            self.timer.cx.disconnect();
        }
        if (self.deal.status.ended) {
            self.timer_div.innerHTML = 'Expired';
            self.timer = null;
        } else {
            var deal = self.deal;
            self.timer = Deal.Timer(deal.status.endDate);
            self.timer.span.id = 'time-remaining-'+deal.dealID;
            self.timer.span.style.fontWeight = 'bold';
            Deal.DOM.replaceChildren(self.timer_div, [
                self.timer.span,
                Deal.DOM.br(),
                'remaining'
            ]); 
        }
    },
    title: function() {
        var self = this;
        return Deal.DOM.span({style:'font-weight:bold'},
	       [Deal.truncate_description(self.deal.detail.title, 50)]);
    },
    selector: function() {
        var self = this;
        var asins = self.deal.asins;
        self.options = {};
        self.color_images = {};
        for (var i=0; i<asins.length; i++) {
            var vars = asins[i].variationData;
            for (var key in vars) {
                var option = self.options[key];
                if (option === undefined) {
                    self.options[key] = option = {};
                }
                option[vars[key]] = 1;
                if (key == 'Color') {
                    self.color_images[key] = asins[i].imageURL;
                }
            }
        }
        var dropdowns = [];
        var colorDropDown = undefined;
        for (var key in self.options) {
            
            if (key == 'Color') {
                //colorDropDown = self.colorSelector(key);
                colorDropDown = self.dropdownSelector(key);
            } else {
                dropdowns.push(self.dropdownSelector(key));
            }       
        }
        if (colorDropDown != undefined) {
            dropdowns.push(colorDropDown);
        }
        return Deal.DOM.div(null, dropdowns);
    },
    
    colorSelector: function(key) {
    },
    dropdownSelector: function(key) {
        var self = this;
        var select_options = [Deal.DOM.option(null, [""])];
	select_options[0].value = 'Select';
	select_options[0].text = 'Select';
        var values = [];
        for (var value in self.options[key]) {
            values.push(value);
        }
        values.sort(self.numOrderAsc);
        if (self.dropDownOptions[key] == undefined) {
            self.dropDownOptions[key] = [];
        }
        for (var i=0; i<values.length; i++) {
            var opt = Deal.DOM.option(null, [values[i]]);
            select_options.push(opt);
            self.dropDownOptions[key].push(opt);
        }
        var select = self.selectors[key] ||
             Deal.DOM.select(null, select_options);
        if (!self.selectors[key]) {
            self.selectors[key] = select;
        }
        self.selectionDropDownElements[key] = select;
        select.onchange = function() {
            self.selectionChange(key, self.selectors[key][select.selectedIndex].text);
        };
        self.selection_span[key] = Deal.DOM.span({style:'color:#E47911'});
        return Deal.DOM.div({style:'margin-top: 10px'}, [
            Deal.DOM.span({style:'font-weight: bold; font-size: 13px;'}, [
                "Select ", key, ": ",
                self.selection_span[key]]),
                Deal.DOM.br(),
                select]);
    },
    isCurrentlyValidOption: function (key, value) {
        var self = this;
        if (self.validOptions &&
            self.validOptions[key] &&
            self.validOptions[key][value] == 1) {
            return true;
        }
        return false;
    },
    resetAllOtherOptions: function (key) {
        var self = this;
        for (var k in self.selectionDropDownElements) {
            if (k == key) {
                continue;
            }
            var selectionDropDownElement = self.selectionDropDownElements[k];
            if (selectionDropDownElement) {
                selectionDropDownElement.selectedIndex=0;
                selectionDropDownElement.onchange();
            }
        }
    },
    selectionChange: function(key, value) {
        var self = this;
        if (value != 'Select') {
            var clearOtherSelections = false;
            if (!self.isCurrentlyValidOption (key, value)) {
                clearOtherSelections = true;
            }
            self.currentSelections[key] = value;
            self.selection[key] = value;
            jQuery (self.selection_span[key])
              .html (value);
            if (clearOtherSelections) {
                self.resetAllOtherOptions (key);
            }
        } else {
            delete self.currentSelections[key];
            delete self.selection[key];
            jQuery (self.selection_span[key])
              .html ('');
        }
        self.asinsForSelection();
        self.updateDropDownStyles(key, value);
        self.update();
    },
    getAllCombinations: function() {
        var self = this;
        for (var i = 0; i < self.deal.asins.length; i++) {
            var asin = self.deal.asins[i];
            var varData = asin.variationData;
            
            var sortedKeys = self.getSortedKeys (varData);
            var keyValues = new Array();
            for (var x = 0; x < sortedKeys.length; x++) {
                sKey = sortedKeys[x];
                var sValue = sKey + ":" + varData[sKey];
                keyValues.push(sValue);
            }
            var combinedArray = self.combine (keyValues);
            for (var x = 0; x < combinedArray.length; x++) {
                combinedArray[x] = combinedArray[x].join(";");
                var joined = combinedArray[x];
                if (!self.allValidCombinations[joined]) {
                    self.allValidCombinations[joined] = new Array();
                }
                self.allValidCombinations[joined].push (asin);
            }
        }
    },
    numOrderAsc: function(a, b) {
	
	var intA = parseInt(a);
	var intB = parseInt(b);
	
	if ( isNaN(intA) && isNaN(intB) ) {
	    if (a > b) {
		return 1;
	    } else if (a < b) {
		return -1;
	    } else {
		return 0;
	    }
	} else if ( isNaN(intA) ) {
	    
	    return 1;
	} else if ( isNaN(intB) ) {
	    
	    return -1;
	} else {
	    
	    if ( intA > intB ) {
		return 1;
	    } else if ( intA < intB ) {
		return -1;
	    } else {
		return 0;
	    }
	}
    },
    getSortedKeys: function(list) {
        var keys = [];
        for (var key in list) {
            keys.push(key);
        }
        keys.sort();
        return keys;
    },
    asinsForSelection: function() {
        var self = this;
        self.selectedAsins = [];
        for (var i=0; i<self.deal.asins.length; i++) {
            var asin = self.deal.asins[i];
            var match = true;
            for (var sk in self.selection) {
                if (asin.variationData &&
                    asin.variationData[sk] != self.selection[sk]) {
                    match = false;
                    break;
                }
            }
            if (match) {
                self.selectedAsins.push(asin);
            }
        }
        
        self.selectedAsins = self.uniqueAsinArray (self.selectedAsins);
        if (self.selectedAsins.length == 1) {
            self.selectedAsin = self.selectedAsins[0];
            self.selection = {};
            for (var k in self.selectedAsin.variationData) {
                self.selection[k] = self.selectedAsin.variationData[k];
            }
            self.updateSelection();
            if (self.selectedAsin.status.percentClaimed < 100
                && !self.selectedAsin.offerServiceSoldOut
                && !self.deal.status.ended
                && self.deal.status.percentClaimed < 100
            ) {
                self.available = true;
            } else {
                self.available = false;
            }
        } else {
            self.selectedAsin = null;
            self.available = false;
            self.updateSelection();
        }
    },
    uniqueAsinArray: function (arr) {
        var self = this;
        var tmpHash  = {};
        var tmpArray = [];
        if (!arr) {
            return tmpArray;
        }
        for (var i = 0; i < arr.length; i++) {
            var asin = arr[i];
            var asinString = asin.asin;
            tmpHash[asinString] = asin;
        }
        var uniqueSortedKeys = self.getSortedKeys (tmpHash);
        for (var i = 0; i < uniqueSortedKeys.length; i++) {
            var key   = uniqueSortedKeys[i];
            var value = tmpHash[key];
            tmpArray.push (value);
        }
        return tmpArray;
    },
    updateSelection: function() {
        var self = this;
        for (var k in self.selection_span) {
            if ( self.selection[k] != undefined ) {
                jQuery (self.selection_span[k])
                  .html (self.selection[k]);
	        } else {
                jQuery (self.selection_span[k])
                  .html ('');
	        }
        }
        for (var k in self.selectors) {
	    self.selectors[k].value = self.selection[k] || '';
	    
	    if ( self.selection[k] && !self.selectors[k].value ) {
		for (var i=0; i<self.selectionDropDownElements[k].options.length; i++) { 
		    if ( self.selectionDropDownElements[k].options[i].text
			 == self.selection[k] ) {
			self.selectors[k].selectedIndex = i;
		    }
		}
	    }
        }
    },
    updateDropDownStyles: function(pKey, pValue) {
        var self = this;
        self.getValidOptions();
        for (var k in self.dropDownOptions) {
            for (var l = 0; l < self.dropDownOptions[k].length; l++) {
                var select = self.dropDownOptions[k][l];
                if (select == null) {
                    continue;
                }
		
                var value = select.value || select.text;
                if ((self.currentSelections.length <= 1
		     && k == pKey && value == pValue) ||
                    (self.validOptions[k] &&
                     self.validOptions[k][value] == 1))
                {
                    select.setAttribute("style", "color: #000000;");
		    
		    if ( !select.value ) {
			select.style.setAttribute("color", "#000000");
		    }
                } else {
                    select.setAttribute("style", "color: #afafaf;");
		    
		    if ( !select.value ) {
			select.style.setAttribute("color", "#afafaf");
		    }
                }
            }
        }
    },
    getValidOptions: function() {
        var self = this;
        self.validOptions = {};
        self.selectableAsins = self.getSelectableAsins();
        for (var k = 0; k < self.selectableAsins.length; k++) {
            var asin = self.selectableAsins[k];
            if (asin == null) {
                continue;
            }
            for (var varName in asin.variationData) {
                var varValue = asin.variationData[varName];
                if (self.validOptions[varName] == undefined) {
                    self.validOptions[varName] = {};
                }
                self.validOptions[varName][varValue] = 1;
            }
        }
    },
    getSelectableAsins: function() {
        var self = this;
        var selectableAsins = [];
        var sortedKeys = self.getSortedKeys (self.currentSelections);
        
        
        if (sortedKeys.length == 0) {
            return self.selectedAsins;
        }
        var keyValues = [];
        for (var i = 0; i < sortedKeys.length; i++) {
            var key   = sortedKeys[i];
            var value = self.currentSelections[key];
            var sValue = key + ":" + value;
            keyValues.push (sValue);
        }
        var depth = sortedKeys.length;
        var tmpDropDownKeys = self.getSortedKeys (self.dropDownOptions);
        var totalVariationalOptionNames = tmpDropDownKeys.length;
        if (depth >= totalVariationalOptionNames) {
            depth = totalVariationalOptionNames - 1;
        }
        var combinedArray = self.getSelectionCombinations (keyValues, depth);
        var tmpAsinHash = {};
        
        for (var i = 0; i < combinedArray.length; i++) {
            var key = combinedArray[i];
            var tmpAsins = self.allValidCombinations[key];
            if (tmpAsins && tmpAsins.length > 0) {
                for (var j = 0; j < tmpAsins.length; j++) {
                    var tmpAsin = tmpAsins[j];
                    if (tmpAsin) {
                        var asinString = tmpAsin.asin;
                        tmpAsinHash[asinString] = tmpAsin;
                    }
                }
            }
        }
        var sortedKeys = self.getSortedKeys (tmpAsinHash);
        for (var i = 0; i < sortedKeys.length; i++) {
            var key   = sortedKeys[i];
            var value = tmpAsinHash[key];
            selectableAsins.push (value);
        }
        return selectableAsins;
    },
    price: function() {
        var self = this;
        self.priceBlock = Deal.DOM.div({style:'margin-top: 10px'});
        self.updatePrice();
        return self.priceBlock;
    },
    updatePrice: function() {
        var self = this;
        if (self.selectedAsin) {
            self.updatePriceSingleAsin();
        } else if (self.selectedAsins.length > 1) {
            self.updatePriceRange();
        } else {
            self.updatePriceNoAsins();
        }
    },
    updatePriceSingleAsin: function() {
        var self = this;
        var asin = this.selectedAsin;
        if (asin.offerServiceSoldOut || !asin.dealPrice || !asin.dealPrice.price) {
            self.updatePriceNoAsins();
            return;
        }
        var listPrice = asin.listPrice;
        var ourPrice = asin.ourPrice;
        var dealPrice = asin.dealPrice;
        var listPriceDiscount = Deal.Price.minus(listPrice, dealPrice);
        var ourPriceDiscount = Deal.Price.minus(ourPrice, dealPrice);
        var percentOffListPrice = Math.round(Deal.Price.percent_off(
            listPrice, dealPrice));
        var percentOffOurPrice = Math.round(Deal.Price.percent_off(
            ourPrice, dealPrice));
        var listPriceFormatted = Deal.Price.format(listPrice);
        var ourPriceFormatted = Deal.Price.format(ourPrice);
        var dealPriceFormatted = Deal.Price.format(dealPrice);
        var listPriceDiscountFormatted = Deal.Price.format(listPriceDiscount);
        var ourPriceDiscountFormatted = Deal.Price.format(ourPriceDiscount);
        var prices = [];
        prices.push(
            Deal.DOM.span({
                id:'list-price-popover',
                style:'font-size: 11px;'+
                    'color: #666666;'+
                    'text-decoration: line-through;'
            }, [listPriceFormatted || ourPriceFormatted]));
        prices.push(' ');
        prices.push(Deal.DOM.span({
            id: 'deal-price-popover',
            style:'font-size: 13px;'+
                'color: #990000;'
        }, [dealPriceFormatted]));
        prices.push(' ');
        prices.push(
            Deal.DOM.span({
                id:'percent-off-popover',
                style:'font-size: 11px;'+
                    'color: #990000;'
            }, [' ('+(percentOffListPrice || percentOffOurPrice)+'% off)']));
        Deal.DOM.replaceChildren(self.priceBlock, prices);
    },
    updatePriceRange: function() {
        var self = this;
        var min = null;
        var max = null;
        for (var i=0; i<self.selectedAsins.length; i++) {
            var asin = self.selectedAsins[i];
            if (asin.status.percentClaimed >= 100
                || asin.offerServiceSoldOut
                || !asin.dealPrice
                || !asin.dealPrice.price
            ) {
                continue;
            }
	    
	    var asinPrice = parseFloat(asin.dealPrice.price);
	    if ( !max || asinPrice > parseFloat(max.price) ) {
		max = asin.dealPrice;
	    }
	    if ( !min || asinPrice < parseFloat(min.price) ) {
		min = asin.dealPrice;
	    }
        }
        if (!min || !max) {
            self.updatePriceNoAsins();
        } else {
            if (min == max) {
                Deal.DOM.replaceChildren(self.priceBlock, [
                    Deal.DOM.span({style:'color:#900;font-size:13px'}, [
                        Deal.Price.format(min)])]);
            } else {
                Deal.DOM.replaceChildren(self.priceBlock, [
                    Deal.DOM.span({style:'color:#900;font-size:13px'}, [
                        Deal.Price.format(min), '-',
                        Deal.Price.format(max)])]);
            }
        }
    },
    updatePriceNoAsins: function() {
        var self = this;
        var options = [];
        for (var k in self.selection) {
            options.push(
                Deal.DOM.span({style:'font-weight: bold'}, [
                    k, ": ",
                    Deal.DOM.span({style:'color:#B00'}, [
                        self.selection[k]])]));
            options.push(Deal.DOM.br());
        }
	/*  Commenting out for now. This functionality needs to be made
	    available if/when we update the selection boxes to swatches.
	    Leaving in place since this is working code, but not to be 
	    used yet.
        if (options.length > 0) {
            Deal.DOM.replaceChildren(self.priceBlock, [
                Deal.DOM.div({
                    style:'border: 1px solid #900;'+
                        'color: #900;'+
                        'text-align: center;'+
                        'background-color: #EBB;'+
                        'padding: 10px;'
                }, [
                    "Sorry, this item is not available in",
                    Deal.DOM.br(),
                    options,
                    "at the Lightning Deal price."
                ])]);
        } else {
            Deal.DOM.replaceChildren(self.priceBlock, [
                Deal.DOM.div({
                    style:'border: 1px solid #900;'+
                        'color: #900;'+
                        'text-align: center;'+
                        'background-color: #EBB;'+
                        'padding: 10px;'
                }, [
                    "Sorry, this item is not available",
                    Deal.DOM.br(),
                    "at the Lightning Deal price."
                ])]);
        }
	*/
    },
    updateSelectionBoxes: function() {
        var self = this;
        var disable = false;
        if (self.cx.buying[self.deal.dealID]) {
            disable = true;
        }
        for (var key in self.selectors) {
            var selector = self.selectors[key];
            selector.disabled = disable;
        }
    },
    buyButton: function() {
        var self = this;
        self.buy_button = Deal.DOM.img({
            style:'cursor:pointer;',
            src:self.images.add_to_cart});
        self.buyButtonContainer = Deal.DOM.div({},
				      self.buy_button);
        self.updateBuyButton();
        return self.buyButtonContainer;
    },
    updateBuyButton: function() {
        var self = this;
        if (self.cx.buying[self.deal.dealID]) {
            self.showBuying();
        } else {
            self.showBuyButton();
        }
    },
    showBuyButton: function() {
        var self = this;
        var selectedAsinPurchaseState =
	    Deal.getPurchaseState(null, self.selectedAsin);
        var cartMsg = null;
	var statusContent = null;
	if ( gbResources.features.waitlist && self.selectedAsin &&
	     selectedAsinPurchaseState == Deal.waitInLineStr ) { 
	    
	    self.buy_button.src = self.images.add_to_cart; 
	    self.buy_button.alt = 'Add to Cart'; 
	    self.buy_button.onclick = null; 
	    self.buy_button.style.opacity = 0.5; 
	    self.buy_button.style.cursor = 'default'; 
	    
            self.buy_button.style.filter = "alpha(opacity=50)"; 
	    statusContent = Deal.DOM.div({'style':'font-weight: bold;'+ 
					  'font-size: 11px;'}, 
				 ["You are on the Waitlist for this option."]); 
	} else if (gbResources.features["timed-checkout"] && self.selectedAsin && 
		   selectedAsinPurchaseState == Deal.inCartStr) { 
        if (self.selectedAsin.status.purchaseStatus.msToExpiry > 0) {
	         self.buy_button.src = self.images.add_to_cart; 
	         self.buy_button.alt = 'Add to Cart'; 
                 self.buy_button.onclick = null;
                 self.buy_button.style.opacity = 0.5;
	         self.buy_button.style.cursor = 'default';
	         
	         self.buy_button.style.filter = "alpha(opacity=50)";
                 cartMsg = Deal.DOM.span({'style':'font-weight: bold;'+
	         		     'font-size: 13px;'
	         		    }, [" In Your Cart!"]);
	         statusContent = self.getStatusContent(false);
        } else {
	        Deal.DOM.replaceChildren(self.buyButtonContainer, [ 
	             Deal.DOM.img({ 
	        	  id:'buying-'+self.deal.dealID, 
	        	  alt:'Checking deal status...', 
	        	  src:self.images.spinner 
	             }),  
	             Deal.DOM.span({ 
	        	  style:'color: #E68221;'+ 
	        		'font-weight: bold;'+ 
	        		    'font-size: 10px;' },
	        	  ["Checking Deal Status"]) 
	             ]); 
	        self.buy_button.onclick = null; 
            return;
        }
        } else if (self.selectedAsin && self.available &&
		   !Deal.isClaimed (null, self.selectedAsin) || 
		   (gbResources.features.waitlist && 
		    selectedAsinPurchaseState == Deal.pendingAtcStr)) { 
            if (self.selectedAsin.status.purchaseStatus.msToExpiry > 0 ||
                self.selectedAsin.status.purchaseStatus.state == Deal.expiredStr ||
                self.selectedAsin.status.purchaseStatus.state == Deal.availableStr) {
	            self.buy_button.src = self.images.add_to_cart; 
	            self.buy_button.alt = 'Add to Cart'; 
	            self.buy_button.style.opacity = 1.0; 
	            
	            self.buy_button.style.filter = "alpha(opacity=100)"; 
	            self.buy_button.style.cursor = 'pointer'; 
	            
	            if ( selectedAsinPurchaseState == Deal.pendingAtcStr ) { 
		        statusContent = self.getStatusContent(true); 
	            } 
	            self.buy_button.onclick =  function(event) { 
		        if (event) { 
		            event.stopPropagation(); 
		            event.preventDefault(); 
		        } 
		        self.cx.buy(self.deal, self.selectedAsin.asin, false); 
		        self.showBuying(); 
		        self.updateSelectionBoxes(); 
                        return false; 
	            }; 
            } else {
	            Deal.DOM.replaceChildren(self.buyButtonContainer, [ 
	                 Deal.DOM.img({ 
	            	  id:'buying-'+self.deal.dealID, 
	            	  alt:'Checking deal status...', 
	            	  src:self.images.spinner 
	                 }),  
	                 Deal.DOM.span({ 
	            	  style:'color: #E68221;'+ 
	            		'font-weight: bold;'+ 
	            		    'font-size: 10px;' },
	            	  ["Checking Deal Status"]) 
	                 ]); 
	            self.buy_button.onclick = null; 
                return;
            }
	} else if ( gbResources.features.waitlist && self.selectedAsin && 
		    self.selectedAsin.status.percentSoldOut < 100 &&
		    self.selectedAsin.status.percentClaimed >= 100 ) { 
	    
	    
	    self.buy_button.src = self.images.join_waitlist; 
	    self.buy_button.alt = 'Join waitlist'; 
	    if ( self.selectedAsin.status.currentlyUnavailable ) {
    		//If the waitlist is full - we show the wait list full button
	    	self.buy_button.alt = 'Waitlist full';
    	   	self.buy_button.src = self.images.waitlist_full;	    	
	    	self.buy_button.style.opacity = 0.5;
                self.buy_button.style.filter = "alpha(opacity=50)";
		self.buy_button.onclick = null;
		self.buy_button.style.cursor = 'default';
	    } else {
		self.buy_button.style.opacity = 1.0;
		
		self.buy_button.style.filter = "alpha(opacity=100)";
		self.buy_button.style.cursor = 'pointer';
		self.buy_button.onclick =  function(event) {
		    if (event) {
			event.stopPropagation();
			event.preventDefault();
		    }
		    self.cx.buy(self.deal, self.selectedAsin.asin, true);
		    self.showBuying();
		    self.updateSelectionBoxes();
		    return false;
		};
	    }
        } else {
	    self.buy_button.src = self.images.add_to_cart; 
	    self.buy_button.alt = 'Add to Cart'; 
            self.buy_button.onclick = null;
            self.buy_button.style.opacity = 0.5;
	    self.buy_button.style.cursor = 'default';
	    
	    self.buy_button.style.filter = "alpha(opacity=50)";
        }
	Deal.DOM.replaceChildren(self.buyButtonContainer,
				 [self.buy_button,
				  cartMsg,
				  statusContent]);
    },
    getStatusContent: function(pending) { 
	var self = this; 
	var statusContent = null; 
	if ( self.deal.pStatus == undefined ||
	     self.deal.pStatus[self.selectedAsin.asin] == undefined ) { 
	    self.deal.pStatus = []; 
	    self.deal.pStatus[self.selectedAsin.asin] = 
		new Deal.PStatus(self.deal); 
	} 
	var tmpPStatus = self.deal.pStatus[self.selectedAsin.asin]; 
	statusContent = 
	    tmpPStatus.getTimerMessage(self.selectedAsin.asin, null, pending); 
	return statusContent; 
    }, 
    showBuying: function() {
        var self = this;
        jQuery(self.statusBoxObject).css("padding-left",50);
        Deal.DOM.replaceChildren(self.buyButtonContainer, [
                    Deal.DOM.img({
                        id:'buying-'+self.deal.dealID,
                        alt:'Checking deal status...',
                        src:self.images.spinner
                    }), 
                    Deal.DOM.span({
                        style:'color: #E68221;'+
                            'font-weight: bold;'+
                            'font-size: 10px;'
                    }, ["Checking Deal Status"])
                ]);
                self.buy_button.onclick = null;
    },
    statusBox: function() {
        var self = this;
        self.statusBoxObject =
          Deal.DOM.span({'style': 'padding-left: 10px; font-weight: bold; font-size: 13px; position: relative; top: -18px; left: 100px;'},
                        Deal.DOM.text(''));
        self.updateStatusBox();
        return self.statusBoxObject;
    },
    updateStatusBox: function() {
        var self = this;
        var statusMessage = "";
        if (self.selectedAsin != undefined) {
	    if (Deal.inState(null, self.selectedAsin, Deal.isClaimedStr)) { 
		statusMessage = "Claimed!"; 
	    } else if (!Deal.inState(null, self.selectedAsin, Deal.pendingAtcStr) && 
		       !Deal.inState(null, self.selectedAsin, Deal.inCartStr) && 
		       !Deal.inState(null, self.selectedAsin, Deal.waitInLineStr) && 
               !Deal.inState(null,self.selectedAsin,Deal.availableStr) &&
               !Deal.inState(null,self.selectedAsin,Deal.expiredStr) &&
		       !self.available) { 
                statusMessage = "Sold Out!";
            }
        }
        self.statusBoxObject.innerHTML = statusMessage;
    },
    combine: function(a) {
      var fn = function(n, src, got, all) {
        if (n == 0) {
          if (got.length > 0) {
            all[all.length] = got;
          }
          return;
        }
        for (var j = 0; j < src.length; j++) {
          fn(n - 1, src.slice(j + 1), got.concat([src[j]]), all);
        }
        return;
      };
      var all = [];
      for (var i=0; i < a.length; i++) {
        fn(i, a, [], all);
      }
      all.push(a);
      return all;
    }
});
})();}
/*
 * Date Format 1.2.3
 * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
 * MIT license
 *
 * Includes enhancements by Scott Trenda <scott.trenda.net>
 * and Kris Kowal <cixar.com/~kris.kowal/>
 *
 * Accepts a date, a mask, or a date and a mask.
 * Returns a formatted version of the given date.
 * The date defaults to the current date/time.
 * The mask defaults to dateFormat.masks.default.
 */
var dateFormat = function () {
	var	token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
		timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
		timezoneClip = /[^-+\dA-Z]/g,
		pad = function (val, len) {
			val = String(val);
			len = len || 2;
			while (val.length < len) val = "0" + val;
			return val;
		};
	
	return function (date, mask, utc) {
		var dF = dateFormat;
		
		if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
			mask = date;
			date = undefined;
		}
		
		date = date ? new Date(date) : new Date;
		if (isNaN(date)) throw SyntaxError("invalid date");
		mask = String(dF.masks[mask] || mask || dF.masks["default"]);
		
		if (mask.slice(0, 4) == "UTC:") {
			mask = mask.slice(4);
			utc = true;
		}
		var	_ = utc ? "getUTC" : "get",
			d = date[_ + "Date"](),
			D = date[_ + "Day"](),
			m = date[_ + "Month"](),
			y = date[_ + "FullYear"](),
			H = date[_ + "Hours"](),
			M = date[_ + "Minutes"](),
			s = date[_ + "Seconds"](),
			L = date[_ + "Milliseconds"](),
			o = utc ? 0 : date.getTimezoneOffset(),
			flags = {
				d:    d,
				dd:   pad(d),
				ddd:  dF.i18n.dayNames[D],
				dddd: dF.i18n.dayNames[D + 7],
				m:    m + 1,
				mm:   pad(m + 1),
				mmm:  dF.i18n.monthNames[m],
				mmmm: dF.i18n.monthNames[m + 12],
				yy:   String(y).slice(2),
				yyyy: y,
				h:    H % 12 || 12,
				hh:   pad(H % 12 || 12),
				H:    H,
				HH:   pad(H),
				M:    M,
				MM:   pad(M),
				s:    s,
				ss:   pad(s),
				l:    pad(L, 3),
				L:    pad(L > 99 ? Math.round(L / 10) : L),
				t:    H < 12 ? "a"  : "p",
				tt:   H < 12 ? "am" : "pm",
				T:    H < 12 ? "A"  : "P",
				TT:   H < 12 ? "AM" : "PM",
				Z:    utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
				o:    (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
				S:    ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
			};
		return mask.replace(token, function ($0) {
			return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
		});
	};
}();

dateFormat.masks = {
	"default":      "ddd mmm dd yyyy HH:MM:ss",
	shortDate:      "m/d/yy",
	mediumDate:     "mmm d, yyyy",
	longDate:       "mmmm d, yyyy",
	fullDate:       "dddd, mmmm d, yyyy",
	shortTime:      "h:MM TT",
	mediumTime:     "h:MM:ss TT",
	longTime:       "h:MM:ss TT Z",
	isoDate:        "yyyy-mm-dd",
	isoTime:        "HH:MM:ss",
	isoDateTime:    "yyyy-mm-dd'T'HH:MM:ss",
	isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};

dateFormat.i18n = {
	dayNames: [
		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
		"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
	],
	monthNames: [
		"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
		"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
	]
};

Date.prototype.format = function (mask, utc) {
	return dateFormat(this, mask, utc);
};
//RW Environment
//File that is a placeholder for the common widget framwork.
if ( !window.DealWidgets) {
    
    window.DealWidgets = {};
};
DealWidgets.newDetailPageCell = function(widget_input) {
    return {
    	widget : widget_input,
    	template_name_overrides: widget_input.template_name_overrides,
        images : widget_input.images,
        dealDisplay : widget_input.dealDisplay,
        parent_el : widget_input.el,
	breaksMAP: widget_input.breaksMap,
        timeout : {},
	selection: 'lightningDeal',
        connections : [],
	dealPrice: null,
	ourPrice: null,
	listPrice: null,
	regSavings: null,
	dealSavings: null,
	regPctOff: null,
	dealPctOff: null,
	regSavingsStr: null,
	dealSavingsStr: null,
	currSavingsStr: null,
	init: function(deal) {
	    var self = this;
	    
	    jQuery('#gbd_lightningDealOption').show('slow');
	    jQuery('#gbd_originalOption').show('slow');
	    
	    self.set_prices(deal, 1);
	    
	    self.init_reg_price_display(deal);
	    
	    self.select_option('lightningDeal', 'original', deal, 1);
	    
	    amznJQ.onReady('gbPriceBlockFields', function() {
		self.init_price_block(deal);
	    });
	},
	init_price_block: function(deal) {
	    
	    
	    if ( !deal.asins ) {
		return;
	    }
	    
	    jQuery('#dealPriceRow').show();
	    
	    jQuery('#dealPriceValue')
		.text(this.dealPrice);
	    var dealPriceMessaging = jQuery('#dealPriceExtraMessaging');
	    
	    if ( jQuery.trim(jQuery('#actualPriceExtraMessaging').html()) != "" ) {
		dealPriceMessaging.html(jQuery('#actualPriceExtraMessaging').html());
	    } else if ( jQuery.trim(jQuery('#shippingMessageStandAlone').html()) != "" ) {
		
		
		this.handleMAProw();
		dealPriceMessaging.html(jQuery('#shippingMessageStandAlone').html());
	    }
	    
	    
	    
	    
	    
	    if ( !this.breaksMAP ) {
		this.set_pb_value('actualPrice', this.ourPrice);
	    }
	    
	    this.update_price_block('lightningDeal','original');
	},
	init_reg_price_display: function(deal) {
	    
	    if ( !deal.asins ) {
		return;
	    }
	    var self = this;
	    
	    jQuery('#gbd_regPriceLabel')
	        .addClass('gbd_deal_title')
	        .text(window.gbResources.getString('gbld-regular-price'));
	    
	    self.show_reg_price();
	    self.show_reg_pctOff();
	    
	    jQuery('#originalSelectionOption').click(
                function() {
		    self.select_option('original', 'lightningDeal', deal);
		}
	    );
	},
        _cleanup: function() {
            for (var i=0; i<this.connections.length; i++) {
                this.connections[i].disconnect();
            }
            this.connections = [];
        },
        show_deal: function(deal) {
            this._cleanup();
            if (!deal) {
                return;
            }	        
            this.show_active(deal);
        },
	set_pb_value: function(field, value) {
	    
	    
	    
	    if ( field == 'actualPrice' && this.breaksMAP ) {
		return;
	    }
	    if ( value ) {
		jQuery('#' + field + 'Value').empty().text(value);
	    }
	},
	set_pb_prices: function() {
	    
	    
	    this.set_pb_value('listPrice', this.listPrice);
	    this.set_pb_value('dealPrice', this.dealPrice);
	    this.set_pb_value('actualPrice', this.ourPrice);
	    this.set_pb_value('youSave', this.currSavingsStr);
	},
	update_prices: function() {
	    
	    
	    this.set_pb_prices();
	    this.show_deal_price();
	    this.show_deal_pctOff();
	    this.show_reg_price();
	    this.show_reg_pctOff();
	},
        get_price: function(deal, priceType) {
	    
	    
	    var priceObj = null;
	    if ( priceObj = deal.asins[0][priceType] ) {
		this[priceType] = Deal.Price.format(priceObj);
	    }
	    return priceObj;
	},
	set_prices: function(deal, first) {
	    
	    
	    if ( deal.asins && deal.asins[0] ) {
		var ourPrice = '';
		
		
		if (first || this.breaksMAP) {
		    this.ourPrice = window.gbResources.getString('map_popover_link_masg_69737');
		} else {
		    ourPrice = this.get_price(deal, 'ourPrice');
		}
		var dealPrice = this.get_price(deal, 'dealPrice');
		var listPrice = this.get_price(deal, 'listPrice');
		
		
		if ( !listPrice && jQuery('#listPriceValue').text() != null ) {
		    this.listPrice = jQuery('#listPriceValue').text();
		}
		
		
		if ( listPrice && dealPrice &&
		     parseFloat(listPrice.price) > parseFloat(dealPrice.price) ) {
		    
		    var dealPctOff = Math.round(Deal.Price.percent_off(listPrice, dealPrice));
		    if ( dealPctOff > 1 ) {
			this.dealPctOff = window.gbResources.getString('csld-percent_off',
								       {discountPercentage: dealPctOff});
		    }
		    
		    this.dealSavings = Deal.Price.minus(listPrice, dealPrice);
		    
		    this.dealSavingsStr = Deal.Price.format(this.dealSavings) + ' ' + this.dealPctOff;
		}
		
		
		if ( listPrice && ourPrice && ourPrice.price &&
		     parseFloat(listPrice.price) > parseFloat(ourPrice.price) ) {
		    var regPctOff = Math.round(Deal.Price.percent_off(listPrice, ourPrice));
		    if ( regPctOff > 1 ) {
			this.regPctOff = window.gbResources.getString('csld-percent_off',
								      {discountPercentage: regPctOff});
		    }
            var pctString = '';
            if (this.regPctOff != null) {
                pctString = this.regPctOff;
            }
		    
		    this.regSavings = Deal.Price.minus(listPrice, ourPrice);
		    
		    this.regSavingsStr = Deal.Price.format(this.regSavings) + ' ' + pctString;
		}
	    }
	},
        show_active: function(deal) {
	    var pState = Deal.getPurchaseState (deal, null);
	    this.set_prices(deal, 0);
	    this.update_prices();
	    this.show_message(deal, pState);
	    this.show_cart_button(deal, pState);
	    this.show_title();
            this.show_pctClaimed(deal);
            this.start_timer(deal, pState);
	    this.determine_display(deal, pState);
        },
	//////
        start_timer: function(deal) {
	    var timeRemainingDisplay = jQuery("#lightningDealTimeRemaining");
	    var timer = Deal.JQTimer(deal,false);
	    this.connections.push(timer.cx);
        },
        show_title: function() {
	    jQuery('#lightningDealLabel').empty()
        	.addClass('gbd_deal_title')
	        .text(window.gbResources.getString('csld-single-cat-ld_price'));
        },
	show_reg_price: function() {
	    
	    jQuery('#gbd_regPrice')
	        .addClass('gbd_deal_price')
	        .text(this.ourPrice);
	},
	show_reg_pctOff: function() {
	    
	    jQuery('#gbd_regPctOff')
	        .addClass('gbd_deal_pct_off')
	        .text(this.regPctOff);
	},
	show_deal_price: function() {
	    
	    jQuery('#lightningDealPrice').empty()
	        .addClass('gbd_deal_price')
	        .text(this.dealPrice);
        },
        show_deal_pctOff: function() {
	    
	    if ( this.dealPctOff ) {
        	var pctOffSpan = jQuery('<span></span>')
        	    .attr('id', 'gbd_dealPercentageOff')
        	    .addClass('gbd_deal_pct_off')
		    .text(this.dealPctOff);
        	jQuery('#lightningDealPctOff').empty().append(pctOffSpan);
	    }
        },
        show_pctClaimed: function(deal) {
		var currPercent = Math.floor(deal.status.percentClaimed);
		var wrapperDiv = jQuery('<div></div>')
		    .attr('class', 'gbd_percentagebar_dp');
        	var pctClaimedText = jQuery('<span></span>')
        	    .attr('id', 'gbd_pctClaimed')
		    .addClass('gbd_pct_claimed_text')
		    .text(window.gbResources.getString('csld-percent_claimed', {percentClaimed: currPercent}));
		var color = (currPercent >= 100) ? 'E4E4E4' :'FFCC66';
		var pctClaimedBar = jQuery('<div></div>')
		    .attr('id', 'gbd_percent_claimed_id')
		    .attr('style', 'background-color:#' + color + ';width:' + currPercent + '%;height:19px;');
        	wrapperDiv.empty().append(pctClaimedBar);
        	jQuery('#lightningDealPctClaimed').empty().append(wrapperDiv).append(pctClaimedText);
        },
	//////
	handleMAProw: function() {
	    
	    
	    if ( this.breaksMAP ) {
		if ( this.selection == 'original') {
		    jQuery('#breaksMAPNewline').show();
		    jQuery('#shippingMessageStandAlone').show();
		} else {
		    jQuery('#breaksMAPNewline').hide();
		    jQuery('#shippingMessageStandAlone').hide();
		}
	    }
	},
	update_price_block: function(show, hide) {
	    
	    
	    
	    if ( show == 'original' ) {
		this.currSavingsStr = this.regSavingsStr;
		show = 'actual';
		hide = 'deal';
	    } else {
		this.currSavingsStr = this.dealSavingsStr;
		show = 'deal';
		hide = 'actual';
	    }
	    jQuery('#youSaveValue').text(this.currSavingsStr);
	    
	    if ( !this.breaksMAP || show != 'actual' ) {
		jQuery('#' + show + 'PriceValue')
	          .removeClass('dp_price_normal')
		  .addClass('priceLarge');
	    }
	    if ( !this.breaksMAP || hide != 'actual' ) {
		jQuery('#' + hide + 'PriceValue')
		  .removeClass('priceLarge')
	          .addClass('dp_price_normal');
	    }
	    jQuery('#' + hide + 'PriceExtraMessaging').hide();
	    jQuery('#' + show + 'PriceExtraMessaging').show();
	    this.handleMAProw();
	},
	select_option: function(show, hide, deal, first) {
	    
	    if ( first || !jQuery('#gbd_' + show + 'Option').hasClass('selected') ) {
		var buyingOptionsContainer = jQuery('#lightningDealBuyingOptionsContainer');
		var heightBefore = buyingOptionsContainer.height();
		var heightAfter = heightBefore + jQuery('#' + show + 'BuyBoxAddToCart').height()
		                    - jQuery('#' + hide + 'BuyBoxAddToCart').height();
		jQuery('#gbd_' + show + 'Option').addClass('selected');
		jQuery('#gbd_' + hide + 'Option').removeClass('selected');
		jQuery('#cBox_' + show + 'Price').addClass('selected');
		jQuery('#cBox_' + hide + 'Price').removeClass('selected');
		jQuery('#' + show + 'PriceInput').attr('checked', 'true');
		jQuery('#' + hide + 'BuyBoxAddToCart').hide();
		buyingOptionsContainer.height(heightBefore);
		buyingOptionsContainer.stop().animate({height:heightAfter}, 'fast', function()
						      {
							  jQuery('#' + show + 'BuyBoxAddToCart').fadeIn('fast');
							  buyingOptionsContainer.css('height', '');
						      });
		this.selection = show;
		this.update_price_block(show, hide);
		this.toggle_quantity(show == 'original');
		this.toggle_ld_data(show, deal);
	    }
	},
	toggle_ld_data: function(show, deal) {
	    
	    if ( show == 'original' ) {
		jQuery('#lightningDealPctClaimed').hide();
		jQuery('#lightningDealTimeRemaining').hide();
	    } else {
		
		
		var pState = Deal.getPurchaseState (deal, null);
		this.show_message(deal, pState);
	    }
	},
	toggle_quantity: function(enable) {
	    
	    if ( enable ) {
		jQuery('#quantity').attr('disabled', '');
	    } else {
		jQuery('#quantity').attr('disabled', 'disabled');
	    }
	},
        show_cart_button: function(deal, pState) {
	    var self = this;
	    jQuery ('#lightningDealSelectionOption').click(
                function() {
		    self.select_option('lightningDeal', 'original', deal);
		}
	    );
	    self.determine_display(deal, pState);
        },
        button_events: function(deal, buttonType) {
        	var self = this;        
        	var buyImg = jQuery("#gbd_buy").each(
        		function() {
                	//Next check for the exitence of the buying div
                	var timeRemainingDisplay = jQuery("#gbd_messagetime_" + deal.dealID);
                	//TODO: Handle Variations
                	/*
                	if (timeRemainingDisplay && timeRemainingDisplay[0] && !template.dealCartButtonData.variationOptionsInCart) {
                		//Add event handler to count down
                		Deal.InCart.JQTimer(deal.asins[0].status.purchaseStatus.expiresDate,timeRemainingDisplay,true);
                	} 
                	if (template.dealCartButtonData.isVariation) {
                		//handle variation popover here
                		if (template.dealCartButtonData.variationOptionsInCart) {
                			//We have a variation deal with one or more options in cart.
                			//We need to attach the event handler to the link not the div.
                			Deal.InCart.JQTimer(template.dealCartButtonData.expiresDate,timeRemainingDisplay,true);
                			var optionsLink = jQuery("#gbd_options_link_" + template.dealCartButtonData.dealID).each(
                					function() {
                						new Deal.VariationPopover(this, deal, self.images, self.widget.cx);
                					}
                			);
                		} else {
                			new Deal.VariationPopover(this, deal, self.images, self.widget.cx);
                		}
                	} else { */        		
                		//handle cart event handling here
                		//attach event handler to the div if there's no active items in cart
        				if (parseInt(deal.status.msToEnd) > 0) {
					    jQuery(this).click(
                    				function(event) {
						    if (event) {
							event.stopPropagation();
							event.preventDefault();
						    }
						    
						    self.widget.cx.buy(deal, deal.asins[0].asin, (buttonType == 'Waitlist' ? true : false));
						    var checkingImg = jQuery("<img></img>")
						        .attr("id", "gbd_buying")
							.attr('alt', '...')
							.attr('src', window.gbResources.getImage('spinner'));
						    var checkingSpan = jQuery("<span></span>")
							.css({'font-size' : '11px'})
							.text(window.gbResources.getString('csld-checking_deal_status'));
						    jQuery(this).parent().empty().css({'background-color':'#FFFFFF','-moz-border-radius':'8px','border-radius':'8px','padding':'5px'})
							.append(checkingSpan).append(checkingImg);
						    return false;
                    				}
					    );
        				}
                	//}
        		}
        	);        
        },
	button_style: function(fullShow, clickable) {
	    var style = fullShow ?
	        'opacity:1.0;filter:"alpha(opacity=100)";' :
	        'opacity:.5;filter:"alpha(opacity=50)";';
	    style += clickable ? 'cursor:pointer;' : 'cursor:default;';
	    return style;
	},
        determine_display: function(deal, pState) {
	    
	    
	    if ( jQuery('#lightningDealPriceInput').attr('checked') == undefined ||
		 !jQuery('#lightningDealPriceInput').attr('checked') ) {
		return;
	    }
	    
	    var src    = window.gbResources.getImage('add_deal_to_cart');
	    var alt    = window.gbResources.getString('csld-add_to_cart');
	    var style  = '';
	    var showButtonEvents = false;
	    var buttonType = 'ATC';
	    var showButton = true;
	    var img = jQuery('<img></img>')
	      .attr('id', 'gbd_buy')
	      .addClass('gbd_cta');
	    if ( pState == Deal.pendingAtcStr ) {
		
		showButtonEvents = true;
		style  = this.button_style(1,1);
	    } else if ( pState == Deal.inCartStr || pState == Deal.claimedStr ) {
		
		showButtonEvents = false;
		style  = this.button_style(0,0);
	    } else if ( deal.status.percentClaimed >= 100 && deal.status.percentSoldOut < 100 ) {
		if  ( pState == Deal.waitInLineStr ) {
		    
		    showButtonEvents = false;
		    style  = this.button_style(0,0);
		} else if (deal.status.currentlyUnavailable && deal.status.currentlyUnavailable != "0") {
		    
		    src    = window.gbResources.getImage('waitlist_full_big');
		    alt    = 'Waitlist full';
		    showButtonEvents = false;
		    style  = this.button_style(1,0);
		    buttonType = 'WaitFull';
		} else {
		    
		    src    = window.gbResources.getImage('join_waitlist_big');
		    alt    = 'Join waitlist';
		    showButtonEvents = true;
		    style  = this.button_style(1,1);
		    buttonType = 'Waitlist';
		}
	    } else if ( pState == Deal.availableStr || pState == Deal.expiredStr ) {
		
		showButtonEvents = true;
		style  = this.button_style(1,1);
	    } else {
		
		return;
	    }
	    var bbATC = jQuery("#lightningDealBuyBoxAddToCart");
	    bbATC.empty().css({'background-color':'#C0DBF2'});
	    img.attr('src', src)
	        .attr('alt', alt)
	        .attr('style', style);
	    bbATC.append(img);
	    if ( showButtonEvents ) {
		this.button_events(deal, buttonType);
	    }
	},
	show_message: function(deal, pState) {
	    var message = '';
	    var timeLeftMsgDiv = null;
	    var waitlistHelp = false;
	    var PStatus = new Deal.PStatus(deal, this.widget.cx.images);
	    if (pState == Deal.pendingAtcStr || pState == Deal.inCartStr) {
		timeLeftMsgDiv = jQuery('<div></div>')
		    .addClass('status_msg_content')
		    .addClass('bb_status_msg_content');
		timeLeftMsgDiv.empty().append(PStatus.getContent(true));
		jQuery("#lightningDealPctClaimed").hide();
		jQuery("#lightningDealTimeRemaining").hide();
	    } else if (pState == Deal.waitInLineStr) {
		message = window.gbResources.getString("csld-on_waitlist");
		if ( this.selection == 'lightningDeal' ) {
		    jQuery("#lightningDealPctClaimed").show();
		}
		jQuery("#lightningDealTimeRemaining").hide();
	    //} else if (deal.parentAsin) {
		//TODO: Handle variations
		//we could be available yet be in cart for one of the asins if this is a variation deal
		//if we are variations - we need to calculate time_remaining as well
	    } else if (pState == Deal.claimedStr) {
		message = window.gbResources.getString('gbld-deal-claimed');
		jQuery("#lightningDealTimeRemaining").hide();
	    } else if ( deal.status.percentClaimed >= 100 &&
			deal.status.percentSoldOut < 100 &&
			deal.status.currentlyUnavailable !== 0) {
		message = window.gbResources.getString('gbld-waitlist-only') + ' ';
		jQuery("#lightningDealTimeRemaining").hide();
		if ( this.selection == 'lightningDeal' ) {
		    jQuery("#lightningDealPctClaimed").show();
		}
		waitlistHelp = true;
	    } else {
		
		if ( this.selection == 'lightningDeal' ) {
		    jQuery("#lightningDealPctClaimed").show();
		    jQuery("#lightningDealTimeRemaining").show();
		}
		jQuery("#lightningDealStatusMessage").empty();
		return;
	    }
	    var messageSpan = jQuery("<span></span>").addClass('item_msg').text(message);
	    var statusMessage = jQuery("#lightningDealStatusMessage").addClass('bb_status_message');
	    statusMessage.empty().append(messageSpan);
	    if ( waitlistHelp ) {
		statusMessage.append('(').append(Deal.help(window.gbResources.getString('gbld-whats-this'), 1))
		    .append(')').css({'font-weight':'normal'});
		jQuery('#waitlist_help').css({'font-family':'Arial','font-size':'12px'});
	    } else if ( timeLeftMsgDiv ) {
		statusMessage.append(timeLeftMsgDiv);
		jQuery('#gbd_status_msg_content').css({'position':'relative'});
		jQuery('#gbd_status_inner_msg').addClass('bb_status_inner_msg');
		jQuery('#gbd_status_inner_msg').removeClass('status_msg_content');
	    }
	}
    };  
};
//File that is a placeholder for the common widget framwork.
if ( !window.DealWidgets) {
//Creating an empty object first
window.DealWidgets = {};
};
//Fill up with the required properties for the base object literal
DealWidgets.newGenericDealWidget = function() {
	var that = null;
	var methods = {		
		//Simple variables
		name : "GenericWidget",
		initial_category : "all",
		initial_state : "available",
		got_metadata : false,
		show_if_no_deals : false,
		no_deals_message : "There are no deals",
		//Objects & Arrays
		cells : [],
		images : {},
		//Functions 
		make_cells: function(cells) {
        Deal.log("make_cells");
          that.cell_deals = [];
          that.cells = [];
        while (  that.middle_div.firstChild) {
              that.middle_div.removeChild(  that.middle_div.firstChild);
        }
        for (var i=0; i<cells; i++) {
            var cell = new   that.Cell(  that);
              that.cells.push(cell);
              that.cell_deals.push(null);
              that.middle_div.appendChild(cell.el);
        }
          that.position_cells();
        },
		init : function(me, p) {
			that = me;
            for (var name in p) {
                   that[name] = p[name];
            }
			for (var image in    that.images) {
				Deal.Widget.preload_img(   that.images[image]);
			}
			   that.make_frame();
			   that.cx.connect('metadata_change',    that, 'metadata_change');
               that.cx.connect('metadata_change',    that, 'metadata_change');
               that.cx.set_status(   that.initial_state);
               that.got_metadata = false;
               that.cx.connect('cell_change',    that, 'cell_change');
               that.cx.connect('page_change',    that, 'page_change');
		}
	};
	return Deal.create({}, methods);
};
//RW Environment
//File that is a placeholder for the common widget framwork.
if ( !window.DealWidgets) {
    //Creating an empty object first
    window.DealWidgets = {};
}
DealWidgets.newDetailPageLDWidget = function(object, typeOverrides) {
    //Create a base object from the Generic Widget if none is passed.
    var baseDealWidget = (object === undefined) ? DealWidgets.newGenericDealWidget() : object;
    var self = null;
    var methods = {
        name: "",
	make_frame : function() {
	    Deal.log(self.alertString);
	    this.show_deals();
	},	    
	cell_change: function(cell, deal) {
	    Deal.log("cell_change(0, "+(deal ? deal.dealID : 'null')+")");
	    if (!self.cell_deals) {
		Deal.log("Too early");
		return;
	    }
	    self.cell_deals[0] = deal;
	    self.cells[0].show_deal(deal);
	},
	page_change: function() {
	    Deal.log("page_change");
	},
	show_deals: function() {
	    Deal.log("show_deals");	
	    self.make_cells();
	    self.cells[0].show_deal(self.deal);
	},   
	make_cells: function() {
	    Deal.log("make_cells");
	    self.cell_deals = [];
	    self.cells = [];
	    var cell = DealWidgets.newDetailPageCell(self);
	    self.cells.push(cell);
	    self.cell_deals.push(null);
	    cell.init(self.deal);
	},
	show_loading: function() {
	    Deal.log("show_loading");
	},
	init: function(p) {
	    for (var name in p) {
		this[name] = p[name];
	    }
	    
	    
	    
	    
	    this.dealID = p.cx.deal_ids[0];
	    this.deal = p.cx.deals.deals[this.dealID];
	    this.make_frame();
	    this.cx.connect('cell_change', this, 'cell_change');
	}
    };
    self = Deal.create(baseDealWidget, methods);
    return self;	
};

