/*
 * a simple script loader for loading JS from JS. 
 */


// initial name space creator.
window.searchMash = window.searchMash || {};

/*
 * searchMash.config
 */ 

searchMash.config = {
    debug: false,
    prompt: "Type what you're looking for",
    searcher_prefix: "sp/",
    sad_image: "/images/sad_image.png",
    spinner_image: "/spinner.gif",
    seesharepage_image: "/images/seeshareicon.png",
    page_size: 8,
    preview_disabled: false,
    preview_toggle: {
        features_disable_text: "click to turn off image preview",
        features_enable_text: "click to turn on image preview",
        cookie_name: "ds"
    },
    lunge_settings: {
        active: true,

        // amount of time spent in thumb required to enable lunge
        stay_time: 300, // ms
        // mouse speed in pixels/s required to enable lunge
        slow_threshold: 10,
        // amount of time required below speedThreshold to enable lunge
        slow_time: 100,

        // amount of extra time to allow lunging into preview
        wait_time: 30 // ms
    },
    youtube: { 
        enabled: false,
        autoplay: false
    }
};

/*
 * searchMash.dbg 
 */ 
(function () {
    var searchMash = window.searchMash,
        mashConfig = searchMash.config,
        log;

    if (mashConfig.debug && searchMash.initDebugPanel ) {
        searchMash.initDebugPanel ();
    } else if (window.console) {
        log = window.console.log.apply ? 
            function () { 
                window.console.log.apply (window.console, arguments);
            }:
            function () { 
                console.log (Array.prototype.join.call (arguments," "));
            };

        searchMash.dbg = function () {  
            if (mashConfig.debug) { 
                log.apply (this, arguments);
            }
        };
    } else { 
        searchMash.dbg = function () {};
    }
}) ();

/*
 * searchMash.util debug timer
 */
(function () {
    var timer_data = {},
        searchMash = window.searchMash,
        dbg  = searchMash.dbg,
        util; 

    util = searchMash.util = searchMash.util || {};

    function time () { 
        return new Date ().getTime ();
    }
    util.time = time;

    window.start_time = window.start_time || time ();
    function delta () { 
        dbg.apply (
            searchMash, 
            [ time () - window.start_time].concat (
                Array.prototype.slice.call (arguments)
            )
        );
    }
    util.delta = delta;

    function timerEnd (label, id) { 
        var data = timer_data[label],
            run = time () - data["start" + id];

        data.max = run > data.max ? run : data.max;
        data.min = run < data.min ? run : data.min;
        data.sum += run;
        data.count ++;
    }
    util.timerEnd = timerEnd;

    function timer (label, id) { 
        timer_data[label] = timer_data[label] || { 
            max: 0,
            min: Infinity,
            count: 0,
            sum: 0
        };
        timer_data[label]["start" + id ] = time ();
    }
    util.timer = timer;

    function timerReport () {
        util.each (timer_data, function (timer,label) { 
            if (timer.count) { 
                dbg ([
                    label,
                    timer.sum,
                    Math.round (timer.sum / timer.count * 100)/100,
                    timer.min,
                    timer.max
                ].join (", "));
                timer.max = timer.count = timer.sum = 0;
                timer.min = Infinity;
            }
        });
    }
    util.timerReport = timerReport;
}) ();

/*
 * searchMash.util 
 */
(function () {
    var W = window,
        d = W.document,
        util = W.searchMash.util,
        dbg = searchMash.dbg,
        UA = navigator.userAgent.toLowerCase(),
        browser = { safari: /safari/.test (UA) },
        id_cache = {}; // for getElemByID

    function isObject (obj) {
        return ( typeof (obj) == "object" && obj !== null );
    }
    util.isObject = isObject;

    function isFunc (thing) {
        return (typeof (thing) === "function" || thing instanceof Function);
    }
    util.isFunc = isFunc;

    function isArray (thing) {   
        return thing && 
            !thing.propertyIsEnumerable('length') && 
            typeof thing === 'object' && 
            typeof thing.length === 'number';
    }
    util.isArray = isArray;


    function getElem (what) { 
        return id_cache[what] || ( 
            id_cache[what] = d.getElementById (what) || 
                d.getElementsByTagName (what)[0] );
    }
    util.getElem = getElem;
    function getElemByID (id) { 
        return id_cache[id] || (id_cache[id] = d.getElementById (id));
    }
    util.getElemByID = getElemByID;
    function flushElemByID () { 
        id_cache = {};
    }
    util.flushElemByID = flushElemByID;

    function getStyle (elem, style) { 
        if (elem.currentStyle) { 
            return elem.currentStyle[style];
        } else if (W.getComputedStyle) { 
            return d.defaultView 
                .getComputedStyle (elem,null) 
                .getPropertyValue (style);
        } else { 
            throw "unable to get current style";
        }
    }
    util.getStyle = getStyle;

    function each (obj, func) {
        var i = 0, 
            len = obj.length;
        
        if ( len ) { 
            for (;i<len;i++) { 
                if (i in obj) { 
                    func (obj[i], i);
                }
            }
        } else { 
            for (i in obj) { 
                if (true) { func (obj[i], i);} // if (true) is for jslint
            }
        }
    }
    util.each = each;

    function queryString2Obj (query_string) {
        var obj = {};

        each (query_string.split('&'), function (item) {
            var k,
                v,
                opt = /^([^=]+)=(.*)/.exec (item);
            if ( opt ) {
                k = opt[1];
                v = decodeURIComponent(opt[2].replace (/\+/g, " "));
                obj[k] = v;
            }
        });
        return obj;
    }
    util.queryString2Obj = queryString2Obj;

    function obj2QueryStr (obj) {
        var queryargs = [];
        each (obj, function (v, k) {
            queryargs.push( k + '=' + encodeURIComponent(v));
        });
        return queryargs.join('&');
    }
    util.obj2QueryStr = obj2QueryStr;


    function extend (obj1, obj2) { 
        each (obj2, function (v,k) { 
            obj1[k] = v;
        });
        return obj1;
    }
    

    /* scale and center *****************************************/ 

    function getHeight (thing) { 
        return thing.naturalHeight || thing.height || thing.scrollHeight;
    }
    function getWidth (thing) { 
        return thing.naturalWidth || thing.width || thing.scrollWidth;
    }

    // TODO this stuff could use some test cases and some documentation!!
    function scaleAndCenterWhenReady (opts, func, count) {
        var max_size = opts.max_size,
            cur_size = opts.cur_size;

        count = count || 1;

        opts.max_width  = opts.max_width  || getWidth  (max_size);
        opts.max_height = opts.max_height || getHeight (max_size);
        opts.cur_width  = opts.cur_width  || getWidth  (cur_size);
        opts.cur_height = opts.cur_height || getHeight (cur_size);

        if (opts.max_width &&
            opts.max_height &&
            opts.cur_width &&
            opts.cur_size) {

            // TODO figure out any border on target and factor that in properly
            opts.extra_width = opts.extra_width || 0;
            opts.extra_height = opts.extra_height || 0;

            if (isFunc (func)) {
                func (scaleAndCenter (opts));
            } else {
                scaleAndCenter (opts);
            }
            return;
        } else if ( count > 10 ) {
            // TODO handle this better?
            throw ("scale and center failed, too many retries");
            return; // otherwise IE debuggger continues into the else block!!!! 
        } else {
            count++;
            setTimeout (function () {
                scaleAndCenterWhenReady (opts, func, count);
            }, 10);
        }
    }
    util.scaleAndCenterWhenReady = scaleAndCenterWhenReady;

    // TODO name this better.
    function toUnit (percent, unit, scale) {
        scale = scale ? scale : 1;
        if ( unit == "px" ) {
            return Math.floor (percent * scale / 100) + unit;
        } else {
            return percent + "%";
        }
    }
    function scaleAndCenter (opts) {
        var max_width    = opts.max_width,
            max_height   = opts.max_height,
            cur_width    = opts.cur_width,
            cur_height   = opts.cur_height,
            extra_width  = opts.extra_width || 0,
            extra_height = opts.extra_height || 0,
            target       = opts.target,
            max_aspect_ratio,
            cur_aspect_ratio,
            height,
            width,
            margin_top,
            style,
            ret;

        if ( ! max_height || ! max_width ) {
            throw "scaleAndCenter bad max_size argument";
        }
        if ( ! cur_height || ! cur_width ) {
            throw "scaleAndCenter bad cur_size argument";
        }

        target = target.get ? target.get(0) :  target;
        style = target.style;

        if (!style ) { 
            throw "target element had no style. lame.";
        }

        opts = extend ({
                scale: "fill",  // or fit
                unit: "%",  // or px
                grow: true,
                center_vertical:  true,
                center_horizontal:  true
            },
            isObject (opts) ? opts : {}
        );

        max_height -= extra_height;
        max_width  -= extra_width;
        if ( ! opts.grow ) { 
            max_height = max_height > cur_height ? cur_height : max_height;
            max_width  = max_width > cur_width ? cur_width : max_width;
        }

        max_aspect_ratio = max_width / max_height;
        cur_aspect_ratio = cur_width / cur_height;

        height = width = 1;
        if ( opts.scale == "fit") {
            if ( cur_aspect_ratio > max_aspect_ratio )  {
                width = 1;
                height = 1 / cur_aspect_ratio * max_aspect_ratio;
            } else if ( cur_aspect_ratio < max_aspect_ratio ) {
                height = 1;
                width = 1 * cur_aspect_ratio / max_aspect_ratio;
            }
        } else { // fill
            if ( cur_aspect_ratio < max_aspect_ratio )  {
                width = 1;
                height = 1 / cur_aspect_ratio * max_aspect_ratio;
            } else if ( cur_aspect_ratio > max_aspect_ratio ) {
                height = 1;
                width = 1 * cur_aspect_ratio / max_aspect_ratio;
            }
        }

        width = Math.floor (width * 100);
        height = Math.floor (height * 100);

        extra_height = extra_height / cur_height * height;
        extra_width = extra_width / cur_width * width;

        if ( opts.center_vertical ) {
            // NOTE! margin-top in percent is in percentage of the width not the
            // height unless you're safari
            if ( opts.unit == "%" && browser.safari ) {
                margin_top = - (height+extra_height) / 2;
            } else {
              // convert to % of container width
              margin_top = - (height+extra_height) / 2 / max_aspect_ratio;
            }

            style.marginTop = toUnit (margin_top, opts.unit, max_width);
            style.top = "50%";
        }

        if ( opts.center_horizontal ) {
            style.position = "absolute";
            style.marginLeft = toUnit (
                - Math.floor ((width+extra_width) / 2),
                opts.unit,
                max_width );
            style.left = "50%";
        }

        ret = {
            "width":       toUnit (width, opts.unit, max_width),
            "height":      toUnit (height, opts.unit, max_height)
        };
        style.width = ret.width;
        style.height = ret.height;
        return {
            width: Math.floor ( width * max_width / 100 ),
            height: Math.floor ( height * max_height / 100 )
        };
    }
    util.scaleAndCenter = scaleAndCenter;

    // add an event handler to an element
    function addEvent (elem, ev, fn) {
        var onev,
            ret;

        function wrapped_fn (e) { 
            e = e || event;
            ret = fn.call (elem, e);
            if (ret === false) { 
                if (e.preventDefault) { 
                    e.preventDefault();
                } else { 
                    e.returnValue = false;
                } 
            }
            return ret;
        }

        if (elem.addEventListener) { 
            elem.addEventListener(ev, wrapped_fn, false);
        } else { 
            onev = "on" + ev;
            if (elem.attachEvent) { 
                elem.attachEvent(onev, wrapped_fn);
            } else { 
                elem[onev] = wrapped_fn;
            }
        }
    }
    util.addEvent = addEvent;

    // log an event to the server, via xhr.
    // NOTE: this logEvent just queues events for later when the full jquery
    // based logEvent is loaded.
    log_queue = util.log_queue = util.log_queue || [];
    function logEvent (log_event) { 
        if (log_event.config.sync) { 
            // TODO use a pixel tag to tell the server, somethign bad happened
            // though that's a race condition too.
            dbg ("synchronous log event requested when only queing possible");
        }
        log_queue.push (log_event);
    }
    util.logEvent = logEvent;

}) ();

/*
 * searchMash.util script loading
 */
(function () {
    var W           = window,
        d           = W.document,
        searchMash  = W.searchMash,
        dbg         = searchMash.dbg,
        util        = searchMash.util,
        delta       = util.delta,
        onload      = {}, 
        loaded      = {},
        getElemByID = util.getElemByID;

    function makeScript (src, onload, d) {
        var loaded = false,
            s = d.createElement ('script');

        s.setAttribute ("type", "text/javascript");
        s.onreadystatechange = function () {
            var readyState = this.readyState;
            if (loaded) {
                delete s.onreadystatechange;
                return;
            }
            if (readyState == 'complete' || readyState == 'loaded' ) {
                loaded = true;
                onload ();
            }
        };
        s.onload = function () {
            if (loaded) { return;}
            loaded = true;
            if (onload) { 
                onload ();
            }
        };
        s.setAttribute ("src", src);
        return s;
    }

    function loadScript (src, onload) {
        var s = makeScript (src,onload, d);
        getElemByID("head").appendChild (s);
        return s;
    }
    util.loadScript = loadScript;

    function loadScriptInIframe (src, handler_name, onload) { 
        var iframe, iframe_window,
            // don't append iframes to the head, though i've seen it done and
            // it seems to work, it causes issues in ie 6/7 when using back
            // button to return to the page 
            dest = util.getElem ("body");
        
        iframe = document.createElement ("iframe");
        dest.appendChild (iframe);
        iframe_window = iframe.contentWindow;
        iframe_document = iframe_window.document;

        iframe.style.display = 'none';
        iframe_document.open ();
        iframe_window[handler_name] = function () {
           window[handler_name].apply (window, arguments);
        };
        iframe_document.write ('<html><body></body></html>');
        iframe_document.body.appendChild (makeScript (src,onload,iframe_document));
        iframe_document.close ();
        return iframe;
    }
    util.loadScriptInIframe = loadScriptInIframe;


    function runfuncs (item) { 
        func = onload[item].shift ();
        if (func) { 
            func (); 
            runfuncs (item);
        }
    }
    function loadCached (item, func) { 
        var loading = !! onload[item];
        if (loading) {
            onload[item].push (func);
        }
        if (loaded[item]) { 
            dbg ("previously loaded:", item);
            runfuncs (item);
        } else if (!loading) { 
            onload[item] = [func];
            delta ("loading script:", item);
            util.loadScript (item, function () { 
                delta ("script loaded:", item);
                loaded[item] = 1;
                runfuncs (item);
            });
        }
    }
    function loadItem (item, func) { 
        var deps_loaded = 0,
            len;

        if (typeof item == "string") { 
            loadCached (item, func);
            return;
        } 

        function check () { 
            deps_loaded++;
            if (deps_loaded == len) {
                func (); 
            }
        }
        if (item.length) {  
            len = item.length;
            each (item, function (script) { 
                loadCached (script, check);
            });
        } else { 
            dbg ("loadItem unable to load: ", item);
        }
    }
    function loadList () { 
        var args = Array.prototype.slice.call (arguments),
            item = args.shift ();

        if (typeof item == "function") { 
            item ();
            loadList.apply (this, args);
        } else if (item) {
            loadItem (item, function () { 
                loadList.apply (this, args);
            });
        }
    }
    util.loadList = loadList;

}) ();

/*
 * searchMash.DispatchStats 
 */

(function () {
    var searchMash = window.searchMash,
        each = searchMash.util.each,
        delta = searchMash.util.delta,
        dbg = searchMash.dbg;

    function DispatchStats () {
        this.total = 0;
        this.complete = 0;
        this.canceled = 0;
        this.successes = 0;
        this.thumbs_done = 0;
        this.stats = {};
        this.done_funcs = [];
        this.first_searcher_done_funcs = [];
        this.thumbs_done_funcs = [];
        return;
    }

    DispatchStats.prototype = {

        // returns a proxy object with the searcher "captured".
        capture: function (searcher) {
            var dispatch_stats = this,
                stats = this.stats,
                searcher_stats,
                check, 
                thumb_done = false,
                done = false,
                obj = {};

            searcher_stats = stats[searcher] = stats[searcher] || {};
            check = function () { dispatch_stats.doneCheck (); };

            return { 
                success: function () {
                    if (done) { return; } 
                    done = true;
                    dispatch_stats.complete ++;
                    dispatch_stats.successes ++;
                    searcher_stats.succeded = 1;
                    check ();
                    dispatch_stats.searcherDone ();
                },

                cancel: function () { 
                    if (done) { return; } 
                    done = true;
                    dispatch_stats.canceled ++;
                    check ();
                },

                error: function () {
                    if (done) { return; } 
                    done = true;

                    dispatch_stats.complete ++;
                    searcher_stats.succeded = 0;
                    check ();
                },
                start: function () {
                    searcher_stats.start = new Date () .getTime ();
                },
                end: function () {
                    var start = searcher_stats.start,
                        end = new Date () .getTime ();

                    searcher_stats.end = end;
                    searcher_stats.run = end - start;
                },
                results: function (num) { 
                    searcher_stats.results = num;
                },

                thumbsDone: function () { 
                    if (thumb_done) { return; } 
                    thumb_done = true;
                    delta ("thumbs done", searcher);
                    dispatch_stats.thumbs_done ++;
                    dispatch_stats.thumbsCheck ();
                }
            };
        },

        runfuncs: function (funcs) { 
            each (funcs, function (v,i) { 
                v.call ();
                delete funcs[i];
            });
        },

        onFirstSearcherDone: function (func) { 
            if (this.first_searcher_done) { 
                func.call (); 
            } else { 
                dbg ("loading func", func);
                this.first_searcher_done_funcs.push (func);
            }
        },

        searcherDone: function () { 
            if ( ! this.first_searcher_done ) { 
                this.first_searcher_done = 1;
                this.runfuncs (this.first_searcher_done_funcs);
            }
        },

        done: function (func) {
            this.done_funcs.push (func);
            this.doneCheck ();
        },

        doneCheck: function () { 
            if (this.complete >= this.total - this.canceled) {
                this.runfuncs (this.done_funcs);
            }
        },

        thumbs: function (func) { 
            this.thumbs_done_funcs.push (func);
            this.thumbsCheck ();
        },

        thumbsCheck: function () { 
            if (this.thumbs_done >= this.total - this.canceled) { 
                this.runfuncs (this.thumbs_done_funcs);
            }
        },

        setTotal: function (total) {
            this.total = total;
            return this;
        }
    };

    searchMash.DispatchStats = DispatchStats;
}) ();

/*
 * searchMash.jsonp 
 */
(function () { 
    var W = window, 
        searchMash = W.searchMash,
        util = searchMash.util,
        dbg = searchMash.dbg,
        id = 0,
        cache = {};

    function jsonp (config) { 
        var data = config.data,
            rid = config.id || config.url,
            timer,
            timedout,
            calledback,
            loaded,
            callback_name,
            elem,
            key;

        if (config.cache) { 
            key = [ data.pg, data.query, data.srid, config.url ].join (":");
            if (cache[key]) {
                dbg ("using cache for " + rid);
                config.success (cache[key]);
                return;
            }
        }

        if (!config.error) { config.error = function () {}; }

        rid = rid + " "  + id;
        callback_name = "jsonp" + id++;

        function cleanup () { 
            if (elem) { elem.parentNode.removeChild (elem); }
            clearTimeout (timer);
            if (config.onReturn ()) { 
                config.onReturn ();
            }
        }

        // TODO callback_name should trigger errors if argument is not 
        // parseable json or if it never gets called
        W[callback_name] = function (response) {
            if (timedout || loaded) { return; }
            calledback = true;
            if (config.cache) { 
                cache[key] = response;
            }
            cleanup ();
            config.success (response);
        };

        // set jsonp callback query argument.
        data[config.jsonp || "callback"] = callback_name;

        if (config.beforeSend) { 
            config.beforeSend ();
        }
        timer = setTimeout (function () { 
            dbg ("timeout", rid);
            if (calledback || loaded) { return; }
            timedout = true;
            cleanup ();
            config.error ("timeout");
        }, config.timeout || 7999);

        elem = util.loadScriptInIframe ( 
            config.url + "?" + util.obj2QueryStr (data), 
            callback_name,
            function () { 
                dbg ("loaded", rid);
                if (timedout || calledback) { return; }
                loaded = true;
                cleanup ();
                config.error ("loaded without callback");
            });
    }
    searchMash.jsonp = jsonp;
}) ();

/* 
 * searchMash min TOOD: rename.
 *
 * call the searchers, and the basic structure in place as quickly as possible
 */
(function () {
    var W = window, 
        d = W.document,

        searchMash = W.searchMash,
        dbg        = searchMash.dbg,
        mashConfig = searchMash.config,
        jsonp      = searchMash.jsonp,
        util       = searchMash.util,

        delta           = util.delta,
        each            = util.each,
        obj2QueryStr    = util.obj2QueryStr,
        queryString2Obj = util.queryString2Obj,
        getElemByID     = util.getElemByID,

        spinner = [
            '<img class="spinner" src="',
            mashConfig.spinner_image,
            '">'
            ].join (""),
        sad_image = [
            '<img class="sad_image" src="',
            mashConfig.sad_image,
            '" style="display:none">'
            ].join (""),
        // TODO shrink any stray true's fan false

        t = true,
        f = false,

        ssHist,
        ssHist_initialized = f,

        // for loadThumb
        thumb_max_width,  
        thumb_max_height,
        thumb_count,

        pages = {},
        Presearch,
        Query;


    function pxtag (name) { 
        var img = new Image ();
        img.src = [mashConfig.spinner_image, "?", name].join ("");
        return img;
    }

    /*
     * continuationLoop (config) process an array over time using setTimeout to
     * allow other parts of the page to do things.
     *
     * for each item in the array config.items  use setTimeout to call
     * config.each.  call config.before first, and config.after after.
     * config.delay can be set to control the setTimeout
     */
    function continuationLoop (config, i, len) {
        var items = config.items,
            before = config.before,
            each = config.each,
            after = config.after,
            delay = config.delay;

        if (!items || !each || !items.length) { return; }

        delay = config.delay || 0;
        if ( ! i ) {
            i = 0;
            len = items.length;
            if (typeof before == "function" || before instanceof Function  ) {
                before ();
            }
        }
        if (!len) { return; }

        if ( i < len ) {
            setTimeout (function () {
                each (items[i], i);
                continuationLoop (config, ++i, len);
            }, delay );
        } else {
            if (typeof after == "function" || after instanceof Function  ) {
                after ();
            }
        }
    }


    function begin (presearch) {
        // split it up based on url arg list format
        if ( !presearch) { 
            return;
        }
        var location = W.location,
            hash = location.hash,
            search = location.search,
            scripts = searchMash.scripts,

            hash_opts  = queryString2Obj (hash.replace (/^#/,"")),
            query_opts = queryString2Obj (search.replace(/^\?/,"")),
            query,
            pg;

        query = query_opts.q;
        if (query && query !== "") {
            if ("pg" in query_opts)  {
                // ok, we have  an pg= in the query string this
                // javscript code uses page args in the hash so lets
                // move it there by "redirecting" the browser a pg in
                // the hash takes precedince
                hash_opts.pg = hash_opts.pg || query_opts.pg;
                delete query_opts.pg;

                W.location =
                    location.protocol + "//" +
                    location.host +
                    location.pathname + "?" +
                    obj2QueryStr (query_opts) +  "#" +
                    obj2QueryStr (hash_opts);
                return;
            } else {
                pg  = hash_opts.pg || 1;
                Presearch = presearch;
                Query = query;
                loadPage (presearch, query, pg);
                util.loadList ( 
                    scripts.jquery,
                    scripts.stage2,
                    function () { delta ("preload done"); }
                );
            }
        }
    }

    function makeBrandingHTML (searcher, brandingobj) {
        var branding = brandingobj || {},
            brandname = branding.name || searcher;

        return [ 
            '<div class="branding" ',
                // TODO convert this to class, or uniq id per page.
                'id="branding-', searcher, '">',
            branding.attribution || '',
            '<span class="logo">',
            '<a href="', 
                branding.url || 'http://www.' + searcher + '.com/',  '" ',
                'title="', brandname, '" ',
                'class="', searcher, '-logo">',
            brandname,
            '</a>',
            '</span>',
            '</div>' ];
            
    }

    function showNav () { 
        var nav = getElemByID ("navigation");
        nav.style.visibility = "visible";
    }
    function hideNav () { 
        var nav = getElemByID ("navigation");
        nav.style.visibility = "hidden";
    }

    function loadHash (hash) { 
        var pg;

        hash = queryString2Obj (hash.replace(/^#/,""));
        pg = hash.pg || 1;
        dbg ("loading pg " + pg);
        loadPage (Presearch, Query, pg);
    }

    function handleNavClick (e) { 
        if (! ssHist_initialized ) { 
            ssHist = window.ssHist;
            ssHist_initialized = t;
            ssHist.init (loadHash);
            // ssHist.logger (function () { 
            //     dbg (Array.prototype.join.call (arguments, " "));
            // });
        }
        // something tricky here.  hashChange will call loadPage 
        // which will update the navigation anchors before we return 
        // so if we return true, from this click, we'll go an extra
        // page forward.
        ssHist.hashChange (this.hash);
        return false; 
    }

    function updateNavigation (page) { 
        var g = getElemByID, 
            prev  = g ("prev"),
            next  = g ("next"),
            prevb = g ("prevb"),
            nextb = g ("nextb");

        // could be done elsewhere once, but 4 now we do this here every time
        prev.onclick = handleNavClick;
        next.onclick = handleNavClick;

        page = parseInt (page, 10);
        if (page > 1) { 
            prev.href = "#pg=" + (page - 1);
           prevb.className = ""
        } else {
           prevb.className = "disabled"
        }
        next.href = "#pg=" + (page + 1);
        nextb.className = "";
    }


    function extendFromMin (div) { 
        loadStage2 (function () { 
            searchMash.extendFromMin (div);
        });
    }

    function loadStage2 (func) { 
        var scripts = searchMash.scripts; // set in search.mako
        util.loadList ( 
            scripts.jquery,
            scripts.stage2,
            func,
            scripts.jquery_autocomplete);
    }

    function removeElem (e) {
        return e && e.parentNode && e.parentNode.removeChild (e);
    }
    util.removeElem = removeElem;

    function searchFail (request) {
        hide (request.results_div);
    }

    function onGetId (id, func, delay)  { 
        _onGetId (id, func, delay || 10, 1);
    }
    function _onGetId (id, func, delay, count)  { 
        var elem = util.getElemByID (id); 

        if (elem) { 
            func (elem);
        } else if (count < 10 ) { 
            dbg ("failed to find ", id, count);
            setTimeout (function () { 
                _onGetId (id, func, delay, ++count);
            }, delay);
        } else { 
            dbg ("onGetID too many retries", id);
        }
    }
    function loadThumb (item, img_id, done) { 
        var sizing_img = new Image (),
            thumbs = item.thumb_images,
            images = item.images,
            tries = 0,
            index = 0,
            count = 0;

        if (!images) { 
            dbg ("WTF?");
            return;
        }

        function nextImage () { 
            if (++index < thumbs.length)  { 
                // dbg (time () - thumb_start, img_id, "next image" );
                tries = 0;
                sizing_img.src = images[thumbs[index]];
                return true;
            }
        }
        function onload () { 
            var 
                cur_width  = sizing_img.width,
                cur_height = sizing_img.height;

            onGetId (img_id, function (img) { 
                if (!thumb_max_width) { 
                    thumb = img.parentNode.parentNode;
                    thumb_max_width = thumb.clientWidth;
                    thumb_max_height = thumb.clientHeight;
                    dbg ("thumb max h w:", thumb_max_height, thumb_max_width);
                }

                if (cur_width < thumb_max_width ||
                    cur_height <  thumb_max_height )  {

                    if (nextImage ()) {
                        return;
                    }
                    // TODO keep the largest thumb found, not just the last.
                }

                util.scaleAndCenter ({
                    target: img,
                    cur_width: cur_width,
                    cur_height: cur_height,
                    max_width: thumb_max_width,
                    max_height: thumb_max_height,
                    scale: "fill",
                    center_horizontal: true,
                    center_vertical: true
                });

                // hide spinner
                img.nextSibling.style.display = "none";
                img.src = sizing_img.src;
                img.style.visibility = "inherit";
                item.preview_disabled = false;
                delta ("thumb done", img_id, thumb_count++);
                done ();
            });
        }
        function onerror () {
            //TODO go to next image
            dbg ("img failed to load", img_id, index, tries, images.length, thumbs[index]);
            if ( tries < 1 ) { 
                tries++;
                sizing_img.src = images[thumbs[index]];
            }
            if (! nextImage ()) { 
                delta ("no more images to try", img_id, thumb_count++);
                onGetId (img_id, function (img) { 
                    // hide spinner
                    img.nextSibling.style.display = "none";
                    // show sad_image
                    img.nextSibling.nextSibling.style.display = "block";
                    done ();
                });
            }
        }

        sizing_img.onload = onload;
        sizing_img.onerror = onerror; 
        sizing_img.src = images[thumbs[index]];
    }

    function handleSearchResponse (request, response) {
        var result_set = response.result_sets,
            id = request.results_id,
            resDiv = request.results_div,
            each = util.each,
            s = request.searcher,
            e = encodeURI,
            stats = request.stats,
            thumbs_done = 0,
            results, html, length,
            remaining;

        delta ("handleSearchResponse", s );

        if (result_set && result_set.length !== undefined) {
            result_set = result_set[0];
        }
        results = result_set && result_set.results;
        length = results && results.length;

        function thumbsdone () { 
            thumbs_done++; 
            if (thumbs_done >= length) { 
                stats.thumbsDone ();
            }
        }

        if (length) {
            resDiv.items = {};
            html = makeBrandingHTML (s, result_set.branding);


            each (results, function (item, i) {
                var img = e (item.images[item.thumb_images[0]]),
                    id = s + "_" + request.pg + "_" + i,
                    img_id = id + "_img",
                    title = item.title;

                item.preview_disabled = true;
                item.pg = request.pg;
                item.offset = i;
                item.query = request.query;
                item.log_ticket = Presearch["logging ticket"];

                resDiv.items[id] = item;
                setTimeout (function () { 
                  loadThumb (item, img_id, thumbsdone);
                },0);
                html.push (
                    '<div>',
                    '<div id="', id, '" class="thumb_container">',
                        // TODO  this div is here so that slectors could
                        // be done of the form  ".thumb_container .class"
                        // those classes can instead just be added to the
                        // thumb_container and selected with this form
                        // ".thumb_container.class" note the lack of space.
                        // that form makes this extra div redudnant. so get
                        // rid of it. 
                        '<div>',
                            '<div class="images">',
                                '<a href="', item.page, '">',
                                    '<img ',
                                        'class="thumb_img" ',
                                        'id="', img_id,  '" ',
                                        'style="visibility:hidden"',
                                        '>',
                                    spinner,
                                    sad_image,
                                '</a>',
                            '</div>',
                        '</div>',
                    '</div>',
                    '</div>');
                });
            remaining = mashConfig.page_size - length;
            for (i=0; i<remaining; i++) { 
                html.push (
                    '<div class="thumb_container empty">',
                        '<div class="images">',
                            '<img src="/images/empty.png">',
                        '</div>',
                    '</div>');
            }
            html = html.join ("");
            resDiv.innerHTML = html;
            extendFromMin (resDiv);
            stats.success ();
            stats.results (results.length);
        } else { 
            searchFail (request);
            if (results && results.length === 0) { 
                stats.results (0);
                stats.cancel ();
                dbg ("no results", s);
            } else { 
                stats.error ();
                dbg ("searcher error", s);
            }
        }
    }

    function logSearcherStats (dispatch_stats, reason, sync) { 
        if (dispatch_stats.logged) { 
            return;
        }
        dispatch_stats.logged = true;
        util.logEvent ({ 
            config: {
                log_ticket: Presearch["logging ticket"],
                post_json: true,
                sync: sync
            },
            event: "searcher stats",
            type: "stats",
            triggered_by: reason,
            searchers: dispatch_stats.stats
        });
    }

    function handleNoResults (dispatch_stats, page) { 
        if (dispatch_stats.successes <= 0) { 
            var div = d.createElement ("div"),
                this_page = pages[page],
                page_div = this_page.div;

            this_page.failed = 1;

            div.className = "no_results";
            div.innerHTML = [ 
                '<img class="sad_image" src="', 
                    mashConfig.sad_image, 
                '"> ',
                "  Sorry, no results were found."
            ].join ("");

            if (page > 1 ) { showNav (); };
            getElemByID ("nextb").className = "disabled";
            page_div.appendChild (div);
            getElemByID ("q").focus ();
            return false;
        }
        return true;
    }

    function hide (elem) { 
        elem.style.display = "none";
    }

    function show (elem) {
        elem.style.display = "";
    }

    function callSearcher (request) {
        var stats = request.stats,
            s = request.searcher;

        jsonp ({
            url: request.url,
            cache: true,
            timeout: 8000,
            //timeout: Math.random () > 0.2 ? 10 : 8000,
            id: s,
            jsonp: "jsoncallback",
            beforeSend: function () { 
                stats.start ();
            },
            data: {
                q: request.query,
                pg: request.pg,
                srid: request.srid
            },
            error: function (reason) {
                dbg ("error calling searcher", s, reason);
                searchFail (request);
                stats.error ();
            },
            onReturn: function () { 
                request.stats.end ();
            },
            success: function (response) {
                // in the case that we've tried again on a next/prev page
                // the reults div is hidden. so we'll show it if it 
                // succeeds. 
                show (request.results_div);
                handleSearchResponse (request, response);
            }
        });
    }

    function resDivId (s, pg) {
        return s + "_results_" + pg;
    }

    function makeResultsDiv (s, pg) {
        var resDiv = d.createElement ("div");

        resDiv.setAttribute ("id", resDivId (s,pg));
        resDiv.className = "results";
        resDiv.innerHTML = makeBrandingHTML (s)
            .concat (['<div class="thumb_placeholder"></div>'], spinner)
            .join ("");
        return resDiv;
    }

    function makeDispatchStats (page_div, length) {
        var dispatch_stats = new searchMash.DispatchStats ();

        dispatch_stats.setTotal (length);
        dispatch_stats.done (function () { 
            delta ("min searchers done");
            logSearcherStats (dispatch_stats, "done");
        });

        dispatch_stats.onFirstSearcherDone (showNav);

        util.addEvent (window, "unload",  function () { 
            logSearcherStats (dispatch_stats, "unload", true);
        });

        return dispatch_stats;
    }

    function makeInitialDispatchStats (page, page_div, length) {
        var dispatch_stats = makeDispatchStats (page_div, length);

        dispatch_stats.done (function () {
            handleNoResults (dispatch_stats, page);
        });
        // TODO next and previous pages need should probably cause logging.
        dispatch_stats.thumbs (function () {
            delta ("thumbs all done");
            loadStage2 (function () {
                searchMash.load_addthis ();
            });
        });
        return dispatch_stats;
    }

    /*
     *  hiding and showing pages:
     *  in order for certain dynamic sizing of images to happen even when the
     *  user has clicked on to the next/prev we have to make the page div
     *  hidden not 'display: none'
     */
    function showPage (page_div) { 
        var style = page_div.style;

        style.visibility = "inherit";
        style.position = "static";
    }
    function hidePage (page_div) { 
        var style = page_div.style;
        style.visibility = "hidden";
        style.position = "absolute";
        style.top = 0;
        style.left = 0;
    }

    function loadPreviousPage (presearch, query, page) {
        var this_page = pages[page], 
            page_div = this_page.div,
            dispatch_stats = this_page.stats,
            searchers      = dispatch_stats.stats,
            retry_searchers = [];

        pages.showing = page_div;
        showPage (page_div);

        each (dispatch_stats.stats, function (searcher,name) {
            if (searcher.succeded === 0 ) {
                dbg ("failed searcher", name);
                retry_searchers.push (name);
            }
        });

        if (retry_searchers.length) {
            dispatch_stats = 
                makeDispatchStats (page_div, retry_searchers.length);
            this_page.stats = dispatch_stats;

            each (retry_searchers, function (s,i) {
                var resDiv = getElemByID (resDivId (s,page)),
                    stats = dispatch_stats.capture (s);

                callSearcher ({
                    url: presearch.where[s] || mashConfig.searcher_prefix + s,
                    query: query,
                    pg: page,
                    srid: presearch.srid,
                    searcher: s,
                    results_div: resDiv,
                    stats: stats
                });
            });
        } 

    }

    function callSearchers (presearch, query, page) {
        var searchcontainer = getElemByID ("searchcontainer"),
            each = util.each,
            searchers = presearch.searchers,
            page_div = d.createElement ("div"),
            dispatch_stats = 
                makeInitialDispatchStats (page, page_div, searchers.length);

        thumb_count = 0;

        searchcontainer
            .appendChild (page_div);
        pages.showing = page_div;

        pages[page] = {div: page_div, stats: dispatch_stats};

        hideNav ();

        each (searchers, function(s, i) {
            var resDiv = makeResultsDiv (s, page),
                request;

            page_div
                .appendChild (resDiv);

            callSearcher ({
                url: presearch.where[s] || mashConfig.searcher_prefix + s,
                query: query,
                pg: page,
                srid: presearch.srid,
                searcher: s,
                results_div: resDiv,
                stats: dispatch_stats.capture (s)
            });

        });
        delta ("min", "searchers called");
    }

    function loadPage (presearch, query, page) {
        var this_page = pages[page];

        updateNavigation (page);
        if (pages.showing) {
            hidePage (pages.showing);
        }

        if (this_page && this_page.failed) {
            removeElem (this_page.div);
            delete pages[page];
        }
        if (pages[page]) {
            loadPreviousPage (presearch, query, page);
        } else {
            callSearchers (presearch, query, page);
        }
    }


    if ( mashConfig.debug && window.console && console.clear ) { 
       console.clear (); 
    }
    delta ("min begin");
    begin (searchMash.presearch);

}) ();

/* 
 * searchmash prompt
 */

(function () {
    var searchMash     = window.searchMash,
        util           = searchMash.util, 
        prompt         = searchMash.config.prompt,
        dbg            = searchMash.dbg,
        q              = util.getElemByID ("q"),
        addEvent       = util.addEvent,
        prompt_showing = false;

    function removeSearchPrompt() {
        prompt_showing = false;
        if (this.value === prompt) {
            this.value = "";
            this.className = '';
        }
    }

    function showSearchPrompt(ele) {
        if (this.value === "" || this.value === prompt) {
            prompt_showing = true;
            this.value = prompt;
            this.className = 'greytext';
        }
    }
    
    function init () { 
        var form; 

        form = q.form;
        addEvent (q, "blur",  showSearchPrompt);
        addEvent (q, "focus", removeSearchPrompt);
        addEvent (form, "submit", function () { 
            // cancel submit if the prompt is showing.
            if (prompt_showing) { 
                q.focus ();
                return false;
            }
        });
        q.focus ();
    }

    if (q) { 
        init ();
    }

}) ();


/* 
 * clear nonjavascript indicator from search on search submit
 */ 
(function () { 
    var util = searchMash.util,
        dbg = searchMash.dbg,
        nj = util.getElemByID ("nj"),
        form, oldsubmit;

    if (nj) {
        form = nj.form;

        // on submit we remove the nj element.
        util.addEvent (form, "submit", function () { 
            util.removeElem (nj); 
        });

        oldsubmit = form.submit;
        form.submit = function () { 
            util.removeElem (nj); 
            try { 
                oldsubmit.apply (form, arguments);
            } catch (e) { 
                // this should be ie6, 
                try  { 
                    // and this bizarly solve it for ie6.
                    oldsubmit ();
                } catch (e2) { 
                    // todo px tag or log this problem.
                    dbg ("submit failed");
                }
            }
        };

    }
})();

/*
 *  share page stuff
 */ 
(function () { 
    var searchMash = window.searchMash, 
        util = searchMash.util, 
        getElemByID = util.getElemByID,
        img = getElemByID ("share_img"),
        frame = getElemByID ("picture"),
        link = getElemByID ("share_link"),
        sizing_img;

    function onload () { 
        searchMash.util.scaleAndCenter ({ 
            scale: "fit",
            unit: "px",
            grow: false,
            cur_height: sizing_img.height,
            cur_width: sizing_img.width,
            max_width: frame.offsetWidth,
            max_height: frame.offsetHeight,
            target: img
        });
    }

    function resizeImage () { 
        sizing_img = new Image ();
        sizing_img.onload = onload;
        sizing_img.src = img.src;
    }

    function onkeydown (e) { 
        if (e.keyCode == 27) { 
            window.location = link.getAttribute ("href");
            return false;
        }
    }

    function bindEscapeKey () { 
        util.addEvent (document, "keydown", onkeydown);
    }

    if (img && frame && link) { 
        resizeImage (); 
        bindEscapeKey ();
    }

}) ();

/* 
 * ssHist: super simple ajax history
 */
(function () {
    var W = window, d = W.document, l = d.location,
        log = function () {},
        poll_interval = 200,
        onchange, last_hash, history_frame;

    function getHash (doc) { 
        doc = doc || d;
        var hash = doc.location.hash;
        return (hash == "#" ? "" : hash) || "";
    }

    function updateHistoryIframe (hash,init) { 
        if (! history_frame) { return; }
        // it's important here to get the current document and location from
        // the iframe every time, when hitting the "back" or "forward" buttons
        // the iframes document is changed so holding a reference will hold the
        // wrong thing.
        var history_doc = history_frame.document,
            history_loc = history_frame.location;

        if (getHash (history_frame) == hash && ! init) { return; }
        history_doc.open ();
        history_doc.close ();
        history_loc.hash = hash;
    }

    function initIframe () {
        var win,
            doc, 
            iframe = d.createElement ("iframe"),
            body = d.getElementsByTagName ("body")[0];

        iframe.style.display = "none";
        iframe.name = "ssHist";
        body.appendChild (iframe);
        win = iframe.contentWindow;

        history_frame = win;

        updateHistoryIframe (last_hash,true);
    }

    function pollForHashChange (iframe) { 
        var lasthash,
          name = iframe ? "iframe" : "main";

        return setInterval (function () { 
            var hash = getHash (iframe ? history_frame : d );
            if (lasthash !== hash) { 
                lasthash = hash;
                log (name, "hash changed", hash);
                hashChange  (hash);
            }
        }, poll_interval);
    }

    function hashChange (hash) {
        log ("hashChange: ", hash, " last: ", last_hash);
        // this check lets every trigger, window.onhashchange, iframe polled
        // hash change, window polled hash change fire independantly,  but we
        // call one onchange per real transition.
        if (hash === last_hash) { return; } 
        last_hash = hash;

        // this will cause the iFrame poll to trigger hashChange if it's not
        // what triggered this call. 
        updateHistoryIframe (hash);
        // this will cause onhashchange and/or the window poll to trigger
        // hashChange if it's not what triggered the current call.
        l.hash = hash;
        onchange (hash);
    }

    function historyInit (func) {
        var UA = navigator.userAgent.toLowerCase(),
            ie6 = /msie 6.0/.test (UA),
            ie7 = /msie 7.0/.test (UA),
            safari = /safari/.test (UA),
            need_history_frame = ie6 || ie7,
            need_polling = safari;

        onchange = func || function () {};
        last_hash = getHash ();
        window.onhashchange = function () {
            var hash = getHash ();
            log ("onhash change triggered:", hash);
            hashChange (hash);
        };
        if (need_polling || need_history_frame)  { 
            log ("need polling");
            pollForHashChange ();
        }
        if (need_history_frame) { 
            log ("need iframe");
            initIframe ();
            pollForHashChange ("iframe");
        }

    }

    window.ssHist = {
        init: historyInit,
        hashChange: hashChange,
        logger: function (func) { 
            log = func;
        },
        poll_interval: function (val) { 
            poll_interval = parseInt(val,10) || poll_interval;
        }
    };

}) ();
