/*
    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(){
Deal = {};
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.min = function(a /* arguments */) {
    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 /* arguments */) {
    var max = arguments[0];
    for (var i=1; i<arguments.length; i++) {
        if (arguments[i] > max) {
            max = arguments[i];
        }
    }
    return max;
};
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.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.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 (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 (i=0; i<src.signals.length; i++) {
            src.__signals__[src.signals[i]] = {};
        }
        for (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 (id in this.__signals__[signal]) {
                this.__signals__[signal][id].disconnect();
            }
            return;
        }
        for (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 = [];
        for (var i=1; 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;
    },
    
    
    
    
    
    call: function(url, data, success, error, retryInterval) {
        var self = this;
        var start = new Date();
        var retries = 0;
	retryInterval = retryInterval ||
            Deal.Service.base_retry_interval ||
            60000;
        var retry = function () {
            
            if (!Deal.Service.continue_requests) {
                error(new Error(
                    "continue_requests is false:"+
                    " no more requests should be made."));
                return;
            }
            var this_url = url;
            if (this_url.indexOf('?') == -1) {
                this_url = this_url+'?nocache='+new Date().getTime();
            } else {
                this_url = this_url+'&nocache='+new Date().getTime();
            }
            jQuery.ajax({
                success: function(result) {
                    if (result.responseMetadata) {
                        Deal.Service.base_retry_interval =
                            result.responseMetadata.baseRetryInterval;
                        Deal.Service.continue_requests =
                            result.responseMetadata.continueRequests;
                    }
                    success(result);
                },
                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);
                    }
                },
                url: this_url,
                type: "POST",
                data: JSON.stringify(data),
                dataType: 'json'});
        };
        retry();
    },
    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
            }, 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) {
        var self = this;
        var deal_ids = self._deal_ids_from_callbacks(deal_ids_to_callbacks);
        var i;
        self.call(
            '/xa/goldbox/GetDeals',
            {
                requestMetadata: {
                    marketplaceID: self.marketplace_id},
                customerID: self.customer_id,
                filter: Deal.Service.DealIDDealFilter(deal_ids),
                ordering: [],
                page: 1,
                resultsPerPage: deal_ids.length
            },
            function(result) {
                var seen={};
                for (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 (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 (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 (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 (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 (i=0; i<deal_ids.length; i++) {
                    deal_id = deal_ids[i];
                    deal_ids_to_callbacks[deal_id][1](error);
                }
            });
    },
    redeem_deal: function(deal_id, success, error) {
        var self = this;
        self.call(
            '/gp/deal/ajax/redeemDeal.html'+
                '?marketplaceID='+self.marketplace_id+
                '&dealID='+ deal_id+
                '&sessionID='+self.session_id,
            {},
            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: {
                
            }
        }
    },
    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);
        }
    },
    _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 (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 (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
     *  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: 
     *  }],
     *  customer: { (optional)
     *      marketplaceID:
     *      dealID:
     *      customerID:
     *      claimed:
     *  }
     */
    signals: [
        
        'change',
        
        'expire',
        
        'status_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 = [];
    },
    _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());
    },
    
    load_from_deal: function(deal) {
        var self = this;
        var now = new Date();
        var attr;
        for (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) {
            for (var i=0; i<self.asins.length; i++) {
                self.asins[i].offerServiceSoldOut = 
                    self.asins[i].offerServiceSoldOut == "1";
            }
        }
        self._init_status(now);
        self.signal('change', self);
    },
    
    load_from_status: function(status) {
        var self = this;
        self.status = status;
        self._init_status(new Date());
        self.signal('change', self);
    }
});
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));
    }
};
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.service = new Deal.Service({
            marketplace_id: p.marketplace_id,
            customer_id: p.customer_id,
            session_id: p.session_id,
            timeout: p.timeout
        });
        
        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.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 = [];
    },
    
    
    _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"])
            ])
        };
        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 (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 (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 (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 (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();
            });
        });
        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) {
        var self = this;
        
        
        if (!self.service.customer_id) {
            var uri = self.login_uri;
            uri = uri.replace(/%257BdealID%257D/,
                deal.dealID);
            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,
                function(result) {
                    delete self.buying[deal.dealID];
                    if (result.redeemed) {
                        window.location = '/gp/cart/view.html'+
                            '?ref=csld_'+deal.dealID;
                    } else {
                        deal.load_from_deal(result.deal);
                    }
                }, function (error) {
                    delete self.buying[deal.dealID];
                    Deal.log("Error redeeming deal: "+error);
                    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.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.Timer = function(t) {
    var self = {};
    self.t = t;
    self.span = Deal.DOM.span();
    self.update = function() {
        var msToT = Deal.max(
            self.t.getTime() - new Date().getTime(),
            0);
        if (msToT <= 0 && self.cx) {
            self.cx.disconnect();
        }
        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) { m = '0'+m; }
        var s = Math.floor(msToT/(1000)%60);
        if (s<10) { s = '0'+s; }
        self.span.innerHTML = h+':'+m+':'+s;
    };
    self.cx = Deal.clock.connect('tick', self.update);
    self.update();
    self.disconnect = function() {
        self.cx.disconnect();
    };
    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.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(image) {
    var div;
    var img;
    var text;
    div = Deal.DOM.div(null, [
        img = Deal.DOM.img({
            src: image
        })
    ]);
    amznJQ.available('popover', function() {
        jQuery(img).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 div;
};
})();}
var CSLD_Category_Center = Deal.Class({
    __init__: function(p) {
        var self = this;
        self.el = p.el;
        self.orderings = p.orderings;
        self.filters = p.filters;
        self.tz = p.tz;
        self.title = p.title;
        self.cx = p.controller;
        self.images = p.images;
        for (var image in self.images) {
            Deal.Widget.preload_img(self.images[image]);
        }
        self.show_if_no_deals = p.show_if_no_deals || false;
        self.no_deals_message = p.no_deals_message || 'There are no deals';
        self.make_frame();
        self.make_cells(1);
        self.cx.connect('cell_change', self, 'cell_change');
        self.cx.connect('page_change', self, 'page_change');
        self.cx.set_cells(1);
        self.cx.set_ordering(self.orderings[0]);
        self.cx.set_filter(self.filters[0]);
        self.cx.set_page(p.show_page || 1);
    },
    
    make_frame: function() {
        var self = this;
        self.page_span = Deal.DOM.span({
            id:'csld-current-page'
        });
        self.pages_span = Deal.DOM.span({
            id:'csld-total-pages'
        });
        self.from_span = Deal.DOM.span();
        self.to_span = Deal.DOM.span();
        self.of_span = Deal.DOM.span();
        self.back_button = Deal.DOM.div({
            id: 'csld-back-button',
            title: 'Back',
            style: 'position: relative; top:95px;'
        }, [
            Deal.DOM.div({
                style: 'left:0; height:50px; width:25px'+
                    '; overflow: hidden; position: absolute'
            }, ['back'])
        ]);
        self.back_button.img = Deal.DOM.div({
                style: 'left:0; height:50px; width:25px'+
                    '; overflow: hidden; position: absolute'+
                    "; background: url('"+self.images.back+"')"
            });
        self.back_button.appendChild(self.back_button.img);
        self.back_button.onmousedown = function(event) {
            if (event) {
                event.stopPropagation();
                event.preventDefault();
            }
            self.back_button.img.style.backgroundPosition = "0 50px";
            self.prev_page();
            return false;
        };
        self.back_button.onmouseup = function(event) {
            if (event) {
                event.stopPropagation();
                event.preventDefault();
            }
            self.back_button.img.style.backgroundPosition = "0 0";
            return false;
        };
        self.next_button = Deal.DOM.div({
            id: 'csld-next-button',
            title: 'Next',
            style: 'position: relative; top:95px;'
        }, [
            Deal.DOM.div({
                style: 'right:0; height:50px; width:25px'+
                    '; overflow: hidden; position: absolute'
            }, ['next'])
        ]);
        self.next_button.img = Deal.DOM.div({
            style: 'right:0; height:50px; width:25px'+
                '; overflow: hidden; position: absolute'+
                "; background: url('"+self.images.next+"')"
        });
        self.next_button.appendChild(self.next_button.img);
        self.next_button.onmousedown = function(event) {
            if (event) {
                event.stopPropagation();
                event.preventDefault();
            }
            self.next_page();
            self.next_button.img.style.backgroundPosition = "0 50px";
            return false;
        };
        self.next_button.onmouseup = function(event) {
            if (event) {
                event.stopPropagation();
                event.preventDefault();
            }
            self.next_button.img.style.backgroundPosition = "0 0";
            return false;
        };
        self.no_deals_message_div = Deal.DOM.div({
            style: 'height: 127px;'+
                'overflow:hidden;'+
                'margin-top: 113px;'+
                'padding: 0 4px 0;'+
                'display:none;'+
                'font-size: 14px;'+
                'text-align: center;'
        });
        self.middle_div = Deal.DOM.div({
            style: 'height: 240px;'+
                'overflow:hidden;'+
                'padding: 0 4px 0;'
            });
        Deal.DOM.appendChildren(self.el, [
            
            Deal.DOM.table({
                    'class':'main',
                    'style':"background: #EFF5F9"+
                        " url('"+self.images.box_tbevel+"')"+
                        " repeat-x center bottom;"
            }, [
                Deal.DOM.tr(null, [
                    Deal.DOM.td({
                        'style':"width: 12px; padding: 0;"+
                            " background: url('"+self.images.box_tl+"')"+
                            " no-repeat left top"}),
                    Deal.DOM.td({
                        'style':"padding: 7px 5px;"+
                            " background: url('"+self.images.box_tc+"')"+
                            " repeat-x right top"
                    }, [Deal.DOM.div({
                        style:'position:relative;'
                    }, [
                        Deal.DOM.div({
                            style:'position: absolute;'+
                                'right:-12px;'+
                                'top:-3px;'
                        }, [Deal.help(self.images.help)]),
                        Deal.DOM.div({
                            'style':'white-space: nowrap;'+
                                'color:#E47911;'+
                                'font-size:14px;'+
                                'font-weight:bold;'
                        }, [self.title])
                    ])
                    ]),
                    Deal.DOM.td({
                        'style':"width: 12px; padding: 0;"+
                            " background: url('"+self.images.box_tr+"')"+
                            " no-repeat right top"})
                ])
            ]),
            
            Deal.DOM.table({
                'class':'main',
                'style':"background: #FFFFFF url('"+self.images.box_div+"')"+
                    " repeat-x center top;"
            }, [
                Deal.DOM.tr({valign:'top'}, [
                    Deal.DOM.td({
                        'style':"width: 12px; padding: 0;"+
                            "background: url('"+self.images.box_l+"')"+
                            " repeat-y left top"
                    }),
                    Deal.DOM.td({
                        width: 25
                    }, [self.back_button]),
                    Deal.DOM.td({
                    }, [self.no_deals_message_div, self.middle_div]),
                    Deal.DOM.td({
                        width: 25
                    }, [self.next_button]),
                    Deal.DOM.td({
                        'style':"width: 12px; padding: 0"+
                            "; background: url('"+self.images.box_r+"')"+
                            " repeat-y right top"
                    })
                ])
            ]),
            
            Deal.DOM.table({
                'class':'main',
                'style':"background: #EFF5F9 url('"+self.images.box_div+"')"+
                    " repeat-x center top;"
            }, [
                Deal.DOM.tr({valign:'top'}, [
                    Deal.DOM.td({
                        'style':"width: 12px; padding: 0;"+
                            " background: url('"+self.images.box_bl+"')"+
                            " no-repeat left bottom"}),
                    Deal.DOM.td({
                        'style':"background: url('"+self.images.box_bc+"')"+
                            " repeat-x left bottom;"
                    }, [
                        Deal.DOM.div({
                            'style':'white-space: nowrap;'+
                                'color: #666666;'+
                                'font-size: 10px;'+
                                'padding: 6px 5px 10px;'
                        }, [
                            "Page ",
                            self.page_span,
                            " of ",
                            self.pages_span
                        ])
                    ]),
                    Deal.DOM.td({
                        'style':"background: url('"+self.images.box_bc+"')"+
                            " repeat-x left bottom;"
                    }, [
                        Deal.DOM.div({
                            'style':'white-space: nowrap;'+
                                'text-align: right;'+
                                'font-size: 10px;'+
                                'padding: 6px 5px 10px;'
                        }, [
                            Deal.DOM.span({
                                'style':'color: #E47911'
                            }, ['\u203A ']),
                            Deal.DOM.a({
                                'href':'/gp/help/customer/display.html?nodeId=200417170'
                            }, ["Learn More"])
                        ])
                    ]),
                    Deal.DOM.td({
                        'style':"width: 12px; padding: 0;"+
                            " background: url('"+self.images.box_br+"')"+
                            " no-repeat right bottom"})
                ])
            ])
        ]);
    },
    make_cells: function(cells) {
        var self = this;
        self.cell_deals = [];
        self.cells = [];
        while (self.middle_div.firstChild) {
            self.middle_div.removeChild(self.middle_div.firstChild);
        }
        for (var i=0; i<cells; i++) {
            var cell = new self.Cell(self);
            self.cells.push(cell);
            self.cell_deals.push(null);
            self.middle_div.appendChild(cell.el);
        }
    },
    cell_change: function(cell, deal) {
        var self = this;
        Deal.log("cell_change("+cell+", "+(deal ? deal.dealID : 'null')+")");
        self.cell_deals[cell] = deal;
        if (!self.animating) {
            self.cells[cell].show_deal(deal);
        }
    },
    page_change: function(page, pages, from, to, of) {
        var self = this;
        self.page_span.innerHTML = page;
        self.pages_span.innerHTML = pages;
        self.from_span.innerHTML = from;
        self.to_span.innerHTML = to;
        self.of_span.innerHTML = of;
        if (of === 0) {
            if (!self.show_if_no_deals) {
                self.el.style.display = 'none';
            }
            self.back_button.style.display = 'none';
            self.next_button.style.display = 'none';
            self.middle_div.style.display = 'none';
            self.no_deals_message_div.style.display = '';
            self.no_deals_message_div.innerHTML = self.no_deals_message;
        } else {
            self.el.style.display = '';
            if (pages == 1) {
                self.back_button.style.display = 'none';
                self.next_button.style.display = 'none';
            } else {
                self.back_button.style.display = '';
                self.next_button.style.display = '';
            }
            self.no_deals_message_div.style.display = 'none';
            self.middle_div.style.display = '';
        }
    },
    next_page: function() {
        var self = this;
        var i;
        self.animating = 1;
        for (i=0; i<self.cells.length; i++) {
            self.cells[i].show_blank();
        }
        self.cx.next_page(true);
        var complete = function() {
            self.animating = 0;
            for (var i=0; i<self.cells.length; i++) {
                self.cells[i].show_deal(self.cell_deals[i]);
            }
        };
        window.setTimeout(complete, 100);
    },
    prev_page: function() {
        var self = this;
        self.animating = 1;
        for (i=0; i<self.cells.length; i++) {
            self.cells[i].show_blank();
        }
        self.cx.prev_page(true);
        var complete = function() {
            self.animating = 0;
            for (var i=0; i<self.cells.length; i++) {
                self.cells[i].show_deal(self.cell_deals[i]);
            }
        };
        window.setTimeout(complete, 100);
    },
    Cell: Deal.Class({
        __init__:function(widget) {
            var self = this;
            self.widget = widget;
            self.images = widget.images;
            self.el = Deal.DOM.div();
            self.timeout = {};
        },
        show_blank: function() {
            var self = this;
            Deal.DOM.clearChildren(self.el);
        },
        show_deal: function(deal) {
            var self = this;
            
            for (var timeout in self.timeout) {
                window.clearTimeout(self.timeout[timeout]);
                delete self.timeout[timeout];
            }
            if (!deal) {
                self.show_blank();
                return;
            }
            if (deal.loading) {
                self.show_loading(deal);
                return;
            }
            if (deal.teaser && deal.teaser.teaser && !deal.status.started) {
                self.show_teaser(deal);
                return;
            }
            self.show_active(deal);
        },
        show_loading: function(deal) {
            var self = this;
            Deal.DOM.replaceChildren(self.el, [
                Deal.DOM.img({
                    id:'loading-'+deal.dealID,
                    style:'display:block;'+
                        'margin:65px auto 65px;', 
                    src:self.images.loading,
                    alt:'Loading...'
                })
            ]);
        },
        show_active: function(deal) {
            var self = this;
            var title;
            Deal.DOM.replaceChildren(self.el, [
                Deal.DOM.table({
                    id: 'active-'+deal.dealID,
                    style:'margin: 0 auto;'
                }, [
                    Deal.DOM.tr({valign:'top'}, [
                        Deal.DOM.td({
                            width:'120',
                            style:'padding-top: 10px;'
                        }, [
                            self.start_time_span(deal)
                        ]),
                        Deal.DOM.td({width:'220'})
                    ]),
                    Deal.DOM.tr({valign:'top'}, [
                        Deal.DOM.td({
                            width:'120',
                            style:'padding-top: 16px;'
                        }, [
                            self.deal_image(deal)
                        ]),
                        Deal.DOM.td({
                            width:'220',
                            style:'padding-top: 16px; padding-left: 15px;'
                        }, [
                            title = self.deal_title(deal),
                            Deal.DOM.div({
                                style:'padding-top: 10px;'
                            }, [self.price_block(deal)]),
                            Deal.DOM.div({
                                style:'text-align: center;'+
                                    'padding-top:10px;'+
                                    'font-size: 10px;'+
                                    'color:#004B91;'
                            }, [
                                self.rating(deal),
                                " ",
                                self.reviews(deal)
                            ])
                        ])
                    ]),
                    Deal.DOM.tr({valign:'top'}, [
                        Deal.DOM.td({
                            width:'120',
                            style:'padding-top: 16px;'
                        }, deal.status.started ? [
                            self.progress_bar(deal),
                            Deal.DOM.div({
                                style:'padding-top: 10px;'+
                                    'font-size: 11px;'+
                                    'text-align: center;'
                            }, [self.time_remaining(deal, 'remaining')])
                        ] : []),
                        Deal.DOM.td({
                            width:'220',
                            style:'padding-top: 16px; padding-left: 15px;'
                        }, [self.buy_area(deal)])
                    ])
                ])
            ]);
            Deal.truncate_text(title);
        },
        show_teaser: function(deal) {
            var self = this;
            var teaser;
            Deal.DOM.replaceChildren(self.el, [
                Deal.DOM.table({
                    id: 'teaser-'+deal.dealID,
                    style:'margin: 0 auto;'
                }, [
                    Deal.DOM.tr({valign:'top'}, [
                        Deal.DOM.td({
                            width:'120',
                            style:'padding-top: 10px;'
                        }, [self.start_time_span(deal)]),
                        Deal.DOM.td({width:'220'})
                    ]),
                    Deal.DOM.tr({valign:'top'}, [
                        Deal.DOM.td({
                            width:'120',
                            style:'padding-top: 25px;'
                        }, [Deal.DOM.img({
                            id:'teaser-image-'+deal.dealID,
                            src:self.images.teaser})]),
                        Deal.DOM.td({
                            width:'220',
                            style:'padding-top: 25px; padding-left: 15px;'
                        }, [
                            Deal.DOM.div({
                                style:'font-weight:bold;'
                            }, ["Upcoming Deal"]),
                            teaser = Deal.DOM.div({
                                id:'teaser-text-'+deal.dealID,
                                style:'color:#666666;'+
                                    'width: 220px;'+
                                    'max-height: 120px;'+
                                    'overflow: hidden;'
                            }, [deal.teaser.teaser]),
                            Deal.DOM.div({
                                style:'font-style:italic;'+
                                    'font-weight:bold;'+
                                    'padding-top:20px;'
                            }, ["Coming Soon!"]),
                            Deal.DOM.div({
                            }, [
                                "Deal starts in: ",
                                self.start_timer(deal, 'start')
                            ])
                        ])
                    ])
                ])
            ]);
            Deal.truncate_text(teaser);
        },
        start_time_span: function(deal) {
            var self = this;
            var tz_offset = self.widget.tz.offset;
            var startDate = new Date(
                deal.startDate.valueOf()+
                    deal.startDate.getTimezoneOffset()*60*1000+
                    tz_offset);
            var hour = startDate.getHours() % 12 || 12;
            var minute = startDate.getMinutes();
            if (minute < 10) {
                minute = "0"+minute;
            }
            var am_pm = deal.startDate.getHours() >= 12 ? 'PM' : 'AM';
            return Deal.DOM.span({
                id:'start-time-'+deal.dealID,
                style:'font-size: 11px;'+
                    'color: #666666;'
            }, [
                hour+":"+minute+" "+am_pm+" "+self.widget.tz.name
            ]);
        },
        start_timer: function(deal, timeout_id) {
            var self = this;
            var span = Deal.DOM.span({
                id:'start-timer-'+deal.dealID
            });
            var update = function() {
                window.clearTimeout(self.timeout[timeout_id]);
                delete self.timeout[timeout_id];
                var msToStart = deal.status.startDate.valueOf() -
                    new Date().valueOf();
                if (msToStart < 0) { msToStart = 0; }
                var h = Math.floor(msToStart/(60*60*1000));
                if (h<10) { h = '0'+h; }
                var m = Math.floor(msToStart/(60*1000)%60);
                if (m<10) { m = '0'+m; }
                var s = Math.round(msToStart/(1000)%60);
                if (s<10) { s = '0'+s; }
                span.innerHTML = h+':'+m+':'+s;
                if (msToStart > 0) {
                    self.timeout[timeout_id] =
                        window.setTimeout(update, msToStart%1000+100);
                }
            };
            update();
            return span;
        },
        deal_url: function(deal) {
            var self = this;
            if (deal.detail.url) {
                return deal.detail.url;
            } else if (deal.asins.length == 1) {
                return '/gp/product/'+deal.asins[0].asin;
            } else {
                Deal.log("fudging title href");
                return '/gp/goldbox';
            }
        },
        deal_image: function(deal) {
            var self = this;
            
            var src = deal.detail.imageAsin;
            if (!src) {
                src = self.images.no_image ||
                    'http://g-ecx.images-amazon.com/images/G/01/x-site/icons/no-img-sm.gif';
            }
            src = src.replace(/\.((_.*_)\.)?(gif|jpg)$/, '.$2_AA120_.$3');
            return Deal.DOM.a({
                href: self.deal_url(deal)
            }, [
                Deal.DOM.img({
                    id:'image-'+deal.dealID,
                    src: src, width:120, height:120
                })
            ]);
        },
        deal_title: function(deal) {
            var self = this;
            return Deal.DOM.a({
                style: 'display: block;'+
                    'font-size: 12px;'+
                    'line-height: 15.6px;'+
                    'height: 33px;'+
                    'width: 237px;'+
                    'overflow: hidden;',
                id:'deal-title-'+deal.dealID,
                href: self.deal_url(deal)
            }, [
                deal.detail.title || "(no title)"
            ]);
        },
        price_block: function(deal) {
            var self = this;
            
            if (!deal.asins || deal.asins.length != 1) {
                return deal.detail.description;
            }
            var sold_out = self.deal_sold_out(deal);
            var claimed = deal.status.percent_claimed >= 100;
            var expired = deal.status.ended;
            var prices = [];
            var divider = Deal.DOM.tr({}, [
                Deal.DOM.td({colspan:4}, [
                    Deal.DOM.div({style:'border-bottom:1px solid #AACFE2;'})
                ])
            ]);
            var asin = deal.asins[0];
            var listPrice = asin.listPrice;
            var ourPrice  = asin.ourPrice;
            var dealPrice = asin.dealPrice;
            var ourPercentOff = Math.round(Deal.Price.percent_off(
                listPrice, ourPrice));
            var dealDiscount = Deal.Price.minus(ourPrice, dealPrice);
            var dealPercentOff = Math.round(Deal.Price.percent_off(
                listPrice, dealPrice));
            var row = function(color, line_through, title, minus, price, percent) {
                var tr = Deal.DOM.tr();
                var td;
                tr.appendChild(td = Deal.DOM.td_nowrap({
                    style:'text-align:right;'+
                        'color:'+color+';'
                }, [title+":"]));
                tr.appendChild(td = Deal.DOM.td_nowrap({
                        style:'text-align: right; color: '+color+';'+
                            'padding-left:10px;'
                    }, [(minus && dealDiscount) ? '-' : '\xA0']));
                tr.appendChild(td = Deal.DOM.td_nowrap({
                    id:title+'-'+deal.dealID,
                    style:'color:'+color+';'+
                        'text-align:right;'
                }, [price]));
                if (line_through) {
                    td.style.textDecoration='line-through';
                }
                if (percent == 'double') {
                    td.colSpan = 2;
                    td.style.textAlign = 'left';
                } else {
                    tr.appendChild(td = Deal.DOM.td_nowrap({
                        id:title+'-percent-off-'+deal.dealID,
                        width: 40,
                        style:'color:'+color+';'+
                            'padding-left:4px;'
                    }, [percent?' ('+percent+'% off)':'']));
                    if (line_through) {
                        td.style.textDecoration='line-through';
                    }
                }
                prices.push(tr);
            };
            if (listPrice) {
                row('#666666', true, "List Price",
                    false, Deal.Price.format(listPrice), null);
            }
            if (sold_out || claimed || expired) {
                row('#990000', false, "Amazon's Price",
                    false, Deal.Price.format(ourPrice),
                    ourPercentOff);
                row('#666666', false, "Lightning Deal Discount",
                    true, Deal.Price.format(dealDiscount, 'Not Available'),
                    null);
                prices.push(divider);
                row('#666666', false, "Lightning Deal Price",
                    false, Deal.Price.format(dealPrice, 'Not Available'),
                    dealPercentOff);
            } else if (!deal.status.started) {
                row('#666666', false, "Amazon's Price",
                    false, Deal.Price.format(ourPrice),
                    ourPercentOff);
                row('#990000', false, "Lightning Deal Discount",
                    false, 'Check Back', 'double');
                prices.push(divider);
                row('#990000', false, "Lightning Deal Price",
                    false, 'Check Back', 'double');
            } else {
                row('#666666', false, "Amazon's Price",
                    false, Deal.Price.format(ourPrice),
                    ourPercentOff);
                row('#000000', false, "Lightning Deal Discount",
                    true, Deal.Price.format(dealDiscount, 'Not Available'),
                    null);
                prices.push(divider);
                row('#990000', false, "Lightning Deal Price",
                    false, Deal.Price.format(dealPrice, 'Not Available'),
                    dealPercentOff);
            }
            return Deal.DOM.table({
                id:'price-block-'+deal.dealID,
                'class':'price-block'
            }, prices);
        },
        rating: function(deal) {
            var self = this;
            if (deal.asins.length !== 1) {
                return;
            }
            var asin = deal.asins[0];
            var rating = asin.reviewData.averageRating;
            if (rating === 0 || rating == '0') {
                return;
            }
            var rounded = Math.round(rating*2)/2;
            var stars = 'stars_'+Math.floor(rounded)+'_'+(rounded%1*10);
            return Deal.DOM.a({
                id: 'rating-'+deal.dealID,
                href: '/gp/product-reviews/'+asin.asin
            }, [Deal.DOM.img({
                align:'absbottom', width:'55',
                alt:rating+' out of 5 stars',
                src:self.images[stars]})
            ]);
        },
        reviews: function(deal) {
            var self = this;
            if (deal.asins.length !== 1) {
                return;
            }
            var asin = deal.asins[0];
            return Deal.DOM.a({
                id: 'reviews-'+deal.dealID,
                href: '/gp/product-reviews/'+deal.asins[0].asin
            }, [
                "("+
                Deal.commify(asin.reviewData.totalReviews)+
                " reviews)"
            ]);
        },
        progress_bar: function(deal) {
            var self = this;
            if (!deal.limitedQuantity) {
                return null;
            }
            var sold_out = self.deal_sold_out(deal);
            var claimed = deal.status.percent_claimed >= 100;
            var expired = deal.status.ended;
            var bg = '#C3DDEB';
            var text = '';
            if (sold_out || claimed || expired) {
                bg = '#CCCCCC';
                text = 'font-weight: bold; color: #999999;';
            }
            var percent;
            if (sold_out) {
                percent = 100;
            } else {
                percent = Deal.commify(deal.status.percentClaimed, 0);
            }
            return Deal.DOM.div({
                style:'position: relative;'+
                    ' border: 1px solid #BBCCDD;'+
                    ' font-size: 11px;'+
                    ' height: 19px'
            }, [
                Deal.DOM.div({
                    id:'progress-bar-'+deal.dealID,
                    style:'width:'+percent+'%;'+
                        'height:19px;'+
                        'position:absolute;'+
                        'background-color:'+bg+';'
                }),
                Deal.DOM.div({
                    style:'position: absolute; width: 100%;'+
                        'font-size: 11px;'+
                        text+
                        'padding: 4px;'+
                        'text-align: center;'
                }, [percent+"% Claimed"])
            ]);
        },
        deal_sold_out: function(deal) {
            var self = this;
            if (!deal.asins || deal.asins.length === 0) {
                return false;
            }
            for (var i=0; i<deal.asins.length; i++) {
                if (!deal.asins[i].offerServiceSoldOut) {
                    return false;
                }
            }
            return true;
        },
        time_remaining: function(deal, timeout_id) {
            var self = this;
            if (deal.status.percentClaimed >= 100 ||
                    deal.status.ended ||
                    self.deal_sold_out(deal)
            ) {
                return null;
            }
            var span = Deal.DOM.span({
                id:'time-remaining-'+deal.dealID
            });
            var update = function() {
                if (self.timeout[timeout_id]) {
                    window.clearTimeout(self.timeout[timeout_id]);
                    delete self.timeout[timeout_id];
                }
                var msToEnd = deal.status.endDate.valueOf() -
                    new Date().valueOf();
                if (msToEnd < 0) { msToEnd = 0; }
                var h = Math.floor(msToEnd/(1000*60*60));
                if (h < 10) { h = '0'+h; }
                var m = Math.floor(msToEnd/(1000*60)%60);
                if (m < 10) { m = '0'+m; }
                var s = Math.floor(msToEnd/1000%60);
                if (s < 10) { s = '0'+s; }
                span.innerHTML = h+':'+m+':'+s+' remaining';
                if (msToEnd > 0) {
                    self.timeout[timeout_id] = window.setTimeout(
                        update, msToEnd%1000);
                }
            };
            update();
            return span;
        },
        buy_area: function(deal) {
            var self = this;
            var div = Deal.DOM.div({
                style:'position: relative;'+
                    'font-size: 11px;'
            });
            for (var i=0; i< deal.asins.length; i++) {
                if (deal.asins[i].isPrimeEligible) {
                    div.appendChild(
                        Deal.DOM.div({
                            id:'prime-'+deal.dealID,
                            style:'position: absolute; left:0;'
                        }, [
                            Deal.DOM.img({
                                alt:'Prime Eligible',
                                src:self.images.prime
                            })
                        ])
                    );
                    break;
                }
            }
            var subdiv;
            div.appendChild(subdiv=Deal.DOM.div({style:'text-align:center'}));
            if (deal.customer && deal.customer.claimed) {
                Deal.DOM.appendChildren(subdiv, [
                    Deal.DOM.div({
                        id:'claimed-'+deal.dealID,
                        style:'font-weight:bold;'+
                            'font-style:italic;'
                    }, ["Deal Claimed!"])
                ]);
            } else if (deal.status.percentClaimed >= 100 ||
                self.deal_sold_out(deal)
            ) {
                Deal.DOM.appendChildren(subdiv, [
                    Deal.DOM.div({
                        id:'sold-out-'+deal.dealID,
                        style:'font-weight:bold;'+
                            'font-style:italic;'
                    }, ["Sold Out!"])
                ]);
            } else if (deal.status.ended) {
                Deal.DOM.appendChildren(subdiv, [
                    Deal.DOM.div({
                        id:'expired-'+deal.dealID,
                        style:'font-weight:bold;'+
                            'font-style:italic;'
                    }, ["Expired!"])
                ]);
            } else if (!deal.status.started) {
                Deal.DOM.appendChildren(subdiv, [
                    Deal.DOM.div({
                        id:'coming-soon-'+deal.dealID,
                        style:'font-weight:bold;'+
                            'font-style:italic;'
                    }, ["Coming Soon!"]),
                    Deal.DOM.div({
                        style:'padding-top: 10px;'
                    }, [
                        "Deal starts in: ",
                        self.start_timer(deal, 'start')
                    ])
                ]);
            } else {
                Deal.DOM.appendChildren(subdiv, [
                    self.buy_button(deal),
                    Deal.DOM.br(),
                    "Discount applied at checkout.",
                    Deal.DOM.br(),
                    "One per customer."
                ]);
            }
            return div;
        },
        buy_button: function(deal) {
            var self = this;
            var button = Deal.DOM.div();
            var show_button = function() {
                Deal.DOM.replaceChildren(button, [
                    Deal.DOM.img({
                        id:'buy-'+deal.dealID,
                        alt:'Add to cart',
                        src:self.images.add_to_cart,
                        style:'cursor: pointer;'
                    })
                ]);
            };
            var show_progress = function() {
                Deal.DOM.replaceChildren(button, [
                    Deal.DOM.img({
                        id:'buying-'+deal.dealID,
                        alt:'Waiting in line...',
                        src:self.images.spinner
                    }),
                    Deal.DOM.span({
                        style:'color: #E68221;'+
                            'font-weight: bold;'
                    }, ["Waiting in Line"])
                ]);
            };
            if (self.widget.cx.buying[deal.dealID]) {
                show_progress();
            } else {
                show_button();
            }
            button.onclick = function(event) {
                if (event) {
                    event.stopPropagation();
                    event.preventDefault();
                }
                self.widget.cx.buy(deal);
                show_progress();
                return false;
            };
            return button;
        }
    })
});
