swole_hamster / MTurk HIT DataBase

// ==UserScript==
// @name        MTurk HIT DataBase
// @namespace https://greasyfork.org/users/710
// @description Extended ability to search HITs you have worked on and other useful tools (CSV export/import, requester notes, requester block, pending/projected earnings)
// @include     https://www.mturk.com/mturk/searchbar*
// @include     https://www.mturk.com/mturk/findhits*
// @include     https://www.mturk.com/mturk/viewhits*
// @include     https://www.mturk.com/mturk/viewsearchbar*
// @include     https://www.mturk.com/mturk/sortsearchbar*
// @include     https://www.mturk.com/mturk/sorthits*
// @include     https://www.mturk.com/mturk/dashboard
// @include     https://www.mturk.com/mturk/preview?*
// @version     1.9.6.1
// @grant       none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js
// @require     http://code.highcharts.com/highcharts.js
// @require https://greasyfork.org/scripts/2351-jsdiff/code/jsdiff.js?version=6256
// @require https://greasyfork.org/scripts/2350-filesaver-js/code/filesaverjs.js?version=6255
// ==/UserScript==

//
// 2012-10-03 0.9.7: This is rewrite of MTurk Extended HIT Search (http://userscripts.org/scripts/show/146277)
//                   with some extra features (and some missing for now: search by date).
//                   It now uses IndexedDB (http://en.wikipedia.org/wiki/Indexed_Database_API)
//
// 2012-10-04 0.9.8: Improved use of indexes, check Pending Payment HITs
//            0.9.9: Minor improvements
//
// 2012-10-04 0.10:  Added date options
//
// 2012-10-07 0.11:  Requester notes, bug fixes
//            0.12:  CSV export
//
// 2012-10-09 0.13: "Block" requesters or specific HITs
//
// 2012-10-10 0.14: Requester Overview, shows summary of all requesters in DB
//
// 2012-10-11 0.15: Blocked HITs are always on bottom of the page
//
// 2012-10-14 0.16: Requester Overview improvements
//
// 2012-10-17 0.17: Bug fixes and error checks
//
// 2012-10-18 0.18: Import HIT data from MTurk Extended HIT Search script
//
// 2012-10-21 0.19: Moved main interface to dashboard, show pending earnings on dashboard,
//                  summary of all requesters with pending HITs.
//
// 2012-10-23 0.20: Added Turkopticon (http://turkopticon.differenceengines.com/) links to overview pages
//            0.21: Fixed overview pages reward to include only 'Paid' and 'Approved - Pending Payment' HITs.
//
// 2012-10-28 0.22: Limited Auto Update.
//            0.23: Minor improvements
//
// 2012-10-30 0.24: Projected earnings for today
//
// 2012-11-02 0.25: Smarter Auto Update
//
// 2012-11-03 0.26: GUI update
//
// 2012-11-05 0.30: Extra non-amazonian script monkeys
//
// 2012-11-06 0.31: Projected earnings progress bar
//
// 2012-11-08 0.32: Minor GUI fixes to look better on Chrome. Looks like it now works on stable Chrome!
//
// 2012-11-13 0.33: Time limits now work with Requester Overview
//
// 2012-11-15 0.34: Bug/compatibility fixes
//
// 2012-11-18 0.40: Daily Overview, update database to use YYYY-MM-DD date format.
//
// 2012-11-22 0.41: R and T button on HIT preview page. Auto-Approval time.
//
// 2012-11-30 0.42: Changes on MTurk pages. Status page in now on one page!
//
// 2012-12-02 1.0: Added @downloadURL and @updateURL
//
// 2012-12-06 1.1: Requester details.
//                 Try to fetch few extra days at first update (not showing on status page).
//
// 2012-12-11 1.2: Import HITs from previously exported CSV-files.
//                 Removed Extended HIT Search import.
//
// 2012-12-13 1.3: Fix CSV-import to put empty string instead if undefined if feedback is empty.
//
// 2012-12-14 1.4: Rewritten database update more properly.
//
// 2012-12-16 1.5: Fixed broken Auto Update (forgot to check that on pervious update).
//
// 2013-02-26 1.6: Fixed IDBTransactionModes for Chrome (note this breaks it for Firefox)
//
// 2013-02-27 1.7: Changed UI bars back to what they used to be.
//

var DAYS_TO_FETCH = [];
var DAYS_TO_FETCH_CHECK;

var HITStorage = {};
var indexedDB = window.indexedDB || window.webkitIndexedDB ||
    window.mozIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.mozIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange;
HITStorage.IDBTransactionModes = { "READ_ONLY": "readonly", "READ_WRITE": "readwrite", "VERSION_CHANGE": "versionchange" };
var IDBKeyRange = window.IDBKeyRange;

HITStorage.indexedDB = {};
HITStorage.indexedDB = {};
HITStorage.indexedDB.db = null;

HITStorage.indexedDB.onerror = function(e) {
    console.log(e);
};
var v = 4;

HITStorage.indexedDB.create = function() {

    var request = indexedDB.open("HITDB", v);

    request.onupgradeneeded = function (e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var new_empty_db = false;

        if(!db.objectStoreNames.contains("HIT")) {
            var store = db.createObjectStore("HIT", { keyPath: "hitId" });

            store.createIndex("date", "date", { unique: false });
            store.createIndex("requesterName", "requesterName", { unique: false });
            store.createIndex("title", "title", { unique: false });
            store.createIndex("reward", "reward", { unique: false });
            store.createIndex("status", "status", { unique: false });
            store.createIndex("requesterId", "requesterId", { unique: false });

            new_empty_db = true;

            // At first update try to get few extra days that do not show on status page
            localStorage['HITDB TRY_EXTRA_DAYS'] = 'YES';
        }
        if(!db.objectStoreNames.contains("STATS")) {
            var store = db.createObjectStore("STATS", { keyPath: "date" });
        }
        if(!db.objectStoreNames.contains("NOTES")) {
            var store = db.createObjectStore("NOTES", { keyPath: "requesterId" });
        }
        if(!db.objectStoreNames.contains("BLOCKS")) {
            var store = db.createObjectStore("BLOCKS", { keyPath: "id", autoIncrement: true });

            store.createIndex("requesterId", "requesterId", { unique: false });
        }

        if (new_empty_db == false)
        {
            alert("HIT DataBase date format must be upgraded (MMDDYYYY => YYYY-MM-DD)\n" +
                  "Please don't close or reload this page until it's done.\n" + 
                  "Press OK to start. This shouldn't take long. (few minutes max)" +
                  "Sorry for the inconvenience.");
            HITStorage.update_date_format(true);
        }
        db.close();
        //alert("DataBase upgraded to version " + v + '!');
    }

    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        db.close();
    };

    request.onerror = HITStorage.indexedDB.onerror;
}

HITStorage.indexedDB.addHIT = function(hitData) {
    // Temporary extra check
    if (hitData.date.indexOf('-') < 0)
    {
        alert('Wrong date format in addHIT()!');
        return;  
    }  

    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_WRITE);
        var store = trans.objectStore("HIT");

        var request = store.put(hitData);

        request.onsuccess = function(e) {
            db.close();
        };

        request.onerror = function(e) {
            console.log("Error Adding: ", e);
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

HITStorage.indexedDB.importHITs = function(hitData) {
    var hits = hitData.length;
    var label = document.getElementById('status_label');

    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_WRITE);
        var store = trans.objectStore("HIT");

        putNextHIT();

        function putNextHIT()
        {
            if (hitData.length > 0)
            {
                store.put(hitData.pop()).onsuccess = putNextHIT;
                label.innerHTML = progress_bar(((hits-hitData.length)/hits*50), 50, '█', '█', '#7fb448', 'grey') + ' (' + hitData.length + ')';
            }
            else
            {
                HITStorage.enable_inputs();
                HITStorage.update_status_label('Import done', 'green');
                db.close();
            }
        }
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

HITStorage.indexedDB.addHITs = function(hitData, day_to_fetch, days_to_update) {
    var hits = hitData.length;
    if (day_to_fetch)
        var label = document.getElementById('status_label');

    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_WRITE);
        var store = trans.objectStore("HIT");

        putNextHIT();

        function putNextHIT()
        {
            if (hitData.length > 0)
            {
                store.put(hitData.pop()).onsuccess = putNextHIT;
                if (day_to_fetch)
                    label.innerHTML = 'Saving ' + day_to_fetch.date + ': ' + progress_bar(((hits-hitData.length)/hits*40), 40, '█', '█', '#7fb448', 'grey');
            }
            else
            {
                // move to next day
                if (day_to_fetch)
                {
                    HITStorage.indexedDB.updateHITstats(day_to_fetch);
                    setTimeout(function() { HITStorage.do_update(days_to_update); }, 2000);
                    HITStorage.update_status_label('Please wait: script monkeys are taking naps 😌', 'red');
                }
                db.close();
            }
        }
    };
    request.onerror = HITStorage.indexedDB.onerror;
};


HITStorage.indexedDB.updateHITstats = function(date)
{
    // Temporary extra check
    if (date.date.indexOf('-') < 0)
    {
        alert('Wrong date format in updateHITstats()!');
        return;
    }

    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["STATS"], HITStorage.IDBTransactionModes.READ_WRITE);
        var store = trans.objectStore("STATS");

        var request = store.put(date);

        request.onsuccess = function(e) {
            db.close();
        };

        request.onerror = function(e) {
            console.log("Error Adding: ", e);
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

HITStorage.prepare_update_and_check_pending_payments = function()
{
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("HIT");
        var index = store.index('status');
        var range = IDBKeyRange.only('Approved&nbsp;- Pending&nbsp;Payment');

        index.openCursor(range).onsuccess = function(event) {
            var cursor = event.target.result;
            if (cursor && DAYS_TO_FETCH.length > 0)
            {
                for (var i=0; i<DAYS_TO_FETCH.length; i++)
                {
                    if ( cursor.value.date == DAYS_TO_FETCH[i].date && cursor.value.reward>0 )
                    {
                        DAYS_TO_FETCH[i].pending_payments = true;
                    }
                }
                cursor.continue();
            }
            else
            {
                if (DAYS_TO_FETCH.length>0) {
                    db.close();
                    HITStorage.update_status_label('Please wait: script monkeys are planning to fetch relevant status pages', 'red');
                    setTimeout(function() { HITStorage.prepare_update(); }, 100);
                }
                else
                {
                    db.close();
                    HITStorage.update_done();
                }
            }
        };
    }
};

// check that number of hits in DB matches what is available
HITStorage.check_update = function()
{
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.update_status_label('Please wait: checking database', 'red');
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("HIT");
        var index = store.index('date');
        var range = IDBKeyRange.bound(DAYS_TO_FETCH_CHECK[DAYS_TO_FETCH_CHECK.length-1].date, DAYS_TO_FETCH_CHECK[0].date, false, false);

        index.count(range).onsuccess = function(event) {
            var count = event.target.result;
            var submitted_hits = 0;

            for (var i=0; i<DAYS_TO_FETCH_CHECK.length; i++)
            {
                submitted_hits += DAYS_TO_FETCH_CHECK[i].submitted;
            }

            if (submitted_hits == count)
            {
                db.close();        
                HITStorage.update_done();
            }
            else
            {
                if (confirm("😞 ERROR! Number of HITs in DataBase does not match number of HITs available! (" + count + " != " + submitted_hits + ")\n"
                            + "Would you like to refetch all status pages now?"))
                {
                    db.close();        
                    DAYS_TO_FETCH = DAYS_TO_FETCH_CHECK.slice(0);
                    HITStorage.update_status_label('Please wait: new script monkeys are fetching relevant status pages', 'red');
                    setTimeout(function() { HITStorage.do_update(DAYS_TO_FETCH.length); }, 100);
                }
                else
                {
                    db.close();        
                    HITStorage.update_done();
                }
            }
        };
    }
};

HITStorage.prepare_update = function()
{
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["STATS"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("STATS");
        var range = IDBKeyRange.bound(DAYS_TO_FETCH[DAYS_TO_FETCH.length-1].date, DAYS_TO_FETCH[0].date, false, false);

        store.openCursor(range).onsuccess = function(event) {
            var cursor = event.target.result;
            if (cursor && DAYS_TO_FETCH.length > 0)
            {
                for (var i=0; i<DAYS_TO_FETCH.length; i++)
                {
                    if ( cursor.value.date == DAYS_TO_FETCH[i].date
                        && cursor.value.submitted == DAYS_TO_FETCH[i].submitted
                        && cursor.value.approved == DAYS_TO_FETCH[i].approved
                        && cursor.value.rejected == DAYS_TO_FETCH[i].rejected
                        && cursor.value.pending == DAYS_TO_FETCH[i].pending)
                    {
                        // This day is already in DB and stats match => no need to fetch
                        // unless there are 'Approved - Pending Payment' HITs
                        if (DAYS_TO_FETCH[i].pending_payments === undefined || DAYS_TO_FETCH[i].pending_payments == false)
                            DAYS_TO_FETCH.splice(i,1);
                    }
                }
                cursor.continue();
            }
            else
            {
                if (DAYS_TO_FETCH.length>0) {
                    db.close();
                    setTimeout(function() { HITStorage.do_update(DAYS_TO_FETCH.length); }, 100);
                }
                else
                {
                    db.close();
                    HITStorage.update_done();
                }
            }
        };
    }
};

HITStorage.indexedDB.term_matches_HIT = function(term, hit)
{
    var keys = ['date', 'requesterName', 'title', 'feedback', 'hitId', 'requesterId'];
    var re = new RegExp(escapeRegExp(term),"ig");
    for (var k in keys)
    {
        //for testing
        if (hit[keys[k]] != null && re.test(hit[keys[k]].trim()))
        {
            return true;
        }
    }
    return false;
}

function escapeRegExp(str) {
    return str.trim().replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

HITStorage.indexedDB.matchHIT = function(hit, options)
{
    if (options.status == '---' || hit.status.match(options.status))
    {
        if (options.search_term == '' || HITStorage.indexedDB.term_matches_HIT(options.term, hit))
        {
            return true;
        }
    }
    return false;
}

function hit_sort_func()
{
    return function(a,b) {
        if (a.date == b.date) {
            if (a.requesterName < b.requesterName)
                return -1;
            if (a.requesterName > b.requesterName)
                return 1;
            if (a.title < b.title)
                return -1;
            if (a.title > b.title)
                return 1;
            if (a.status < b.status)
                return -1;
            if (a.status > b.status)
                return 1;
        }
        if (a.date > b.date)
            return 1;
        if (a.date < b.date)
            return -1;
    };
}

HITStorage.indexedDB.getHITs = function(options) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("HIT");

        var req;
        var results = [];
        var index;
        var range;

        if (options.from_date || options.to_date)
        {
            if (options.from_date != '' || options.to_date != '')
            {
                index = store.index('date');
                if (options.from_date == options.to_date)
                {
                    range = IDBKeyRange.only(options.from_date);
                }
                else if (options.from_date != '' && options.to_date != '')
                {
                    range = IDBKeyRange.bound(options.from_date, options.to_date, false, false);
                }
                else if (options.from_date == '' && options.to_date != '')
                {
                    range = IDBKeyRange.upperBound(options.to_date, false);
                }
                else
                {
                    range = IDBKeyRange.lowerBound(options.from_date, false);
                }
                req = index.openCursor(range);
            }
        }
        else if (options.index && options.index != '')
        {
            index = store.index(options.index);
            range = IDBKeyRange.only(options.term);
            req = index.openCursor(range);
        }
        else if (options.status == 'Rejected' || options.status == 'Pending Approval' 
                 || options.status == 'Approved' || options.status == 'Paid')
        {
            var s = (options.status == 'Approved')? 'Approved&nbsp;- Pending&nbsp;Payment' : options.status;
            options.index = 'status';
            index = store.index(options.index);
            range = IDBKeyRange.only(s);
            req = index.openCursor(range);
        }
        else
        {
            req = store.openCursor();
        }

        req.onsuccess = function(event) {
            var cursor = event.target.result;
            if (cursor) {
                if (HITStorage.indexedDB.matchHIT(cursor.value, options))
                    results.push(cursor.value);

                cursor.continue();
            }
            else {
                results.sort(hit_sort_func());

                if (options.export_csv && options.export_csv == true)
                    HITStorage.export_csv(results);
                else
                    HITStorage.show_results(results);

                if (options.donut == '---')
                    document.getElementById('container').style.display = 'none';
                else if (options.donut != '')
                    HITStorage.prepare_donut(results, options.donut);
            }
            db.close();
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

//
// Show summary of all requesters
//
HITStorage.indexedDB.requesterOverview = function(options) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("HIT");
        var index;
        var req;

        // [ requesterId, requesterName, sum(hits), sum(rewards), rejected, pending ]
        var results = [];
        var tmp_results = {};
        if (options.from_date || options.to_date)
        {
            if (options.from_date != '' || options.to_date != '')
            {
                index = store.index('date');
                if (options.from_date == options.to_date)
                {
                    range = IDBKeyRange.only(options.from_date);
                }
                else if (options.from_date != '' && options.to_date != '')
                {
                    range = IDBKeyRange.bound(options.from_date, options.to_date, false, false);
                }
                else if (options.from_date == '' && options.to_date != '')
                {
                    range = IDBKeyRange.upperBound(options.to_date, false);
                }
                else
                {
                    range = IDBKeyRange.lowerBound(options.from_date, false);
                }
                req = index.openCursor(range);
            }
            req.onsuccess = function(event) {
                var cursor = event.target.result;
                if (cursor) {
                    var hit = cursor.value;
                    var rejected = (hit.status == 'Rejected') ? 1 : 0;
                    var pending = (hit.status.match(/Approved|Paid|Rejected/) == null) ? 1 : 0;
                    var reward = (pending>0 || rejected>0 )? 0: hit.reward;

                    if (tmp_results[hit.requesterId] === undefined)
                    {
                        tmp_results[hit.requesterId] = [];
                        tmp_results[hit.requesterId][0] = hit.requesterId;
                        tmp_results[hit.requesterId][1] = hit.requesterName;
                        tmp_results[hit.requesterId][2] = 1;
                        tmp_results[hit.requesterId][3] = reward;
                        tmp_results[hit.requesterId][4] = rejected;
                        tmp_results[hit.requesterId][5] = pending;
                    }
                    else
                    {
                        tmp_results[hit.requesterId][1] = hit.requesterName;
                        tmp_results[hit.requesterId][2] += 1;
                        tmp_results[hit.requesterId][3] += reward;
                        tmp_results[hit.requesterId][4] += rejected;
                        tmp_results[hit.requesterId][5] += pending;
                    }
                    cursor.continue();
                }
                else {
                    for (var key in tmp_results) {
                        results.push(tmp_results[key]);
                    }
                    // sort by total reward
                    results.sort(function(a,b) { return b[3]-a[3]; });
                    if (options.export_csv == true)
                        HITStorage.show_requester_overview_csv(results);
                    else        
                        HITStorage.show_requester_overview(results, '(' + options.from_date + '–' + options.to_date + ')');
                    HITStorage.update_status_label('Script monkeys are ready', 'green');
                    setTimeout( function() { HITStorage.update_status_label("Search powered by non-amazonian script monkeys"); }, 3000);
                    HITStorage.enable_inputs();
                }
                db.close();
            };
        }
        else {
            index = store.index('requesterId');
            req = index.openCursor();
            req.onsuccess = function(event) {
                var cursor = event.target.result;
                if (cursor) {
                    var hit = cursor.value;
                    var rejected = (hit.status == 'Rejected') ? 1 : 0;
                    var pending = (hit.status.match(/Approved|Paid|Rejected/) == null) ? 1 : 0;
                    var reward = (pending>0 || rejected>0 )? 0: hit.reward;
                    if (results.length == 0)
                    {
                        results.push([hit.requesterId, hit.requesterName, 1, reward, rejected, pending]);
                    }
                    else if (results[0][0] == hit.requesterId)
                    {
                        results[0][2] += 1;
                        results[0][3] += reward;
                        results[0][4] += rejected;
                        results[0][5] += pending;
                    }
                    else
                    {
                        results.unshift([hit.requesterId, hit.requesterName, 1, reward, rejected, pending]);
                    }
                    cursor.continue();
                }
                else {
                    // sort by total reward
                    results.sort(function(a,b) { return b[3]-a[3]; });
                    if (options.export_csv == true)
                        HITStorage.show_requester_overview_csv(results);
                    else        
                        HITStorage.show_requester_overview(results);
                    HITStorage.update_status_label('Script monkeys are ready', 'green');
                    setTimeout( function() { HITStorage.update_status_label("Search powered by non-amazonian script monkeys"); }, 3000);
                    HITStorage.enable_inputs();
                }
                db.close();
            };
        }
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

//
// Show summary of one requester
//
HITStorage.indexedDB.showRequester = function(requesterId) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("HIT");
        var index;
        var results = [];

        index = store.index('requesterId');
        var range = IDBKeyRange.only(requesterId);
        index.openCursor(range).onsuccess = function(event) {
            var cursor = event.target.result;
            if (cursor) {
                results.push(cursor.value);
                cursor.continue();
            }
            else {
                results.sort(function(a,b)
                             {
                                 if (a.date > b.date)
                                     return -1;
                                 if (a.date < b.date)
                                     return 1;
                                 return 0;
                             });
                HITStorage.show_requester(results);
                HITStorage.update_status_label('Script monkeys are ready', 'green');
                setTimeout( function() { HITStorage.update_status_label("Search powered by non-amazonian script monkeys"); }, 3000);
                HITStorage.enable_inputs();
            }
            db.close();
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};


// Show summary of pending HITs
HITStorage.indexedDB.pendingOverview = function(options) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("HIT");
        var index;
        var req;

        // [ requesterId, requesterName, sum(pendings), sum(rewards) ]
        var results = [];
        var tmp_results = {};

        index = store.index('status');
        range = IDBKeyRange.only('Pending Approval');
        index.openCursor(range).onsuccess = function(event) {
            var cursor = event.target.result;
            if (cursor) {
                var hit = cursor.value;
                console.log(hit);
                if (tmp_results[hit.requesterId] === undefined)
                {
                    tmp_results[hit.requesterId] = [];
                    tmp_results[hit.requesterId][0] = hit.requesterId;
                    tmp_results[hit.requesterId][1] = hit.requesterName;
                    tmp_results[hit.requesterId][2] = 1;
                    tmp_results[hit.requesterId][3] = hit.reward;
                }
                else
                {
                    tmp_results[hit.requesterId][1] = hit.requesterName;
                    tmp_results[hit.requesterId][2] += 1;
                    tmp_results[hit.requesterId][3] += hit.reward;
                }
                cursor.continue();
            }
            else {
                for (var key in tmp_results) {
                    results.push(tmp_results[key]);
                }
                // sort by pending hits
                results.sort(function(a,b) { return b[2]-a[2]; });
                if (options.export_csv == true)
                    HITStorage.show_pending_overview_csv(results);
                else        
                    HITStorage.show_pending_overview(results);
                HITStorage.update_status_label('Script monkeys are ready', 'green');
                setTimeout( function() { HITStorage.update_status_label("Search powered by non-amazonian script monkeys"); }, 3000);
                HITStorage.enable_inputs();
            }
            db.close();
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

// Show summary of daily stats
HITStorage.indexedDB.statusOverview = function(options) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["STATS"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("STATS");
        var req;

        var results = [];

        if (options.from_date || options.to_date)
        {
            if (options.from_date != '' || options.to_date != '')
            {
                if (options.from_date == options.to_date)
                {
                    range = IDBKeyRange.only(options.from_date);
                }
                else if (options.from_date != '' && options.to_date != '')
                {
                    range = IDBKeyRange.bound(options.from_date, options.to_date, false, false);
                }
                else if (options.from_date == '' && options.to_date != '')
                {
                    range = IDBKeyRange.upperBound(options.to_date, false);
                }
                else
                {
                    range = IDBKeyRange.lowerBound(options.from_date, false);
                }
                req = store.openCursor(range);
            }
        }
        else
        {
            req = store.openCursor();
        }
        req.onsuccess = function(event) {
            var cursor = event.target.result;
            if (cursor) {
                if (cursor.value.submitted > 0)
                    results.push(cursor.value);
                cursor.continue();
            }
            else {
                if (options.export_csv == true)
                    HITStorage.show_status_overview_csv(results);
                else        
                    HITStorage.show_status_overview(results, '(' + options.from_date + '–' + options.to_date + ')');
                HITStorage.update_status_label('Script monkeys are ready', 'green');
                setTimeout( function() { HITStorage.update_status_label("Search powered by non-amazonian script monkeys"); }, 3000);
                HITStorage.enable_inputs();
            }
            db.close();
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

HITStorage.indexedDB.getHIT = function(id) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("HIT");

        var request = store.get(id);

        request.onsuccess = function(e) {
            db.close();
            showDetails(e.target.result.note);
        };

        request.onerror = function(e) {
            console.log("Error Getting: ", e);
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

HITStorage.indexedDB.addNote = function(id, note) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["NOTES"], HITStorage.IDBTransactionModes.READ_WRITE);
        var store = trans.objectStore("NOTES");
        var request;

        if (note == '')
            request = store.delete(id);
        else
            request = store.put({requesterId: id, note: note});

        request.onsuccess = function(e) {
            db.close();
        };

        request.onerror = function(e) {
            console.log("Error Adding: ", e);
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

HITStorage.indexedDB.blockHITS = function(requesterId, title, hitElement, titleElement) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;

        if (!db.objectStoreNames.contains("BLOCKS"))
        {
            db.close();
            return;
        }
        var trans = db.transaction(["BLOCKS"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("BLOCKS");
        var index = store.index("requesterId");
        var range = IDBKeyRange.only(requesterId);

        index.openCursor(range).onsuccess = function(event) {
            var cursor = event.target.result;
            if (cursor && cursor.value.re)
            {
                if (cursor.value.re.test(title))
                {
                    hitElement.style.display = 'none';
                    titleElement.addEventListener("click", unblock_func(requesterId, title));

                    titleElement.style.fontSize = 'small';          

                    // move blocked hits to the bottom
                    var table = hitElement.parentNode.parentNode.parentNode.parentNode.parentNode;
                    var hit = hitElement.parentNode.parentNode.parentNode.parentNode;
                    table.removeChild(hit);
                    table.appendChild(hit);
                }
                cursor.continue();
            }
            else
            {
                db.close();
            }
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

HITStorage.indexedDB.addBlock = function(requesterId, re) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["BLOCKS"], HITStorage.IDBTransactionModes.READ_WRITE);
        var store = trans.objectStore("BLOCKS");
        var request;

        request = store.put({requesterId: requesterId, re: re});

        request.onsuccess = function(e) {
            db.close();
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

// Removes all blocks for requesterId, where RE matches this HIT title
HITStorage.indexedDB.removeBlocks = function(requesterId, title) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        if (!db.objectStoreNames.contains("BLOCKS"))
        {
            db.close();
            return;
        }
        var trans = db.transaction(["BLOCKS"], HITStorage.IDBTransactionModes.READ_WRITE);
        var store = trans.objectStore("BLOCKS");
        var index = store.index("requesterId");
        var range = IDBKeyRange.only(requesterId);

        index.openCursor(range).onsuccess = function(event)
        {
            var cursor = event.target.result;
            if (cursor)
            {
                if (cursor.value.re.test(title))
                    store.delete(cursor.value.id);
                db.close();
            }
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

HITStorage.indexedDB.updateNoteButton = function(id, label) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;

        if (!db.objectStoreNames.contains("NOTES"))
        {
            label.title = 'Update HIT database on statusdetail page to use this feature';
            db.close();
            return;
        }
        var trans = db.transaction(["NOTES"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("NOTES");

        store.get(id).onsuccess = function(event)
        {
            if (event.target.result === undefined)
            {
                label.textContent = '';
            }
            else
            {
                var note = event.target.result.note;
                label.textContent = note;
                label.style.border = '1px dotted';
                if (note.indexOf('!') >= 0)
                    label.style.color = 'red';
                else
                    label.style.color = 'black';
            }
            db.close();
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};


HITStorage.indexedDB.colorRequesterButton = function(id, button) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        if (!db.objectStoreNames.contains("HIT"))
        {
            button.title = 'Update HIT database on statusdetail page to use this feature';
            db.close();
            return;
        }
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("HIT");

        var index = store.index("requesterId");
        index.get(id).onsuccess = function(event)
        {
            if (event.target.result === undefined)
            {
                button.style.backgroundColor = 'pink';
            }
            else
            {
                button.style.backgroundColor = 'lightgreen';
                button.style.fontWeight = 'bold';
            }
            db.close();
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

HITStorage.indexedDB.colorTitleButton = function(title, button) {
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        if (!db.objectStoreNames.contains("HIT"))
        {
            button.title = 'Update HIT database on statusdetail page to use this feature';
            db.close();
            return;
        }
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("HIT");

        var index = store.index("title");
        index.get(title).onsuccess = function(event)
        {
            if (event.target.result === undefined)
            {
                button.style.backgroundColor = 'pink';
            }
            else
            {
                button.style.backgroundColor = 'lightgreen';
                button.style.fontWeight = 'bold';
            }

            db.close();
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

HITStorage.indexedDB.deleteDB = function () {
    var deleteRequest = indexedDB.deleteDatabase("HITDB");
    deleteRequest.onsuccess = function (e)
    {
        alert("deleted");
    }
    deleteRequest.onblocked = function (e)
    {
        alert("blocked");
    }
    deleteRequest.onerror = HITStorage.indexedDB.onerror;
}

HITStorage.indexedDB.get_pending_approvals = function() {
    var element = document.getElementById('pending_earnings_value');
    var header_element = document.getElementById('pending_earnings_header');
    if (element == null)
        return;  

    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("HIT");

        var result = 0;
        var index;
        var range;

        index = store.index('status');
        range = IDBKeyRange.only('Pending Approval');

        index.openCursor(range).onsuccess = function(event) {
            var cursor = event.target.result;
            if (cursor) {
                result += cursor.value.reward;
                cursor.continue();
            }
            else {
                element.textContent = '$' + result.toFixed(2);
                if (header_element != null)
                    header_element.textContent = 'Pending earnings (HITDB updated: ' + localStorage['HITDB UPDATED']+ ')';
            }
            db.close();
        };
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

HITStorage.indexedDB.get_pending_payments = function() {
    var element = document.getElementById('pending_earnings_value');
    if (element == null)
        return;  

    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("HIT");

        var result = 0;
        var index;
        var range;

        index = store.index('status');
        range = IDBKeyRange.only('Approved&nbsp;- Pending&nbsp;Payment');

        index.openCursor(range).onsuccess = function(event) {
            var cursor = event.target.result;
            if (cursor) {
                result += cursor.value.reward;
                cursor.continue();
            }
            else {
                element.title = 'Approved - Pending Payment: $' + result.toFixed(2);
            }
        }
        db.close();
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

HITStorage.indexedDB.get_todays_projected_earnings = function(date) {
    var element = document.getElementById('projected_earnings_value');
    if (element == null)
        return;  

    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_ONLY);
        var store = trans.objectStore("HIT");

        var result = 0;
        var rejected = 0;
        var index;
        var range;

        index = store.index('date');
        range = IDBKeyRange.only(date);

        index.openCursor(range).onsuccess = function(event) {
            var cursor = event.target.result;
            if (cursor) {
                if (cursor.value.status == 'Rejected')
                    rejected += cursor.value.reward;
                else
                    result += cursor.value.reward;
                cursor.continue();
            }
            else {
                element.textContent = '$' + result.toFixed(2);
                element.title = '$' + rejected.toFixed(2) + ' rejected';

                if (localStorage['TODAYS TARGET'] !== undefined)
                {
                    var target = parseFloat(localStorage['TODAYS TARGET']).toFixed(2);
                    var my_target = document.getElementById('my_target');

                    var progress = Math.floor(result/target*40);
                    if (progress > 40)
                        progress = 40;
                    my_target.innerHTML = progress_bar(progress, 40, '█', '█', '#7fb448', 'grey') + '&nbsp;' +
                        ((result>target)? '+' : '') + (result-target).toFixed(2);
                    my_target.style.fontSize = '9px';
                }
            }
        }
        db.close();
    };
    request.onerror = HITStorage.indexedDB.onerror;
};

// Update database date format from MMDDYYYY to YYYY-MM-DD
// Shouldn't break anything even if used on already updated db
HITStorage.update_date_format = function(verbose)
{
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["HIT"], HITStorage.IDBTransactionModes.READ_WRITE);
        var store = trans.objectStore("HIT");

        store.openCursor().onsuccess = function(event) {
            var cursor = event.target.result;
            if (cursor)
            {
                if (cursor.value.date.indexOf('-') < 0)
                {
                    var i = cursor.value;
                    i.date = convert_date(i.date);
                    i.requesterName = i.requesterName.trim(); 
                    i.title = i.title.trim();
                    cursor.update(i);
                }
                cursor.continue();
            }
            else
            {
                db.close();
                HITStorage.update_stats_date_format(verbose);
            }
        };
    }
}

HITStorage.update_stats_date_format = function(verbose)
{
    var request = indexedDB.open("HITDB", v);
    request.onsuccess = function(e) {
        HITStorage.indexedDB.db = e.target.result;
        var db = HITStorage.indexedDB.db;
        var trans = db.transaction(["STATS"], HITStorage.IDBTransactionModes.READ_WRITE);
        var store = trans.objectStore("STATS");

        store.openCursor().onsuccess = function(event) {
            var cursor = event.target.result;
            if (cursor)
            {
                if (cursor.value.date.indexOf('-') < 0)
                {
                    var i = cursor.value;
                    i.date = convert_date(i.date);
                    cursor.delete();
                    store.put(i);
                }
                cursor.continue();
            }
            else
            {
                // DB should be fully updated
                db.close();
                if (verbose == true)
                    alert('Date conversion done.');
            }
        };
    }
};

/* ------------------------------------------------------------- */

HITStorage.prepare_donut = function (donutData, type)
{
    if (type == '---')
        return;
    var countHits = true;
    if (type.match('REWARDS'))
        countHits = false;

    var tmpData = {};
    var topRequesters = [];
    var topHits = [];
    var sum = 0;

    for (var i=0; i < donutData.length; i++) {
        var requesterName = donutData[i].requesterName.trim() + " (" + donutData[i].requesterId + ")";
        var hitTitle = donutData[i].title;
        var hitReward = donutData[i].reward;
        sum += (countHits) ? 1 : hitReward;

        if (tmpData[requesterName]) {
            tmpData[requesterName]['HITS'] += (countHits) ? 1 : hitReward;
        }
        else {
            tmpData[requesterName] = {};
            tmpData[requesterName]['HITS'] = (countHits) ? 1 : hitReward;
        }
        if (tmpData[requesterName][hitTitle])
            tmpData[requesterName][hitTitle] += (countHits) ? 1 : hitReward;
        else
            tmpData[requesterName][hitTitle] = (countHits) ? 1 : hitReward;

    }

    for (var key in tmpData) {
        topRequesters.push({name: key, y: tmpData[key]['HITS']});
    }
    topRequesters.sort(function(a,b){return b.y-a.y});

    var colors = Highcharts.getOptions().colors;

    for (var i=0; i<topRequesters.length; i++) {
        var tmpHits = [];
        topRequesters[i].color = colors[i];
        for (var key2 in tmpData[topRequesters[i].name]) {
            if (key2 != 'HITS') {
                tmpHits.push({name: key2, y: tmpData[topRequesters[i].name][key2], color: colors[i]});
            }
        }
        tmpHits.sort(function(a,b){return b.y-a.y});
        for (var j=0; j<tmpHits.length ; j++) {
            var brightness = 0.2 - (j / tmpHits.length) / 5;
            tmpHits[j].color = Highcharts.Color(colors[i]).brighten(brightness).get();
        }
        topHits = topHits.concat(tmpHits);
    }

    document.getElementById('container').style.display = 'block';


    chart = new Highcharts.Chart({
        chart: {
            renderTo: 'container',
            type: 'pie'
        },
        title: {
            text: 'Requesters and HITs matching your latest search'
        },
        yAxis: {
            title: {
                text: ''
            }
        },
        plotOptions: {
            pie: {
                shadow: false,
                dataLabels: { enabled: true}
            }
        },
        tooltip: {
            animation: false,
            valuePrefix: (countHits)? '' : '$',
            valueSuffix: (countHits)? ' HITs' : '',
            valueDecimals: (countHits)? 0 : 2,
            pointFormat: (countHits)? '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> (of all ' + sum + ' HITs)<br/>' :
            '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> (of all $' + sum.toFixed(2) + ')<br/>'
        },
        series: [{
            name: 'Requesters',
            data: topRequesters,
            size: '60%',
            dataLabels: {
                formatter: function() {
                    if (countHits) {
                        return this.y/sum >= 0.20 ? this.point.name: null;
                    }
                    else {
                        return this.y/sum >= 0.20 ? this.point.name : null;
                    }
                },
                color: 'black',
                distance: -10
            }
        }, {
            name: 'HITs',
            data: topHits,
            innerSize: '60%',
            dataLabels: {
                formatter: function() {
                    if (countHits) {
                        return this.y/sum > 0.05 ? this.point.name : null;
                    }
                    else {
                        return this.y/sum > 0.05 ? this.point.name : null;
                    }
                },
                color: 'black',
            }
        }]
    });
}

// Stolen from Today's Projected Earnings (http://userscripts.org/scripts/show/95331)
HITStorage.getHTTPObject = function()  
{ 
    if (typeof XMLHttpRequest != 'undefined')
    { 
        return new XMLHttpRequest();
    }
    try
    { 
        return new ActiveXObject("Msxml2.XMLHTTP");
    } 
    catch (e) 
    { 
        try
        { 
            return new ActiveXObject("Microsoft.XMLHTTP"); 
        } 
        catch (e) {} 
    } 
    return false;
}

// Stolen from Today's Projected Earnings (http://userscripts.org/scripts/show/95331)
// date format MMDDYYYY!
HITStorage.process_page = function(link, date, hitData)
{
    var page = HITStorage.getHTTPObject();
    page.open("GET", link, false);
    page.send(null);
    return HITStorage.parse_data(page.responseText, date, hitData);
}

// Partly stolen from Today's Projected Earnings (http://userscripts.org/scripts/show/95331)
// date format MMDDYYYY!
HITStorage.parse_data = function(page_text, date, hitData)
{
    var index  = 0;
    var index2 = 0;
    var page_html = document.createElement('div');
    page_html.innerHTML = page_text;

    var requesters = page_html.getElementsByClassName('statusdetailRequesterColumnValue');
    var titles = page_html.getElementsByClassName('statusdetailTitleColumnValue');
    var amounts = page_html.getElementsByClassName('statusdetailAmountColumnValue');
    var statuses = page_html.getElementsByClassName('statusdetailStatusColumnValue');
    var feedbacks = page_html.getElementsByClassName('statusdetailRequesterFeedbackColumnValue');

    var requesterName;
    var hitTitle;
    var hitReward;
    var hitStatus;
    var requesterId;
    var hitId;

    for(var k = 0; k < amounts.length; k++)
    {
        requesterName = requesters[k].textContent;
        requesterLink = requesters[k].childNodes[1].href;
        hitTitle      = titles[k].textContent;
        index = amounts[k].innerHTML.indexOf('$');
        hitReward     = parseFloat(amounts[k].innerHTML.substring(index+1));
        hitStatus     = statuses[k].innerHTML;
        hitFeedback   = feedbacks[k].textContent;


        requesterId = getQueryVariable(requesterLink,"requesterId");
        subject = getQueryVariable(requesterLink,"subject");
        subject = subject.split("+");
        hitId = subject[subject.length-1];

        var hit = {
            hitId         : hitId,
            date          : convert_date(date),
            requesterName : requesterName.trim(),
            requesterLink : requesterLink.trim(),
            title         : hitTitle.trim(),
            reward        : hitReward,
            status        : hitStatus,
            feedback      : hitFeedback.trim(),
            requesterId   : requesterId
        };

        //HITStorage.indexedDB.addHIT(hitData);
        hitData.push(hit);
    }

    return amounts.length;
}

//Used to simplify getting requester ID's and such
function getQueryVariable(url,variable)
{
    var query = url.substring(1);
    var vars = query.split("?")[1].split("&");
    for (var i=0;i<vars.length;i++) 
    {
        var pair = vars[i].split("=");
        if(pair[0] == variable)
        {
            return pair[1];
        }
    }
    return(false);
}

// Returns available days (YYYY-MM-DD) 
HITStorage.getAllAvailableDays = function(try_extra_days)
{
    var days = [];

    var page = HITStorage.getHTTPObject();
    page.open("GET", 'https://www.mturk.com/mturk/status', false);
    page.send(null);

    var page_html = document.createElement('div');
    page_html.innerHTML = page.responseText;

    var dateElements = page_html.getElementsByClassName('statusDateColumnValue');
    var submittedElements = page_html.getElementsByClassName('statusSubmittedColumnValue');
    var approvedElements = page_html.getElementsByClassName('statusApprovedColumnValue');
    var rejectedElements = page_html.getElementsByClassName('statusRejectedColumnValue');
    var pendingElements = page_html.getElementsByClassName('statusPendingColumnValue');
    var earningsElements = page_html.getElementsByClassName('statusEarningsColumnValue');

    for (var i=0; i<dateElements.length; i++)
    {
        var date = dateElements[i].childNodes[1].href.substr(53);
        date = convert_date(date);

        days.push( { date: date,
                    submitted: parseInt(submittedElements[i].textContent),
                    approved : parseInt(approvedElements[i].textContent),
                    rejected : parseInt(rejectedElements[i].textContent),
                    pending  : parseInt(pendingElements[i].textContent),
                    earnings : parseFloat(earningsElements[i].textContent.slice(1)) });
    }

    if (try_extra_days > 0)
    {
        var date = days[days.length-1].date;
        var d = new Date();
        d.setFullYear(parseInt(date.substr(0,4)), parseInt(date.substr(5,2))-1, parseInt(date.substr(8,2)));

        for (var i=0; i<try_extra_days; i++)
        {      
            d.setDate(d.getDate()-1);
            var month = '0' + (d.getMonth() + 1);
            var day = '0' + d.getDate();
            if (month.length > 2)
                month = month.substr(1);
            if (day.length > 2)
                day = day.substr(1);
            date = '' + d.getFullYear() + '-' + month + '-' + day;

            days.push( { date: date,
                        submitted: -1,
                        approved : -1,
                        rejected : -1,
                        pending  : -1,
                        earnings : -1 } );
        }
    }

    return days;
}

HITStorage.getLatestHITs = function()
{
    if (localStorage['HITDB AUTO UPDATE'] === undefined || localStorage['HITDB AUTO UPDATE'] == 'OFF')
        return;  

    if (localStorage['HITDB TIMESTAMP'] !== undefined)
    {
        if (new Date().getTime() < new Date(parseInt(localStorage['HITDB TIMESTAMP'])).getTime() + 90000)
        {
            return;
        }
    }
    localStorage['HITDB TIMESTAMP'] = new Date().getTime();

    var auto_button = document.getElementById('auto_button');
    var page = HITStorage.getHTTPObject();
    page.open("GET", 'https://www.mturk.com/mturk/status', false);
    page.send(null);
    auto_button.textContent += ' +';

    var page_html = document.createElement('div');
    page_html.innerHTML = page.responseText;

    var dateElements = page_html.getElementsByClassName('statusDateColumnValue');
    var submittedElements = page_html.getElementsByClassName('statusSubmittedColumnValue');
    var approvedElements = page_html.getElementsByClassName('statusApprovedColumnValue');
    var rejectedElements = page_html.getElementsByClassName('statusRejectedColumnValue');
    var pendingElements = page_html.getElementsByClassName('statusPendingColumnValue');
    var earningsElements = page_html.getElementsByClassName('statusEarningsColumnValue');

    if (dateElements[0].childNodes[1].textContent.trim() != 'Today')
        return;

    var url = dateElements[0].childNodes[1].href;
    var date = url.substr(53); // keep MMDDYYYY
    var submitted = parseInt(submittedElements[0].textContent);
    //var approved = parseInt(approvedElements[0].textContent);
    //var rejected = parseInt(rejectedElements[0].textContent);
    //var pending  = parseInt(pendingElements[0].textContent);
    //var earnings = parseFloat(earningsElements[0].textContent.slice(1));
    var pages_done = null;
    if (localStorage['HITDB AUTOUPDATE PAGES'] !== undefined)
    {
        pages_done = JSON.parse(localStorage['HITDB AUTOUPDATE PAGES']);
    }
    if (pages_done == null || pages_done.date != date)
        pages_done = {date: date};

    var new_hits = 0;
    var page = 1 + Math.floor(submitted/25);
    page = (page<1) ? 1 : page;

    var hitData = [];  
    if (submitted != pages_done.submitted)
    {
        url = "https://www.mturk.com/mturk/statusdetail?sortType=All&pageNumber=" + page + "&encodedDate=" + date;
        HITStorage.process_page(url, date, hitData);
        new_hits += submitted - pages_done.submitted;
        pages_done.submitted = submitted;
        localStorage['HITDB AUTOUPDATE PAGES'] = JSON.stringify(pages_done);
        auto_button.textContent += '+';
    }

    if (page > 1)
    {
        extra_page = page-1;

        while (extra_page >= 1)
        {
            if (pages_done[extra_page] != true)
            {
                url = "https://www.mturk.com/mturk/statusdetail?sortType=All&pageNumber=" + extra_page + "&encodedDate=" + date;
                if (HITStorage.process_page(url, date, hitData) == 25)
                {
                    pages_done[extra_page] = true;
                    localStorage['HITDB AUTOUPDATE PAGES'] = JSON.stringify(pages_done);
                    auto_button.textContent += '+';
                }
                break;
            }
            extra_page -= 1;
        }    
    }
    HITStorage.indexedDB.addHITs(hitData);
}

// Gets status details for given date (MMDDYYYY)
// Collects all HITs for given date to hitData array
HITStorage.getHITData = function(day_to_fetch, hitData, page, days_to_update)
{
    var dataDate = convert_iso_date(day_to_fetch.date);
    page = page || 1;
    detailed_status_page_link = "https://www.mturk.com/mturk/statusdetail?sortType=All&pageNumber=" + page + "&encodedDate=" + dataDate;            

    if (HITStorage.process_page(detailed_status_page_link, dataDate, hitData) == 0)
    {
        if (day_to_fetch.submitted == -1 || hitData.length == day_to_fetch.submitted)
        {
            setTimeout(function(){ HITStorage.indexedDB.addHITs(hitData, day_to_fetch, days_to_update); }, 1000);
        }
        else
        {
            alert("There was an error while fetching HITs for date: " + day_to_fetch.date + ".\n" +
                  "Script monkeys expected " + day_to_fetch.submitted + " bananas, but got " + hitData.length + "! 😞");
            HITStorage.update_done();
        }
    }
    else
    {
        HITStorage.update_status_label('Please wait: script monkeys are fetching status pages (' +
                                       day_to_fetch.date + ', page ' + page + ')', 'red');
        setTimeout(function(){ HITStorage.getHITData(day_to_fetch, hitData, page+1, days_to_update); }, 1000);
    }  
}

HITStorage.formatTime = function(msec)
{
    if (isNaN(msec))
        return "-";
    var seconds = Math.floor(msec / 1000) % 60;
    var minutes = Math.floor((msec / 1000) / 60) % 60;
    var hours = Math.floor(((msec / 1000) / 60) / 60) % 24;
    var days = Math.floor(((msec / 1000) / 60) / 60 / 24);

    if (hours > 0)
        seconds = "";
    else
        seconds = "" + seconds + "s";
    minutes == 0 ? minutes = "" : minutes = "" + minutes + "m ";
    hours == 0   ? hours = "" : hours = "" + hours + "h ";

    if (days > 0)
        return '' + days + ' day' + ((days>1)? 's' : ' ') + hours;
    return hours + minutes + seconds;
}

HITStorage.update_status_label = function(new_status, color)
{
    var label = document.getElementById('status_label');
    label.innerHTML = new_status;
    label.style.color = color || 'black';
}

// validate input field dates
// Accept YYYY-MM-DD
HITStorage.validate_date = function(input)
{
    date = input.value;  

    if (date.match(/^[01]\d\/[0123]\d\/20\d\d$/) != null)
    {
        var d = date.split('\/');
        date = d[2] + '-' + d[0] + '-' + d[1];
        input.value = date;
    }

    if (date.match(/^$|^20\d\d\-[01]\d\-[0123]\d$/) != null)
    {
        input.style.backgroundColor = 'white';
        return true;
    }
    input.style.backgroundColor = 'pink';
    return false;
}

HITStorage.validate_dates = function()
{
    from = document.getElementById('from_date');
    to = document.getElementById('to_date');  

    if (HITStorage.validate_date(from) && HITStorage.validate_date(to))
    {
        if (from.value > to.value && to.value != '')
        {
            alert('Invalid date!');
            return false;
        }

        return true;
    }
    alert('Invalid date!');
    return false;
}

HITStorage.start_search = function()
{
    if (HITStorage.validate_dates() == false)
        return;

    HITStorage.update_status_label('Using local HIT database', 'green');

    var options = {};
    options.term = document.getElementById('search_term').value;
    options.status = document.getElementById('status_select').value;
    options.donut = document.getElementById('donut_select').value;
    options.from_date = document.getElementById('from_date').value;
    options.to_date = document.getElementById('to_date').value;
    options.export_csv = document.getElementById('export_csv').checked;

    HITStorage.disable_inputs();
    setTimeout(function(){ HITStorage.do_search(options); }, 500);
}

HITStorage.disable_inputs = function()
{
    document.getElementById('delete_button').disabled = true;
    document.getElementById('search_button').disabled = true;
    document.getElementById('update_button').disabled = true;
    document.getElementById('overview_button').disabled = true;
    document.getElementById('import_button').disabled = true;
    document.getElementById('pending_button').disabled = true;
    document.getElementById('status_button').disabled = true;
    document.getElementById('from_date').disabled = true;
    document.getElementById('to_date').disabled = true;
    document.getElementById('search_term').disabled = true;
    document.getElementById('status_select').disabled = true;
    document.getElementById('donut_select').disabled = true;
}

HITStorage.enable_inputs = function()
{
    document.getElementById('delete_button').disabled = false;
    document.getElementById('search_button').disabled = false;
    document.getElementById('update_button').disabled = false;
    document.getElementById('overview_button').disabled = false;
    document.getElementById('import_button').disabled = false;
    document.getElementById('pending_button').disabled = false;
    document.getElementById('status_button').disabled = false;
    document.getElementById('from_date').disabled = false;
    document.getElementById('to_date').disabled = false;
    document.getElementById('search_term').disabled = false;
    document.getElementById('status_select').disabled = false;
    document.getElementById('donut_select').disabled = false;
}


HITStorage.do_search = function(options)
{
    HITStorage.indexedDB.getHITs(options);

    setTimeout( function() { HITStorage.update_status_label("Search powered by non-amazonian script monkeys"); }, 3000);

    HITStorage.enable_inputs();
}

HITStorage.show_results = function(results)
{
    resultsWindow = window.open();
    resultsWindow.document.write("<html><head><title>Status Detail Search Results</title></head><body>\n");
    resultsWindow.document.write("<h1>HITs matching your search:</h1>\n");
    resultsWindow.document.write('<table style="border: 1px solid black;border-collapse:collapse;width:90%;margin-left:auto;margin-right:auto;">\n');
    resultsWindow.document.write('<tr style="background-color:lightgrey"><th>Date</th><th>Requester</th><th>HIT Title</th><th>Reward</th><th>Status</th><th>Feedback</th></tr>\n');

    var odd = true;
    var sum = 0;
    var sum_rejected = 0;
    var sum_approved = 0;
    var sum_pending = 0;


    var new_day = false;

    for (var i=0; i<results.length; i++) {
        odd = !odd;
        sum += results[i].reward;
        if (results[i].status == 'Rejected')
            sum_rejected += results[i].reward;
        else if (results[i].status == 'Pending Approval')
            sum_pending += results[i].reward;
        else
            sum_approved += results[i].reward;

        if (i>0 && (results[i-1].date != results[i].date))
            new_day = true;
        else
            new_day = false;
        resultsWindow.document.write(HITStorage.format_hit_line(results[i], odd, HITStorage.status_color(results[i].status), new_day ));
    }

    resultsWindow.document.write('<tr style="background-color:lightgrey"><th></th><th></th><th></th><th>$' + sum.toFixed(2) + '</th><th></th><th></th></tr>\n');
    resultsWindow.document.write("</table>");
    resultsWindow.document.write("<p>Found " + results.length + " matching HITs. $" + sum_approved.toFixed(2) + " approved, " +
                                 "$" + sum_rejected.toFixed(2) + " rejected and $" + sum_pending.toFixed(2) + " pending.</p>");
    resultsWindow.document.write("</body></html>")
    resultsWindow.document.close();
}

HITStorage.status_color = function(status)
{
    var color = "green";

    if (status.match("Pending Approval"))
        color = "orange";
    else if (status.match("Rejected"))
        color = "red";

    return color;
}

HITStorage.format_hit_line = function(hit, odd, status_color, new_day)
{
    var line = '<tr style="background-color:';
    if (odd)
        line += '#f1f3eb;';
    else
        line += 'white;';
    line += ' valign=top;';
    if (new_day)
        line += ' border: 0px dotted #000000; border-width: 2px 0px 0px 0px">';
    else
        line += '">';

    line += '<td>' + hit.date + '</td>';
    if (hit.requesterLink != null)
        line += '<td style="width:165px"><a href="' + hit.requesterLink + '" title="Contact this Requester">' + hit.requesterName + '</a></td>';
    else
        line += '<td style="width:165px">' + hit.requesterName + '</td>';
    line += '<td style="width:213px">' + hit.title + '</td>';
    line += '<td style="width:45px">$' + hit.reward.toFixed(2) + '</td>';
    line += '<td style="color:' + status_color + '; width:55px">' + hit.status + '</td>';
    line += '<td><div style="width:225px; overflow:hidden">' + hit.feedback + '</div></td>';
    line += '</tr>\n';
    return line;
}

HITStorage.show_pending_overview = function(results)
{
    resultsWindow = window.open();
    resultsWindow.document.write("<html><head><title>Summary of Pending HITs</title></head><body>\n");
    resultsWindow.document.write("<h1>Summary of Pending HITs</h1>\n");
    resultsWindow.document.write('<table style="border: 1px solid black;border-collapse:collapse;width:90%;margin-left:auto;margin-right:auto;">\n');
    resultsWindow.document.write('<tr style="background-color:lightgrey"><th>requesterId</th><th>Requester</th><th></th><th>Pending</th><th>Rewards</th>\n');

    // 'requesterId,requesterName,pending,reward';
    var odd = false;
    var sum = 0;
    var pending = 0;

    for (var i=0; i<results.length; i++) {
        odd = !odd;
        sum += results[i][3];
        pending += results[i][2];
        resultsWindow.document.write(HITStorage.format_pending_line(results[i], odd, i));
    }

    resultsWindow.document.write('<tr style="background-color:lightgrey"><th>' + results.length + ' different requesterIds</th><th></th><th></th><th style="text-align: right">' + pending + '</th><th style="text-align: right">$' + sum.toFixed(2) + '</th>\n');
    resultsWindow.document.write("</table>");
    resultsWindow.document.write("</body></html>")
    resultsWindow.document.close();

    for (var i=0; i<results.length; i++)
    {
        resultsWindow.document.getElementById('id-' + i).addEventListener("click", search_func(results[i][0], 'requesterId'), false);
        resultsWindow.document.getElementById('id2-' + i).addEventListener("click", show_requester_func(results[i][0]) , false);
    }
}

HITStorage.show_status_overview = function(results, date)
{
    resultsWindow = window.open();
    resultsWindow.document.write("<html><head><title>Daily HIT stats</title></head><body>\n");
    if (date)
        resultsWindow.document.write("<h1>Daily HIT stats</h1>\n");
    else
        resultsWindow.document.write("<h1>Daily HIT stats (' + date + ')</h1>\n");
    resultsWindow.document.write('<table style="border: 1px solid black;border-collapse:collapse;width:90%;margin-left:auto;margin-right:auto;">\n');
    resultsWindow.document.write('<tr style="background-color:lightgrey"><th>Date</th><th>Submitted</th><th>Approved</th><th>Rejected</th><th>Pending</th><th>Earnings</th>\n');

    var odd = false;
    var sum = 0;
    var submitted = 0;
    var approved = 0;
    var rejected = 0;
    var pending = 0;
    var new_month = false;  

    for (var i=results.length-1; i>=0; i--) {
        odd = !odd;
        sum += results[i].earnings;
        submitted += results[i].submitted;
        approved += results[i].approved;
        rejected += results[i].rejected;
        pending += results[i].pending;
        if (i<results.length-1)
            new_month = (results[i].date.substr(0,7) != results[i+1].date.substr(0,7));
        resultsWindow.document.write(HITStorage.format_status_line(results[i], odd, new_month));
    }

    resultsWindow.document.write('<tr style="background-color:lightgrey"><th>' + results.length + ' days</th><th style="text-align: left">' + submitted +
                                 '</th><th style="text-align: left">' + approved +
                                 '</th><th style="text-align: left">' + rejected +
                                 '</th><th style="text-align: left">' + pending +
                                 '</th><th style="text-align: left">$' + sum.toFixed(2) + '</th>\n');
    resultsWindow.document.write("</table>");
    resultsWindow.document.write("</body></html>")
    resultsWindow.document.close();

    for (var i=0; i<results.length; i++)
        resultsWindow.document.getElementById(results[i].date).addEventListener("click", search_func('', 'date', results[i].date, results[i].date), false);
}

HITStorage.show_requester_overview = function(results, date)
{
    resultsWindow = window.open();
    resultsWindow.document.write("<html><head><title>Requester Overview</title></head><body>\n");
    if (date)
        resultsWindow.document.write("<h1>Requester Overview " + date + "</h1>\n");
    else
        resultsWindow.document.write("<h1>Requester Overview</h1>\n");
    resultsWindow.document.write('<table style="border: 1px solid black;border-collapse:collapse;width:90%;margin-left:auto;margin-right:auto;">\n');
    resultsWindow.document.write('<tr style="background-color:lightgrey"><th>requesterId</th><th>Requester</th><th></th><th>HITs</th><th>Pending</th><th>Rewards</th><th colspan="2">Rejected</th></tr>\n');

    // 'requesterId,requesterName,hits,pending,reward,rejected';
    var odd = false;
    var sum = 0;
    var hits = 0;
    var rejected = 0;
    var pending = 0;
    var new_day = false;
    var top = true;
    var dot_line;

    for (var i=0; i<results.length; i++) {
        odd = !odd;
        sum += results[i][3];
        hits += results[i][2];
        rejected += results[i][4];
        pending += results[i][5];
        dot_line = false;
        if (i==10)
        {
            dot_line = true;
            top = false;
        }
        if (i>10 && results[i][3] == 0 && results[i-1][3] != 0)
            dot_line = true;

        resultsWindow.document.write(HITStorage.format_overview_line(results[i], odd, dot_line, top, i));
    }

    resultsWindow.document.write('<tr style="background-color:lightgrey"><th>' + results.length + ' different requesterIds</th>' +
                                 '<th></th><th></th><th style="text-align: right">' + hits + '<th style="text-align: right">' + pending +
                                 '</th><th style="text-align: right">$' + sum.toFixed(2) + '</th><th style="text-align: right">' + rejected + '</th>' +
                                 '<th style="text-align: right">' +
                                 (rejected/hits*100).toFixed(2) + '%</th></tr>\n');
    resultsWindow.document.write("</table>");
    resultsWindow.document.write("<p>Reward includes all 'Paid' and 'Approved - Pending Payment' HITs. " +
                                 "Reward does not include any bonuses.</p>");
    resultsWindow.document.write("</body></html>")
    resultsWindow.document.close();

    for (var i=0; i<results.length; i++)
    {
        resultsWindow.document.getElementById('id-' + i).addEventListener("click", search_func(results[i][0], 'requesterId'), false);
        resultsWindow.document.getElementById('id2-' + i).addEventListener("click", show_requester_func(results[i][0]) , false);
    }
}

HITStorage.show_requester = function(results)
{
    resultsWindow = window.open();
    resultsWindow.document.write('<html><head><title>' + results[0].requesterName + '</title></head><body>\n');
    resultsWindow.document.write('<h1>' + results[0].requesterName + ' (' + results[0].requesterId + ')</h1>\n');

    resultsWindow.document.write('You have submitted ' + results.length + ' HITs for this requester. Earliest ' + results[results.length-1].date +
                                 ', latest ' + results[0].date);

    resultsWindow.document.write('<p><a href="https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId=' + results[0].requesterId + '">' +
                                 'Search HITs created by this requester</a></p>');


    resultsWindow.document.write('<p><a href="http://turkopticon.differenceengines.com/' + results[0].requesterId + '">' +
                                 'See reviews about this requester on Turkopticon</a> or ');
    resultsWindow.document.write('<a href="' + TO_report_link(results[0].requesterId,results[0].requesterName) + '">' +
                                 'review this requester on Turkopticon</a></p>');

    var reward = 0;
    var hits = 0;
    var sum = 0;
    var rejected = 0;
    var approved = 0;
    var pending  = 0;
    var all_rejected = 0;
    var all_approved = 0;
    var all_pending  = 0;

    resultsWindow.document.write('<table style="border: 1px solid black;border-collapse:collapse;margin-left:10px;margin-right:auto;">\n');
    resultsWindow.document.write('<tr style="background-color:lightgrey"><th>Month' + 
                                 '</th><th>Submitted' +
                                 '</th><th>Approved' +
                                 '</th><th>Rejected' +
                                 '</th><th>Pending' +
                                 '</th><th>Earnings</th></tr>\n');

    for (var i=0; i<results.length; i++) {
        hits++;
        if (results[i].status == 'Rejected')
        {
            all_rejected++;
            rejected++;
        }
        else if (results[i].status == 'Pending Approval')
        {
            all_pending++;
            pending++;
        }
        else
        {
            all_approved++;
            approved++;
            sum += results[i].reward;
            reward += results[i].reward;
        }

        if (i==results.length-1 || (i<results.length-1 && (results[i].date.substr(0,7) != results[i+1].date.substr(0,7))))
        {
            resultsWindow.document.write('<tr><td style="text-align: right">' + results[i].date.substr(0,7) +
                                         '</td><td style="text-align: right">' + hits +
                                         '</td><td style="text-align: right">' + approved +
                                         '</td><td style="text-align: right">' + rejected +
                                         '</td><td style="text-align: right">' + pending +
                                         '</td><td style="text-align: right">$' + reward.toFixed(2) + '</td></tr>\n');
            reward = 0;
            hits = 0;
            approved = 0;
            rejected = 0;
            pending = 0;
        }
    }
    resultsWindow.document.write('<tr style="background-color:lightgrey"><th>' + 
                                 '</th><th style="text-align: right">' + results.length +
                                 '</th><th style="text-align: right">' + all_approved +
                                 '</th><th style="text-align: right">' + all_rejected +
                                 '</th><th style="text-align: right">' + all_pending +
                                 '</th><th style="text-align: right">$' + sum.toFixed(2) + '</th></tr>\n');
    resultsWindow.document.write('</table>');

    resultsWindow.document.write('<p>Rewards do not include any bonuses</p>');

    resultsWindow.document.write("</body></html>");
    resultsWindow.document.close();
}

function TO_report_link(requesterId, requesterName)
{
    return 'http://turkopticon.differenceengines.com/report?requester[amzn_id]=' + requesterId +
        '&requester[amzn_name]=' + encodeURI(requesterName.trim());
}

HITStorage.format_overview_line = function(req, odd, dot_line, top, i)
{
    var color;
    if (top)
        color = (odd)? 'ffffe0;' : '#eee8aa;';
    else
        color = (odd)? 'white;' : '#f1f3eb;';
    var line = '<tr style="background-color:' + color;
    if (dot_line)
        line += ' border: 0px dotted #000000; border-width: 2px 0px 0px 0px';
    line += '">';
    line += '<td><button type="button" title="Show all HITs" style="height: 16px;font-size: 8px; padding: 0px;" id="id-' +
        i + '">&gt;&gt;</button>' +
        '<button type="button" title="Show details about requester" style="height: 16px;font-size: 8px; padding: 0px;" id="id2-' +
        i + '">+</button> ' + req[0].trim() +
        '</td>';
    line += '<td><a title="Requesters Turkopticon page" target="_blank" href="http://turkopticon.differenceengines.com/' + req[0].trim() + '">[TO]</a> ';
    line += req[1].trim() + '</td>';
    line += '<td style="width: 50px"><a title="Report requester to Turkopticon" target="_blank" href="' + TO_report_link(req[0], req[1]) + '">[report]</a></td>';
    line += '<td style="text-align: right">' + req[2] + '</td>';
    line += '<td style="text-align: right">' + req[5] + '</td>';
    line += '<td style="text-align: right">$' + req[3].toFixed(2) + '</td>';
    var p = (req[4]/req[2]*100).toFixed(1);
    var pc = (p>0)? 'red' : 'green'; 
    line += '<td style="text-align: right; color:' + pc + ';">' + req[4] + '</td>';
    line += '<td style="text-align: right; color:' + pc + ';">' + p + '%</td>';
    line += '</tr>\n';
    return line;
}

HITStorage.format_pending_line = function(req, odd, i)
{
    console.log(req);
    var color = (odd)? 'white;' : '#f1f3eb;';
    var line = '<tr style="background-color:' + color;
    line += '">';
    line += '<td style="white-space: nowrap; width: 150px; margin-right: 10px;"><button type="button" title="Show all HITs" style="height: 16px;font-size: 8px; padding: 0px;" id="id-' +
        i + '">&gt;&gt;&gt;</button>' +
        '<button type="button" title="Show details about requester" style="height: 16px;font-size: 8px; padding: 0px;" id="id2-' +
        i + '">+</button> ' + req[0].trim() + '</td>';
    line += '<td><a title="Requesters Turkopticon page" target="_blank" href="http://turkopticon.differenceengines.com/' + req[0].trim() + '">[TO]</a> ';
    line += req[1].trim() + '</td>';
    line += '<td style="width: 50px"><a title="Report requester to Turkopticon" target="_blank" href="' + TO_report_link(req[0], req[1]) + '">[report]</a></td>';
    line += '<td style="text-align: right">' + req[2] + '</td>';
    line += '<td style="text-align: right">$' + req[3].toFixed(2) + '</td>';
    line += '</tr>\n';
    return line;
}

HITStorage.format_status_line = function(d, odd, new_month)
{
    var color = (odd)? 'white;' : '#f1f3eb;';
    var line = '<tr style="background-color:' + color;
    if (new_month)
        line += ' border: 0px dotted #000000; border-width: 2px 0px 0px 0px">';
    else
        line += '">';
    line += '<td><button type="button" title="Show all HITs" style="height: 16px;font-size: 8px; padding: 0px;" id="' +
        d.date + '">&gt;&gt;&gt;</button> ' + d.date + '</td>';
    line += '<td>' + d.submitted + '</td>';
    line += '<td>' + d.approved + '</td>';
    line += '<td>' + d.rejected + '</td>';
    line += '<td>' + d.pending + '</td>';
    line += '<td>$' + d.earnings.toFixed(2) + '</td>';
    line += '</tr>\n';
    return line;
}

HITStorage.show_pending_overview_csv = function(results)
{
    var csvData = [];
    csvData.push(["requesterId","requesterName","pending","reward","\n"]);
    for (var i=0; i<results.length; i++) {
        csvData.push(HITStorage.format_pending_line_csv(results[i]));
    }
    var blob = new Blob(csvData, {type: "text/csv;charset=utf-8"});
    saveAs(blob, "pending_overview.csv");
}

HITStorage.format_pending_line_csv = function(req)
{
    var line = [];
    line.push(req[0].trim());
    line.push('"' + req[1].trim() + '"');
    line.push(req[2]);
    line.push(req[3].toFixed(2));
    line.push('\n');
    return line;
}


HITStorage.show_requester_overview_csv = function(results)
{
    var csvData = [];
    csvData.push(['requesterId','requesterName','hits','reward','rejected','pending','\n']);
    for (var i=0; i<results.length; i++) {
        csvData.push(HITStorage.format_overview_line_csv(results[i]));
    }
    var blob = new Blob(csvData, {type: "text/csv;charset=utf-8"});
    saveAs(blob, "requester_overview.csv");
}

HITStorage.format_overview_line_csv = function(req)
{
    var line = [];
    line.push(req[0].trim());
    line.push('"' + req[1].trim() + '"');
    line.push(req[2]);
    line.push(req[3].toFixed(2));
    line.push(req[4]);
    line.push(req[5]);
    line.push('\n');
    return line;
}

HITStorage.show_status_overview_csv = function(results)
{
    var csvData = [];
    csvData.push(['Date','Submitted','Approved','Rejected','Pending','Earnings','\n']);
    for (var i=results.length-1; i>=0; i--) {
        csvData.push(HITStorage.format_status_line_csv(results[i]));
    }
    var blob = new Blob(csvData, {type: "text/csv;charset=utf-8"});
    //location.href='data:text/csv;charset=utf8,' + encodeURIComponent(csvData);
    saveAs(blob, "status_overview.csv");
}

HITStorage.format_status_line_csv = function(d)
{
    var line = [];
    line.push('"' + d.date + '"');
    line.push(d.submitted);
    line.push(d.approved);
    line.push(d.rejected);
    line.push(d.pending);
    line.push(d.earnings.toFixed(2));
    line.push('\n');
    return line;
}

HITStorage.export_csv = function(results)
{
    var csvData = [];
    csvData.push(['hitId','date','requesterName','requesterId','title','reward','status','feedback','\n']);
    for (var i=0; i<results.length; i++) {
        csvData.push(HITStorage.format_csv_line(results[i]));
    }
    var blob = new Blob(csvData, {type: "text/csv;charset=utf-8"});
    //location.href='data:text/csv;charset=utf8,' + encodeURIComponent(csvData);
    saveAs(blob, "hit_database.csv");
}

HITStorage.format_csv_line = function(hit)
{
    var line = [];
    line.push('"' + hit.hitId.trim() + '"');
    line.push('"' + hit.date.trim() + '"');
    line.push('"' + hit.requesterName.trim() + '"');
    line.push('"' + hit.requesterId.trim() + '"');
    line.push('"' + hit.title.trim() + '"');
    line.push(hit.reward.toFixed(2));
    line.push('"' + hit.status.trim().replace(/\&nbsp;/g,' ') + '"');
    line.push('"' + hit.feedback.trim() + '"');
    line.push('\n');
    return line;
}

HITStorage.do_update = function(days_to_update)
{
    if (DAYS_TO_FETCH.length<1)
    {
        HITStorage.check_update();
        return;
    }
    HITStorage.update_status_label('Please wait: ' + progress_bar(days_to_update-DAYS_TO_FETCH.length, days_to_update) +
                                   ' (' + (days_to_update-DAYS_TO_FETCH.length) + '/' + days_to_update + ')', 'red');

    var hits = [];
    setTimeout(function(){ HITStorage.getHITData( DAYS_TO_FETCH.shift(), hits, 1, days_to_update); }, 2000);
}

HITStorage.update_done = function()
{
    HITStorage.update_status_label('Script monkeys have updated your local database', 'green');
    setTimeout( function() { HITStorage.update_status_label("Search powered by non-amazonian script monkeys"); }, 5000);

    HITStorage.enable_inputs();

    localStorage['HITDB UPDATED'] = new Date().toString();

    var e = document.getElementById('user_activities.date_column_header.tooltip').parentNode.parentNode.childNodes[2].childNodes[1].childNodes[1];
    if (e != null && e.textContent.trim() == 'Today') {
        var today = e.href.slice(-8);
        today = convert_date(today);
        HITStorage.indexedDB.get_todays_projected_earnings(today);
    }
    HITStorage.indexedDB.get_pending_approvals();
    HITStorage.indexedDB.get_pending_payments();
}


HITStorage.update_database = function()
{
    HITStorage.disable_inputs();

    if (localStorage['HITDB TRY_EXTRA_DAYS'] == 'YES') {
        DAYS_TO_FETCH = HITStorage.getAllAvailableDays(20);
        delete localStorage['HITDB TRY_EXTRA_DAYS'];
    }
    else
    {
        DAYS_TO_FETCH = HITStorage.getAllAvailableDays();
    }
    DAYS_TO_FETCH_CHECK = DAYS_TO_FETCH.slice(0);

    // remove extra days from checklist
    for (var i=0; i<DAYS_TO_FETCH_CHECK.length; i++)
    {
        if (DAYS_TO_FETCH_CHECK[i].submitted == -1) {
            DAYS_TO_FETCH_CHECK = DAYS_TO_FETCH_CHECK.slice(0,i);
            break;
        }
    }

    DAYS_TO_FETCH = DAYS_TO_FETCH_CHECK.slice(0);
    HITStorage.update_status_label('Please wait: script monkeys are preparing to start working', 'red');
    setTimeout(function(){ HITStorage.prepare_update_and_check_pending_payments(); }, 100);  
}

HITStorage.show_overview = function()
{
    if (HITStorage.validate_dates() == false)
        return;
    var options = {};
    options.term = document.getElementById('search_term').value;
    options.status = document.getElementById('status_select').value;
    options.donut = document.getElementById('donut_select').value;
    options.from_date = document.getElementById('from_date').value;
    options.to_date = document.getElementById('to_date').value;
    options.export_csv = document.getElementById('export_csv').checked;

    HITStorage.update_status_label('Please wait: script monkeys are picking bananas 😋', 'red');
    HITStorage.disable_inputs();
    HITStorage.indexedDB.requesterOverview(options);
}

HITStorage.show_pendings = function()
{
    var options = {};
    options.term = document.getElementById('search_term').value;
    options.status = document.getElementById('status_select').value;
    options.donut = document.getElementById('donut_select').value;
    options.from_date = document.getElementById('from_date').value;
    options.to_date = document.getElementById('to_date').value;
    options.export_csv = document.getElementById('export_csv').checked;

    HITStorage.update_status_label('Please wait: script monkeys are picking bananas 😋', 'red');
    HITStorage.disable_inputs();
    HITStorage.indexedDB.pendingOverview(options);
}

HITStorage.show_status = function()
{
    if (HITStorage.validate_dates() == false)
        return;
    var options = {};
    options.term = document.getElementById('search_term').value;
    options.status = document.getElementById('status_select').value;
    options.donut = document.getElementById('donut_select').value;
    options.from_date = document.getElementById('from_date').value;
    options.to_date = document.getElementById('to_date').value;
    options.export_csv = document.getElementById('export_csv').checked;

    HITStorage.update_status_label('Please wait: script monkeys are picking bananas 😋', 'red');
    HITStorage.disable_inputs();
    HITStorage.indexedDB.statusOverview(options);
}

var IMPORT_DIALOG = null;

function import_dialog()
{
    if (IMPORT_DIALOG == null)
    {
        IMPORT_DIALOG = document.createElement('div');
        IMPORT_DIALOG.style.display = 'block';

        IMPORT_DIALOG.style.position = 'fixed';
        IMPORT_DIALOG.style.width = '600px';
        //IMPORT_DIALOG.style.height = '400px';
        IMPORT_DIALOG.style.height = '90%';
        IMPORT_DIALOG.style.left = '50%';
        IMPORT_DIALOG.style.right = '50%';
        IMPORT_DIALOG.style.margin = '-300px 0px 0px -300px';
        //IMPORT_DIALOG.style.top = '400px';
        IMPORT_DIALOG.style.bottom = '10px';
        IMPORT_DIALOG.style.padding = '10px';
        IMPORT_DIALOG.style.border = '2px';
        IMPORT_DIALOG.style.textAlign = 'center';
        IMPORT_DIALOG.style.verticalAlign = 'middle';
        IMPORT_DIALOG.style.borderStyle = 'solid';
        IMPORT_DIALOG.style.borderColor = 'black';
        IMPORT_DIALOG.style.backgroundColor = 'white';
        IMPORT_DIALOG.style.color = 'black';
        IMPORT_DIALOG.style.zIndex = '100';

        var table = document.createElement('table');
        var input = document.createElement('textarea');
        var input2 = document.createElement('input');
        var label = document.createElement('label');
        var label2 = document.createElement('label');

        label.textContent = 'Paste CSV-file in the textarea below.';
        label2.textContent = 'CVS separator: ';
        input.style.width = '100%';
        input.style.height = '90%';

        input2.maxLength = '1';
        input2.size = '1';
        input2.defaultValue = ',';

        var import_button = document.createElement('button');
        import_button.textContent = 'Import HITs';
        import_button.addEventListener("click", import_dialog_close_func(true, input, input2), false);
        import_button.style.margin = '5px';
        var cancel_button = document.createElement('button');
        cancel_button.textContent = 'Cancel';
        cancel_button.addEventListener("click", import_dialog_close_func(false, input, input2), false);
        cancel_button.style.margin = '5px';

        IMPORT_DIALOG.appendChild(label);
        IMPORT_DIALOG.appendChild(document.createElement('br'));
        IMPORT_DIALOG.appendChild(label2);
        IMPORT_DIALOG.appendChild(input2);
        IMPORT_DIALOG.appendChild(document.createElement('br'));
        IMPORT_DIALOG.appendChild(input);
        IMPORT_DIALOG.appendChild(document.createElement('br'));
        IMPORT_DIALOG.appendChild(cancel_button);
        IMPORT_DIALOG.appendChild(import_button);
        document.body.appendChild(IMPORT_DIALOG);
    }
    else
    {
        IMPORT_DIALOG.style.display = 'block';
    }
}


/*
 * CSVToArray() function is taken from:
 *
 * 	Blog Entry:
 * 	Ask Ben: Parsing CSV Strings With Javascript Exec() Regular Expression Command
 *	
 *	 Author:
 *	 Ben Nadel / Kinky Solutions
 *	
 *	 Link:
 *	 http://www.bennadel.com/index.cfm?event=blog.view&id=1504
 *	
 *	 Date Posted:
 *	 Feb 19, 2009 at 10:03 AM
 */
// This will parse a delimited string into an array of
// arrays. The default delimiter is the comma, but this
// can be overriden in the second argument.
function CSVToArray( strData, strDelimiter ) {
    // Check to see if the delimiter is defined. If not,
    // then default to comma.
    strDelimiter = (strDelimiter || ",");

    // Create a regular expression to parse the CSV values.
    var objPattern = new RegExp(
        (
            // Delimiters.
            "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +

            // Quoted fields.
            "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +

            // Standard fields.
            "([^\"\\" + strDelimiter + "\\r\\n]*))"
        ),
        "gi"
    );

    // Create an array to hold our data. Give the array
    // a default empty first row.
    var arrData = [[]];

    // Create an array to hold our individual pattern
    // matching groups.
    var arrMatches = null;


    // Keep looping over the regular expression matches
    // until we can no longer find a match.
    while (arrMatches = objPattern.exec( strData )){

        // Get the delimiter that was found.
        var strMatchedDelimiter = arrMatches[ 1 ];

        // Check to see if the given delimiter has a length
        // (is not the start of string) and if it matches
        // field delimiter. If id does not, then we know
        // that this delimiter is a row delimiter.
        if (
            strMatchedDelimiter.length &&
            (strMatchedDelimiter != strDelimiter)
        ){

            // Since we have reached a new row of data,
            // add an empty row to our data array.
            arrData.push( [] );

        }


        // Now that we have our delimiter out of the way,
        // let's check to see which kind of value we
        // captured (quoted or unquoted).
        if (arrMatches[ 2 ]){

            // We found a quoted value. When we capture
            // this value, unescape any double quotes.
            var strMatchedValue = arrMatches[ 2 ].replace(
                new RegExp( "\"\"", "g" ),
                "\""
            );

        } else {

            // We found a non-quoted value.
            var strMatchedValue = arrMatches[ 3 ];

        }


        // Now that we have our value string, let's add
        // it to the data array.
        arrData[ arrData.length - 1 ].push( strMatchedValue );
    }

    // Return the parsed data.
    return( arrData );
}

function import_dialog_close_func(save, input, separator)
{
    return function()
    {
        if (save == true)
        {

            var lines = [];
            var hits = [];
            var dates = [];

            if (input.value.length > 0)
                lines = CSVToArray(input.value, separator.value);

            var errors = 0;
            for (var i = 0; i<lines.length; i++)
            {
                var error = false;
                try {
                    if (lines[i][0] == null || lines[i][0] == 'hitId')
                        continue;

                    if(lines[i][6] == 'Approved - Pending Payment')
                        lines[i][6] = 'Approved&nbsp;- Pending&nbsp;Payment';        

                    if (lines[i].length != 8)
                        error = true;

                    var hit = {
                        hitId         : lines[i][0],
                        date          : convert_date(lines[i][1]),
                        requesterName : lines[i][2],
                        //This line was null in the version I was using. I added it in, giving it the proper format.
                        //This setting is for the links to contact the requester in the status window
                        requesterLink : "https://www.mturk.com/mturk/contact?subject=Regarding+Amazon+Mechanical+Turk+HIT+"+lines[i][0]+"&requesterId="+lines[i][3]+"&requesterName="+lines[i][2].replace(" ","+"), 
                        requesterId   : lines[i][3],
                        title         : lines[i][4],
                        reward        : parseFloat(lines[i][5]),
                        status        : lines[i][6],
                        feedback      : lines[i][7] || "" // If no feedback, put empty string
                    };
                    //This status thing is actually for the Hit Status page (daily overview). It was non-existent with the current version, I added the functionality in here.
                    //This sets up a simple associative array for the initial "date" entry for the hit stats. See below for implementation
                    var status = {
                        date          : hit.date,
                        approved      : (hit.status != "Rejected" ? 1 : 0),
                        earnings      : (hit.status != "Rejected" ? hit.reward : 0),
                        pending       : (hit.status != "Pending" ? 0 : 1),
                        rejected      : (hit.status == "Rejected" ? 1 : 0),
                        submitted     : 1
                    };
                } catch(err) { error = true; }

                if (error == false){
                    hits.push(hit);
                    //Implementation of status stuff. First I see if the object exists in my "dates" array,
                    var index = lookup(hit.date, "date", dates);
                    if (index != -1){
                        //if it does, add each value except date to update it. The values will either be 1 or 0, so just += should give the proper values (and it does based on testing
                        for (var key in dates[index]){
                            if (key != "date")
                                dates[index][key] += status[key];
                        }
                    }
                    else
                        dates.push(status); //if the date doesn't exist in the array, add it as an initial object
                }
                else
                    errors++;
            }
            if (hits.length < 1)
            {
                alert('No HITs found!');
                return;
            }
            else if (confirm('Found ' + hits.length + ' HITs' + (errors>0? ' and ' + errors + (errors==1? ' error' : ' errors') : '') + 
                             '.\nDo not reload this page until import is ready.\n' +
                             'Press Ok to start.') == true)
            {
                HITStorage.disable_inputs();
                HITStorage.update_status_label('Please wait: importing HITs', 'red');
                IMPORT_DIALOG.style.display = 'none';
                input.value = '';
                HITStorage.indexedDB.importHITs(hits);
                //You have to call updateHITstats on a date object:
                //object = { date:"yyyy-mm-dd", (approved|pending|rejected):int num(Approved|Pending|Rejected), earnings:float totalEarningsForDay, submitted:int numHitsSubmittedThatDay }
                //Easiest hack to do so, parse over the dates objects I manipulated above, call update hit stats on each of them.
                for (var i = 0; i < dates.length; i++){ 
                    HITStorage.indexedDB.updateHITstats(dates[i]);
                }
                return;
            }
            else { return; }
        }

        IMPORT_DIALOG.style.display = 'none';
        input.value = '';
    };
}

//simple lookup function for searching I'm reusing.
function lookup (needle, key, haystack) {
    for (var i = 0; i < haystack.length; i++){
        if (haystack[i][key] == needle)
            return i;
    }
    return -1;
}

function get_requester_id(s) {
    var idx = 12 + s.search('requesterId=');
    return s.substr(idx);
}

function show_requester_func(requesterId)
{
    return function()
    {
        HITStorage.indexedDB.showRequester(requesterId);
    };  
}

function search_func(key, index, d1, d2)
{
    d1 = d1 || '';
    d2 = d2 || d1;
    return function()
    {
        HITStorage.indexedDB.getHITs({term: key, index: index, status: '---', from_date: d1, to_date: d2, donut: '', this_day: ''});
    };   
}

function visible_func(element, visible)
{
    return function()
    {
        element.style.visibility = (visible)? 'visible' : 'hidden';
    };   
}

function delete_func()
{
    return function()
    {
        if (confirm('This will remove your local HIT DataBase!\nContinue?'))
        {
            HITStorage.indexedDB.deleteDB();
        }
    };   
}

function import_func()
{
    return function()
    {
        import_dialog();
    };   
}

function note_func(id, label)
{
    return function()
    {
        note = prompt('Note for requesterId \'' + id + '\':', label.textContent);

        if (note == null)
        {
            return;  
        }    

        HITStorage.indexedDB.addNote(id, note);
        label.textContent = note;

        label.style.border = '1px dotted';
        if (note.indexOf('!') >= 0)
            label.style.color = 'red';
        else
            label.style.color = 'black'; 
    };   
}

function block_func(requesterId, title, hitElement)
{
    return function()
    {
        re = prompt('Block HITs from requesterId \'' + requesterId + '\' matching:\n' +
                    '(default matches only exactly same HIT title, leave empty to match all HITS)', '^'
                    + title.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1") + '$');

        if (re == null)
        {
            return;  
        }
        re = new RegExp(re);

        if (!re.test(title)) {
            if (confirm("Your regular expression does not match current HIT title.\nSave it anyway?") == false)
                return;
        }

        HITStorage.indexedDB.addBlock(requesterId, re);
    };   
}

function unblock_func(requesterId, title)
{
    return function()
    {
        var unblock = confirm('Unblocking removes all blocks that match this HITs title and requesterId.');
        if (unblock == true)
        {
            HITStorage.indexedDB.removeBlocks(requesterId, title);
        }
    };  
}

function auto_update_func()
{
    return function()
    {
        var button = document.getElementById('auto_button');    

        if (localStorage['HITDB AUTO UPDATE'] === undefined)
        {
            alert('Enable Hit DataBase Auto Update\nWhen enabled, script will fetch last ' +
                  'statusdetail pages and add them to database when this page is reloaded ' +
                  'and at least two minutes have passed from last update. You still need to ' +
                  'do full update from dashboard every now and then.');
            button.textContent = 'Auto Update is ON';
            button.style.color = 'green';
            localStorage['HITDB AUTO UPDATE'] = 'ON';
        }
        else if (localStorage['HITDB AUTO UPDATE'] == 'ON')
        {
            button.textContent = 'Auto Update is OFF';
            button.style.color = 'red';
            localStorage['HITDB AUTO UPDATE'] = 'OFF';
        }
        else
        {
            button.textContent = 'Auto Update is ON';
            button.style.color = 'green';
            localStorage['HITDB AUTO UPDATE'] = 'ON';
        }
    };   
}

function set_target_func(date)
{
    return function()
    {
        var target = localStorage['TODAYS TARGET'];
        if (target === undefined)
            target = '';
        else
            target = parseFloat(localStorage['TODAYS TARGET']).toFixed(2);
        target = prompt('Set your target:', target);

        if (target == null)
            return;    
        target = parseFloat(target);

        localStorage['TODAYS TARGET'] = target.toFixed(2);
        if (date != null)
            HITStorage.indexedDB.get_todays_projected_earnings(date);
    };   
}

function random_face()
{
    var faces = ['😁','😃','😄','😇','😈','😉','😊','😋','😌','😍','😐','😎','😸','😹','😺','😻'];
    var n = Math.floor((Math.random()*faces.length));
    return '<span style="color: black; font-weight: normal;" title="Featured non-amazonian script ' + ((n>11) ? '... kitten?': 'monkey') + '">' + faces[n] + '</span>';
}

function progress_bar(done, max, full, empty, c1, c2)
{
    max = (max<1)? 1 : max;  
    done = (done<0)? 0 : done;  
    done = (done>max)? max : done;  

    var bar = '<span style="color: ' + (c1||'green') + '">';
    for (var i=0; i<done; i++)
    {
        bar += full || '■';
    }
    bar += '</span><span style="color: ' + (c2||'black') + '">';
    for (var i=done; i<max; i++)
    {
        bar += empty || '⬜';
    }
    bar += '</span>';
    return bar;
}

// convert date to more practical form (MMDDYYYY => YYYY-MM-DD)
function convert_date(date)
{
    if (date.indexOf('-') > 0)
        return date;
    var day   = date.substr(2,2);
    var month = date.substr(0,2);
    var year  = date.substr(4,4);
    return (year + '-' + month + '-' + day);
}

// convert date from YYYY-MM-DD to MMDDYYYY if it isn't already
function convert_iso_date(date)
{
    if (date.indexOf('-') < 0)
        return date;
    var t = date.split('-');
    return t[1] + t[2] + t[0];
}

// Format date for display YYYY-MM-DD, DD/MM/YYYY or DD.MM.YYYY
function display_date(date, format)
{
    if (format === undefined || format == null)
        return date;

    var d = date.split('-');

    if (format == 'little')
    {
        return d[2] + '.' + d[1] + '.' + d[0];
    }
    if (format == 'middle')
    {
        return d[1] + '/' + d[2] + '/' + d[0];
    }
}

HITStorage.indexedDB.create();

// Backup plan
//HITStorage.update_date_format(true);

if (document.location.href.match('https://www.mturk.com/mturk/dashboard'))
{  
    var footer = document.getElementsByClassName('footer_separator')[0];
    if (footer == null)
        return;

    var extra_table = document.createElement('table');
    extra_table.width = '700';
    extra_table.style.boder = '1px solid black';
    extra_table.align = 'center';
    extra_table.cellSpacing = '0px';
    extra_table.cellPadding = '0px';
    var row1 = document.createElement('tr');
    var row2 = document.createElement('tr');
    var td1 = document.createElement('td');
    var content_td = document.createElement('td');
    var whatsthis = document.createElement('a');

    row1.style.height = '25px';
    td1.setAttribute('class', 'white_text_14_bold');
    td1.style.backgroundColor = '#7fb448';//'#7fb4cf';
    td1.style.paddingLeft = '10px';
    td1.innerHTML = 'HIT DataBase' + random_face() + ' ';
    content_td.setAttribute('class', 'container-content');  

    whatsthis.href = 'http://userscripts.org/scripts/show/149548';
    whatsthis.setAttribute('class', 'whatis');
    whatsthis.textContent = '(What\'s this?)';

    extra_table.appendChild(row1);
    row1.appendChild(td1);
    td1.appendChild(whatsthis);
    extra_table.appendChild(row2);
    row2.appendChild(content_td);
    footer.parentNode.insertBefore(extra_table, footer);  

    var my_bar = document.createElement('div');
    var search_button = document.createElement('button');
    var status_select = document.createElement('select');
    var label = document.createElement('label');
    var label2 = document.createElement('label');
    var input = document.createElement('input');
    var donut_select = document.createElement('select');
    var csv_label = document.createElement('label');
    var csv = document.createElement('input');

    var update_button = document.createElement('button');
    var delete_button = document.createElement('button');
    var pending_button = document.createElement('button');
    var overview_button = document.createElement('button');
    var import_button = document.createElement('button');
    var status_button = document.createElement('button');

    var from_input = document.createElement('input');
    var to_input = document.createElement('input');
    var date_label1 = document.createElement('label');
    var date_label2 = document.createElement('label');
    date_label1.textContent = 'from date ';
    date_label2.textContent = ' to ';
    from_input.setAttribute('id', "from_date");
    to_input.setAttribute('id', "to_date");
    to_input.setAttribute('maxlength', "10");
    from_input.setAttribute('maxlength', "10");
    to_input.setAttribute('size', "10");
    from_input.setAttribute('size', "10");
    from_input.title = 'Date format YYYY-MM-DD\nOr leave empty.';
    to_input.title = 'Date format YYYY-MM-DD\nOr leave empty.';

    var donut_options = [];
    donut_options[0] = document.createElement("option");
    donut_options[1] = document.createElement("option");
    donut_options[2] = document.createElement("option");
    donut_options[0].text = "---";
    donut_options[1].text = "Donut Chart HITS";
    donut_options[2].text = "Donut Chart REWARDS";
    donut_options[0].value = "---";
    donut_options[1].value = "HITS";
    donut_options[2].value = "REWARDS";

    var status_options = [];
    status_options[0] = document.createElement("option");
    status_options[1] = document.createElement("option");
    status_options[2] = document.createElement("option");
    status_options[3] = document.createElement("option");
    status_options[4] = document.createElement("option");
    status_options[5] = document.createElement("option");
    status_options[0].text = "Pending Approval";
    status_options[0].style.color = "orange"; 
    status_options[1].text = "Rejected";
    status_options[1].style.color = "red"; 
    status_options[2].text = "Approved - Pending Payment";
    status_options[2].style.color = "green"; 
    status_options[3].text = "Paid";
    status_options[3].style.color = "green"; 
    status_options[4].text = "Paid AND Approved";
    status_options[4].style.color = "green"; 
    status_options[5].text = "ALL";
    status_options[0].value = "Pending Approval";
    status_options[1].value = "Rejected";
    status_options[2].value = "Approved";
    status_options[3].value = "Paid";
    status_options[4].value = "Paid|Approved";
    status_options[5].value = "---";

    search_button.setAttribute('id', "search_button");
    input.setAttribute('id', "search_term");
    status_select.setAttribute('id', "status_select");
    label.setAttribute('id', "status_label");
    donut_select.setAttribute('id', "donut_select");
    delete_button.setAttribute('id', "delete_button");
    update_button.setAttribute('id', "update_button");
    overview_button.setAttribute('id', "overview_button");
    import_button.setAttribute('id', "import_button");
    pending_button.setAttribute('id', "pending_button");
    status_button.setAttribute('id', "status_button");

    my_bar.style.marginLeft = 'auto';
    my_bar.style.marginRight = 'auto';
    my_bar.style.textAlign = 'center';
    label.style.marginLeft = 'auto';
    label.style.marginRight = 'auto';
    label.style.textAlign = 'center';

    var donut = document.createElement('div');
    donut.setAttribute('id', "container");
    donut.style.display = 'none';

    content_td.appendChild(my_bar);
    my_bar.appendChild(delete_button);
    my_bar.appendChild(import_button);
    my_bar.appendChild(update_button);
    my_bar.appendChild(document.createElement("br"));
    my_bar.appendChild(pending_button);
    my_bar.appendChild(overview_button);
    my_bar.appendChild(status_button);
    my_bar.appendChild(document.createElement("br"));
    my_bar.appendChild(donut_select);
    my_bar.appendChild(status_select);
    my_bar.appendChild(label2);
    my_bar.appendChild(input);
    my_bar.appendChild(search_button);
    my_bar.appendChild(document.createElement("br"));
    my_bar.appendChild(date_label1);
    my_bar.appendChild(from_input);
    my_bar.appendChild(date_label2);
    my_bar.appendChild(to_input);
    my_bar.appendChild(csv_label);
    my_bar.appendChild(csv);
    my_bar.appendChild(document.createElement("br"));  
    my_bar.appendChild(label);
    my_bar.appendChild(document.createElement("br"));  
    (footer.parentNode).insertBefore(donut, footer);

    my_bar.style.textAlign = "float";
    search_button.textContent = "Search";
    search_button.title = "Search from local HIT database\nYou can set time limits and export as CSV-file";
    label2.textContent = " HITs matching: ";
    input.value = "";

    label.textContent = "Search powered by non-amazonian script monkeys";

    for (var i=0; i<status_options.length; i++)
        status_select.options.add(status_options[i]);
    for (var i=0; i<donut_options.length; i++)
        donut_select.options.add(donut_options[i]);

    update_button.title = "Fetch status pages and copy HITs to local indexed database.\nFirst time may take several minutes!";
    update_button.textContent = "Update database";
    update_button.style.color = 'green';
    update_button.style.margin = '5px 5px 5px 5x';
    delete_button.textContent = "Delete database";
    delete_button.style.color = 'red';
    delete_button.style.margin = '5px 5px 5px 5px';
    delete_button.title = "Delete Local DataBase!";
    import_button.textContent = "Import";
    import_button.style.margin = '5px 5px 5px 5px';
    import_button.title = "Import HIT data from exported CSV-file";
    overview_button.textContent = "Requester Overview";
    overview_button.style.margin = '0px 5px 5px 5px';
    overview_button.title = "Summary of all requesters you have worked for\nYou can set time limit and export as CSV-file";
    pending_button.textContent = "Pending Overview";
    pending_button.style.margin = '0px 5px 5px 5px';
    pending_button.title = "Summary of all pending HITs\nYou can export as CSV-file";
    status_button.textContent = "Daily Overview";
    status_button.style.margin = '0px 5px 5px 5px';
    status_button.title = "Summary of each day you have worked on MTurk\nYou can set time limit and export as CSV-file";

    pending_button.addEventListener("click", HITStorage.show_pendings, false);
    overview_button.addEventListener("click", HITStorage.show_overview, false);
    search_button.addEventListener("click", HITStorage.start_search, false);
    update_button.addEventListener("click", HITStorage.update_database, false);
    delete_button.addEventListener("click", delete_func(), false);
    import_button.addEventListener("click", import_func(), false);
    status_button.addEventListener("click", HITStorage.show_status, false);

    csv_label.textContent = 'export CSV';
    csv_label.title = 'Export results as comma-separated values';
    csv_label.style.verticalAlign = 'middle';
    csv_label.style.marginLeft = '50px';
    csv.title = 'Export results as comma-separated values';
    csv.setAttribute('type', 'checkbox');
    csv.setAttribute('id', 'export_csv');
    csv.style.verticalAlign = 'middle';

    from_input.value = '';
    to_input.value = '';

    var table = document.getElementById('bonus_earnings_amount');
    if (table != null)
    {
        table = table.parentNode.parentNode.parentNode.parentNode;
        var pending_tr = document.createElement('tr');
        var pending_td1 = document.createElement('td');
        var pending_td2 = document.createElement('td');
        var today_tr = document.createElement('tr');
        var today_td1 = document.createElement('td');
        var today_td2 = document.createElement('td');

        pending_tr.setAttribute('class', 'even');
        pending_td1.setAttribute('class', 'metrics-table-first-value');
        pending_td1.setAttribute('id', 'pending_earnings_header');
        pending_td2.setAttribute('id', 'pending_earnings_value');
        today_tr.setAttribute('class', 'odd');
        today_td1.setAttribute('class', 'metrics-table-first-value');
        today_td1.setAttribute('id', 'projected_earnings_header');
        today_td2.setAttribute('id', 'projected_earnings_value');

        pending_tr.appendChild(pending_td1);
        pending_tr.appendChild(pending_td2);
        today_tr.appendChild(today_td1);
        today_tr.appendChild(today_td2);
        table.appendChild(pending_tr);
        table.appendChild(today_tr);

        pending_td1.style.borderTop = '1px dotted darkgrey';
        pending_td2.style.borderTop = '1px dotted darkgrey';
        today_td1.style.borderBottom = '1px dotted darkgrey';
        today_td2.style.borderBottom = '1px dotted darkgrey';

        today_td1.title = 'This value can be inaccurate if HITDB has not been updated recently';
        pending_td1.title = 'This value can be inaccurate if HITDB has not been updated recently';

        if (localStorage['HITDB UPDATED'] === undefined)
            pending_td1.textContent = 'Pending earnings';
        else
            pending_td1.textContent = 'Pending earnings (HITDB updated: ' + localStorage['HITDB UPDATED'] + ')'; 
        today_td1.innerHTML = 'Projected earnings for today &nbsp;&nbsp;';
        today_td2.textContent = 'ಠ_ಠ';
        pending_td2.textContent = 'ಠ_ಠ';


        var e = document.getElementById('user_activities.date_column_header.tooltip').parentNode.parentNode.childNodes[2].childNodes[1].childNodes[1];
        var today = null;
        if (e != null && e.textContent.trim() == 'Today') {
            today = convert_date(e.href.slice(-8));
            HITStorage.indexedDB.get_todays_projected_earnings(today);
        }
        HITStorage.indexedDB.get_pending_approvals();
        HITStorage.indexedDB.get_pending_payments();

        var target = document.createElement('span');
        target.setAttribute('id', 'my_target');
        target.textContent = 'click here to set your target';
        target.style.fontSize = 'small';
        target.style.color = 'blue';
        today_td1.appendChild(target);
        target.addEventListener("click", set_target_func(today), false);
    }
}
else if (document.location.href.match('https://www.mturk.com/mturk/preview'))
{
    var table = document.getElementById('requester.tooltip');
    if (table == null)
        return;
    table = table.parentNode.parentNode.parentNode;
    var title = table.parentNode.parentNode.parentNode.parentNode.getElementsByTagName('div')[0].textContent.trim();

    var extra_row = document.createElement('tr');
    var td_1 = document.createElement('td');
    var td_2 = document.createElement('td');

    var requesterId = document.getElementsByName('requesterId')[0].value;
    var auto_approve = parseInt(document.getElementsByName('hitAutoAppDelayInSeconds')[0].value);

    var buttons = [];
    var b = ['Requester', 'HIT Title'];
    for (var i=0; i<b.length; i++)
    {
        buttons[i] = document.createElement('button');
        buttons[i].textContent = b[i];
        buttons[i].id = b[i] + 'Button' + i;
        buttons[i].style.fontSize = '10px';
        buttons[i].style.height = '18px';
        buttons[i].style.width = '80px';
        buttons[i].style.border = '1px solid';
        buttons[i].style.paddingLeft = '3px';
        buttons[i].style.paddingRight = '3px';
        buttons[i].style.backgroundColor = 'lightgrey';
        buttons[i].setAttribute('form', 'NOThitForm');
        td_1.appendChild(buttons[i]);
    }
    buttons[0].title = 'Search requester ' + requesterId + ' from HIT database';
    buttons[1].title = 'Search title \'' + title + '\' from HIT database';

    HITStorage.indexedDB.colorRequesterButton(requesterId, buttons[0]);
    HITStorage.indexedDB.colorTitleButton(title, buttons[1]);
    buttons[0].addEventListener("click", search_func(requesterId, 'requesterId'), false);
    buttons[1].addEventListener("click", search_func(title, 'title'), false);

    td_2.innerHTML = '<span class="capsule_field_title">Auto-Approval:</span>&nbsp&nbsp' + HITStorage.formatTime(auto_approve*1000) + '';
    td_1.colSpan = '3';
    td_2.colSpan = '8';

    extra_row.appendChild(td_1);
    extra_row.appendChild(td_2);
    table.appendChild(extra_row);
}
else
{
    for (var item=0; item<10; item++) {
        var tooltip = document.getElementById('requester.tooltip--' + item);
        if (tooltip == null)
            break; // no need to continue
        var titleElement = document.getElementById('capsule' + item + '-0');
        var emptySpace = tooltip.parentNode.parentNode.parentNode.parentNode.parentNode;

        var hitItem = tooltip.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode;//.parentNode;

        var requesterLabel = tooltip.parentNode;
        var requesterId = tooltip.parentNode.parentNode.getElementsByTagName('a');
        var title = titleElement.textContent.trim();

        requesterId = get_requester_id(requesterId[1].href);

        var buttons = [];
        var row = document.createElement('tr');
        var div = document.createElement('div');
        emptySpace.appendChild(row);
        row.appendChild(div);

        /* Turkopticon link next to requester name */
        //to_link = document.createElement('a');
        //to_link.textContent = ' TO ';
        //to_link.href = 'http://turkopticon.differenceengines.com/' + requesterId;
        //to_link.target = '_blank';
        //to_link.title = requesterId + ' on Turkopticon';
        //tooltip.parentNode.parentNode.appendChild(to_link);
        /*-----------------------------------------*/

        HITStorage.indexedDB.blockHITS(requesterId, title, hitItem, titleElement);

        var b = ['R', 'T', 'N', 'B'];
        for (var i=0; i<b.length; i++)
        {
            buttons[i] = document.createElement('button');
            buttons[i].textContent = b[i];
            buttons[i].id = b[i] + 'Button' + i;
            buttons[i].style.height = '18px';
            buttons[i].style.fontSize = '10px';
            buttons[i].style.border = '1px solid';
            buttons[i].style.paddingLeft = '3px';
            buttons[i].style.paddingRight = '3px';
            buttons[i].style.backgroundColor = 'lightgrey';
            div.appendChild(buttons[i]);
        }
        buttons[0].title = 'Search requester ' + requesterId + ' from HIT database';
        buttons[1].title = 'Search title \'' + title + '\' from HIT database';
        buttons[2].title = 'Add a requester note';
        buttons[3].title = '"Block" requester';

        var notelabel = document.createElement('label');
        notelabel.textContent = '';
        notelabel.id = b[i] + 'notelabel' + item;
        notelabel.style.height = '18px';
        notelabel.style.fontSize = '10px';
        notelabel.style.marginLeft = '10px';
        notelabel.style.padding = '1px';
        notelabel.style.backgroundColor = 'transparent';
        HITStorage.indexedDB.updateNoteButton(requesterId, notelabel);
        div.appendChild(notelabel);

        HITStorage.indexedDB.colorRequesterButton(requesterId, buttons[0]);
        HITStorage.indexedDB.colorTitleButton(title, buttons[1]);
        buttons[0].addEventListener("click", search_func(requesterId, 'requesterId'), false);
        buttons[1].addEventListener("click", search_func(title, 'title'), false);
        buttons[2].addEventListener("click", note_func(requesterId, notelabel), false);
        buttons[3].addEventListener("click", block_func(requesterId, title, hitItem), false);

        div.style.margin = "0px";

        buttons[2].style.visibility = "hidden"; // "visible"
        buttons[2].parentNode.addEventListener("mouseover", visible_func(buttons[2], true), false);
        buttons[2].parentNode.addEventListener("mouseout", visible_func(buttons[2], false), false);
        buttons[2].addEventListener("mouseout", visible_func(buttons[2], false), false);
        buttons[3].style.visibility = "hidden"; // "visible"
        buttons[3].parentNode.addEventListener("mouseover", visible_func(buttons[3], true), false);
        buttons[3].parentNode.addEventListener("mouseout", visible_func(buttons[3], false), false);
        buttons[3].addEventListener("mouseout", visible_func(buttons[3], false), false);
    }

    var auto_button = document.createElement('button');
    auto_button.setAttribute('id', 'auto_button');
    auto_button.title = 'HIT DataBase Auto Update\nAutomagically update newest HITs to database when reloading this page';

    if (localStorage['HITDB AUTO UPDATE'] === undefined)
    {
        auto_button.textContent = 'Auto update ?';
        auto_button.style.color = 'red';
    }
    else if (localStorage['HITDB AUTO UPDATE'] == 'ON')
    {
        auto_button.textContent = 'Auto Update is ON';
        auto_button.style.color = 'green';
    }
    else
    {
        auto_button.textContent = 'Auto Update is OFF';
        auto_button.style.color = 'red';
    }

    //var element = document.body.childNodes[13].childNodes[3].childNodes[1].childNodes[0].childNodes[5];
    var element = document.getElementsByName("/sort")[0];
    //element.insertBefore(auto_button, element.firstChild);
    element.parentNode.insertBefore(auto_button, element.nextSibling);
    auto_button.addEventListener("click", auto_update_func(), false);

    setTimeout(HITStorage.getLatestHITs, 100);
}