/*\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/*\
**	Written By Bijan Hoomand 															**
**																						**
**	This is a free software, You can copy/edit this code, but please send me your  		**
**	updates if you made some useful changes! I like my name to stay up					**
**  there forever, so, please do not remove it! Free to use, don't abuse!				**
**  																					**
**  Easy Farsi is a script that can turn any text input/textarea into a Farsi editor.   **
**  Using it is very easy and there is no limit on the number of fields that can use	**
**  Easy Farsi.																			**
**  You don't need to define any global variables for each text input/textarea and it's	**
**  no need to put a name, event handler (onkeypress, onkeydown or whatever!) or 		**
**  anything else for your fields. It simply works by adding some attributes to your	**
**  tags. The very basic attribute is "useFarsi='true'".								**
**																						**
**  Visit "www.irandiba.net/hoomand/easyfarsi" to get the latest version of EasyFarsi 	**
**  plus a thorough Farsi tutorial on the various options that it provides.				**
**																						**
**	You can always find me in here:  hoomand@irandiba.net								**
/*\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/*/

function retElementsArray( tagName, type, name )
{
	var specTag = new Array();
	var counter = 0;
	
	
	// We're defining these variables to use later in our conditions.
	// We might have a special type or name that we wana look up, or we just
	// have a tagName to look up with no further ado! So, having a type and
	// having or not having a name and having a name and having or not having
	// a type makes 4 conditions. For reducing the number of nested ifs that
	// we encounter in this situation, we can use 2 simple evals on the following
	// variables.
	var typeMatch = ( type == "" ) ? ( "true" ) : ( "document.getElementsByTagName(tagName)[i].type.toLowerCase() == type.toLowerCase()");
	var nameMatch = ( name == "" ) ? ( "true" ) : ( "document.getElementsByTagName(tagName)[i].name == name" );

	for(var i=0; i<document.getElementsByTagName(tagName).length; i++)
		if ( eval(document.getElementsByTagName(tagName)[i].getAttribute("useFarsi")) && eval(typeMatch) && eval(nameMatch) )
		{
			specTag[counter] = document.getElementsByTagName(tagName)[i];
			++counter;
		}

	return specTag;
}

function changeDirectionAlignment( which, lang )
{
	switch( lang )
	{
		case "eng":
					which.style.direction = "ltr";
					which.style.textAlign = "left";
					break;
		case "far":
					which.style.direction = "rtl";
					which.style.textAlign = "right";
					break;
	}
}

function findEnglishRadioButton( radioButtArr )
{
	if ( radioButtArr[0].getAttribute("value") == "eng" || radioButtArr[0].getAttribute("value") == "far" )
		return ( ( radioButtArr[0].getAttribute("value") == "eng" ) ? (0) : (1) );
	else
		// If in the first radio button we can not find the which attribute
		// , that shows the language that it is used for, we return 1, i.e.
		// we assume that the 2nd radio button is for English and the first
		// is for Farsi. So, remember that putting which for the first radio
		// button is so necessary, unless you accept the Farsi then English
		// succession!
		return ( 1 );
	
}

// This is the main class, all the functions that you saw above, are
// gonna be used in here!
function easyFarsi()
{

	var textInputArr = retElementsArray( "input", "text", "" );
	var txtAreaArr = retElementsArray( "textarea", "", "" );
	
	var totalElements = textInputArr.concat(txtAreaArr);


	for( i in totalElements )
	{
		// Setting the default language, i.e. Farsi
		if ( !totalElements[i].getAttribute("language") )
			totalElements[i].setAttribute("language", "far");
		
		// Pseudocode: onchangelang(change the dir & alignment too)
		if ( !totalElements[i].getAttribute("changeDirOnLangSet") )
			totalElements[i].setAttribute("changeDirOnLangSet", "false");
			
		// These variables will contain the keyCode of a up/down key.
		// When a new key is down/up, we compare it to the previous
		// up/down key, if one of them is Alt and another is Ctrl,
		// or vice versa, we know the alt+ctrl is pressed! I don't
		// simply use if ( event.altKey && event.ctrlKey ), because
		// it doesn't work in Mozilla.
		totalElements[i].setAttribute("keyU", "");
		totalElements[i].setAttribute("keyD", "");
		
		if ( totalElements[i].getAttribute("indicator") )
		{
			indiType = totalElements[i].getAttribute("indicator").split(",")[0].toLowerCase();
			indiName = totalElements[i].getAttribute("indicator").split(",")[1];
			
			switch( indiType )
			{
				case "radio":
									var radioButts = new Array();
									radioButts = retElementsArray( "input", "radio", indiName );
									
									// Check if we found the right radio buttons, i.e. they're
									// only 2 radio buttons with the same name, one for Farsi &
									// one for English.
									if ( radioButts.length == 2 )
									{
										var engIndex = findEnglishRadioButton(radioButts);
										var farIndex = ( engIndex == 0 ) ? (1) : (0);
									}
									else
									{
										alert("Warning: A Radio Button called '" + indiName + "' has more or less than 2 instances and it's illegal,\n" +
											  "the textareas and input texts that use these radio buttons will not work");
										break;
									}

									// Well, when someone clicks on the Farsi or English radio buttons
									// that our input/textarea uses, it should change the direction/alignment
									// of our input/textarea. But we can't simply use totalElements[i].style.whatever in
									// our radioButts[eng/farIndex].onclick, because it doesn't recognize
									// it and our totalElements is not a global array. I'm somehow doing a trick
									// making an array of all the inputs/textareas that use these set of radio buttons
									// and then appending this array to a property of our radio buttons set called 'callers'.
									// So, if it's the first time that I encounter an input/textarea that wants to use
									// this set of radio buttons, I create this property putting my 1 length array in it.
									// Later on I will add new inputs/textareas to the end of this array that whenever 
									// someone clicks on our set of radio buttons, we iterate through the whole array and change
									// the direction/alignment of all the inputs/textareas that use these set of radio buttons.
									if ( !radioButts[0].callers )
									{
										var callers = new Array();
										callers[0] = totalElements[i];
										radioButts[0].callers = callers;
										radioButts[1].callers = callers;
										radioButts[( callers[0].getAttribute("language") == "eng" ) ? (engIndex) : (farIndex)].checked = true;
									}
									else
									{
										var callers = radioButts[0].callers;
										callers[callers.length] = totalElements[i];
										
										radioButts[0].callers = callers;
										radioButts[1].callers = callers;
									}
									
									// If there are other inputs/textareas that use this set of radio buttons,
									// then whatever language the first element of the array has, the others
									// have to have it too as their default language. Of course later when we 
									// change the language in one of them, the others should correspond as well. 
									// It means that from the beginning their languages should be the same too. 
									// Even if there are no other inputs/textareas, then callers[0] refers to 
									// the current element ( i.e. totalElements[i] ), why? Lemme give you a hint! 
									// Take a look at the above  condition, the else doesn't get excecuted in this case!

									totalElements[i].setAttribute("language", radioButts[0].callers[0].getAttribute("language") );
									changeDirectionAlignment( totalElements[i], totalElements[i].getAttribute("language") );
								


									radioButts[engIndex].onclick = function()
																		{
																			for ( i in this.callers )
																			{
																				if ( eval(this.callers[i].getAttribute("changeDirOnLangSet")) )
																					changeDirectionAlignment( this.callers[i], "eng" );
																				this.callers[i].setAttribute("language", "eng");
																			}
																		}



									radioButts[farIndex].onclick = function()
																		{
																			for ( i in this.callers )
																			{
																				if ( eval(this.callers[i].getAttribute("changeDirOnLangSet")) )
																					changeDirectionAlignment( this.callers[i], "far" );
																				this.callers[i].setAttribute("language", "far");
																			}
																		}
									
									// These functions that you see in all the cases, are used
									// only when the user presses Alt+Ctrl in one of the input/
									// textareas that are a part of this group (i.e. they use the same
									// indicator, the indicator's callers array holds all the instances
									// of these objects). In this case, we have to iterate through
									// the callers array and update the language/direction/alignment of
									// all the group members. In case of using the indicator itself for 
									// changing the language, everything is programmed in the onclick
									// event handler of the indicator, not in these functions. Of course
									// in all the switch cases you can find a function with this name, and
									// what they do is logically the same, but it's different if it's a radio
									// button, a button or an image. Got it?!!
									totalElements[i].changeLanguage = function()
																		{
																			var currentLang = this.getAttribute("language");
																			var newLang = ( currentLang == "far" ) ? ( "eng" ) : ( "far" );
																			
																			var indiName = this.getAttribute("indicator").split(",")[1];
																			
																			var radioButts = new Array();
																			radioButts = retElementsArray( "input", "Radio", indiName );
																			
																			for ( i in radioButts[0].callers )
																			{
																				if ( eval(radioButts[0].callers[i].getAttribute("changeDirOnLangSet")) )
																					changeDirectionAlignment( radioButts[0].callers[i], newLang );

																				radioButts[0].callers[i].setAttribute("language", newLang);
																			}
																			
																			var engIndex = findEnglishRadioButton(radioButts);
																			var farIndex = ( engIndex == 0 ) ? (1) : (0);
																			
																			radioButts[( newLang == "eng" ) ? ( engIndex ) : ( farIndex )].checked = true;
																				
																		}
									break;
				case "button":
									
									var butts = new Array();
									butts = retElementsArray( "input", "button", indiName );
									
									// Check if we found the right button, i.e. bautts.length == 1
									if ( butts.length == 1 )
									{
										var butt = butts[0];
									}
									else
									{
										alert("Warning: A button called '" + indiName + "' has more or less than 1 instance and it's illegal,\n" +
											  "the textareas and input texts that use this button will not work");
										break;
									}


									if ( !butt.callers )
									{
										var callers = new Array();
										callers[0] = totalElements[i];
										butt.callers = callers;
										butt.value = butt.getAttribute( ( (callers[0].getAttribute("language") == "eng") ? ("far") : ("eng") ) + "Value" );
									}
									else
									{
										var callers = butt.callers;
										callers[callers.length] = totalElements[i];
										butt.callers = callers;
									}

									totalElements[i].setAttribute("language", butt.callers[0].getAttribute("language") );
									changeDirectionAlignment( totalElements[i], totalElements[i].getAttribute("language") );
								


									butt.onclick = function()
																		{
																			var currentLang = this.callers[0].getAttribute("language");
																			var toBeLang = ( currentLang == "far" ) ? ("eng") : ("far");
																			this.value = this.getAttribute( currentLang + "Value" );

																			for ( i in this.callers )
																			{
																				if ( eval(this.callers[i].getAttribute("changeDirOnLangSet")) )
																					changeDirectionAlignment( this.callers[i], toBeLang);

																				this.callers[i].setAttribute("language", toBeLang);
																			}
																		}




									
									totalElements[i].changeLanguage = function()
																		{
																			var currentLang = this.getAttribute("language");
																			var newLang = ( currentLang == "far" ) ? ( "eng" ) : ( "far" );
																			
																			var indiName = this.getAttribute("indicator").split(",")[1];
																			
																			var butts = new Array();
																			butts = retElementsArray( "input", "button", indiName );
																			var butt = butts[0];
																			
																			for ( i in butt.callers )
																			{
																				if ( eval(butt.callers[i].getAttribute("changeDirOnLangSet")) )
																					changeDirectionAlignment( butt.callers[i], newLang );

																				butt.callers[i].setAttribute("language", newLang);
																			}
																			
																			butt.value = butt.getAttribute( currentLang + "Value" );
																			
																		}
									break;
				case "image":
				
									var imgs = new Array();
									imgs = retElementsArray( "img", "", indiName );

									// Check if we found the right image, i.e. imgs.length == 1
									if ( imgs.length == 1 )
									{
										var img = imgs[0];

									}
									else
									{
										alert("Warning: An image called '" + indiName + "' has more or less than 1 instance and it's illegal,\n" +
											  "the textareas and input texts that use this image will not work");
										break;
									}


									if ( !img.callers )
									{
										var callers = new Array();
										callers[0] = totalElements[i];
										img.callers = callers;
										
										var engSrc = img.getAttribute("engSrc");
										var farSrc = img.getAttribute("farSrc");

										// We're trying to preload the image/images in here.
										var preloadTemp = Array();
										preloadTemp[0] = new Image;
										preloadTemp[1] = new Image;
										preloadTemp[0].src = engSrc.split(",")[0];
										preloadTemp[1].src = farSrc.split(",")[0];
										
										if ( engSrc.split(",").length > 1 )
										{
											preloadTemp[2] = new Image;
											preloadTemp[2].src = engSrc.split(",")[1];
										}
										if ( farSrc.split(",").length > 1 )
										{
											preloadTemp[3] = new Image;
											preloadTemp[3].src = farSrc.split(",")[1];
										}

										// Some baby initiation! Always the img.src picture is against the current
										// language used in the inputs/textareas related to it, why? Because when
										// we click on the image, we expect the language to change to the language it shows!
										img.src = img.getAttribute( ( (callers[0].getAttribute("language") == "far") ? ("eng") : ("far") ) + "Src" ).split(",")[0];
										img.style.cursor = "hand";
									}
									else
									{
										var callers = img.callers;
										callers[callers.length] = totalElements[i];
										img.callers = callers;
									}

									totalElements[i].setAttribute("language", img.callers[0].getAttribute("language") );
									changeDirectionAlignment( totalElements[i], totalElements[i].getAttribute("language") );
				
									img.onmouseover = function() 
														{
															var currentSrc = ( this.callers[0].getAttribute("language") == "far" ) ? ("engSrc") : ("farSrc");
															
															if ( this.getAttribute(currentSrc).split(",").length > 1 )
																this.src = this.getAttribute(currentSrc).split(",")[1];
														}
									img.onmouseout = function() 
														{
															var currentSrc = ( this.callers[0].getAttribute("language") == "far" ) ? ("engSrc") : ("farSrc");
															this.src = this.getAttribute(currentSrc).split(",")[0];
														}

									img.onclick = function()
																		{
																			var currentLang = this.callers[0].getAttribute("language");
																			this.src = this.getAttribute( currentLang + "Src" );
																			var toBeLang = ( currentLang == "far" ) ? ("eng") : ("far");

																			for ( i in this.callers )
																			{
																				if ( eval(this.callers[i].getAttribute("changeDirOnLangSet")) )
																					changeDirectionAlignment( this.callers[i], toBeLang);

																				this.callers[i].setAttribute("language", toBeLang);
																			}
																			
																			// Haha! If I didn't put this in here, when you clicked on the image,
																			// its src would have been corrupted and you couldn't see any image
																			// anymore, unless you pointed your mouse to another place. I don't
																			// know why, but I know that when we change the src, it can not handle
																			// the onmouseover/out that we defined previously, so, in the end of the
																			// operation, I call the onmouseover handler manually to fix this bug!
																			img.onmouseover();
																		}

									totalElements[i].changeLanguage = function()
																		{
																			var currentLang = this.getAttribute("language");
																			var newLang = ( currentLang == "far" ) ? ( "eng" ) : ( "far" );
																			
																			var indiName = this.getAttribute("indicator").split(",")[1];
																			
																			var imgs = new Array();
																			imgs = retElementsArray( "img", "", indiName );
																			var img = imgs[0];
																			
																			
																			for ( i in img.callers )
																			{
																				if ( eval(img.callers[i].getAttribute("changeDirOnLangSet")) )
																					changeDirectionAlignment( img.callers[i], newLang );

																				img.callers[i].setAttribute("language", newLang);
																			}
																			
																			img.src = img.getAttribute( currentLang + "Src" ).split(",")[0];
																		}
									break;
			}
									
		}
		else
		{
			// Initiating the direction and allignment of our element.
			// Other elements that have an indicator can not be set
			// before we find out the language of the first element
			// that uses that indicator. So, their initiation occurs
			// inside the above switch. But for those poor elements
			// without an indicator, we're sure that the language given
			// in their original tag, is surely the correct language. So,
			// we set it in here.
			changeDirectionAlignment( totalElements[i], totalElements[i].getAttribute("language") );
	
			// This is for those elements who don't have an indicator.
			// It's so easy, when Ctrl+Alt is pressed, it's no need to
			// set the relative radio buttons/image/picture to the new
			// language and find all the other inputs/textareas that 
			// depend on each other and set them too. Just simply change
			// the current element's language and we're done!
			totalElements[i].changeLanguage = function()
												{
														var currentLang = this.getAttribute("language");
														var newLang = ( currentLang == "far" ) ? ( "eng" ) : ( "far" );
														if ( eval(this.getAttribute("changeDirOnLangSet")) )
															changeDirectionAlignment( this, newLang );
														this.setAttribute("language", newLang);
														return true;
												}
		}

		// In this part of code we're gonna make the onkeyup, onkeydown,
		// and onkeypress events which are the same for all the inputs/
		// textareas.
		// Only thing that makes a different is in the onkeydown/up when
		// alt+ctrl is pressed, I call the language changer handler for
		// that element itself. So, that handler is created before, it 
		// means even though the name of the function is the same for
		// everything, but it's created differenly for different elements.

		// The onkeypress function is exactly the same for everything, not
		// even a function that makes any differene logically!

		totalElements[i].onkeyup = function(e)
										{
											keyup( e||event, this );
										}
																		
		totalElements[i].onkeydown = function(e)
										{
											keydown( e||event, this );
										}
		
		totalElements[i].onkeypress = function(e)
										{
											var key = ( window.event ) ? ( window.event.keyCode ) : ( e.which );
											if ( key == 13 ) 
												return true; 
											if ( key > 127 )
												return false;

											// Check if the language is farsi
											if ( this.getAttribute("language") == "far" ) 
											{
												if ( key == 0x0020 && window.event.shiftKey) // Shift-space -> ZWNJ        What's this?
													window.event.keyCode = 0x200C;
												else
													window.event.keyCode = farsikeys[key - 0x0020];
												
												// This is for J key in Farsi! Like J in Bijan!!
												if (farsikeys[key - 0x0020] == 92) 
													window.event.keyCode = 0x0698;
												// This one is for P in Farsi! Better to say the ~ key under the ESC button!
												if (farsikeys[key - 0x0020] == 8205) 
												{
													window.event.keyCode = 0x067E;
												}
											}
										
											return true;
										}
	}
}

function keyup( oEvent, element )
{	
	var key = oEvent.keyCode;
	if ( element.getAttribute("keyU") == element.getAttribute("keyD" ) != "" )
	{
		element.setAttribute("keyD", "");
		element.setAttribute("keyU", "");
	}

	if ( 
			(key == 17 &&  element.getAttribute("keyD") == 18) ||
			(key == 18 &&  element.getAttribute("keyD") == 17)
	   )
		element.changeLanguage();
	else
		element.setAttribute("keyU", key);
}
function keydown( oEvent, element )
{
	var key = oEvent.keyCode;
	if ( element.getAttribute("keyU") == element.getAttribute("keyD" ) != "" )
	{
		element.setAttribute("keyD", "");
		element.setAttribute("keyU", "");
	}

	// We don't need the condition that we used in keyup, because it never 
	// happens that the  combination that we're looking for, takes place 
	// when we only click on alt||ctrl. So, we should only check it when 
	// we release a button.
	element.setAttribute("keyD", key);

}

// The Farsi language charcodes, if you simply change it to Japanese
// charcodes for example, you will have an easyJapanese!
var farsikeys = [
	0x0020, 0x0021, 0x061B, 0x066B, 0x00A4, 0x066A, 0x060C, 0x06AF,      
	0x0029, 0x0028, 0x002A, 0x002B, 0x0648, 0x002D, 0x002E, 0x002F,      
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
	0x0038, 0x0039, 0x003A, 0x06A9, 0x003E, 0x003D, 0x003C, 0x061F,      
	0x066C, 0x0624, 0x200C, 0x0698, 0x064A, 0x064D, 0x0625, 0x0623,      
	0x0622, 0x0651, 0x0629, 0x00BB, 0x00AB, 0x0621, 0x004E, 0x005D,      
	0x005B, 0x0652, 0x064B, 0x0626, 0x064F, 0x064E, 0x0056, 0x064C,            
	0x0058, 0x0650, 0x0643, 0x062C, 0x0698, 0x0686, 0x00D7, 0x0640,            
	0x067E, 0x0634, 0x0630, 0x0632, 0x06CC, 0x062B, 0x0628, 0x0644,            
	0x0627, 0x0647, 0x062A, 0x0646, 0x0645, 0x0626, 0x062F, 0x062E,            
	0x062D, 0x0636, 0x0642, 0x0633, 0x0641, 0x0639, 0x0631, 0x0635,            
	0x0637, 0x063A, 0x0638, 0x007D, 0x007C, 0x007B, 0x007E            
	
];
