// Emacs settings: -*- mode: Fundamental; tab-width: 4; -*-

////////////////////////////////////////////////////////////////////////////
//                                                                        //
// Arbitrary Precision Integer Calculator                                 //
// Or, test program for my Javascript Bignum library                      //
//                                                                        //
// Copyright (c) 2009, Andrew Birrell                                     //
//                                                                        //
////////////////////////////////////////////////////////////////////////////


//
// Timing and display
//

function numFromId(id) {
	var value = document.getElementById(id).value;
	if (value == "" || value == Number("a").toString()) return new Bignum();
	var num = new Bignum(value);
	if (num.NaN) alert("Syntax error in \"" + id + "\"");
	return num;
}

function showValue(s, id) {
	document.getElementById(id).innerHTML = s;
}

var recordedTime = 0;

function recordTime() {
	divModIter = 0;
	recordedTime = (new Date()).getTime();
}

function result(res, txt) {
	var startedAt = recordedTime;
	recordTime();
	var elapsed = recordedTime - startedAt;
	showValue(elapsed/1000, "timer");
	push(res.toString(), txt);
}	


//
// Stack operations
//

var stack = new Array();
var stackTxt = new Array();

function showStack() {
	var frag = "<INPUT TYPE=TEXT SIZE=15 VALUE=\"";
	var s = "";
	for (var i = stack.length-1; i >= 0; i--) {
		var n = stack[i];
		if (s != "") s += "<br>";
		s += frag + stackTxt[i] +
			"\" onblur=\"newTxt(" + i + ",this.value)\"> ";
		if (n.length <= 50) {
			s += n;
		} else {
			s += n.substr(0, 25) + "..." + n.substr(n.length-25, 25);
			s += " (" + (n.length-(n[0]=='-'?1:0)) + " digits)"
		}
	}
	showValue((s == "" ?
		frag + "\" READONLY> empty" : s), "stack");
	var cStr = (stack.length == 0 ? "empty" : stack[stack.length-1]);
	document.getElementById("c").value =
		cStr + (cStr == Number("a").toString() ? " (not a number)" : "");
}

function newTxt(i, txt) {
	stackTxt[i] = txt;
}

function push(n, txt) {
	stack.push(n);
	stackTxt.push(txt);
	showStack();
}

function pushContents(id, txt) {
	var n = document.getElementById(id).value;
	if (n != "") {
		push(n, txt ? txt : id);
	}
	return false;
}

function copyStackTop(id) {
	if (stack.length > 0) {
		var n = stack[stack.length-1];
		stackTxt[stack.length-1];
		document.getElementById(id).value = n;
	}
	return false;
}

function pop() {
	if (stack.length > 0) {
		stack.pop();
		stackTxt.pop();
		showStack();
	}
	return false;
}

function exch() {
	if (stack.length > 1) {
		var top = stack.pop();
		var topTxt = stackTxt.pop();
		var next = stack.pop();
		var nextTxt = stackTxt.pop();
		push(top, topTxt);
		push(next, nextTxt);
	}
	return false;
}

function roll() {
	if (stack.length > 1) {
		push(stack.shift(), stackTxt.shift());
	}
	return false;
}

function clearStack() {
	stack = new Array();
	stackTxt = new Array();
	showStack();
	return false;
}


//
// The operations
//

function compAB() {
	var a = numFromId("a");
	var b = numFromId("b");
	recordTime();
	var res = a.comp(b);
	var txt = (res < 0 ? "a < b" : ( res > 0 ? "a > b" : "a == b"));
	result(res, txt);
	return false;
}

function addAB() {
	var a = numFromId("a");
	var b = numFromId("b");
	recordTime();
	result(a.add(b), "a + b");
}

function subAB() {
	var a = numFromId("a");
	var b = numFromId("b");
	recordTime();
	result(a.sub(b), "a - b");
	return false;
}

function mulAB() {
	var a = numFromId("a");
	var b = numFromId("b");
	recordTime();
	result(a.mul(b), "a * b");
	return false;
}

function divAB() {
	var a = numFromId("a");
	var b = numFromId("b");
	recordTime();
	result(a.div(b), "a / b");
	return false;
}

function modAB() {
	var a = numFromId("a");
	var b = numFromId("b");
	recordTime();
	result(a.mod(b), "a mod b");
	return false;
}

function powAB() {
	var a = numFromId("a");
	var b = numFromId("b");
	recordTime();
	result(a.pow(b), "a ** b");
	return false;
}

function mulABmodM() {
	var a = numFromId("a");
	var b = numFromId("b");
	var m = numFromId("m");
	recordTime();
	result(a.mul(b, m), "a * b mod m");
	return false;
}

function powABmodM() {
	var a = numFromId("a");
	var b = numFromId("b");
	var m = numFromId("m");
	recordTime();
	result(a.pow(b, m), "a ** b mod m");
	return false;
}

function gcdAB() {
	var a = numFromId("a");
	var b = numFromId("b");
	recordTime();
	result(a.gcd(b), "gcd(a, b)");
	return false;
}

function inverseAM() {
	var a = numFromId("a");
	var m = numFromId("m");
	recordTime();
	result(a.inverse(m), "inverse(a, m)");
	return false;
}

function isPrimeA() {
	var a = numFromId("a");
	recordTime();
	result(a.isPrime(), "isPrime(a)");
	return false;
}

function randomA() {
	var elt = document.getElementById("a");
	var a = Number(elt.value);
	recordTime();
	result(bigRandom(a), "random(a)");
	return false;
}

function genPrimeA() {
	var elt = document.getElementById("a");
	var a = Number(elt.value);
	recordTime();
	if (a > 2048) {
		result(new Bignum("too many bits"), "genPrime(a)");
	} else {
		var res = bigPrime(a);
		result(res, "genPrime(a)");
	}
	return false;
}

function genRsaPQ() {
	var p = new Bignum(stack.pop());
	stackTxt.pop();
	var q = new Bignum(stack.pop());
	stackTxt.pop();
	push(q.toString(), "Q");
	push(p.toString(), "P");
	var public = new Bignum(65537);
	var plain = new Bignum("123456789011223344556677889900");
	recordTime();
	var p1 = p.sub(bigOne);
	var q1 = q.sub(bigOne);
	// PKCS #1 v 2.0 uses lambda = LCM(p-1, q-1) instead of totient(p, q).
	var lambda = p1.mul(q1).div(p1.gcd(q1));
	if (public.gcd(lambda).absComp(bigOne) != 0) {
		alert("Public exponent " + e + " isn't coprime with lambda." +
			" Try again with different primes.");
	} else {
		var secret = public.inverse(lambda);
		var m = p.mul(q);
		var cipher = plain.pow(secret, m);
		var decrypted = cipher.pow(public, m);
		push(plain.toString(), "plain text");
		result(decrypted, "decrypted text");
		push(cipher.toString(), "cipher text");
		document.getElementById("m").value = m;
		push(m.toString(), "modulus");
		push(secret.toString(), "secret exponent");
		push(public.toString(), "public exponent");
	}
	return false;
}


//
// Initialization
//

function init() {
	showStack();
	showValue("Radix = " + radix +
		", radixBits = " + radixBits, "radix");
	document.getElementById("a").focus();
}

