// ==UserScript==
// @name           Kampyle Table 0,1
// @namespace      Kampyle
// @description    Expand table to add new elements
// @include        https://www.google.com/analytics/reporting/top_content?*
// @include        https://www.google.com/analytics/reporting/content_drilldown?*
// @include        https://www.google.com/analytics/reporting/entrances?*
// @include        https://www.google.com/analytics/reporting/exits?*
// @include        https://www.google.com/analytics/reporting/content_title_detail?*
// ==/UserScript==

login = '';
private_key = '';

encrypted_private_key = SHA256(private_key);
wsdl_url = 'http://www.kampyle.com/';

//available pages: Content by Title, Content Drilldown, Top Landing Pages, Top Exits

//Begin Date Functionality

//Cleanse out google date reference in case there is a refresh
document.getElementById('f_primaryBegin').value = "";
document.getElementById('f_primaryEnd').value = "";


//End Date Functionality



//create the tab
var tag_element;    
tag_html = '<a href="#" onclick="tabView.a.TabSelected=1; return false;"><img src="http://media.juiceanalytics.com/images/1_Yahoo_logo.gif" width="40" height="11"></a>';
tag_element = window.document.createElement ("LI");
tag_element.innerHTML = tag_html;
tag_element.id = 'tab_1';  //for google internal code
tag_element.addEventListener("click", dostuff, true);  //launch dostuff function when tab is clicked

//if the first tab is clicked again, unshade the second tab that we created
other_tag = document.getElementById('tab_0');
other_tag.addEventListener("click", unshade, true);

//make the tag appear
html_insert_after(other_tag,tag_element);


                
function dostuff()
{   //this is the main worker that does stuff when the tab is clicked
    //calculate date
    //check to see if the java date tool has been activated; it uses that date if it has and the date from the url otherwise
    var date1 = new RegExp(/(\d{8})/);
    var date2 = new RegExp(/(\d{8})&/);
    urlstartdate = date1.exec(window.location.href)[1];
    urlenddate = date2.exec(window.location.href)[1];
    var changeddate1 = document.getElementById('f_primaryBegin').value;
    var changeddate2 = document.getElementById('f_primaryEnd').value;
    if (changeddate1)  
    {
        startdate = date_to_string(changeddate1);
        enddate = date_to_string(changeddate2);
    }
    else
    {
        startdate = add_dashes_to_date_string(urlstartdate);
        enddate = add_dashes_to_date_string(urlenddate);
    }
    
    //end calculate date
    
    //figure out name of the site in order to pass to API
    elem_drop_down = document.getElementById('profile')
    site_name = "http://" + elem_drop_down.options[elem_drop_down.selectedIndex].innerHTML
    //end figure out name of site
    
    tag_element.className = "current";  //internal google marker to designate which tab is 'current' and highlighed
    other_tag.className = "";  //remove highliting for sort
    
    clean_columns(6)  //add columns or remove columns as necessary
    
    theTable = document.getElementById('f_table_data');  //point at the table
    for(a=0;a<theTable.childNodes[3].rows.length;a++)  //remove highlighting in all cells
    {
        for(b=2;b<theTable.childNodes[3].rows[a].cells.length;b++)
        {
           theTable.childNodes[3].rows[a].cells[b].className = "";
        }
    }
        
    for(b=1;b<theTable.childNodes[1].rows[0].cells.length;b++)  //remove highlighting in header row
    {
       theTable.childNodes[1].rows[0].cells[b].className = "";
    }

    theTable.childNodes[1].rows[0].cells[1].innerHTML = 'Avg Grade';  //change column headers
    theTable.childNodes[1].rows[0].cells[2].innerHTML = 'Feedback Groups';
    theTable.childNodes[1].rows[0].cells[3].innerHTML = 'Feedbacks';
    theTable.childNodes[1].rows[0].cells[4].innerHTML = 'Most Reported Feedback';

    //clean should-be-empty columns and populate rows
    for(a=0;a<theTable.childNodes[3].rows.length;a++)  //empty last two columns and populate the cells
    {
        populate_row(site_name + theTable.childNodes[3].rows[a].cells[1].firstChild.firstChild.textContent, startdate, enddate, a);
        //populate_row(theTable.childNodes[3].rows[a].cells[1].firstChild.firstChild.firstChild, startdate, enddate, a);
    }

    

}

function clean_columns(n)
{   //this function adjusts the number of columns on the page to n
    locTable = document.getElementById('f_table_data');
    for(a=0;a<locTable.childNodes[3].rows.length;a++)  //empty last two columns and populate the cells
    {
        for(b=8;b>=n;b--) //empty any columns >= 6
        {
            //locTable.childNodes[3].rows[a].cells[b].innerHTML = "";
            if (locTable.childNodes[3].rows[a].cells[b])
            {locTable.childNodes[3].rows[a].deleteCell(b);}
        }
    }
    for(b=7;b>=(n-1);b--) //empty any header columns >= 5
    {
        var adSidebar = locTable.childNodes[1].rows[0].cells[b];
        if (adSidebar) {
            adSidebar.parentNode.removeChild(adSidebar);
        }
    }
    //add in any missing columns until there are 0-5
    for(a=0;a<locTable.childNodes[3].rows.length;a++)
    {
        for(b=2;b<n;b++) //empty any columns >= 6
        {
            //locTable.childNodes[3].rows[a].cells[b].innerHTML = "";
            if (locTable.childNodes[3].rows[a].cells[b])
            {locTable.childNodes[3].rows[a].deleteCell(b);}
            
            locTable.childNodes[3].rows[a].insertCell(b);
        }   
    }
    
    for(b=2;b<n-1;b++) //empty any header columns >= 5
    {
        if (!locTable.childNodes[1].rows[0].cells[b])
        {
            test = locTable.childNodes[1].rows[0].cells[b-1]
            test.parentNode.insertBefore(locTable.childNodes[1].rows[0].cells[1].cloneNode(1),test.nextSibling)
        }
    }
}

function row_callBack(r, b, row)
{   //this is a listener function that is called whenever the call to the server 
    //finishes running and returns us some data to put into the table
    result = b.getElementsByTagName('getPagesInfoResponse');
    theTable = document.getElementById('f_table_data');
    
    theTable.childNodes[3].rows[row].cells[2].textContent = result[0].getElementsByTagName('avg_grade')[0].textContent;
    theTable.childNodes[3].rows[row].cells[3].textContent = result[0].getElementsByTagName('fb_groups')[0].textContent;
    theTable.childNodes[3].rows[row].cells[4].textContent = result[0].getElementsByTagName('unread_fbs')[0].textContent;
    theTable.childNodes[3].rows[row].cells[5].textContent = result[0].getElementsByTagName('most_reported_feedback_type')[0].textContent;
    
}

function populate_row(page_urls, start_date, end_date, row)
{   //this function is a wrapper for the SOAP client
    //it is run every time an individual row needs to be populated
    var pl = new SOAPClientParameters();
    pl.add("login", login);
    pl.add("encrypted_private_key", encrypted_private_key);
    pl.add("page_urls", Array(page_urls += ''));
    pl.add("start_date", start_date);
    pl.add("end_date", end_date);

    SOAPClient.invoke(wsdl_url, "getPagesInfo", pl, false, row_callBack, row);
}

function html_insert_it(element, new_element) {
    //shortcut function to insert html before an element
  element.parentNode.insertBefore(new_element, element);
};

function html_insert_after(element, new_element) {
    //shortcut function to insert html after an element
  element.parentNode.insertBefore(new_element, element.nextSibling);
};

function unshade()
{   //shortcut function to unshade an element
        tag_element.className = "";  
}

function date_to_string(datum)
{   
    //convert mm/dd/yyyy to yyyy-mm-dd
    var re = new RegExp(/(\d{2})\/(\d{2})\/(\d{4})/);
    vals = re.exec(datum);
    return (vals[3] + '-' + vals[1] + '-' + vals[2]);
}

function add_dashes_to_date_string(datum)
{
    //turn yyyymmdd into yyyy-mm-dd
    return datum.substr(0,4) + '-' + datum.substr(4,2) + '-' + datum.substr(6,2);
}

/*****************************************************************************\

 Javascript "SOAP Client" library
 
 @version: 2.4 - 2007.12.21
 @author: Matteo Casati - http://www.guru4.net/
 
 @Heavily edited by Sal Uryasev of http://www.juiceanalytics.com/ for use with Greasemonkey
 @removed original parts are commented out
 
\*****************************************************************************/

function SOAPClientParameters()
{
	var _pl = new Array();
	this.add = function(name, value) 
	{
		_pl[name] = value; 
		return this; 
	}
	this.toXml = function()
	{
		var xml = "";
		for(var p in _pl)
		{
			switch(typeof(_pl[p])) 
			{
                case "string":
                case "number":
                case "boolean":
                case "object":
                    xml += "<" + p + ">" + SOAPClientParameters._serialize(_pl[p]) + "</" + p + ">";
                    break;
                default:
                    break;
            }
		}
		return xml;	
	}
}
SOAPClientParameters._serialize = function(o)
{
    var s = "";
    switch(typeof(o))
    {
        case "string":
            s += o.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); break;
        case "number":
        case "boolean":
            s += o.toString(); break;
        case "object":
            // Date
            if(o.constructor.toString().indexOf("function Date()") > -1)
            {
        
                var year = o.getFullYear().toString();
                var month = (o.getMonth() + 1).toString(); month = (month.length == 1) ? "0" + month : month;
                var date = o.getDate().toString(); date = (date.length == 1) ? "0" + date : date;
                var hours = o.getHours().toString(); hours = (hours.length == 1) ? "0" + hours : hours;
                var minutes = o.getMinutes().toString(); minutes = (minutes.length == 1) ? "0" + minutes : minutes;
                var seconds = o.getSeconds().toString(); seconds = (seconds.length == 1) ? "0" + seconds : seconds;
                var milliseconds = o.getMilliseconds().toString();
                var tzminutes = Math.abs(o.getTimezoneOffset());
                var tzhours = 0;
                while(tzminutes >= 60)
                {
                    tzhours++;
                    tzminutes -= 60;
                }
                tzminutes = (tzminutes.toString().length == 1) ? "0" + tzminutes.toString() : tzminutes.toString();
                tzhours = (tzhours.toString().length == 1) ? "0" + tzhours.toString() : tzhours.toString();
                var timezone = ((o.getTimezoneOffset() < 0) ? "+" : "-") + tzhours + ":" + tzminutes;
                s += year + "-" + month + "-" + date + "T" + hours + ":" + minutes + ":" + seconds + "." + milliseconds + timezone;
            }
            // Array
            else if(o.constructor.toString().indexOf("function Array()") > -1)
            {
                for(var p in o)
                {
                    if(!isNaN(p))   // linear array
                    {
                        (/function\s+(\w*)\s*\(/ig).exec(o[p].constructor.toString());
                        var type = RegExp.$1;
                        switch(type)
                        {
                            case "":
                                type = typeof(o[p]);
                            case "String":
                                type = "string"; break;
                            case "Number":
                                type = "int"; break;
                            case "Boolean":
                                type = "bool"; break;
                            case "Date":
                                type = "DateTime"; break;
                        }
                        s += "<" + type + ">" + SOAPClientParameters._serialize(o[p]) + "</" + type + ">"
                    }
                    else    // associative array
                        s += "<" + p + ">" + SOAPClientParameters._serialize(o[p]) + "</" + p + ">"
                }
            }
            // Object or custom function
            else
                for(var p in o)
                    s += "<" + p + ">" + SOAPClientParameters._serialize(o[p]) + "</" + p + ">";
            break;
        default:
            break; // throw new Error(500, "SOAPClientParameters: type '" + typeof(o) + "' is not supported");
    }
    return s;
}

function SOAPClient() {}

SOAPClient.username = null;
SOAPClient.password = null;

SOAPClient.invoke = function(url, method, parameters, async, callback, row)
{
    // if(async)
    //  SOAPClient._loadWsdl(url, method, parameters, async, callback);
    // else
	return SOAPClient._loadWsdl(url, method, parameters, async, callback, row);
}

// private: wsdl cache
SOAPClient_cacheWsdl = new Array();

// private: invoke async
SOAPClient._loadWsdl = function(url, method, parameters, async, callback, row)
{
	// load from cache?
	//var wsdl = SOAPClient_cacheWsdl[url];
	//if(wsdl + "" != "" && wsdl + "" != "undefined")
		//return SOAPClient._sendSoapRequest(url, method, parameters, async, callback, wsdl);
	// get wsdl
	// var xmlHttp = SOAPClient._getXmlHttp();
	//     xmlHttp.open("GET", url + "?wsdl", async);
	//     if(async) 
	//     {
	//         xmlHttp.onreadystatechange = function() 
	//         {
	//             if(xmlHttp.readyState == 4)
	//                 SOAPClient._onLoadWsdl(url, method, parameters, async, callback, xmlHttp);
	//         }
	//     }
	//     xmlHttp.send(null);
	//     if (!async)
	//         return SOAPClient._onLoadWsdl(url, method, parameters, async, callback, xmlHttp);
	GM_xmlhttpRequest({
	    method: 'GET',
	    url: url + "?wsdl",
	    headers: {
	        'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
            'Accept': 'application/atom+xml,application/xml,text/xml',
	    },
	    onload: function(responseDetails) {
            var xmlobject = (new DOMParser()).parseFromString(responseDetails.responseText, 'text/xml');
	        return SOAPClient._onLoadWsdl(url, method, parameters, async, callback, xmlobject, row);
	    }
	})
	}
SOAPClient._onLoadWsdl = function(url, method, parameters, async, callback, responseXML, row)
{
	var wsdl = responseXML; //var wsdl = req.responseXML;
	SOAPClient_cacheWsdl[url] = wsdl;	// save a copy in cache
	return SOAPClient._sendSoapRequest(url, method, parameters, async, callback, wsdl, row);
}
SOAPClient._sendSoapRequest = function(url, method, parameters, async, callback, wsdl, row)
{
	// get namespace
	var ns = (wsdl.documentElement.attributes["targetNamespace"] + "" == "undefined") ? wsdl.documentElement.attributes.getNamedItem("targetNamespace").nodeValue : wsdl.documentElement.attributes["targetNamespace"].value;
	// build SOAP request
	var sr = 
				"<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
				"<soap:Envelope " +
				"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
				"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
				"xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
				"<soap:Body>" +
				"<" + method + " xmlns=\"" + ns + "\">" +
				parameters.toXml() +
				"</" + method + "></soap:Body></soap:Envelope>";
	// send request
    // var xmlHttp = SOAPClient._getXmlHttp();
    // if (SOAPClient.userName && SOAPClient.password){
    //  xmlHttp.open("POST", url, async, SOAPClient.userName, SOAPClient.password);
    //  // Some WS implementations (i.e. BEA WebLogic Server 10.0 JAX-WS) don't support Challenge/Response HTTP BASIC, so we send authorization headers in the first request
    //  xmlHttp.setRequestHeader("Authorization", "Basic " + SOAPClient._toBase64(SOAPClient.userName + ":" + SOAPClient.password));
    // }
    // else
    //  xmlHttp.open("POST", url, async);
    var soapaction = ((ns.lastIndexOf("/") != ns.length - 1) ? ns + "/" : ns) + method;
    // xmlHttp.setRequestHeader("SOAPAction", soapaction);
    // xmlHttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    // if(async) 
    // {
    //  xmlHttp.onreadystatechange = function() 
    //  {
    //      if(xmlHttp.readyState == 4)
    //          SOAPClient._onSendSoapRequest(method, async, callback, wsdl, xmlHttp);
    //  }
    // }
    // xmlHttp.send(sr);
    // if (!async)
    //  return SOAPClient._onSendSoapRequest(method, async, callback, wsdl, xmlHttp);
    GM_xmlhttpRequest({
	    method: 'POST',
	    url: url,
	    headers: {
            // 'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
            //             'Accept': 'application/atom+xml,application/xml,text/xml',
            'SOAPAction': soapaction,
            'Content-Type': "text/xml"
	    },
	    data: sr,
	    onload: function(responseDetails) {
	        
            var xmlobject = (new DOMParser()).parseFromString(responseDetails.responseText, 'text/xml');
            //return SOAPClient._onSendSoapRequest(url, method, parameters, async, callback, xmlobject);
	        return SOAPClient._onSendSoapRequest(method, async, callback, wsdl, xmlobject, row);
	    }
	})
}

SOAPClient._onSendSoapRequest = function(method, async, callback, wsdl, xmlobject, row) 
{
	var o = null;
	var nd = SOAPClient._getElementsByTagName(xmlobject, method + "Result");
	if(nd.length == 0)
		nd = SOAPClient._getElementsByTagName(xmlobject, "return");	// PHP web Service?
	if(nd.length == 0)
	{
		if(xmlobject.getElementsByTagName("faultcode").length > 0)
		{
		    if(async || callback)
		        o = new Error(500, xmlobject.getElementsByTagName("faultstring")[0].childNodes[0].nodeValue);
			else
			    throw new Error(500, xmlobject.getElementsByTagName("faultstring")[0].childNodes[0].nodeValue);			
		}
	}
	else
		o = SOAPClient._soapresult2object(nd[0], wsdl);
    if(!async)
	    return callback(o, xmlobject, row);
    if(callback)
        callback(o, xmlobject, row);
}
SOAPClient._soapresult2object = function(node, wsdl)
{
    var wsdlTypes = SOAPClient._getTypesFromWsdl(wsdl);
    return SOAPClient._node2object(node, wsdlTypes);
}
SOAPClient._node2object = function(node, wsdlTypes)
{
	// null node
	if(node == null)
		return null;
	// text node
	if(node.nodeType == 3 || node.nodeType == 4)
		return SOAPClient._extractValue(node, wsdlTypes);
	// leaf node
	if (node.childNodes.length == 1 && (node.childNodes[0].nodeType == 3 || node.childNodes[0].nodeType == 4))
		return SOAPClient._node2object(node.childNodes[0], wsdlTypes);
	var isarray = SOAPClient._getTypeFromWsdl(node.nodeName, wsdlTypes).toLowerCase().indexOf("arrayof") != -1;
	// object node
	if(!isarray)
	{
		var obj = null;
		if(node.hasChildNodes())
			obj = new Object();
		for(var i = 0; i < node.childNodes.length; i++)
		{
			var p = SOAPClient._node2object(node.childNodes[i], wsdlTypes);
			obj[node.childNodes[i].nodeName] = p;
		}
		return obj;
	}
	// list node
	else
	{
		// create node ref
		var l = new Array();
		for(var i = 0; i < node.childNodes.length; i++)
			l[l.length] = SOAPClient._node2object(node.childNodes[i], wsdlTypes);
		return l;
	}
	return null;
}
SOAPClient._extractValue = function(node, wsdlTypes)
{
	var value = node.nodeValue;
	switch(SOAPClient._getTypeFromWsdl(node.parentNode.nodeName, wsdlTypes).toLowerCase())
	{
		default:
		case "s:string":			
			return (value != null) ? value + "" : "";
		case "s:boolean":
			return value + "" == "true";
		case "s:int":
		case "s:long":
			return (value != null) ? parseInt(value + "", 10) : 0;
		case "s:double":
			return (value != null) ? parseFloat(value + "") : 0;
		case "s:datetime":
			if(value == null)
				return null;
			else
			{
				value = value + "";
				value = value.substring(0, (value.lastIndexOf(".") == -1 ? value.length : value.lastIndexOf(".")));
				value = value.replace(/T/gi," ");
				value = value.replace(/-/gi,"/");
				var d = new Date();
				d.setTime(Date.parse(value));										
				return d;				
			}
	}
}
SOAPClient._getTypesFromWsdl = function(wsdl)
{
	var wsdlTypes = new Array();
	// IE
	var ell = wsdl.getElementsByTagName("s:element");	
	var useNamedItem = true;
	// MOZ
	if(ell.length == 0)
	{
		ell = wsdl.getElementsByTagName("element");	     
		useNamedItem = false;
	}
	for(var i = 0; i < ell.length; i++)
	{
		if(useNamedItem)
		{
			if(ell[i].attributes.getNamedItem("name") != null && ell[i].attributes.getNamedItem("type") != null) 
				wsdlTypes[ell[i].attributes.getNamedItem("name").nodeValue] = ell[i].attributes.getNamedItem("type").nodeValue;
		}	
		else
		{
			if(ell[i].attributes["name"] != null && ell[i].attributes["type"] != null)
				wsdlTypes[ell[i].attributes["name"].value] = ell[i].attributes["type"].value;
		}
	}
	return wsdlTypes;
}
SOAPClient._getTypeFromWsdl = function(elementname, wsdlTypes)
{
    var type = wsdlTypes[elementname] + "";
    return (type == "undefined") ? "" : type;
}
// private: utils
SOAPClient._getElementsByTagName = function(document, tagName)
{
	try
	{
		// trying to get node omitting any namespaces (latest versions of MSXML.XMLDocument)
		return document.selectNodes(".//*[local-name()=\""+ tagName +"\"]");
	}
	catch (ex) {}
	// old XML parser support
	return document.getElementsByTagName(tagName);
}
// private: xmlhttp factory
SOAPClient._getXmlHttp = function() 
{
	try
	{
		if(window.XMLHttpRequest) 
		{
			var req = new XMLHttpRequest();
			// some versions of Moz do not support the readyState property and the onreadystate event so we patch it!
			if(req.readyState == null) 
			{
				req.readyState = 1;
				req.addEventListener("load", 
									function() 
									{
										req.readyState = 4;
										if(typeof req.onreadystatechange == "function")
											req.onreadystatechange();
									},
									false);
			}
			return req;
		}
		if(window.ActiveXObject) 
			return new ActiveXObject(SOAPClient._getXmlHttpProgID());
	}
	catch (ex) {}
	throw new Error("Your browser does not support XmlHttp objects");
}
SOAPClient._getXmlHttpProgID = function()
{
	if(SOAPClient._getXmlHttpProgID.progid)
		return SOAPClient._getXmlHttpProgID.progid;
	var progids = ["Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];
	var o;
	for(var i = 0; i < progids.length; i++)
	{
		try
		{
			o = new ActiveXObject(progids[i]);
			return SOAPClient._getXmlHttpProgID.progid = progids[i];
		}
		catch (ex) {};
	}
	throw new Error("Could not find an installed XML parser");
}

SOAPClient._toBase64 = function(input)
{
	var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
	var output = "";
	var chr1, chr2, chr3;
	var enc1, enc2, enc3, enc4;
	var i = 0;

	do {
		chr1 = input.charCodeAt(i++);
		chr2 = input.charCodeAt(i++);
		chr3 = input.charCodeAt(i++);

		enc1 = chr1 >> 2;
		enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
		enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
		enc4 = chr3 & 63;

		if (isNaN(chr2)) {
			enc3 = enc4 = 64;
		} else if (isNaN(chr3)) {
			enc4 = 64;
		}

		output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) +
		keyStr.charAt(enc3) + keyStr.charAt(enc4);
	} while (i < input.length);

	return output;
}

/**
*
*  Secure Hash Algorithm (SHA256)
*  http://www.webtoolkit.info/
*
*  Original code by Angel Marin, Paul Johnston.
*
**/

function SHA256(s){

    var chrsz   = 8;
    var hexcase = 0;

    function safe_add (x, y) {
        var lsw = (x & 0xFFFF) + (y & 0xFFFF);
        var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
        return (msw << 16) | (lsw & 0xFFFF);
    }

    function S (X, n) { return ( X >>> n ) | (X << (32 - n)); }
    function R (X, n) { return ( X >>> n ); }
    function Ch(x, y, z) { return ((x & y) ^ ((~x) & z)); }
    function Maj(x, y, z) { return ((x & y) ^ (x & z) ^ (y & z)); }
    function Sigma0256(x) { return (S(x, 2) ^ S(x, 13) ^ S(x, 22)); }
    function Sigma1256(x) { return (S(x, 6) ^ S(x, 11) ^ S(x, 25)); }
    function Gamma0256(x) { return (S(x, 7) ^ S(x, 18) ^ R(x, 3)); }
    function Gamma1256(x) { return (S(x, 17) ^ S(x, 19) ^ R(x, 10)); }

    function core_sha256 (m, l) {
        var K = new Array(0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0xFC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x6CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2);
        var HASH = new Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19);
        var W = new Array(64);
        var a, b, c, d, e, f, g, h, i, j;
        var T1, T2;

        m[l >> 5] |= 0x80 << (24 - l % 32);
        m[((l + 64 >> 9) << 4) + 15] = l;

        for ( var i = 0; i<m.length; i+=16 ) {
            a = HASH[0];
            b = HASH[1];
            c = HASH[2];
            d = HASH[3];
            e = HASH[4];
            f = HASH[5];
            g = HASH[6];
            h = HASH[7];

            for ( var j = 0; j<64; j++) {
                if (j < 16) W[j] = m[j + i];
                else W[j] = safe_add(safe_add(safe_add(Gamma1256(W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);

                T1 = safe_add(safe_add(safe_add(safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
                T2 = safe_add(Sigma0256(a), Maj(a, b, c));

                h = g;
                g = f;
                f = e;
                e = safe_add(d, T1);
                d = c;
                c = b;
                b = a;
                a = safe_add(T1, T2);
            }

            HASH[0] = safe_add(a, HASH[0]);
            HASH[1] = safe_add(b, HASH[1]);
            HASH[2] = safe_add(c, HASH[2]);
            HASH[3] = safe_add(d, HASH[3]);
            HASH[4] = safe_add(e, HASH[4]);
            HASH[5] = safe_add(f, HASH[5]);
            HASH[6] = safe_add(g, HASH[6]);
            HASH[7] = safe_add(h, HASH[7]);
        }
        return HASH;
    }

    function str2binb (str) {
        var bin = Array();
        var mask = (1 << chrsz) - 1;
        for(var i = 0; i < str.length * chrsz; i += chrsz) {
            bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32);
        }
        return bin;
    }

    function Utf8Encode(string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++) {

            var c = string.charCodeAt(n);

            if (c < 128) {
                utftext += String.fromCharCode(c);
            }
            else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }

        return utftext;
    }

    function binb2hex (binarray) {
        var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
        var str = "";
        for(var i = 0; i < binarray.length * 4; i++) {
            str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
            hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8  )) & 0xF);
        }
        return str;
    }

    s = Utf8Encode(s);
    return binb2hex(core_sha256(str2binb(s), s.length * chrsz));

}


