﻿var refreshTimeInterval = 3000;
var g_refreshCheckpoint_PollingInterval = 8000;
var g_refreshProperties_PollingInterval = 5000;
var defaultTimeoutInterval = 60000;
var g_CachedConnectionEnabled = true;
var disconnectInterval = 3600000; // One Hour
var disconnectTimer = null;
var statusTimer;
var KeySelectDirection = new SelectIndexDirection();

//ToDo: Remove this message. This should not be used anymore.
var disconnectedMessage = "Disconnected from server. Please refresh the window";

var VirtualizationPlatform = new VirtualizationPlatformEnum();
var VirtualMachineState = new VirtualMachineStatusEnum();
var DVDConnection = new DVDConnectionEnum();

// Duplication of Microsoft.VirtualManager.Remoting.VirtualizationPlatform
// To locally determin what type of host is running so that the correct Remote connect
// method can be used.
function VirtualizationPlatformEnum()
{
    this.Unknown = 0;
    this.VirtualServer = 1;
    this.HyperV = 2;
    this.VMWareVC3 = 3;
    this.VMWareESX = 4;
}

// Duplication of Microsoft.VirtualManager.Utils.VMComputerSystemState
// to locally determine the state of the machine independent of locale.
function VirtualMachineStatusEnum()
{
        this.Running = 0;
        this.PowerOff = 1;
        this.PoweringOff = 2;
        this.Saved = 3;
        this.Saving = 4;
        this.Restoring = 5;
        this.Paused = 6;
        this.DiscardSavedState = 10;
        this.Starting = 11;
        this.MergingDrives = 12;
        this.Deleting = 13;
        this.DiscardingDrives = 80;
        this.Pausing = 81; 
        this.UnderCreation = 100;
        this.CreationFailed = 101;
        this.Stored = 102;
        this.UnderTemplateCreation = 103;
        this.TemplateCreationFailed = 104;
        this.CustomizationFailed = 105;
        this.UnderUpdate = 106;
        this.UpdateFailed = 107;
        this.UnderMigration =  200;
        this.MigrationFailed = 201;
        this.CreatingCheckpoint = 210;
        this.DeletingCheckpoint = 211;
        this.RecoveringCheckpoint = 212;
        this.CheckpointFailed = 213;
        this.InitializingCheckpointOperation = 214;
        this.FinishingCheckpointOperation = 215;
        this.Missing = 220;
        this.HostNotResponding = 221;       
        this.Unsupported = 222;
        this.IncompleteVMConfig = 223;
        this.P2VCreationFailed = 240;
        this.V2VCreationFailed = 250;
        this.UnsupportedSharedFiles = 224;
        this.UnsupportedCluster = 225;
}

// Duplication of Microsoft.VirtualManager.Remoting.DVDConnection
function DVDConnectionEnum()
{
    this.NoMedia = 0;
    this.ISOImage = 1;
    this.PhysicalDrive = 2;
}

function clearContents(node)
{
    while (node.hasChildNodes())
    {
        node.removeChild(node.firstChild);
    }
}

function onFailedNotify(error, userContext)
{
    window.location = "Error.aspx"; //error already saved on server -> go to error page.
}

function onFailed(request, userContext)
{

}

function onFailedEmail(request, userContext)
{
    window.document.location.href = 'mailto:';
}

function contactAdmin()
{
    LiveStatusService.GetContactEmail(
            onRetrieveContactEmail,
            onFailedEmail);
}

function onRetrieveContactEmail(contactEmail)
{
    window.document.location.href = 'mailto:' + contactEmail;
}

function DoPostBackForControl ( control )
{
    if ( control == null )
        return;
    
    if ( control.name == undefined )
        return;
        
    __doPostBack(control.name, '' );
}

function GetClientLanguageString()
{
    if ( navigator.browserLanguage != null && navigator.browserLanguage != undefined )
    {
        return navigator.browserLanguage;
    }
    else if ( navigator.language != null && navigator.language != undefined )
    {
        return navigator.language;
    }
    else
    {
        return navigator.userLanguage;
    }
}

// Returns a numeric representation ( the number of bytes ) from
// a disk size string.
// For example, 45KB would return 46080 
// or 1MB would return 1048576
function GetNumericValueFromDiskSizeString( diskSizeString )
{
    if ( diskSizeString == null )
        return null;

    var unit = 0;
    var size = 0;
    
    diskSizeString = diskSizeString.replace(",", ""); //remove commas.
    
    var unitIndex = diskSizeString.indexOf ( "KB" );
    
    if ( unitIndex >= 0 )
    {
        unit = 1024; //KB         
    }
    else 
    {    
        unitIndex = diskSizeString.indexOf ( "MB" );
    
        if ( unitIndex >= 0 )
        {
            unit = 1048576; //MB
        }
        else
        {
            unitIndex = diskSizeString.indexOf ( "GB" );
            
            if ( unitIndex >= 0 )
            {
                unit = 1073741824; //GB
            }
        }
    }
        
    
    size = diskSizeString.substring( 0, unitIndex );
    return unit * size;   
}

function CloseWindow()
{
    window.open('','_self'); //suppress the "Are you sure you want to close" confirmation for test automation
    window.close();
}

function CenterObjectInWindow( object )
{
    if ( object == null )
        return;
        
    var windowWidth = document.body.offsetWidth;
    var windowHeight = document.body.offsetHeight;
    
    var objectWidth = object.offsetWidth;
    var objectHeight = object.offsetHeight;
        
    object.style.left = ( windowWidth - objectWidth ) /2 ;
    object.style.top = ( windowHeight - objectHeight ) /2;
}

function DisplayObject( object )
{
    if ( object != null && object.style != null )
    {
        object.style.display="";
    }
}

function HideObject ( object )
{
    if ( object != null && object.style != null )
    {   
        object.style.display="none";
    }    
}

function ObjectExists( object )
{
    if ( typeof ( object ) == 'undefined' )
        return false;
    
    if ( object == null )
        return false;
        
    return true;
}

function CloneArray( source, destination )
{
    for ( x in source )
    {
        destination[x] = source[x];
    }
}

function GetObj(ID)
{
    return document.getElementById(ID);
}

//Creates a string representation of an error DIV which is used
//as the "on error" display for a failed activeX load.
function createRemoteControlObjectErrorDiv( titleMessage, bodyMessage )
{
    if ( titleMessage == null || titleMessage == "" || bodyMessage == null || bodyMessage == "")
        return "";

    var template = '<div class="activeXFailedInstallOuterDiv"><span class="activeXFailedInstallIconSpan"><img src="Images/SmallIcons/icon_Warning.png" alt=""/></span><span class="activeXFailedInstallTitleSpan">{0}</span><div class="activeXFailedInstallMessageDiv">{1}</div></div></object>';
   
    template = template.replace( "{0}", titleMessage );
    template = template.replace( "{1}", bodyMessage );
    
    return template;   
}

function appendVMRCObject ( parentElement )
{
    if ( parentElement == null )
        return;
        
    var newInnerHTML = '<object style="width:100%;height:100%" ' +
				'codebase="/activex/VMRCActiveXClient.cab#version=1,1,603,0" ' +
				'classid="CLSID:D3CCEFAF-8EE1-40FE-BE25-366E2B016DAB">' +
				'<param name="ShrinkEnabled" value="1"/>' +
				'<param name="AdministratorMode" value="0"/>' +
				'<param name="MenuEnabled" value="0"/>' +
				'<param name="ReducedColorsMode" value="0"/>' +
				'<param name="AutoSize" value="1"/>' +				
				'<param name="ViewOnlyMode" value="0"/>' +
				'<param name="HostKey" value="Key_RightAlt"/>';									

    //enter localized edition of the error text.
	var lblVirtualServerFailedInstallTitle = document.getElementById('lblVirtualServerFailedInstallTitle');
	var lblVirtualServerFailedInstallMessage = document.getElementById('lblVirtualServerFailedInstallMessage');
	   
    if ( lblVirtualServerFailedInstallTitle != null && lblVirtualServerFailedInstallMessage != null )
	{
	    newInnerHTML += createRemoteControlObjectErrorDiv( ReadInnerText(lblVirtualServerFailedInstallTitle), ReadInnerText(lblVirtualServerFailedInstallMessage) );	    
	} 	
	newInnerHTML += '</object>';
			
    parentElement.innerHTML = newInnerHTML;				
	parentElement.firstChild.ShrinkEnabled = true; //doesn't get set by param for some reason
	parentElement.firstChild.AdministratorMode = false;
	parentElement.firstChild.style.width = "100%";	
	parentElement.firstChild.style.height = "100%";  

}

function appendViridianObject ( parentElement )
{
    if ( parentElement == null )
        return;
    
    var newInnerHTML = "";

    //This variable is filled in at build time with the correct version number.
    var hyperVActiveXVersion = "2,0,4521,0"; 

    //If the version number is still a string replace with the default.
    if( hyperVActiveXVersion.indexOf('BUILD_VERSION_COMMAS') > 0 )
	hyperVActiveXVersion = "1,0,0,0"; //for building in visual studio
    
    if ( navigator.platform.toLowerCase() == "win64" )
    {
        newInnerHTML = "<object class='hypervAX' codebase='./activex/vmmctlax_amd64.cab#version=" + hyperVActiveXVersion + "' classid='CLSID:AA2FCC44-64E5-437A-AEDE-8854387EB9F4'>";
    }
    else 
    {    
        newInnerHTML = "<object class='hypervAX' codebase='./activex/vmmctlax_i386.cab#version=" + hyperVActiveXVersion + "' classid='CLSID:AA2FCC44-64E5-437A-AEDE-8854387EB9F4'>";
    }    
    
    //Insert localized error text
    var lblHyperVFailedInstallTitle = document.getElementById('lblHyperVFailedInstallTitle');
	var lblHyperVFailedInstallMessage = document.getElementById('lblHyperVFailedInstallMessage');
		
    if ( lblHyperVFailedInstallTitle != null && lblHyperVFailedInstallMessage != null )
	{
	    newInnerHTML += createRemoteControlObjectErrorDiv( ReadInnerText(lblHyperVFailedInstallTitle), ReadInnerText(lblHyperVFailedInstallMessage) );	    
	} 	
	newInnerHTML += '</object>';
                    
    parentElement.innerHTML = newInnerHTML;      
    
}

function appendRemoteDesktopObject ( parentElement )
{
    appendViridianObject( parentElement ); //use hyper-v's remote desktop method.
    parentElement.UsingRemoteDesktop = true;
}

function appendVMWareObject ( parentElement, VM )
{    
    if ( parentElement == null || VM == null )
        return;        
        
    var HostName = VM.Host;
    var HostVersion = VM.HostVirtualizationVersion;
    
    var vmwareMksCLSID = "338095E4-1806-4ba3-AB51-38A3179200E9"; // clsid for post vmware 3.0
    
    if ( HostVersion != null && HostVersion.startsWith("3.0") ) //3.0.X versions use the old clsid
    {
        vmwareMksCLSID = "DC7D77DA-E1AC-4D40-930B-B87B2954E034"; 
    }
    
    //ESX 3i does not have the activeX on it's Host, so we must get it from the VC server.
    if ( VM.IsRunningOnEmbeddedHost && VM.VirtualizationManager != null && VM.VirtualizationManager != "" )
    {   
        HostName = VM.VirtualizationManager;
    }
    
    //ToDo: Update with correct path for firefox etc.
    var newInnerHTML = '<object codebase="https://' + HostName + 
                '/ui/plugin/msie/vmware-mks.cab#version=2,0,1,0" ' +
				'width="100%" height="100%" ' + 
				'classid="CLSID:' + vmwareMksCLSID + '">' + 
				'<PARAM NAME="_cx" VALUE="27120">' + 
				'<PARAM NAME="_cy" VALUE="20346">' + 
				'<PARAM NAME="fullScreen" VALUE="0">';

    //enter localized edition of the error text.
	var lblVMWareMksFailedInstallTitle = document.getElementById('lblVMWareMksFailedInstallTitle');
	var lblVMWareMksFailedInstallMessage = document.getElementById('lblVMWareMksFailedInstallMessage');
	
    //Currently the host name is not readable if it is stopped/paused etc.
    //Without the host name, the activeX path is not calculatable.
    if ( HostName == null || HostName == "" ) 
    {
        newInnerHTML += "<div/>"; //the activeX will install when the vm is running.
    }
    else if ( navigator.platform.toLowerCase() == "win64" )
    {
        var lblVMWareMks64bitNotSupportedTitle = document.getElementById('lblVMWareMks64bitNotSupportedTitle');
	    var lblVMWareMks64bitNotSupportedMessage = document.getElementById('lblVMWareMks64bitNotSupportedMessage');
	    
	    newInnerHTML += createRemoteControlObjectErrorDiv( ReadInnerText(lblVMWareMks64bitNotSupportedTitle), ReadInnerText(lblVMWareMks64bitNotSupportedMessage) );
        
    }
    else if ( lblHyperVFailedInstallTitle != null && lblHyperVFailedInstallMessage != null )
	{
	    var MessageText = ReadInnerText(lblVMWareMksFailedInstallMessage);
	    
	    if ( MessageText != null )
	    {
	        MessageText = MessageText.replace("%host%", HostName );
	        MessageText = MessageText.replace("%hosturl%", "https://" + HostName + "/ui/" );
	        
	        newInnerHTML += createRemoteControlObjectErrorDiv( ReadInnerText(lblVMWareMksFailedInstallTitle), MessageText );	    
        }	    
	} 	
	newInnerHTML += '</object>';			
     
    parentElement.innerHTML = newInnerHTML;				
    
}


function ConnectVMRCRemoteControl( vmmrcControl, VM, thumbnailMode )
{
    if ( vmmrcControl == null || VM == null )
        return;
        
    if ( g_CachedConnectionEnabled == true && g_CachedConnectionObject == null )
    {
        var successFunction = function ( RdpConnectionParameters )
        {  
            g_CachedConnectionEnabled = ( RdpConnectionParameters != null );                
            g_CachedConnectionObject = RdpConnectionParameters;
            ConnectVMRCRemoteControl ( vmmrcControl, VM, thumbnailMode );
        }
        
        LiveStatusService.GetRdpConnectionParameters( successFunction, onFailedNotify );
        return;
    }

    if ( thumbnailMode == true )
    {
        vmmrcControl.ViewOnlyMode = true; //set readonly for thumbnails
    }    

    try
    {       
        if ( vmmrcControl.ServerAddress == "" )
        {   
            vmmrcControl.ServerDisplayName = VM.Name;
            vmmrcControl.ServerPort = VM.HostPort;        
            vmmrcControl.ServerAddress = VM.Host;  
            
            if ( g_CachedConnectionEnabled == true && g_CachedConnectionObject != null )
            {
                vmmrcControl.UserDomain = g_CachedConnectionObject.Domain;
                vmmrcControl.UserName = g_CachedConnectionObject.UserName;
                vmmrcControl.UserPassword = g_CachedConnectionObject.Password;
            }   
            
            vmmrcControl.Connect();    
        }    
        else if(vmmrcControl.State == 0 ||   //Not Connected            
                vmmrcControl.State == 4)     //Connection Failed
        {
            vmmrcControl.Connect();             
        }   
        else
        {
             return; //VMRC already connected so do nothing
        }
    }
    catch ( exp )
    {
        //if the activeX is not installed, the connect method will fail. 
        //we let it fail silently until it gets installed.
    }
}

function ConnectViridianRemoteControl( hyperVControl, VM, thumbnailMode, completeCallback )
{
    var ResultObject = new ViridianConnectionErrorObject();
    var VMInstanceID = VM.VMId;    
    var port = VM.HostPort;
    
    if ( g_CachedConnectionEnabled == true && g_CachedConnectionObject == null )
    {
        var successFunction = function ( RdpConnectionParameters )
        {              
            g_CachedConnectionEnabled = ( RdpConnectionParameters != null );                
            g_CachedConnectionObject = RdpConnectionParameters;
            ConnectViridianRemoteControl ( hyperVControl, VM, thumbnailMode, completeCallback );
        }
        
        LiveStatusService.GetRdpConnectionParameters( successFunction, onFailedNotify );
        return;
    }
    
    var width = 800; //default resolution
    var height = 600;
    
    if ( hyperVControl.offsetWidth != null && hyperVControl.offsetWidth != 0 )
        width = hyperVControl.offsetWidth;
    if ( hyperVControl.offsetHeight != null && hyperVControl.offsetHeight != 0 )
        height = hyperVControl.offsetHeight;
    
    try
    {
        hyperVControl.EnableThumbnailMode = thumbnailMode;        
        
        if ( g_CachedConnectionEnabled == true && g_CachedConnectionObject != null )
        {
            hyperVControl.UserDomain = g_CachedConnectionObject.Domain;
            hyperVControl.UserName = g_CachedConnectionObject.UserName;
            hyperVControl.UserPassword = g_CachedConnectionObject.Password;
        }        
        
        if ( hyperVControl.CanConnectViaHost() )
        {   
            hyperVControl.ConnectViaHost(VM.Host, port, width, height, VMInstanceID);
        }
        else        
        {
            if ( hyperVControl.IsOSCapableToConnectViaHost() )
            {
                ResultObject.ErrorOccurred = true;
                ResultObject.ConnectViaHostFailDueToCredSSPDisabled = true;                
            }        
            else if ( VM.ComputerName != null )
            {
                hyperVControl.Connect(VM.ComputerName, width, height);
            }
            else
            {                
                ResultObject.ErrorOccurred = true;
                ResultObject.ConnectViaHostFailDueToNoICs = true;
            }      
        }        
    }
    catch ( hypvException )
    {
        ResultObject.ErrorOccurred = true;
        //ToDo: Determine how to handle errors here. Should we show a predefined message or this exception message.
        //ErrorOccurred.ErrorMessage = hypvException.Message;         
    }
    
    if ( ObjectExists( completeCallback ) )
    {
        completeCallback ( ResultObject );
    }
}

function ConnectRemoteDesktopControl(rdControl, VM, completeCallback)
{   
    if ( g_CachedConnectionEnabled == true && g_CachedConnectionObject == null )
    {
        var successFunction = function ( RdpConnectionParameters )
        {  
            g_CachedConnectionEnabled = ( RdpConnectionParameters != null );                
            g_CachedConnectionObject = RdpConnectionParameters;
            ConnectRemoteDesktopControl ( rdControl, VM, completeCallback );
        }
        
        LiveStatusService.GetRdpConnectionParameters( successFunction, onFailedNotify );
        return;
    }        
    
    var ResultObject = new ViridianConnectionErrorObject();
        
    var width = 800; //default resolution
    var height = 600;
    
    if ( rdControl.offsetWidth != null )
        width = rdControl.offsetWidth;
    if ( rdControl.offsetHeight != null )
        height = rdControl.offsetHeight;
    
    try
    {    
        if ( g_CachedConnectionEnabled == true && g_CachedConnectionObject != null )
        {
            rdControl.UserDomain = g_CachedConnectionObject.Domain;
            rdControl.UserName = g_CachedConnectionObject.UserName;
            rdControl.UserPassword = g_CachedConnectionObject.Password;
        }
        
        if ( VM.ComputerName != null )
        {
            rdControl.Connect(VM.ComputerName, width, height);
        }
        else
        {                
            ResultObject.ErrorOccurred = true;
            ResultObject.ConnectViaHostFailDueToNoICs = true;
        }           
    }
    catch ( rdException )
    {
        ResultObject.ErrorOccurred = true;       
    }
    
    if ( ObjectExists( completeCallback ) )
    {
        completeCallback ( ResultObject );
    }
}

function ViridianConnectionErrorObject()
{
    var ErrorOccurred = false;
    var ErrorMessage;
    
    //Special case - cannot connect via host and no ics
    var ConnectViaHostFailDueToNoICs = false;
    //Special case 2 - OS can support CredSSP but it is not enabled. 
    var ConnectViaHostFailDueToCredSSPDisabled = false;
}

function ConnectVMWareRemoteControl( mksControl, VM )
{       
    LiveStatusService.GetVMConnectionParameters( VM.ID, function( connectionParameters )    
    {           
        if ( connectionParameters != null && mksControl != null )
        {
            try
            {
                mksControl.connect( connectionParameters.Host, connectionParameters.Port, connectionParameters.VmIdentification,
                    connectionParameters.AuthenticationTicket, connectionParameters.AuthenticationTicket);
            }
            catch ( error  )
            {
                //ToDo: determine how to detect if the activeX is being installed.
                //During installation we cannot call "connect". 
            }            
        }
    }, OnTicketRetrievalError );       
}

function IsActiveXInstalled(VM, ActiveXObject, forceRemoteDesktop )
{
    if ( VM == null ) 
        return false;   
    
    var testProperty = null;
    
    //Here we invoke simple get properties where the only real cause of an error should be that the 
    //activeX is not installed. This is a quick method as apposed to reading the registry for each 
    //activeX.
    try
    {
        if ( VM.ConnectViaHostAction && !forceRemoteDesktop ) 
        {
            switch ( VM.HostVirtualizationType )
	        {
	            case VirtualizationPlatform.VirtualServer:	        
	                testProperty = ActiveXObject.ServerDisplayWidth;
	                break;
	            case VirtualizationPlatform.HyperV:	        
	                testProperty = ActiveXObject.EnableThumbnailMode;
	                break;
	            case VirtualizationPlatform.VMWareVC3:	        
	            case VirtualizationPlatform.VMWareESX:
	                testProperty = ActiveXObject.VMScreenWidth;	                
	                break;	            
	            default:
	                return false;	     
            }       	                        
        }
        else 
        {            
            testProperty = ActiveXObject.EnableThumbnailMode;        
        }		
	}   
	catch ( activeXNotInstalledEx )	
	{
	    return false;	
	}
	
	return ( testProperty != undefined );
}

function GetKeyCodeFromEvent( e )
{ 
    if ( window.event ) //IE
    {
        return e.keyCode;
    }
    else
    {
        return e.which; // Netscape/Firefox/Opera
    }        
}

// This simple helper method gets the element which is the target of the passed in action (e)
// taking into consideration different browsers.
function GetEventTarget( e )
{
    if ( window.event ) //IE
    {
        return e.srcElement;
    }
    else
    {
        return e.target; //Firefox
    }
}

function LimitFieldAtLength( e, limit )
{
    //always allow non-addative actions
    switch( GetKeyCodeFromEvent(e)) 
    {
        case 8: // backspace            
        case 9: // tab            
        case 27: // escape    
        case 33: //pg up
        case 34: //pg down
        case 35: //end
        case 36: //home                  
        case 37: // left            
        case 38: // up            
        case 39: // right            
        case 40: // down            
        case 46: // delete
            return true;            
    }    
    
    return (ReadInnerText( e.srcElement).length < limit);  
}

function IsCarmineException ( error )
{
    return ( error != null && 
            error._exceptionType != undefined && 
            error._exceptionType.indexOf( 'CarmineException') >= 0 ); 
}

function GetEllipsisText ( text, lengthBeforeEllipsis )
{
    if ( text == null )
        return null;

    if ( text.length <= lengthBeforeEllipsis )
        return text;
    
    return text.substring( 0, lengthBeforeEllipsis ) + "...";
}

function ReadInnerText( element )
{
    if ( element == null )
        return "";

    if ( document.all )
    {
        return element.innerText; //IE 5,6,7,8
    }
    else
    {
        return element.textContent; //FireFox
    }        
}

function SetInnerText ( element, text )
{
    if ( element == null )
        return;

    if ( document.all )
    {
        element.innerText = text; //IE 5,6,7,8
    }
    else
    {
        element.textContent = text; //FireFox
    }  
}


function GetMachineIdFromQueryString()
{
    var qs = new QueryString();
    return qs.GetParam("vmid");
}

function GetQueryStringParam( paramName )
{
    var qs = new QueryString();
    return qs.GetParam( paramName );
}

function OnTicketRetrievalError(error, userContext)
{
    //ToDo: Decide what to do on error.
    alert ( error._message );
}

function EvalWithYield( script )
{
    window.setTimeout(script, 1);
}

function LinkedList ()
{
    this.length = 0;
    this.Add = LinkedList_Add;
    this.Remove = LinkedList_Remove;
    this.RemoveItemWithKey = LinkedList_RemoveItemWithKey;
    this.GetItemAt = LinkedList_GetItemAt;
    this.GetItemWithKey = LinkedList_GetItemWithKey;
    this.GetIndexWithKey = LinkedList_GetIndexWithKey;
    
    this.FirstNode = null;
    this.LastNode = null;    
}

function LinkedList_Add( Key, Value )
{
    if ( this.length == 0 )
    {
        this.FirstNode = new LinkedListNode();
        this.FirstNode.Key = Key;
        this.FirstNode.Value = Value;
        this.LastNode = this.FirstNode;        
    }    
    else
    {
        var PreviousLastNode = this.LastNode;
        this.LastNode = new LinkedListNode();
        PreviousLastNode.NextNode = this.LastNode;
        this.LastNode.Key = Key;
        this.LastNode.Value = Value;                
    }
    
    this.length = this.length + 1;
}

function LinkedList_GetItemAt( Index )
{
    if ( Index < 0 )
        return null;
                
    if ( Index >= this.length )
        return null;
    
    if ( Index == 0 )    
    {
        return this.FirstNode.Value;
    }
    
    var i = 0;
    var CurrentItem = this.FirstNode;
    
    for ( i = 0; i < Index; i++  )
    {
        CurrentItem = CurrentItem.NextNode;
    }    
    
    return CurrentItem.Value;
}

function LinkedList_GetItemWithKey( Key )
{
    if ( Key == null )
        return null;
                
    if ( this.length == 0 )
        return null;
    
    var CurrentItem = this.FirstNode;
    
    do
    {
        if ( CurrentItem.Key == Key )
            return CurrentItem.Value;
        
        CurrentItem = CurrentItem.NextNode;        
    }
    while ( CurrentItem != null )
        
    return null;
}

function LinkedList_GetIndexWithKey( Key )
{
    if ( Key == null )
        return -1;
                
    if ( this.length == 0 )
        return -1;
    
    var CurrentItem = this.FirstNode;
    var CurIndex = 0;
    
    do
    {
        if ( CurrentItem.Key == Key )
        {
            return CurIndex;
        }
        
        CurrentItem = CurrentItem.NextNode;        
        CurIndex++;
    }
    while ( CurrentItem != null )
        
    return -1;
}

function LinkedList_RemoveItemWithKey ( Key )
{
    var itemFound = false;
    var ParentListItem = null;
    var CurrentListItem = this.FirstNode;
    
    if ( Key == null )
        return;
                
    if ( this.length == 0 )
        return;
    
    if ( this.FirstNode.Key == Key )
    {
        this.FirstNode = this.FirstNode.NextNode;
        
        if ( this.length == 1 )
            this.LastNode = null;    
        
        itemFound = true;    
    }
    else
    {
        for ( i = 0; i < this.length -1; i ++ )
        {
            ParentListItem = CurrentListItem;
            CurrentListItem = CurrentListItem.NextNode;            
            
            if ( CurrentListItem.Key == Key )
            {
                itemFound = true;
                ParentListItem.NextNode = CurrentListItem.NextNode;
                if ( ParentListItem.NextNode == null ) //on last node
                {
                    this.LastNode = ParentListItem;
                }
                break;                
            }                
        }
    }   
    
    if ( itemFound )
        this.length--;
}

function LinkedList_Remove ( Value )
{
    var itemFound = false;
    var ParentListItem = null;
    var CurrentListItem = this.FirstNode;
    
    if ( this.length == 0 )
        return;        
    
    if ( this.FirstNode.Value == Value )
    {
        this.FirstNode = this.FirstNode.NextNode;
        
        if ( this.length == 1 )
            this.LastNode = null;   
            
        itemFound = true;
    }
    else
    {
        for ( i = 0; i < this.length -1; i ++ )
        {
            ParentListItem = CurrentListItem;
            CurrentListItem = CurrentListItem.NextNode;            
            
            if ( CurrentListItem.Value == Value )
            {
                ParentListItem.NextNode = CurrentListItem.NextNode;
                
                if ( ParentListItem.NextNode == null ) //on last node
                {
                    this.LastNode = ParentListItem;
                }
                
                itemFound = true;
                break;                
            }                
        }
    }   

    if ( itemFound )    
        this.length--;
    
}

// The internal representation of a linked list node. This is never exposed.
function LinkedListNode()
{
    var Key = null;
    var Value = null;
    var NextNode = null;
}

//Query string class. An instance of this class will read the query string
//from the currently loaded page and parse it, providing an easy way to read each parameter.
function QueryString()
{
    this.GetParam=QueryString_GetParam;
    
    this.fullQueryString = location.search.substring( 1, location.search.length ); //remove ? from query string
    this.ParamLookup = new Array();
    
    if ( this.fullQueryString == null || this.fullQueryString.length <= 0 )
        return;
    
    var allArguments = this.fullQueryString.split('&'); //get each name/value pair seperately    
        
    for ( var i = 0; i < allArguments.length; i++ )
    {
        var namevalue = allArguments[i].split('=');
        
        var name = unescape( namevalue[0] ).toUpperCase();
        var value = namevalue.length > 1 ? unescape(namevalue[1]) : "";
        
        this.ParamLookup[name] = value;                
    }
}

function QueryString_GetParam( paramName )
{    
    if ( paramName == null )
        return null;
    
    return this.ParamLookup[paramName.toUpperCase()]; 
}

function SelectIndexDirection ()
{
    this.Up = -1;
    this.Down = 1;
}
