/*	DECIPHER.JS		1-18-2011		JavaScript
	Copyright (C)1994-1996, 2007, 2008, 2011 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License (GPL)
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Assists with solving substitution cipher puzzles such as are sometimes published in newspapers.
A substitution cipher is text in which every letter A-Z has been transposed to some other
letter A-Z. The text looks like nonsense, but if you can determine which letter in the
ciphered text stands for which letter in the alphabet, you can restore the text to its
original state.

The program loads the source text, then optionally does a preliminary decode in which the 
most common letters in the text are translated to the most commonly occurring letters in 
most English texts. It may or may not be correct, but it's a start. 
Then it provides an interface where you can easily and quickly swap letter
translations and see the results immediately in the text. This is a lot easier than doing
it on paper, but of course if you use this program you are cheating!

-----
TO DO

Revised letter frequencies, from a tv show: ETAONI....JKXQZ.
TH is the most common letter pair. EA is the most common vowel pair.

Could be automated, checking the resulting words against a dictionary,
and swapping letters until they are all known words.

*/
//----------------------------------------------------------------------------
// GLOBAL VARIABLES
var letters;		//translation table
var intext = "";	//incoming ciphered text, cleaned to avoid XSS and creation of links.
//----------------------------------------------------------------------------
// DISPLAY DECIPHERED TEXT, IN TWO FORMATS. 
// A TEXTAREA CAN DISPLAY TEXT WITH LINE BREAKS SAME AS THE INPUT, BUT IT CAN'T DISPLAY COLOR.
// AN HTML PARAGRAPH CAN DISPLAY COLOR, BUT CAN'T EASILY PRESERVE LINE BREAKS.
function showtrans()
{
var i, j;
var lockcolor = "red", unlockcolor = "black";
var coutput = "";

// PUT THE ALPHABET IN THE CELLS OF THE FIRST TABLE ROW
// PUT THE LETTER FREQUENCIES IN THE SECOND TABLE ROW.
var ft = document.getElementById("LetterFreqTable");
for(i = 0 ; i < 26 ; i++)
{
	ft.rows[0].cells[i].innerHTML = (letters[i].lock ? 
									letters[i].trans.bold().fontcolor(lockcolor) : 
									letters[i].trans.fontcolor(unlockcolor));
	ft.rows[1].cells[i].innerHTML = letters[i].freq;
}

//SHOW THE DECODED TEXT
coutput = "";									//FORMATTED TEXT FOR THE HTML OUTPUT
var pcoutput = "";								//PLAIN TEXT FOR THE TEXTAREA.
for(i = 0 ; i < intext.length ; i++)			// FOR EACH CHAR IN INTEXT,
{
	var found = false;
	for(j = 0 ; j < 26 ; j++)					// LOOK IT UP IN TABLE
		if(intext.charAt(i) == letters[j].orig)	// IF FOUND, IT'S A LETTER
		{                                  		// SHOW ITS TRANSLATION
			coutput += (letters[j].lock ? 
						letters[j].trans.bold().fontcolor(lockcolor) : 
						letters[j].trans.fontcolor(unlockcolor));
			pcoutput += letters[j].trans;
			found = true;
			break;							// BREAK LOOP TO DO NEXT CHAR
		}
	if(!found)								// OUTPUT NON-LETTER AS-IS
	{
		if(intext.charAt(i) == '\t')				// expand tabs to spaces
		{
			coutput += "&nbsp;&nbsp;&nbsp;&nbsp;";	
			pcoutput += "    ";	
		}
		else
		{
			coutput += intext.charAt(i).fontcolor(unlockcolor);
			pcoutput += intext.charAt(i);
		}
	}
}
document.getElementById("PlainTextOut").innerHTML = coutput;
document.getElementById("PlainTextareaOut").value = pcoutput;
}
//----------------------------------------------------------------------------
// SWAP TRANSLATIONS FOR 2 LETTERS
// RETURNS true IF OK, false IF SWAP DISALLOWED
function swaplets(a, b)	// LETTERS WHOSE TRANSLATIONS ARE TO BE SWAPPED
{
var adex, bdex;				// adex and bdex are INDEXES OF A AND B IN letters[].TRANS
for(adex = 0 ; (adex < 26) && (letters[adex].trans != a) ; adex++); // LOCATE A
for(bdex = 0 ; (bdex < 26) && (letters[bdex].trans != b) ; bdex++); // LOCATE B

if((adex > 25) || (bdex > 25))					// WASN'T FOUND
	return(false);
if((letters[adex].lock) || (letters[bdex].lock))	// TEST FOR LOCKED
	return(false);

var ch = letters[adex].trans;					// SWAP THEIR TRANSLATIONS
letters[adex].trans = letters[bdex].trans;
letters[bdex].trans = ch;
return(true);
}
//----------------------------------------------------------------------------
// rotation backwards is actually rotation forward by 26-1 positions.
function Rotate(dir)
{
var i;
var source = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ";
var count = ((dir === 1) ? 1 : 25);
	for(i = 0 ; i < 26 ; i++)	
		letters[i].trans = source.charAt(source.indexOf(letters[i].trans) + count);
PrepareForUserCommand();
}
//----------------------------------------------------------------------------
function Swap()
{
var buf = document.getElementById("ActionIn").value.replace(/[^A-Za-z]/g,"").toUpperCase();
if(buf.length < 2)
	alert("Before clicking Swap, enter in the Command Line the two alphabetic letters you want to swap.");

if(!swaplets(buf.charAt(0),buf.charAt(1)))
	alert("One or both letters are locked (or invalid) and cannot be swapped.");

PrepareForUserCommand();
}
//----------------------------------------------------------------------------
function Lock(state)
{
buf = document.getElementById("ActionIn").value.replace(/[^A-Za-z]/g,"").toUpperCase();
if(!buf.length)
	alert("Before clicking Lock or Unlock, enter in the Command Line the alphabetic letters you want to lock or unlock.");
for(j = 0 ; j < buf.length ; j++)
	for(i = 0 ; i < 26 ; i++)
		if(letters[i].trans == buf.charAt(j))
		{
			letters[i].lock = state;
			break;
		}
PrepareForUserCommand();
}
//----------------------------------------------------------------------------
function PrepareForUserCommand()
{
showtrans();									// DISPLAY NEW TRANSLATION
document.getElementById("ActionIn").value = "";	// EMPTY THE COMMAND LINE
document.getElementById("ActionIn").focus();	// AND GIVE IT THE FOCUS.
}
//----------------------------------------------------------------------------
function Decipher(StartMethod)
{
//NOT using var to declare the variable supposedly makes it GLOBAL
//but you should always use var, and declare globals outside any function.
var i, j, buf, source;

letters = new Array();
// PUT LETTERS IN THE TRANSLATION TABLE
source = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for(i = 0 ; i < 26 ; i++)
{
	letters.push(new Object());
	letters[i].orig = source.charAt(i);	// LETTER FROM INPUT TEXT
	letters[i].trans = source.charAt(i);// LETTER IT'S TRANSLATED TO
	letters[i].freq = 0;				// FREQUENCY OF ORIG IN INPUT TEXT
	letters[i].lock = false;			// PREVENTS TRANS FROM BEING CHANGED
}

// GET INPUT TEXT with Basic HTML cleaning, even though textarea does not allow auto-conversion of hyperlinks
intext = document.getElementById("CipheredTextIn").value.replace(/[<>&/%\\#]/g,".").toUpperCase();
if(intext.length)
{
	// TALLY LETTER FREQUENCIES
	for(i = 0 ; i < 26 ; i++)						// FOR EACH LETTER,
		for(j = 0 ; j < intext.length ; j++)		// SEARCH FOR IT,
			if(intext.charAt(j) == letters[i].orig)	// AND, WHEN FOUND,
				letters[i].freq++;					// INCREMENT COUNT

	// sort by decreasing frequency
	letters.sort(function(a,b){return b.freq - a.freq;}); 
	if(StartMethod === 'etaoin')
	{
		// INITIALLY ASSIGN TRANSLATIONS FOR MOST FREQUENT LETTERS, TO ETAOIN.
		// This doesn't always result in the assignments I expect, maybe because of the letter locking,
		// and sometimes a letter is already what it should be, and can't be swapped with itself.
		// ASSIGN MOST FREQUENT LETTER TO 'E', TEMPORARILY LOCK IN PLACE,
		// 2ND MOST FREQUENT = 'T', ETC.
		swaplets(letters[0].orig,'E'); letters[0].lock = true;
		swaplets(letters[1].orig,'T'); letters[1].lock = true;
		swaplets(letters[2].orig,'A'); letters[2].lock = true;
		swaplets(letters[3].orig,'O'); letters[3].lock = true;
		swaplets(letters[4].orig,'I'); letters[4].lock = true;
		swaplets(letters[5].orig,'N'); letters[5].lock = true;
		swaplets(letters[6].orig,'S'); letters[6].lock = true;
		swaplets(letters[7].orig,'H'); letters[7].lock = true;
		swaplets(letters[8].orig,'R'); letters[8].lock = true;
		swaplets(letters[9].orig,'D'); letters[9].lock = true;
		swaplets(letters[10].orig,'L'); letters[10].lock = true;
		swaplets(letters[11].orig,'U'); letters[11].lock = true;
		// SHUFFLE THESE SELDOM-USED TO END OF ARRAY, IF POSSIBLE
		swaplets(letters[25].orig,'Q'); letters[25].lock = true;
		swaplets(letters[24].orig,'J'); letters[24].lock = true;
		swaplets(letters[23].orig,'X'); letters[23].lock = true;
		swaplets(letters[22].orig,'Z');
		
		for(i = 0 ; i < 26 ; i++)			// RE-UNLOCK THEM ALL
			letters[i].lock = false;
	}
	if(StartMethod === 'reverse')
		for(i = 0 ; i < 13 ; i++)
			swaplets(source[i], source[25-i]);
}
PrepareForUserCommand();
}

