//
// Default vars, can be overridden by Chan itself via HEAD SCRIPT insertion on Page_Load (ish)
//
var Chan_Chan_Root              = "/Chan";
var Chan_Version				= "0.7.1.10";
var Chan_IsIE6 = false /*@cc_on || @_jscript_version <= 5.7 @*/;


//
// Default Language is English, these strings can be overridden by subsequently loading a custom Locale script
//
var STR_FORM_IS_INVALID         = "Could not send the form because it was invalid, please correct the highlighted entries before trying again.";
var STR_FIELD_IS_MANDATORY      = "This field is required.";

var STR_STRING_TOO_SHORT        = "Entered text is too short, currently: {1} chars, Min: {2}";		//{1} = Current Length , {2} = Minimum Length
var STR_STRING_TOO_LONG         = "Entered text is too long, currently: {1} chars, Max: {2}";		//{1} = Current Length , {2} = Maximum Length

var STR_NUMERIC_IS_NAN          = "A numeric value is required";
var STR_NUMERIC_REFORMATTED     = "This field has been reformatted to meet its Min,Max and Decimal Places, please check the new value before re-submitting.";



//
// Client side page load
//
function C__Page_Load()
{
	//apply any IE6 fixes in ie6.js
	//if( window.Chan_IE6_Page_Load )
	//	Chan_IE6_Page_Load();
	
	//initialise any Grids in Controls_Grid.js
	//if( window.CC_Grid_Load )
	//	CC_Grid_Load();
		
	//initialise any Image Panels
	if( window.CC_ImagePanel_Init )
		CC_ImagePanel_Init();	
		
	//initialise any scrolling panels
	//if( window.C__Scroll_Init )
	//	C__Scroll_Init();

}

//
// Generic Event container
//
function C__Event( e )
{
	if (!e) 
		var e = window.event;
		
	this.Event = e;
		
	if (e.target) 
		this.Target = e.target;
	else 
	if (e.srcElement) 
		this.Target = e.srcElement;
	
	// Safari
	if (this.Target.nodeType == 3)
		this.Target = this.Target.parentNode;

	return this;
}

//
// Convert input string into [a] HTML escaped string
//
function C__IntoHtml( s )
{
	if( s == "" )
		return "&nbsp;";
		
	return s.replace( /&/g , "&amp;" ).replace( /\"/g , "&quot;" ).replace( /</g , "&lt;" ).replace( />/g , "&gt;" ).replace( / /g , "&nbsp;" );
}

//
// Convert input string out o[f a] HTML escaped string
//
function C__OutoHtml( s )
{
	if( s == "&nbsp;" )
		return "";
		
	return s.replace( /&nbsp;/g , " " ).replace( /&quot;/g , "\"" ).replace( /&lt;/g , "<" ).replace( /&gt;/g , ">" ).replace( /&amp;/g , "&" );
}








//
//	SHARED / GENERIC FUNCTIONS
//

//
// Generic validation function, switches on object type
//
function C__Validate( input_object )
{
	var type = input_object.className;
	
	//multiple class names?! find the chan one in there
	if( type.indexOf( " " ) > -1 )
	{
		var a = type.split( " " );
		var ia = 0;
		var na = a.length;
		
		for( ia = 0 ; ia < na ; ia ++ )
			if( a[ia].indexOf( "CC_" ) > -1 )
			{
				type = a[ia];
				break;
			}
	}
	
	//remove suffixes
	//type = type.replace( "_Wide" , "" );
	//type = type.replace( "_Thin" , "" );
	
	switch( type )
	{
		case "CC_Numeric":
			return CC_Numeric_Validate( input_object );
			break;
	
		case "CC_Upload":
			return CC_Upload_Validate( input_object );
			break;
	
		case "CC_TextBox":
			return CC_TextBox_Validate( input_object );
			break;
		
		case "CC_Password":
			return CC_TextBox_Validate( input_object );
			break;
	
		default:
			break;
	}	
	
	return true;
}


//
// Shows or hides a control's invalid image depending on the control's
// parameters, specifically it's Valid bool
//
function C__SetInvalidImage( ctl )
{
	var IMG_ID = ctl.ID + "___InvalidImage";
	var IMG = document.getElementById( IMG_ID );
	
	//if the control is Valid remove any "invalid" image associated with it	
	if( ctl.Valid )
	{
		//remove any old Invalid image from the DOM
		if( IMG )
			IMG.parentNode.removeChild( IMG );
	}
	else
	{
		//no image, spawn one
		if( !IMG )
		{
			IMG = document.createElement( "IMG" );
			IMG.id = IMG_ID;
			IMG.src = Chan_Chan_Root + "/Invalid.gif";
			IMG.className = "CC_Invalid";
			
			//i.e. insertAfter(this) !
			ctl.DOMObject.parentNode.insertBefore( IMG , ctl.DOMObject.nextSibling );	
		}

		IMG.setAttribute( "alt" , ctl.InvalidReason );
		IMG.setAttribute( "title" , ctl.InvalidReason );
	}
}

//
// Adds leading zeros to a numeric input
//
function C__AddLeadingZeros( n , digits )
{ 
	var a = (n + ".").split(".");

	while(a[0].length < digits)
	{
		a[0] = "0" + a[0]; 
	}
	
	if( a[1] != "" )
		return a[0] + "." + a[1];
	
	return a[0];
}

//
// Accepts a string in the format "A {1} C" with a number of substitutes 
// e.g. var1 = "B". Not very scientific at the moment but it gets the job done
//
function C__String_Reduce( str , var1 , var2 , var3 , var4 )
{
	str = str.replace( "{1}" , var1 != null ? var1 : "" );
	str = str.replace( "{2}" , var2 != null ? var2 : "" );
	str = str.replace( "{3}" , var3 != null ? var3 : "" );
	str = str.replace( "{4}" , var4 != null ? var4 : "" );
	
	return str;
}









//
//	EVENTS AND VALIDATION
//
//	CLIENT EVENTS SUCH AS ONKEYDOWN AND ONBLUR CAN PERFORM SUPERFICIAL REFORMATTING
//	OF AN INPUT'S CONTENTS, BUT MUST NOT SET/UNSET THE INVALID IMAGE
//
//	VALIDATION SHOULD ALSO PERFORM THE SAME CLEANUP FIRST AND WARN THE USER (SO
//	CLEANUP NOT CAUGHT IN ONBLUR CAN BE ADDRESSED). ONLY THE FORM SUBMIT VALIDATION
//	FUNCTION MAY SET/UNSET THE INVALID IMAGE
//

//  ----------------------------------------------------------------------------
//
//  Chan.Controls.Form
//
//  ----------------------------------------------------------------------------

function CC_Form_Validate( input_object , notify_object )
{
	var ii = 0;
	var ni = input_object.elements.length;
	var Valid = true;
	var ValidTemp = true;
	
	for( ii = 0 ; ii < ni ; ii ++ )
	{
		//need to fetch and validate all controls, not just fail out on
		//the first one we find. but we don't want the whole form to be
		//flagged as Valid if the very last control happens to be valid
		ValidTemp = C__Validate( input_object.elements[ ii ] );	
			
		if( !ValidTemp)
			Valid = false;
	}
	
	//notify the user if bad things happenned either via  a DOM object
	//(such as a bolded paragraph) or via a standard alert popup
	if( !Valid )
	{
		var s = STR_FORM_IS_INVALID;
		
		if( notify_object != null )
			notify_object.innerHTML = s;
		else
			alert( s );
	}
	
	return Valid;
}


//  ----------------------------------------------------------------------------
//
//	CLASSES
//
//	All should publicly expose
//
//	ID			(string)
//	Text		(string)
//	Value		(variant type)
//	Valid		(bool)
//	InvalidReason	(string)
//	DOMObject	(HTML DOM input handle)
//
//	Most will also have the following:
//
//	Mandatory	(bool)
//
//  ----------------------------------------------------------------------------

//  ----------------------------------------------------------------------------
//
//  Chan.Controls.DecimalBox
//  Chan.Controls.Int32Box
//
//  ----------------------------------------------------------------------------

//
// Converts a DOM input into a local Chan Numeric Control
//
function CC_Numeric_New( input_object )
{
	this.DOMObject	= input_object;
	this.ID			= String( input_object.id );
	this.Text		= String( input_object.value );
	this.Value		= parseFloat( input_object.value );	
	this.Valid		= true;						//assume valid
	this.InvalidReason = "";

	this.Mandatory	= (input_object.getAttribute( "mandatory" ) == "true");
	
	//
	// Attributes / parameters - may be supported by html5 one day
	//
	
	this.Min		= parseFloat( input_object.getAttribute( "min" ) );
	this.Max		= parseFloat( input_object.getAttribute( "max" ) );

	//decimal places, default is none (integer)
	this.Places		= parseInt( input_object.getAttribute( "places" ) );
	if( isNaN( this.Places ) )
		this.Places = 0;
		
	//leading zeros, default is 0 (ignore)
	this.Digits		= parseInt( input_object.getAttribute( "digits" ) );
	if( isNaN( this.Digits ) )
		this.Digits = 0;
		
	return this;
	
	//functions:
	//void ...OnBlur( input )       - actual HTML embedded event
	//bool ...OnBlur_Ctl( Control )
	//bool ...Validate( input )
}

//
// Validate function, invoked from parent form
//
function CC_Numeric_Validate( input_object )
{
	var ctl = CC_Numeric_New( input_object );
	
	//call the onblur first, which will autoformat any min/max/dp settings for us
	//it will return false if it made changes to the value
	if( !CC_Numeric_OnBlur_Ctl( ctl ) )
	{
		//ctl.InvalidReason = STR_NUMERIC_REFORMATTED;
		//ctl.Valid = false;
	}

	if( ctl.Valid && ctl.Mandatory && ctl.Text == "" )
	{
		ctl.InvalidReason = STR_FIELD_IS_MANDATORY;
		ctl.Valid = false;
	}

	//basic negateable numeric test
	if( ctl.Valid && isNaN( ctl.Value ) )
	{
		ctl.InvalidReason = STR_NUMERIC_IS_NAN;
		ctl.Valid = false;
	}
	
	//show (or hide) our invalid marker image
	C__SetInvalidImage( ctl );

	return ctl.Valid;
}

//
// onblur, reformat according to validation rules
//
function CC_Numeric_OnBlur( input_object )
{
	var ctl = CC_Numeric_New( input_object );
	
	CC_Numeric_OnBlur_Ctl( ctl );
}

//
// overload of above - accepts custom control object
//
function CC_Numeric_OnBlur_Ctl( ctl )
{
	var Clean = true;
	
	//set decimal places
	if( !isNaN(ctl.Value) )
	{
		//floor to min value
		if( !isNaN(ctl.Min) && ctl.Value < ctl.Min )
		{
			ctl.Value = ctl.Min;
			Clean = false;
		}

		//ceiling to max value
		if( !isNaN(ctl.Max) && ctl.Value > ctl.Max )
		{
			ctl.Value = ctl.Max;
			Clean = false;
		}
		
		var sTmp = String( ctl.Value );
				
		//fixed number of decimal places and/or leading zeros
		//BUG : should truncate not round!
		if( ctl.Value.toFixed )
		{
			sTmp = ctl.Value.toFixed( ctl.Places )
		
			if( sTmp != parseFloat(ctl.Text) )
				Clean = false;
		}
	
		//write the value back into the document
		if( ctl.Digits > 0 )
			ctl.DOMObject.value = AddLeadingZeros( sTmp , ctl.Digits );
		else
			ctl.DOMObject.value = sTmp;
	}
	
	return Clean;
}




//  ----------------------------------------------------------------------------
//
//	Chan.Controls.TextBox
//
//  ----------------------------------------------------------------------------

//
// Converts a DOM input into a local Chan TextBox Control
//
function CC_TextBox_New( input_object )
{
	this.DOMObject	= input_object;
	this.ID			= String( input_object.id );
	this.Text		= String( input_object.value );
	this.Value		= this.Text;	
	this.Valid		= true;						//assume valid
	this.InvalidReason = "";
	
	this.Mandatory	= (input_object.getAttribute( "mandatory" ) == "true");
	
	//input.text will have this natively handled but textarea will not
	this.MaxLength	= parseInt( input_object.getAttribute( "maxlength" ) );
	
	//mainly needed for Password subtype
	this.MinLength	= parseInt( input_object.getAttribute( "minlength" ) );
		
	return this;
	
	//functions:
	//bool ...Validate( input )		
}

function CC_TextBox_Validate( input_object )
{
	var ctl = CC_TextBox_New( input_object );
	
	//alert( ctl.Text.length + " / " + ctl.MaxLength );

	//basic mandatory check
	if( ctl.Mandatory && ctl.Text == "" )
	{
		ctl.InvalidReason = STR_FIELD_IS_MANDATORY;
		ctl.Valid = false;
	}
		
	//need to handle this for textarea
	if( ctl.Valid && ctl.Text.length > ctl.MaxLength )
	{
		ctl.Valid = false;
		ctl.InvalidReason = C__String_Reduce( STR_STRING_TOO_LONG , ctl.Text.length , ctl.MaxLength );
	}
	//	ctl.DOMObject.value = ctl.Text.substr( 0 , ctl.MaxLength );
	
	//min length is for username / password fields etc.
	if( ctl.Valid && ctl.Text.length < ctl.MinLength )
	{
		ctl.Valid = false;
		ctl.InvalidReason = C__String_Reduce( STR_STRING_TOO_SHORT , ctl.Text.length , ctl.MinLength );
	}

	C__SetInvalidImage( ctl );
	
	return ctl.Valid;
}


//
// onblur, reformat according to validation rules
//
//function CC_TextBox_OnBlur( input_object )
//{
//
//}
//
//
// overload of above - accepts custom control object
//
//function CC_TextBox_OnBlur_Ctl( ctl )
//{
//	
//}

//  ----------------------------------------------------------------------------
//
//	Chan.Controls.Upload
//
//  ----------------------------------------------------------------------------

//
// Converts a DOM input into a local Chan TextBox Control
//
function CC_Upload_New( input_object )
{
	this.DOMObject	= input_object;
	this.ID			= String( input_object.id );
	this.Text		= String( input_object.value );
	this.Value		= this.Text;	
	this.Valid		= true;						//assume valid
	this.InvalidReason = "";
	
	this.Mandatory	= (input_object.getAttribute( "mandatory" ) == "true");
		
	return this;
	
	//functions:
	//bool ...Validate( input )		
}

function CC_Upload_Validate( input_object )
{
	var ctl = CC_Upload_New( input_object );

	//basic mandatory check
	if( ctl.Mandatory && ctl.Text == "" )
	{
		ctl.InvalidReason = STR_FIELD_IS_MANDATORY;
		ctl.Valid = false;
	}

	C__SetInvalidImage( ctl );
	
	return ctl.Valid;
}

//
// Client side implimentation of the "Change" button which allows
// you to select a different file for upload than the current one
//
function CC_Upload_Change( event , UploadID )
{
	//UploadID is original upload control
	//UploadID + "___Current" is disabled textbox showing filename
	//UploadID + "___Change" is "Change" button
	//UploadID + "___Path" is temp path on server
	
	/*alert( "1/5" );
	
	var upl = document.getElementById( UploadID );
	var txt = document.getElementById( UploadID + "___Current" );
	var btn = document.getElementById( UploadID + "___Change" );
	//var hid = document.getElementById( UploadID + "___Path" );
	
	alert( "2/5" );
	
	//if upl exists then we're Undo-ing our request to change
	//files
	
	if( upl != null )
	{
		document.removeNode( upl );
		
		//show the textbox
		txt.style.display = "";
		
		//change the button text
		btn.value = "Change";	
	}
	else
	{
		alert( "3/5" );
	
		//re-create the upload control
		upl = document.createElement( "INPUT" );
		upl.type = "file";
		upl.id = UploadID;
		upl.name = UploadID;
		upl.className = "CC_Upload";
		
		alert( "4/5" );
		
		//insert it before the textbox in the DOM tree
		txt.parentNode.insertBefore( upl , txt );
		
		alert( "5/5" );
		
		//hide the textbox
		txt.style.display = "none";
		
		//change the button text
		btn.value = "Undo";
	}*/
}




//
// Image 
//

//
// Used by an input type="image" to replace another image with 
// it's own zoomed in one
//
// ImageID   - id of target DOM IMG tag to change
// Path      - src for IMG tag
// FormName  - [Optional] - Name of form in which to assign Path to the INPUT named "[ImageID]" value (base64 encoded)
// Tag       - [Optional] - value to assign to an INPUT named "[ImageID]___Tag"
//
function CC_Image_Zoom( ImageID , Path , FormName , Tag )
{
	//incorrect javascript version, revert to postback
	if( !document.getElementById )
		return true;
	
	var h = document.getElementById( ImageID );
	
	//target image not found, fail silently
	if( !h )
		return false;
	
	h.src = Chan_Chan_Root + "/Clear.gif";		//clear it first whilst the desired one loads, devs can then put a "Loading..." background image behind it if they want
		
	//(new Image()).src = Path;
	
	h.src = Path;
	
	// Target Image may have its Remember parameter set to true so we'll need to set its
	// hidden form field also
	if( FormName != null && FormName != "" && document.forms[ FormName ] != null  && document.forms[ FormName ][ ImageID ] != null )
	{
		document.forms[ FormName ][ ImageID ].value = C__B64Encode( Path );
		
		if( Tag != null && document.forms[ FormName ][ ImageID + "___Tag" ] != null )
			document.forms[ FormName ][ ImageID + "___Tag" ].value = Tag;
	}
	
	return false;
}

//
// Simply switch an image's src
//
function CC_Image_Switch( hImage , Path )
{
	if( hImage == null )
		return;
	
	hImage.src = Path;
}

















//
// Helper Functions
//


//
// Base64 code adapted from Tyler Akins -- http://rumkin.com
//

var C__B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

function C__B64Encode(input) 
{
	if( input == null || input == "" )
		return "";

	//if( window.atob )
	//	return window.atob(input);
	
	var output = "";
	var c1, c2, c3;
	var e1, e2, e3, e4;
	var i = 0;

	do 
	{
	  c1 = input.charCodeAt(i++);
	  c2 = input.charCodeAt(i++);
	  c3 = input.charCodeAt(i++);

	  e1 = c1 >> 2;
	  e2 = ((c1 & 3) << 4) | (c2 >> 4);
	  e3 = ((c2 & 15) << 2) | (c3 >> 6);
	  e4 = c3 & 63;

	  if (isNaN(c2)) {
		 e3 = e4 = 64;
	  } else if (isNaN(c3)) {
		 e4 = 64;
	  }

	  output = C__Concat( output , C__B64.charAt(e1) , C__B64.charAt(e2) , C__B64.charAt(e3) , C__B64.charAt(e4) );
	  
	} while (i < input.length);

	return output;
}

function C__B64Decode(input) 
{
	if( input == null || input == "" )
		return "";

	//if( window.btoa )
	//	return window.btoa(input);
	
   var output = "";
   var c1, c2, c3;
   var e1, e2, e3, e4;
   var i = 0;

   // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
   input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

   do 
   {
      e1 = C__B64.indexOf(input.charAt(i++));
      e2 = C__B64.indexOf(input.charAt(i++));
      e3 = C__B64.indexOf(input.charAt(i++));
      e4 = C__B64.indexOf(input.charAt(i++));

      c1 = (e1 << 2) | (e2 >> 4);
      c2 = ((e2 & 15) << 4) | (e3 >> 2);
      c3 = ((e3 & 3) << 6) | e4;

      output = output + String.fromCharCode(c1);

      if (e3 != 64) 
	  {
         output = output + String.fromCharCode(c2);
      }
      if (e4 != 64) 
	  {
         output = output + String.fromCharCode(c3);
      }
   } while (i < input.length);

   return output;
}


//
// For quickly joining strings
//

function C__Concat( s1 , s2 , s3 , s4 , s5 , s6 , s7 , s8 )
{	
	var a = new Array();
	
	a.push(s1);
	a.push(s2);
	a.push(s3);
	a.push(s4);
	a.push(s5);
	a.push(s6);
	a.push(s7);
	a.push(s8);
		
	return a.join("");
}



