// -*- Mode: c++; c-basic-offset: 4; -*- // hrmph, no javaspit mode in emacs ...
/* Code to implement a substitution cipher.
 * $Id: substitute.js,v 1.5 2007-12-09 16:23:49 eddy Exp $
 */
var pstatus = null;
function warn (text) {
    if (pstatus != null)
	pstatus.data = text;
}

function letterClass () {
    function Letter (boss, cipher, plain) {
	this.cipher = cipher; // letter
	this.plain = plain; // input node
	this.value = null;
	if (plain.value)
	    this.Set(boss, plain.value);

	var letter = this; // for the sake of:
	plain.onreset = plain.onchange = function () {
	    warn("");
	    if (!this.value) {
		letter.Clear(boss);
	    } else if (letter.Set(boss, this.value.toUpperCase())) {
		warn("More than one cipher letter mapped to: " + this.value);
	    }
	    boss.Update();
	}
    }

    Letter.prototype = {
	'Set': function (boss, value) {
	    if (this.value) this.Clear(boss);
	    this.value = value;

	    var peers = boss.reverse[value];
	    var i = peers ? peers.length : 0;
	    if (i <= 0) {
		boss.reverse[value] = [ this ];
		return false; // Unproblematic.
	    }

	    peers[i] = this;
	    peers.length = 1 + i; // not needed ?
	    for (var i in peers)
		peers[i].plain.className = "error";
	    return true;
	},

	'Clear': function (boss) {
	    if (this.value == null) {
		alert("Clearing unset Letter !");
		return;
	    }
	    var peers = boss.reverse[this.value];
	    this.value = null;

	    var i = peers.length;
	    while (i-- > 0)
		if (peers[i] == this) {
		    while (++i < peers.length)
			peers[i-1] = peers[i];
		    delete peers[--i];
		    peers.length = i; // needed ?
		    this.plain.className = "";
		    delete this.plain.className;
		    break;
		}
	    if (i < 0) alert("Failed to find Letter in reverse lookup !");
	    else if (peers.length == 1) { // Clear error indicator on remaining ex-peer:
		peers[0].plain.className = "";
		delete peers[0].plain.className;
	    }
	}
    };

    return Letter;
}

function ignore(cell) {
    if (cell && cell.className != "irrelevant") {
	cell.className = "irrelevant";
	return 1;
    }
    return 0;
}

var converter = null;
function converterClass() {
    function Converter () {} // this doesn't yet have Converter as prototype !
    Converter.prototype = {
	'grid': document.getElementById('conversion'),
	'display': document.getElementById("transformed").firstChild,
	'Letter': letterClass(),
	'Mapping': function () {
	    var bok = {};
	    for (var key in this.lookup) {
		var input = this.lookup[key].plain;
		if (input.value)
		    bok[key] = input.value;
	    }
	    return bok;
	},

	'SetText': function () {
	    converter = new Converter();
	    converter.Prepare(this.Mapping());
	    converter.Update();
	},

	'Prepare': function (prior) {
	    var text = this.ciphertext.value;
	    letters = {};
	    var i = text.length;
	    while (i-- > 0) {
		var ch = text.charAt(i).toUpperCase();
		if (ch.match(/\s/)) /* skip space */ ;
		else if (letters[ch]) letters[ch] += 1;
		else letters[ch] = 1;
	    }

	    /* Bind inputs to Letter objects handling letters. */
	    var inrow = this.grid.firstChild.firstChild;
	    var freqrow = inrow.nextSibling;
	    var outrow = freqrow.nextSibling;
	    var i = 1;

	    this.reverse = {}; // shall be filled by Letter constructor
	    for (var key in letters) {
		if (i >= outrow.childNodes.length) {
		    var ent = outrow.lastChild.cloneNode(true);
		    outrow.appendChild(ent);
		}
		if (i >= freqrow.childNodes.length) {
		    var ent = freqrow.lastChild.cloneNode(true);
		    freqrow.appendChild(ent);
		}
		if (i >= inrow.childNodes.length) {
		    var ent = inrow.lastChild.cloneNode(true);
		    inrow.appendChild(ent);
		}
		var incell = inrow.childNodes.item(i);
		var freqcell = freqrow.childNodes.item(i);
		var outcell = outrow.childNodes.item(i++);

		incell.className = freqcell.className = outcell.className = "";
		incell.firstChild.data = key;
		freqcell.firstChild.data = '' + letters[key];
		// TODO: does this trigger outcell.onchange ?
		outcell.firstChild.value = prior[key] ? prior[key] : '';

		letters[key] = new this.Letter(this, key, outcell.firstChild);
	    }
	    this.lookup = letters;

	    // Hide the rest of the table (if any):
	    while (ignore(inrow.childNodes.item(i)) +
		   ignore(freqrow.childNodes.item(i)) +
		   ignore(outrow.childNodes.item(i)) > 0)
		i++;
	},

	'Update' : function () {
	    var text = '';
	    var inspan = false;
	    var given = this.ciphertext.value;
	    var i = 0;
	    while (i < given.length) {
		var ch = given.charAt(i++);
		if (ch == '\n') {
		    if (text) // ignore leading newlines
			text += " <br>\n";
		} else if (ch.match(/\s/)) {
		    if (text) // ignore leading space
			text += ch;
		} else {
		    var out = this.lookup[ch.toUpperCase()].plain;
		    if (out && out.value) {
			if (!inspan) {
			    text += '<span class="decoded">';
			    inspan = true;
			}
			var ar = out.value;
			text += (ch == ch.toLowerCase())
			    ? ar.toLowerCase() : ar.toUpperCase();
		    } else {
			if (inspan) {
			    text += '</span>';
			    inspan = false;
			}
			text += ch;
		    }
		}
	    }
	    this.display.innerHTML = text;
	}
    };
    Converter.prototype.ciphertext = Converter.prototype.grid.parentNode.firstChild.firstChild;

    return Converter;
}

function Initialize() {
    var toy = document.getElementById("tool");
    pstatus = toy.lastChild.firstChild; // Text node in final p of toy div.
    pstatus.data = toy.className = "";

    SetText();
}

function SetText() {
    if (converter == null) {
	var CConv = converterClass();
	converter = new CConv("", {});
    }
    converter.SetText();
}
