// ==UserScript==
// @name           ProsperTools
// @author         WealthBoy
// @namespace      http://wealthboy.com
// @description    Gets the estimated return statistics on listing and search pages, performs auto-login, displays additional revolving credit info, and will remove results from searches that do not match the investment preferences.
// @include        http://www.prosper.com/*
// @include        https://www.prosper.com/*
// ==/UserScript==

// unique method borrowed from: http://www.jslab.dk/library.Array.unique.php
Array.prototype.unique =
  function() {
    var a = [];
    var l = this.length;
    for(var i=0; i<l; i++) {
      for(var j=i+1; j<l; j++) {
        // If this[i] is found later in the array
        if (this[i] === this[j])
          j = ++i;
      }
      a.push(this[i]);
    }
    return a;
  };

// Function to add commas to a number
// Borrowed from http://geekswithblogs.net/shahed/archive/2006/11/27/99191.aspx
function addCommas( strValue ) {
/************************************************
DESCRIPTION: Inserts commas into numeric string.

PARAMETERS:
  strValue - source string containing commas.

RETURNS: String modified with comma grouping if
  source was all numeric, otherwise source is
  returned.

REMARKS: Used with integers or numbers with
  2 or less decimal places.
*************************************************/
  var objRegExp  = new RegExp('(-?[0-9]+)([0-9]{3})');
  strValue = String(strValue);

    //check for match to search criteria
    while(objRegExp.test(strValue)) {
       //replace original string with first group match,
       //a comma, then second group match
       strValue = strValue.replace(objRegExp, '$1,$2');
    }
  return strValue;
}

// This function parses the estimates from the HTML of the bid page
function parseBidPage(bidpagehtml, requestID) {
  estimations = bidpagehtml.match(/-?\d{1,3}\.\d{2}%<\/td>/g).join().replace(/\-?(\d{1,3}\.\d{2})%<\/td>,?/g, "$1\n").split("\n");
  estimations = {loss: parseFloat(estimations[0]), adjustment: parseFloat(estimations[1]), fees: parseFloat(estimations[2]), listingID: requestID};

  // Get the current max rate
  currentrate = bidpagehtml.match(/"lblCurrMinRateField">\d{1,3}\.\d{2}%<\/span> or less/).join().replace(/"lblCurrMinRateField">(\d{1,3}\.\d{2})%<\/span> or less/, "$1");
  estimations.currentrate = Math.round(parseFloat(currentrate)*100)/100;

  // Calculate the current estimated return
  estimations.estimatedreturn = Math.round((estimations.currentrate - estimations.loss - estimations.adjustment - estimations.fees)*100)/100;
  return estimations;
}

// This function will retrieve the estimate info and return an object containing the estimate values to a callback function
function getEstimates(requestID, callback) {
  // Look up the estimations
  GM_xmlhttpRequest({
    method: "GET",
    url: "https://www.prosper.com/secure/lend/place_bid.aspx?listingID=" + requestID,
    onload: function (response) {
      callback(parseBidPage(response.responseText, requestID));
    }
  });
}

// This function will prompt the user for login information
function setLoginInfo() {
  if (GM_getValue("username") != undefined) {
    GM_setValue("username", prompt("Please enter your email address.", GM_getValue("username")));
    GM_setValue("password", prompt("Please enter your password.", GM_getValue("password")));
  } else {
    GM_setValue("username", prompt("Please enter your email address.", ""));
    GM_setValue("password", prompt("Please enter your password.", ""));
  }
  GM_setValue("bypasslogin", false);
}

// Register the Greasemonkey command to disable automatic logins
GM_registerMenuCommand("Set Username/Password", setLoginInfo);

// Register the Greasemonkey command to provide the login information
GM_registerMenuCommand("Disable Automatic Login", function() {
  GM_setValue("bypasslogin", confirm("Disable the automatic login?"));
  if (GM_getValue("bypasslogin")) {
    GM_setValue("username", "");
    GM_setValue("password", "");
  }
});

// Register the Greasemonkey command to set investment preferences.
GM_registerMenuCommand("Set Investment Preferences", function() {
  if (GM_getValue("minrate") != undefined) {
    GM_setValue("minrate", prompt("Please enter minimum return.", GM_getValue("minrate")));
    GM_setValue("bidamount", prompt("Please enter the amount you want on all bids.", GM_getValue("bidamount")));
    GM_setValue("maxloss", prompt("Please enter the maximum loss.", GM_getValue("maxloss")));
  } else {
    GM_setValue("minrate", prompt("Please enter minimum return.", ""));
    GM_setValue("bidamount", prompt("Please enter the amount you want on all bids.", ""));
    GM_setValue("maxloss", prompt("Please enter the maximum loss.", ""));
  }
});

// If we're not logged in and we're trying to do something other than log in, nothing is going to work
if (document.getElementById("ctlLoginStatus_hlSignIn") != undefined && document.location.href.search("login.aspx") == -1)
  return;

// If we're trying to log in, perform the auto-login
if (document.location.href.search("login.aspx") != -1) {
  // Don't do anything if we're supposed to bypass the auto-login
  if (GM_getValue("bypasslogin", true))
    return null;

  // Prompt the user for login info if we don't have it yet
  if (GM_getValue('username') == undefined || GM_getValue('username') == "")
    setLoginInfo();

  // Perform the login
  document.getElementById('cmpLogin_tbEmail').value = GM_getValue('username');
  document.getElementById('cmpLogin_tbPwd').value = GM_getValue('password');
  document.getElementById('cmpLogin_btnSubmit').click();
  return;
}

// If we're on a listing page, load the estimations
if (document.location.href.search("listing.aspx") != -1) {
  // Calculate the total available credit
  revolvingbalance = document.body.innerHTML.match(/cmpCreditInformation_lblRevolvingCreditBalanceValue">\$[0-9,]*<\/span>/).join().replace(/cmpCreditInformation_lblRevolvingCreditBalanceValue">\$([0-9,]*)<\/span>/, "$1").replace(/,/g, "");
  revolvingbalance = parseFloat(revolvingbalance);
  utilization = document.body.innerHTML.match(/cmpCreditInformation_lblBankcardUtilizationValue">[0-9,]*%<\/span>/).join().replace(/cmpCreditInformation_lblBankcardUtilizationValue">([0-9,]*)%<\/span>/, "$1").replace(/,/g, "");
  utilization = parseFloat(utilization)/100;
  if (utilization != 0) {
    totalcredit = Math.round(revolvingbalance / utilization / 100)*100;
    availablecredit = Math.round((totalcredit - revolvingbalance)/100)*100;
    totalcredit = "$" + addCommas(totalcredit);
    availablecredit = "$" + addCommas(availablecredit);
  }
  else {
    totalcredit = 'Indeterminate';
    availablecredit = 'Indeterminate';
  }

  // Add the total credit info
  totalcreditnode = document.createElement('tr');
  document.getElementById("cmpCreditInformation_lblRevolvingCreditBalanceValue").parentNode.parentNode.parentNode.insertBefore(totalcreditnode, document.getElementById("cmpCreditInformation_lblRevolvingCreditBalanceValue").parentNode.parentNode);
  totalcreditnode.style.backgroundColor = "rgb(238, 238, 238)";
  totalcreditnode.innerHTML = '<td></td><td></td><td class="list_body_label small" style="padding:2px 4px;" nowrap="nowrap">Total revolving credit:</td><td class="list_body_field small separator_right" style="padding:2px 4px;">' + totalcredit + '</td><td></td><td></td>';

  // Add the available credit info
  availablecreditnode = document.createElement('tr');
  document.getElementById("cmpCreditInformation_lblRevolvingCreditBalanceValue").parentNode.parentNode.parentNode.insertBefore(availablecreditnode, document.getElementById("cmpCreditInformation_lblRevolvingCreditBalanceValue").parentNode.parentNode);
  availablecreditnode.style.backgroundColor = "rgb(238, 238, 238)";
  availablecreditnode.innerHTML = '<td></td><td></td><td class="list_body_label small" style="padding:2px 4px;" nowrap="nowrap">Available credit:</td><td class="list_body_field small separator_right" style="padding:2px 4px;">' + availablecredit + '</td><td></td><td></td>';

  // Get the listing ID for the current listing
  listingID = document.location.href.replace(/.*listingID=(.*)/, "$1");

  // Get and display the estimates
  getEstimates(listingID, function(estimations) {
    // Find the location for the results
    tablebody = document.getElementById('ctrlListingSummary_tdUploadedImage').nextSibling.nextSibling.childNodes[2].childNodes[1];

    // Display the estimates
    tablebody.innerHTML += '<tr><td class="list_body_label" nowrap="nowrap">Estimated Loss:</td><td class="list_body_field" nowrap="nowrap">-' + estimations.loss + '%<td></tr>';
    tablebody.innerHTML += '<tr><td class="list_body_label" nowrap="nowrap">Adjustment:</td><td class="list_body_field" nowrap="nowrap">-' + estimations.adjustment + '%<td></tr>';
    tablebody.innerHTML += '<tr><td class="list_body_label" nowrap="nowrap">Fees:</td><td class="list_body_field" nowrap="nowrap">-' + estimations.fees + '%<td></tr>';

    // Display the estimated return calculation
    document.getElementById('ctrlListingSummary_ctrlCurRate_l').innerHTML += '<br>Est. Return: ' + estimations.estimatedreturn + '%';
  });

  return;
}

// If we're on a search page or watch list page, load the estimations
if (document.location.href.search("search_results.aspx") != -1 || document.location.href.search("watched_listings.aspx") != -1) {
  // Build an array with all of the listing IDs
  listings = document.body.innerHTML.match(/http:\/\/www\.prosper\.com\/lend\/listing\.aspx\?listingID=(\d{6})"/g).join();
  listings = listings.replace(/http:\/\/www\.prosper\.com\/lend\/listing\.aspx\?listingID=(\d{6})"/g, "$1");
  listings = listings.split(",").unique();

  // Change the result count display
  document.getElementById("ctrlListingSearchResultDataGrid_lblNumSearchResults").innerHTML = "0";
  document.getElementById("ctrlListingSearchResultDataGrid_cmpBottomNavBar_lblRecordCount").innerHTML = "0";
  document.getElementById("ctrlListingSearchResultDataGrid_lblSearchParams").innerHTML = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Loading estimates and filtering results. (0% complete)";

  // Create the element for the total listing count
  listingcount = document.createElement('div');
  document.getElementById("ctrlListingSearchResultDataGrid_lblSearchParams").appendChild(listingcount);
  listingcount.id = "prosper_tools_listingcount";
  listingcount.style.display = "none";
  listingcount.innerHTML = listings.length;

  // Create the element for tracking the # of listings reviewed
  completioncount = document.createElement('div');
  document.getElementById("ctrlListingSearchResultDataGrid_lblSearchParams").appendChild(completioncount);
  completioncount.id = "prosper_tools_completioncount";
  completioncount.style.display = "none";
  completioncount.innerHTML = 0;

  // Loop through all of the listing IDs
  for (y = 0; y < listings.length; y++) {
    // Load the estimate page and display the results
    getEstimates(listings[y], function(estimations) {
      // Update the % complete
      totalcount = parseFloat(document.getElementById("prosper_tools_listingcount").innerHTML);
      completedcount = parseFloat(document.getElementById("prosper_tools_completioncount").innerHTML)+1;
      document.getElementById("prosper_tools_completioncount").innerHTML = completedcount;
      if (completedcount != totalcount)
        document.getElementById("ctrlListingSearchResultDataGrid_lblSearchParams").innerHTML = document.getElementById("ctrlListingSearchResultDataGrid_lblSearchParams").innerHTML.replace(/\(([\d])*% complete/, "(" + Math.round(100*completedcount/totalcount) + "% complete");
      else
        document.getElementById("ctrlListingSearchResultDataGrid_lblSearchParams").innerHTML = "";

      // Select the proper results location
      if (document.location.href.search("search_results.aspx") != -1)
        resultRows = document.getElementById('ctrlListingSearchResultDataGrid_ListingDataGrid').childNodes[1].childNodes;
      if (document.location.href.search("watched_listings.aspx") != -1)
        resultRows = document.getElementById('cmpWatchedListings_grid').childNodes[1].childNodes;

      // Loop through the results and find the listing
      for (x = 0; x < resultRows.length; x++) {
        row = resultRows[x];
        try {
          if (row.innerHTML.search('listingID=' + estimations.listingID) != -1) {
            // If we don't meet the minimum return, delete the row
            if (GM_getValue("minrate") != undefined && GM_getValue("minrate") != "") {
              if (parseFloat(GM_getValue("minrate")) > estimations.estimatedreturn) {
                row.parentNode.removeChild(row);
                return;
              }
            }
            // if we exceed the maximum allowed loss, delete the row
            if (GM_getValue("maxloss") != undefined && GM_getValue("maxloss") != "") {
              if (parseFloat(GM_getValue("maxloss")) < estimations.loss) {
                row.parentNode.removeChild(row);
                return;
              }
            }
            // Display the result
            row.childNodes[3].innerHTML += '<div class="small"><b>' + estimations.estimatedreturn + '%</b> est. return</div>';
            row.childNodes[3].innerHTML += '<div class="small"><b>-' + estimations.loss + '%</b> est. loss</div>';

            // Update the listing count
            document.getElementById("ctrlListingSearchResultDataGrid_lblNumSearchResults").innerHTML = parseFloat(document.getElementById("ctrlListingSearchResultDataGrid_lblNumSearchResults").innerHTML)+1;
            document.getElementById("ctrlListingSearchResultDataGrid_cmpBottomNavBar_lblRecordCount").innerHTML = parseFloat(document.getElementById("ctrlListingSearchResultDataGrid_cmpBottomNavBar_lblRecordCount").innerHTML)+1;
            return;
          }
        }
        catch (err) {
          // Do nothing when we hit a row that breaks the code
        }
      }
    });
  }
  return;
}

// If we're on the bid page, fill out the form
if (document.location.href.search("place_bid.aspx") != -1) {
  if (GM_getValue("minrate") == undefined || GM_getValue("minrate") == "")
    return;
  listingID = document.location.href.replace(/https:\/\/www\.prosper\.com\/secure\/lend\/place_bid\.aspx\?listingID=(.*)/, "$1");
  estimations = parseBidPage(document.body.innerHTML, listingID);
  document.getElementById("tbAmount").value = GM_getValue("bidamount");
  document.getElementById("tbMinRate").value = Math.round((parseFloat(GM_getValue("minrate")) + estimations.loss + estimations.adjustment + estimations.fees)*100)/100;
  document.getElementById("tbMinRate").focus();
  document.getElementById("tbAmount").focus();
}