MTurk HIT DataBase

Extended ability to search HITs you have worked on and other useful tools (CSV export/import, requester notes, requester block, pending/projected earnings)

Verzia zo dňa 03.06.2015. Pozri najnovšiu verziu.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==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.5
// @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;

if (!localStorage["20150602fix"])
{
    alert("ATTENTION!!!! Your hit database may not be functioning correctly. You will need to convert it, due to issues introduced by some code changes by mturk staff. This will involve a few steps. Please visit http://tinyurl.com/q58mxr6 and follow the instructions to convert. This message will disappear once the hitDB has been properly converted.");
    if (!localStorage["20150602alert"])
        localStorage["20150602alert"] = 1;
    else{
        var alertCount = parseInt(localStorage["20150602alert"]);
        localStorage["20150602alert"] = alertCount+1;
    }
    if (parseInt(localStorage["20150602alert"]) == 3)
    {
        alert("Alert has displayed 3 times, alert will not display anymore. Please convert your database if you have not done so already.");
        localStorage["20150602fix"] = 1;
    }
}

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('lnk_show_earnings_details');
    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);
}