//
//	(c)2004 GE Medical Systems
//
// <SCRIPT>



var rexFnName = /function\s(.*)\x28/;


//
// GetFnName
//
function GetFnName(fn)
{
	if (fn == null)
		return "";

	rexFnName.exec(fn.toString());
	return RegExp.$1;
}

//
// DeriveFrom
//
function DeriveFrom(ctorDerived, ctorBase)
{
	var prot1 = ctorBase.prototype;
	var prot2 = ctorDerived.prototype;

	for (var prop in prot1)
	{
		if (prot2[prop] == null)
		{
			prot2[prop] = prot1[prop];
		}
	}

	var strBaseCTor = "Ctor" + GetFnName(ctorBase);
	
	if (ctorDerived.toString().search(strBaseCTor) < 0)
	{
		// if you get here, ctorDerived does not call the base class CTor
		debugger;
	}
	prot2[strBaseCTor] = ctorBase;
}



function GetFrameElement(frame)
{
	if (frame.frameElement != null)	
		return frame.frameElement;
		
	var frameElements = frame.parent.document.all.tags("IFRAME");
	for (var iframe = 0; iframe < frameElements.length; iframe++ )
	{
		var elem = frameElements(iframe);

		if (frame.parent.frames(elem.id) == frame)
			return elem;
	}
	var frameElements = frame.parent.document.all.tags("FRAME");
	for (var iframe = 0; iframe < frameElements.length; iframe++ )
	{
		var elem = frameElements(iframe);

		if (frame.parent.frames(elem.id) == frame)
			return elem;
	}
	
}

//
// function testHTML
//
// Purpose: 
// Ret:
function TestHTML(strHTML)
{
	var xmldoc = new ActiveXObject("Microsoft.XMLDOM");
	xmldoc.async = false;
	if (!xmldoc.loadXML(strHTML))
	{
		alert(xmldoc.parseError.reason +"\n" +
			xmldoc.parseError.srcText);
	}
}


// TODO: Remove CreateXmlDoc when all docs can use version 4


//
// CreateXmlDoc
//
function CreateXmlDoc(oSrc, bShowError /* = true */, bResolveExternals /*=false*/)
{
	if (bShowError == null)
	{
		bShowError = true;
	}
	
	var xmlResult = new ActiveXObject('MSXML.DOMDocument');
	xmlResult.async = false;
	xmlResult.validateOnParse = false;
	xmlResult.resolveExternals = (bResolveExternals == true);

	if (oSrc == null)
		return xmlResult;

	var bLoaded = false;

	if (typeof(oSrc) == 'string' && oSrc.match('\s*<') != null)	// oSrc is XML string
	{
		bLoaded = xmlResult.loadXML(oSrc);
	}
	else								// oSrc is path or stream or domdocument
	{
		bLoaded = xmlResult.load(oSrc);
	}
	if (!bLoaded)
	{
		if (bShowError)
		{
			ShowXmlError(xmlResult);
		}
		return null;
	}
	return xmlResult;
}


//
// CreateXmlDoc4
//
function CreateXmlDoc4(oSrc, bShowError /* = true */, bResolveExternals /*=false*/)
{
	if (bShowError == null)
	{
		bShowError = true;
	}

	var xmlResult;
	if (GetApp().IsInViewerExe())
	{
		xmlResult = external.CreateObject('{88D969C0-F192-11D4-A65F-0040963251E5}');
	}
	else
	{
		xmlResult = new ActiveXObject('Msxml2.DOMDocument.4.0');
	}
	
	xmlResult.setProperty("SelectionNamespaces", "xmlns:wv='x-schema:WVDataSchema.xml' xmlns:wvdata='x-schema:WVDataSchema.xml' xmlns:dicom='x-schema:DicomSchema.xml' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:ui='x-schema:UISettingsSchema.xml' xmlns:tb='x-schema:toolbarschema.xml' xmlns:skin='x-schema:skinschema.xml' xmlns:ps='http://med.ge.com/WebSpeed'");
	xmlResult.async = false;
	xmlResult.validateOnParse = false;
	xmlResult.resolveExternals = (bResolveExternals == true);
	if (!xmlResult.resolveExternals)
	{
		xmlResult.setProperty("NewParser", true);
	}

	if (oSrc == null)
		return xmlResult;

	var bLoaded = false;

	if (typeof(oSrc) == 'string' && oSrc.match('\s*<') != null)	// oSrc is XML string
	{
		bLoaded = xmlResult.loadXML(oSrc);
	}
	else								// oSrc is path or stream or domdocument
	{
		strBase = GetApp().GetApplicationDataUrl(location);
		bLoaded = xmlResult.load(strBase.toLowerCase() + '/' + oSrc);
	}
	if (!bLoaded)
	{
		if (bShowError)
		{
			ShowXmlError(xmlResult);
		}
		return null;
	}

	return xmlResult;
}


//
// ShowXmlError
//
function ShowXmlError(doc)
{
	TRACE('error parsing xml: ' + doc.parseError.reason + '\n' + doc.parseError.srcText);
	if (doc.parseError.filepos == 0)	// could not load xml file, connection to server lost
	{
		_global.ShowUserError(101, this);
	}
	else
	{
		_global.ShowUserErrorText(GetLocaleString('msgUnexpected'), this);
	}
}


//------------------------------------------------------------------------------
// Date related functions
//------------------------------------------------------------------------------

//
// ValidateDate
//
// Purpose: Validate a date. Only valid, fully specified dates are accepted.
//
function ValidateDate(strDate)
{
	return ToDicomDate(strDate) != null;
}

//
// ToDicomDate
//
// Purpose: Convert a date to Dicom format. Only valid, fully specified
// dates are converted. Return null if the date is invalid.
//
function ToDicomDate(strDate)
{
	var strResult;
	var bValid;
	var nYear;
	var nMonth;
	var nDay;
	var strLocalToday;
	var strLocalYesterday;

	if(strDate == null)
	{
		return null;
	}

	bValid = false;
	strResult = null;
	strLocalToday = GetLocaleString('msgToday').toLowerCase();
	strLocalYesterday = GetLocaleString('msgYesterday').toLowerCase();
	strDate = strDate.toLowerCase();

	// Do not accept month/year combination as valid date (SCR: 3740)
	if (strDate.match(/^([0-9]+)[-\.\/]([0-9]+)$/)) 
		return null;

	// Date with slashes or dashes: yyyy/mm/dd or yyyy-mm-dd or yyyy.mm.dd
	// 1999/12/31 -> 19991231
	var r = null;
	if(r = strDate.match(/^([0-9]+)[-\.\/]([0-9]+)[-\.\/]([0-9]+)$/))
	{
		// Attempt generic yyyy/mm/dd format.
		nYear = parseInt(r[1], 10);
		nMonth = parseInt(r[2], 10);
		nDay = parseInt(r[3], 10);
		bValid = ValidateYearMonthDay(nYear, nMonth, nDay);
		strResult = ToDicomDateString(nYear, nMonth, nDay);
	}
	// DICOM dates: yyyymmdd 
	// no convertion needed, still check ranges.
	else if(r = strDate.match(/^([0-9]{4})([0-9]{2})([0-9]{2})$/))
	{
		nYear = parseInt(r[1], 10);
		nMonth = parseInt(r[2], 10);
		nDay = parseInt(r[3], 10);
		bValid = ValidateYearMonthDay(nYear, nMonth, nDay);
		strResult = strDate;
	}
	else if(strDate == strLocalToday)
	{
		strResult = DicomToday();
		bValid = true;
	}
	else if(strDate == strLocalYesterday)
	{
		strResult = DicomYesterday();
		bValid = true;
	}

	// Try locale format dates if generic convertion fails.
	if(!bValid)
	{
		strResult = LocaleToDicomDate(strDate);
		bValid = strResult != null;
	}

	return bValid ? strResult : null;
}

//
// DicomToday
// 
// Return a DICOM format date for today.
//
function DicomToday()
{
	var date = new Date;
	return ToDicomDateString(date.getFullYear(), date.getMonth() + 1, date.getDate());
}

//
// DicomYesterday
// 
// Return a DICOM format date for yesterday.
//

function DicomYesterday()
{
	var date = new Date;
	var nYear  = date.getFullYear();
	var nMonth = date.getMonth() + 1;
	var nDay  = date.getDate() - 1; 

	if (nDay == 0)
	{
		nMonth--;	
		if (nMonth == 0)
		{
			nYear--;
			nMonth = 12;
		}
		nDay = DaysInMonth(nYear, nMonth);
	}
	return ToDicomDateString(nYear, nMonth, nDay);
}

//
// ToDicomDateString
//
function ToDicomDateString(nYear, nMonth, nDay)
{
	return ((nYear * 10000) + (nMonth * 100) + nDay).toString();
}

//
// ValidateYearMonthDay
//
function ValidateYearMonthDay(nYear, nMonth, nDay)
{
	bResult = 
		   nYear > 1584  && nYear < 3000
		&& nMonth > 0    && nMonth <= 12
		&& nDay > 0      && nDay <= DaysInMonth(nYear, nMonth);
	return bResult;
}

//
// IsLeap
//
function IsLeap(nYear)
{
	bResult = nYear % 400 == 0 || (nYear % 100 != 0 && nYear % 4 == 0);
	return bResult;
}

//
// DaysInMonth
//
// Return the number of days in a certain month, take care of February in leap years.
//
function DaysInMonth(nYear, nMonth)
{
	var daysInMonth = new Array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

	return (nMonth == 2 && IsLeap(nYear)) ? 29 : daysInMonth[nMonth];
}

//
// LocaleToDicomDate
//
// Convert a string with a date in the locale format
// to a Dicom date. Return NULL if date parsing failed.
//
function LocaleToDicomDate(strDate)
{
	var vtDate = _global._viewapp.FromLocaleDate(strDate);
	if(vtDate != null)
	{
		var date = new Date(vtDate);
		return ToDicomDateString(date.getFullYear(), date.getMonth() + 1, date.getDate());
	}
	return null;
}

//
// ToLocaleDate
//
function ToLocaleDate(strDate)
{
	return _global._viewapp.ToLocaleDate(strDate);
}

//------------------------------------------------------------------------------
// Date range related functions
//------------------------------------------------------------------------------


function ValidateDateFrom(strDate)
{
	return ToDicomDateFrom(strDate) != null;
}


function ValidateDateTo(strDate)
{
	return ToDicomDateTo(strDate) != null;
}

function ToDicomDateFrom(strDate)
{
	if(strDate == "") 
	{
		return "";
	}
	// Handle the case of a year alone.
	if(strDate.match(/^[0-9]{4}$/))
	{
		strDate = strDate + "0101";
	}
	return ToDicomDate(strDate);
}

function ToDicomDateTo(strDate)
{
	if(strDate == "")
	{
		return "";
	}
	// Handle the case of a year alone.
	if(strDate.match(/^[0-9]{4}$/))
	{
		strDate = strDate + "1231";
	}
	return ToDicomDate(strDate);
}


//
// ValidateDateRange
// 
// Purpose: Validate a date range, format is checked and range are checked.
//
function ValidateDateRange(strDateRange)
{
	return ToDicomDateRange(strDateRange) != null;
}

//
// ValidateDateRangeRestriction
// 
// Purpose: Validate a date range, format is checked and ranges are checked.
// against the restricted range. The range should fit in the restriction. Open
// ranges are optionally allowed, as they can be restricted to the range anyway.
// Since a date range can be a single date, this function can also be used to
// check single dates against the date range.
//
function ValidateDateRangeRestriction(strDateRange, strDateRestriction, bAllowOpen)
{
	strDateRange = ToDicomDateRange(strDateRange);
	strDateRestriction = ToDicomDateRange(strDateRestriction);

	if(strDateRange == null || strDateRestriction == null)
	{
		return false;
	}
	if(strDateRange == '')
	{
		return strDateRestriction == '';
	}

	var r = SplitDateRange(strDateRestriction);
	var strFromRestriction = r[0];
	var strToRestriction   = r[1];

	r = SplitDateRange(strDateRange);
	var strFrom = r[0];
	var strTo   = r[1];

	var bResult = true;
	if(strFrom && strFromRestriction)
	{
		bResult = bResult && strFrom >= strFromRestriction;
	}
	if(strFrom && strToRestriction)
	{
		bResult = bResult && strFrom <= strToRestriction;
	}
	if(strTo && strToRestriction)
	{
		bResult = bResult && strTo <= strToRestriction;
	}
	if(strTo && strFromRestriction)
	{
		bResult = bResult && strTo >= strFromRestriction;
	}
	if(!bAllowOpen && (strFrom == null || strFrom == "") && strFromRestriction && strFromRestriction != "")
	{
		return false;
	}
	if(!bAllowOpen && (strTo == null || strTo == "") && strToRestriction && strToRestriction != "")
	{
		return false;
	}

	return bResult;
}

//
// SplitDateRange
//
// Purpose: split a date range into two parts, return an
// array with the from date in [0] and the to date in [1].
//
function SplitDateRange(strDateRange)
{
	var strTo = '';
	var strFrom = '';
	var strDash = '-';
	
	// Remove leading and trailing space characters
	strDateRange = TrimWS(strDateRange);
	// Remove space characters around separator characters
	strDateRange = strDateRange.replace(/ *([-\.\/]) */g, '$1');

	// Split date with dashes
	// 4 Digit year last.
	if(r = strDateRange.match(/^([0-9]+[-\.\/][0-9]+[-\.\/][0-9]+)-([0-9]{4})$/))
	{
		strFrom = r[1];
		strTo = r[2];
	}
	// 4 Digit year first
	else if(r = strDateRange.match(/^([0-9]{4})-([0-9]+[-\.\/][0-9]+[-\.\/][0-9]+)$/))
	{
		strFrom = r[1];
		strTo = r[2];
	}
	// Dashed date first
	else if(r = strDateRange.match(/^([0-9]+[-\.\/][0-9]+[-\.\/][0-9]+)-(.*)$/))
	{
		strFrom = r[1];
		strTo = r[2];
	}
	// Dashed date last
	else if(r = strDateRange.match(/^(.*)-([0-9]+[-\.\/][0-9]+[-\.\/][0-9]+)$/))
	{
		strFrom = r[1];
		strTo = r[2];
	}
	// SCR: 2822 - Check for two dashed date. 
	else if(r = strDateRange.match(/^(.*)-(.*)-(.*)$/))
	{
		strTo = strFrom = strDateRange;
		strDash = '';
	}
	// Split on dash.
	else if(r = strDateRange.match(/^(.*)-(.*)$/))
	{
		strFrom = r[1];
		strTo = r[2];
	}
	// Cannot split, assume to is same as from.
	else
	{
		strTo = strFrom = strDateRange;
		strDash = '';
	}

	var result = new Array();
	result[0] = strFrom;
	result[1] = strTo;
	result[2] = strDash; // remember that we had a dash.
	return result;
}

//
// ToDicomDateRange
// 
// Purpose: Convert date range to DICOM date range, format and range is checked.
// Return null if the date range cannot be parsed or is invalid.
// The date range has the format yyyymmdd-yyyymmdd, -yyyymmdd, or yyyymmdd-
//
function ToDicomDateRange(strDateRange)
{
	if(strDateRange == "" || strDateRange == null)
	{
		return strDateRange;
	}

	var r = _global.SplitDateRange(strDateRange);
	var strFrom = ToDicomDateFrom(r[0]);
	var strTo   = ToDicomDateTo(r[1]);
	
	// Smallest date first please.
	if(strFrom != "" && strTo != "" && strFrom > strTo)
	{
		strFrom = ToDicomDateFrom(r[1]);
		strTo   = ToDicomDateTo(r[0]);
	}

	if(strFrom == null || strTo == null)
	{
		return null;
	}

	return strFrom + "-" + strTo;
}

//------------------------------------------------------------------------------
// Validate functions
//------------------------------------------------------------------------------


//
// ValidateValue
//
function ValidateValue(strType, strValue, strRestrValue, strRestr)
{
	// when field is mandatory, no ? wildcards may be used
	if ((strRestr == 'mandatory') &&
		(strValue.search(/\?/) != -1))
	{
		return false;
	}

	switch (strType)
	{
	case 'name':
	case 'string':
	case 'number':
	case 'sex':

		if ((strRestrValue != null) && (strRestr != 'open'))
		{
			// TODO: Check against the restriction, using another function:
			// _global.ValidateRestriction(strValue, strRestrValue);

			var rgstrValues = strValue.split('\\');
			for (var i = 0; i < rgstrValues.length; i++)
			{

				if (!_global.MatchWildcardConcat(rgstrValues[i], strRestrValue))
				{
					return false;
				}
			}
		}
		break;

	case 'date':
		var bResult = _global.ValidateDateRange(strValue);
		if (bResult && (strRestrValue != null) && (strRestr != 'open'))
		{
			bResult = _global.ValidateDateRangeRestriction(strValue, strRestrValue);
		}
		return bResult;
		break;

	default:
		_global.ASSERT(false);
		break;
	}

	return true;
}

//---------------------------------------------------------------------------------------


//
// CRowCol ctor
//
function CRowCol(row, col)
{
	this._row =	row;
	this._col =	col;

	return this;
}


//
// MakeStringSafe
//
function MakeStringSafe(strInput)
{
	var strOutput = strInput; 
	strOutput = strOutput.replace(/\\/g, '\\\\');
	strOutput = strOutput.replace(/\"/g, '\\"')
	strOutput = strOutput.replace(/\'/g, "\\'")

	for	(var ich = 0; ich != strInput.length; ich++)
	{
		var ch = strInput.charCodeAt(ich);
		if	(ch < 32 || ch > 126)
		{
			var	strHex = ch.toString(16);
			if (strHex.length <	2)
			{
				strHex = '0' + strHex;
			}
			strOutput = strOutput.replace(strInput.charAt(ich), '\\x' + strHex);
		}
	}

	return strOutput;
}

DeriveFrom(AttributeSubjectImpl, SubjectImpl);

//
// AttributeSubjectImpl
//
function AttributeSubjectImpl(strFunctionName)
{
	this.CtorSubjectImpl(strFunctionName);

	this.NotifyObservers = AttributeSubjectImpl__NotifyObservers;
	this._rgsetting	= new Array();
}


//
// AttributeSubjectImpl__NotifyObservers
//
function AttributeSubjectImpl__NotifyObservers(iattribute, setting)
{ with (this) {

	if (_rgsetting[iattribute] === setting)
		return;

	TRACE('enter: notify observers of: ' + _strFunctionName);

	for	(var iobserver in _rgobserver)
	{
		_rgobserver[iobserver][_strFunctionName](iattribute, setting, null);
	}

	TRACE('leave: notify observers of: ' + _strFunctionName);

	_rgsetting[iattribute] = setting;
}}


//
// SubjectImpl
//
function SubjectImpl(strFunctionName)
{
	if (strFunctionName == null)
		this._strFunctionName	= "OnNotify";
	else
		this._strFunctionName	= strFunctionName;
	this._rgobserver		= new Array();
	this.AddObserver		= SubjectImpl__AddObserver;
	this.RemoveObserver		= SubjectImpl__RemoveObserver;
	this.NotifyObservers	= SubjectImpl__NotifyObservers;
}

//
// SubjectImpl__AddObserver
//
function SubjectImpl__AddObserver(observer)
{ with (this) {
	ASSERT(observer[_strFunctionName] != null, window);
	_rgobserver[_rgobserver.length] =	observer;
}}

//
// SubjectImpl__RemoveObserver
//
function SubjectImpl__RemoveObserver(observer)
{ with (this) {
	_rgobserver = RemoveElem(_rgobserver, observer);
}}


//
// SubjectImpl__NotifyObservers
//
function SubjectImpl__NotifyObservers(p1, p2, p3)
{ with (this) {

	TRACE('enter: notify observers of: ' + _strFunctionName);

	for (var iobserver in _rgobserver)
	{
	try
		{
			_rgobserver[iobserver][_strFunctionName](p1, p2, p3);
		}
		catch (e) { ASSERT(false); }
	}

	TRACE('leave: notify observers of: ' + _strFunctionName);
}}

//
// Observer__OnNotify
//
function Observer__OnNotify(p1, p2, p3)
{ with (this) {
	_observer[_strFnNotify](p1, p2, p3);
}}

//
// ParseURL()
//
function ParseURL(strURL)
{
	var	ichQM	= strURL.indexOf('?');
	var	strArgs	= ichQM	!= -1 ?	strURL.substring(ichQM + 1)	: '';
	var	rgstr	= strArgs.split('&');

	var	mpstrNameValue = new Object();
	ParseNameValueString(mpstrNameValue, strArgs, '&', '=');
	return mpstrNameValue;
}


//
// EscapeParam()
//
// Stolen from RadStore, do we need a stdami.js?
function EscapeParam(param)
{
	if (typeof(param) == 'string')
	{
		var s = escape(param);
		s = s.replace(/\+/g, "%2B");
		s = s.replace(/\\/g, '\\\\');
		return s;
	}
	return param;
}



//
//	TrimWS()
//
function TrimWS(str)
{
	for	(var ichL =	0; ichL	< str.length; ichL++)
	{
		if (str.charAt(ichL) !=	' ')
			break;
	}

	for	(var ichR =	str.length;	ichR-- > ichL; )
	{
		if (str.charAt(ichR) !=	' ')
			break;
	}

	return str.substring(ichL, ichR	+ 1);
}


//
// MsgFormat
//
function MsgFormat()
{
	var rgArg = MsgFormat.arguments;
	var str = rgArg[0];

	// note: arguments are 1-based
	for (iarg = 1; iarg < rgArg.length; ++iarg)
		str = str.replace( new RegExp( "%" + iarg, "g"), rgArg[iarg]);
	return str;
}


//
// ArrayCopy()
//
function ArrayCopy(rgSrc)
{
	return rgSrc.slice(0);
}


//
// ArrayCopyKeepSortOrder
//
// Purpose: create copy of new array, but keep sort order of original array
//
function ArrayCopyKeepSortOrder(rgOriginal, rgNew, strIdField)
{
	if (rgNew.length <= 1 || rgOriginal == null || rgOriginal.length == 0)
		return rgNew;
		
	var iitem;
	
	var rgIdOriginal = new Array();
	for (iitem in rgOriginal)
	{
		rgIdOriginal[iitem] = rgOriginal[iitem][strIdField];
	}

	var rgIdNew = new Array();
	for (iitem in rgNew)
	{
		rgIdNew[iitem] = rgNew[iitem][strIdField];
	}

	// first, copy items that already existed
	var rgSorted = new Array();
	for (iitem in rgIdOriginal)
	{
		var iindexNew = FindElemIndex(rgIdNew, rgIdOriginal[iitem]);
		if (iindexNew != -1)
			rgSorted[rgSorted.length] = rgNew[iindexNew];
	}

	// finally, copy new studies that were not copied already
	for (iitem in rgIdNew)
	{
		if (FindElemIndex(rgIdOriginal, rgIdNew[iitem]) == -1)
			rgSorted[rgSorted.length] = rgNew[iitem];
	}

	ASSERT(rgSorted.length == rgNew.length);
	return rgSorted;
}


//
// RGOptionFromStr()
//
function RGOptionFromStr(str)
{
	if (str.indexOf(s_strDelimRGOption) != 0)
		return null;

	str = str.substr(s_strDelimRGOption.length,	str.length - 2 * s_strDelimRGOption.length); //	strip leading and trailing delimiter

	var mpstrNameValue = new Array();
	ParseNameValueString(mpstrNameValue, str, ',', s_strDelimItem);

	var rg = new Array();
	for(var strName in mpstrNameValue)
	{
		rg[rg.length] = new Option(strName, mpstrNameValue[strName]);
	}
	return rg;
}


//
// RGRowColFromStr()
//
function RGRowColFromStr(str)
{
	if (str.indexOf(s_strDelimRGRowCol) != 0)
		return null;

	str = str.substr(s_strDelimRGRowCol.length,	str.length - 2 * s_strDelimRGRowCol.length); //	strip leading and trailing delimiter

	var mpstrNameValue = new Array();
	ParseNameValueString(mpstrNameValue, str, ',', s_strDelimItem);

	var rg = new Array();
	for (var strName in mpstrNameValue)
	{
		var strVal = mpstrNameValue[strName];
		var rgval = strVal.split(' ');
		if (rgval.length > 1)
			rg[strName] = new CRowCol(parseInt(rgval[0], 10), parseInt(rgval[1], 10));
	}
	return rg;
}


//
// RGFromStr()
//
function RGFromStr(str)
{
	if (str.indexOf(s_strDelimRG) != 0)
		return null;

	str = str.substr(s_strDelimRG.length, str.length - 2 * s_strDelimRG.length); //	strip leading and trailing delimiter

	var mpstrNameValue = new Array();
	ParseNameValueString(mpstrNameValue, str, ',', s_strDelimItem);

	var rg = new Array();
	for (var strName in mpstrNameValue)
	{
		rg[strName] = mpstrNameValue[strName];
	}
	return rg;
}

//
// ParseNameValueString()
//
function ParseNameValueString(mp, str, strDelim, strDelimItem)
{
	var rg = str.split(strDelim);

	for (var iitem in rg)
	{
		var strNameValue = rg[iitem];
		var ichEQ = strNameValue.indexOf(strDelimItem);

		var strName = ichEQ != -1 ? strNameValue.substr(0, ichEQ) : strNameValue;
		strName = strName.replace(/^\s/g, '');

		var strValue = ichEQ !=	-1 ? strNameValue.substr(ichEQ + strDelimItem.length) : '';

		if (strName	!= '')
		{
			mp[strName]	= strValue;
		}
	}
}


var _mpstrimgCache = new Array();

//
// CacheImage
//
// Purpose: 
// Ret:
function CacheImage(strURLImage)
{
	if (_mpstrimgCache[strURLImage] == null)
	{
		_mpstrimgCache[strURLImage] = new Image();
		_mpstrimgCache[strURLImage].src = strURLImage;
	}
}



//
// FindElemIndex
//
function FindElemIndex(rg, obj)
{
	for (var iindex in rg)
	{
		if (rg[iindex] == obj)
		{
			return parseInt(iindex, 10);
		}
	}
	return -1;
}




//
// AddUnique
//
function AddUnique(rg, obj)		
{
	if (FindElemIndex(rg, obj) > 0) 
		return;
	
	rg[rg.length] = obj;
}



//
// RemoveAt
//
function RemoveAt(rg, iRemove)
{
	var	rgNew =	new	Array();
	for	(var i in rg)
	{
		if (i != iRemove)
			rgNew[rgNew.length]	= rg[i];
	}
	return rgNew;
}

//
// RemoveElem
//
function RemoveElem(rg, elemRemove)
{
	var rgNew =	new	Array();

	if (typeof(elemRemove.Equals) != 'unknown' && elemRemove.Equals != null)
	{
		for (var i in rg)
		{
			if (!elemRemove.Equals(rg[i]))
				rgNew[rgNew.length]	= rg[i];
		}
	}
	else
	{
		for (var i in rg)
		{
			if (rg[i] != elemRemove)
				rgNew[rgNew.length] = rg[i];
		}
	}
	return rgNew;
}



// ======================================================================================


var _mpstrobj = new Array();
var idlast = 0;

//
// GetPeer
//
function GetPeer(str, frame)
{
	return _mpstrobj[str];	
}

//
// FrameConnector
//
// Purpose: 
// Ret:
function FrameConnector(peer, strURL)
{
	this._frame = null;
	this._peer = peer;
	this._strID = "";
	this._strURL = strURL;
	
	this.PublishPeerObject(this._peer);
}

//
// PublishPeerObject
//
function FrameConnector.prototype.PublishPeerObject()
{ with (this) {
	++idlast;
	var str = "object" + idlast;
	_mpstrobj[str] = this;
	_strID = str;
}}


//
// UnPublishPeerObject
//
function FrameConnector.prototype.UnPublishPeerObject()
{ with (this) {
	_mpstrobj[_strID] = null;
	if (_frame != null)
		DisconnectPeer(_frame);
}}

//
// Url
//
function FrameConnector.prototype.Url()
{ with (this) {
	return _strURL;
}}

//
// ConnectPeer
//
function FrameConnector.prototype.ConnectPeer(frame)
{ with (this) {
	if (_mpstrobj[_strID] == null)
		return false;

	this._frame = frame;
	return true;
}}

//
// DisconnectPeer
//
function FrameConnector.prototype.DisconnectPeer(frame)
{ with (this) {
	if (_frame == null)
		return;

	if (_peer.OnUnloadFrame != null)
		_peer.OnUnloadFrame(frame);
	
	_global.ASSERT(_frame == frame);
	_mpstrobj[_strID] = null;
	_frame = null;
	frame._peer = null;
}}


//
// ReloadHtml
//
function FrameConnector.prototype.ReloadHtml()
{ with (this) {
	if (_frame == null)
		return;
	
	var frame = _frame;

	// make sure no pages currently loading will connect to our peer
	UnPublishPeerObject();
	PublishPeerObject();

	GetFrameElement(frame).objectID = _strID;
	frame.document.location.replace(Url());
}}


//
// LoadHtml
//
function FrameConnector.prototype.LoadHtml(frame)
{ with (this) {
	if (_frame != null)
	{
		ASSERT(false);
		return;
	}

	_frame = frame;

	// make sure no pages currently loading will connect to our peer
	UnPublishPeerObject();
	PublishPeerObject();

	GetFrameElement(frame).objectID = _strID;
	frame.document.location.replace(Url());
}}


var s_strUrl = "JavaScript:'<script>window._global = parent._global; var _frameConnector = null; _global.ConnectMe(window);</script>'";


function ConnectMe(frame)
{
	var frameConnector = GetPeer(GetFrameElement(frame).objectID);

	if (!frameConnector.ConnectPeer(frame))
		return;
		
	frame._frameConnector = frameConnector;

	frame.document.writeln("<script event='onload' for='window'>if (_frameConnector != null) _frameConnector._peer.OnLoadFrame(_frameConnector._frame);</script>");
	frame.document.writeln("<script event='onunload' for='window'>_frameConnector.DisconnectPeer(this);</script>");
	frame.document.writeln(frameConnector._peer.StrHtml());
}


// ======================================================================================


s_rgStaticFunction = new Array();

function DisposeStaticFunctions()
{
	for (var i in s_rgStaticFunction)
	{
		s_rgStaticFunction[i]._obj = null;
	}
	s_rgStaticFunction = null;
	
}
//
// CreateStaticFunction
//
// Purpose: Create static function wrappers for JScript member functions 
// Ret: returns a pointer to a new static function 
function CreateStaticFunction(object, strFn)
{
	var pfn = new Function("var obj = arguments.callee._obj; if (obj != null) obj." + strFn + "();");
	pfn._obj = object;
	
	s_rgStaticFunction.push(pfn);
	return pfn;
}

// ======================================================================================


//
// CClassSet c'tor
//
function CClassSet()
{
	this._rgFlag = arguments;

	with (this) 
	{
		for (var iFlag = 0; iFlag != _rgFlag.length; iFlag++)
		{
			this[_rgFlag[iFlag]] = false;
		}
	}
}

//
// ReadElement
//
function CClassSet.prototype.ReadElement(el)
{ with (this) {
	var strClass = el.className;
	var rgClass = strClass.split(' ');

	for (var iFlag = 0; iFlag != _rgFlag.length; iFlag++)
	{
		this[_rgFlag[iFlag]] = FindElemIndex(rgClass, _rgFlag[iFlag]) != -1;
	}

}}

//
// WriteElement
//
function CClassSet.prototype.WriteElement(el)
{ with (this) {

	var strClass = el.className;
	var rgClass = strClass.split(' ');

	for (var iFlag = 0; iFlag != _rgFlag.length; iFlag++)
	{
		var strFlag = _rgFlag[iFlag];
		if (this[strFlag])
		{
			if (FindElemIndex(rgClass, strFlag) == -1)
			{
				rgClass[rgClass.length] = strFlag;
			}
		}
		else
		{
			var iClass = FindElemIndex(rgClass, strFlag);
			if (iClass != -1)
			{
				rgClass = RemoveElem(rgClass, strFlag);
			}

		}
	}

	strClass = rgClass.join(' ');
	if (el.className != strClass)
	{
		el.className = strClass;
	}
}}

//
// OffsetToWindow
//
function OffsetToWindow(htmlElement)
{
	var point = new Object;
	point.left = 0;
	point.top  = 0;
	
	var node = htmlElement;
	
	while (node.offsetParent != null)
	{
		point.left += node.offsetLeft;
		point.top  += node.offsetTop;

		node = node.offsetParent;
	}

	return point;
}


//
// DicomToDate
//
function DicomToDate(strDate, strTime)
{
	var date = new Date(0,0,0);
	if (strDate != null && strDate.length == 8)
	{
		// note that months are zero based!
		date = new Date(strDate.substr(0, 4), strDate.substr(4,2).valueOf() - 1, strDate.substr(6,2));
	}
	if (strTime != null && strTime.length >= 4)
	{
		date.setHours(strTime.substr(0, 2));
		date.setMinutes(strTime.substr(2, 2));
		date.setSeconds(strTime.substr(4, 2));
	}
	return date;
}

//
// SetElemAttribute
//
function SetElemAttribute(elem, name, value)
{
	if (name.substr(0, 2).toLowerCase() == 'on')
	{
		return false;	// cannot change events
	}

	switch(name.toLowerCase())
	{
		case 'class':
			elem.className = value;
			break;

		case 'style':
			elem.STYLE = value;
			break;

		default:
			elem.setAttribute(name, value);			
			break;
	}
	return true;
}

//
// UpdateElementFromXHtml
//
function UpdateElementFromXHtml(doc, elem, nodeOld, nodeNew)
{

	RecurseElementFromXHtml(doc, elem, nodeOld, nodeNew);
}

//
// CopyAttributes
//
function CopyAttributes(doc, elem, nodeOld, nodeNew)
{
	var attrs = nodeNew.attributes;
	for (var iattr = 0; iattr != attrs.length; iattr++)
	{
		var attr = attrs.item(iattr);
		if (attr.value == nodeOld.getAttribute(attr.name))
			continue;
		if (SetElemAttribute(elem, attr.name, attr.value))
			continue;

		// give up, create new node
		var elemNew = doc.createElement(nodeNew.cloneNode(false).xml);
		elem.replaceNode(elemNew);
		elem = elemNew;
		break;
	}

	var attrsOld = nodeOld.attributes;
	for (var iattr = 0; iattr != attrsOld.length; iattr++)
	{
		var attr = attrsOld.item(iattr);
		if (nodeNew.getAttribute(attr.name) == null)
		{
			SetElemAttribute(elem, attr.name, null);
		}
	}
	return elem;
}

//
// InsertChildAt
//
function InsertChildAt(ichildNode, doc, elem, itnodeNew)
{
	if (itnodeNew.nodeTypeString == 'text')
	{

		var node = doc.createTextNode(itnodeNew.nodeValue);
		if (ichildNode == elem.childNodes.length)
		{
			elem.appendChild(node);
		}
		else
		{
			elem.insertBefore(node, elem.childNodes.item(ichildNode));
		}
	}
	else
	{
		var node = doc.createElement(itnodeNew.cloneNode(false).xml);
		RecurseElementFromXHtml(doc, node, null, itnodeNew);

		if (ichildNode == elem.childNodes.length)
		{
			elem.appendChild(node);
		}
		else
		{
			elem.insertBefore(node, elem.childNodes.item(ichildNode));
		}
	}
}

//
// UpdateChildren
//
function UpdateChildren(doc, elem, nodeOld, nodeNew)
{
	var childrenNew = nodeNew.selectNodes('*|text()');
	var itnodeNew = childrenNew.nextNode();

	var itnodeOld = null;
	var childrenOld = null;
 
	if (nodeOld != null)
	{
		childrenOld = nodeOld.selectNodes('*|text()');
		itnodeOld = childrenOld.nextNode();
	}

	var ichildNode = 0;
	while (itnodeNew != null && itnodeOld != null)
	{
		var bMatch = false;

		if (itnodeNew.xml == itnodeOld.xml)
		{
			bMatch = true;
		}
		else if (itnodeNew.nodeTypeString == 'text' && itnodeOld.nodeTypeString == 'text')
		{
			bMatch = true;
			if (itnodeNew.nodeValue != itnodeOld.nodeValue)
				elem.childNodes.item(ichildNode).nodeValue = itnodeNew.nodeValue;
		}
		else if (itnodeNew.nodeName == itnodeOld.nodeName)
		{
			if (itnodeNew.getAttribute('id') == null ||
				itnodeNew.getAttribute('id') == itnodeOld.getAttribute('id'))
			{
				bMatch = true;
				try
				{
				
					if (itnodeNew.nodeName == 'table' && 
						itnodeNew.firstChild.childNodes.length != itnodeOld.firstChild.childNodes.length)
					{
						elem.childNodes.item(ichildNode).outerHTML = itnodeNew.xml;
					}
					else
					{
						RecurseElementFromXHtml(doc, elem.childNodes.item(ichildNode), itnodeOld, itnodeNew);
					}
				}
				catch(e)
				{
					if (e.description == "unexpected element")
					{
						bMatch = false;
						
						// remove unexpected child node and try again
						elem.removeChild(elem.childNodes.item(ichildNode));
						continue;
					}
				}
			}
		}

		if (bMatch)
		{
			itnodeNew = childrenNew.nextNode();
			itnodeOld = childrenOld.nextNode();
			ichildNode++;
			continue;
		}

		if (childrenNew.length > childrenOld.length)
		{	// insert new node
			InsertChildAt(ichildNode, doc, elem, itnodeNew);
			itnodeNew = childrenNew.nextNode();
			ichildNode++;
		}
		else
		{	// remove old node
			itnodeOld = childrenOld.nextNode();
			elem.childNodes.item(ichildNode).removeNode(true);	
//TODO: replace node if new and old equal size
		}
	}

	while (itnodeOld != null)
	{
		elem.childNodes.item(ichildNode).removeNode(true);	
		itnodeOld = childrenOld.nextNode();
	}

	while (itnodeNew != null)
	{
		InsertChildAt(ichildNode, doc, elem, itnodeNew);
		itnodeNew = childrenNew.nextNode();
		ichildNode++;
	}

	return elem;
}


//
// RecurseElementFromXHtml
//
function RecurseElementFromXHtml(htmlDoc, elem, nodeOld, nodeNew)
{
	if (elem.tagName == null)
	{
		// SCR: 2822
		// unexpected text node found
		var e = new Exception;
		e.description = "unexpected element";
		throw e;
	}

	if (nodeOld == null)
	{
		nodeOld = nodeNew.cloneNode(false);
		return UpdateChildren(htmlDoc, elem, nodeOld, nodeNew);
	}

	ASSERT(elem.tagName.toLowerCase() == nodeOld.nodeName.toLowerCase());
	if (nodeNew == null)
	{
		elem.removeNode(true);
		return elem;
	}

	elem = CopyAttributes(htmlDoc, elem, nodeOld, nodeNew);
	elem = UpdateChildren(htmlDoc, elem, nodeOld, nodeNew);

	return elem;
}


//
// MakeAbsoluteUrl
//
// Purpose: Create absolute url with correct protocol prefix
// Ret:
//
function MakeAbsoluteUrl(strUrl, strUrlBase)
{
	if (strUrl.substr(0,4).toLowerCase() == 'http')
		return strUrl;
	
	if (strUrl.substr(0,1) == '/')
	{
		var re = /[^\/]\/[^\/]/;
		strUrlBase = strUrlBase.substr(0, strUrlBase.search(re)+1);
	}

	return strUrlBase + strUrl;
}


//
// GetNodeString
//
// Purpose: Get string from node using pattern query
// Ret:
//
function GetNodeString(node, strPattern)
{
	var nodeFound = node.selectSingleNode(strPattern);
	if (nodeFound == null)
		return '';

	return nodeFound.text;
}


// Async request class ----------------------------------------------------------------------------

//
// CAsyncRequest
//
// Purpose: Asynchronious server request
// Ret:
//
function CAsyncRequest()
{
	this._xmlRequest	= null;
	this._xmlResponse	= null;
	this._fnCallBack	= null;
	this._bAborting		= false;
}


//
// CAsyncRequest.prototype.GetReponseDoc
//
// Purpose: Create response document
// Ret:
//
function CAsyncRequest.prototype.GetResponseDoc()
{ with (this) {

	if (_xmlResponse == null)
	{
		_xmlResponse = CreateXmlDoc4();
		_xmlResponse.setProperty("NewParser", false);
		_xmlResponse.async              = true;
		_xmlResponse.onreadystatechange = CreateStaticFunction(this, "OnReadyStateChange");

	}

	return _xmlResponse;
}}


//
// CAsyncRequest.prototype.Post
//
// Purpose: Post server request
// Ret: True if response for xmlRequest is still in cache, post not performed
//
function CAsyncRequest.prototype.Post(xmlRequest, fnCallBack)
{ with (this) {

	_fnCallBack = fnCallBack;

	var xmlResponse = GetResponseDoc();
	if (xmlResponse.readyState != 4)
	{
		_bAborting = true;
		xmlResponse.abort();
		_bAborting = false;
	}
	else
	{
		if ((_xmlRequest != null) &&
			(xmlRequest.xml == _xmlRequest.xml))
		{
			return true;
		}
	}

	_xmlRequest = xmlRequest;
	xmlResponse.load(_baseBin + "?" + EscapeParam(xmlRequest.xml));
	return false;
}}


//
// CAsyncRequest.prototype.OnReadyStateChange
//
// Purpose: OnReadyStateChange event handler
// Ret:
//
function CAsyncRequest.prototype.OnReadyStateChange()
{ with (this) {

	if (!IsReady())
		return;

	if (_bAborting)
		return;

	_fnCallBack();
}}
	

//
// CAsyncRequest.prototype.IsReady
//
// Purpose: Ready state flag
// Ret:
//
function CAsyncRequest.prototype.IsReady()
{ with (this) {

	var xmlResponse = GetResponseDoc();
	return xmlResponse.readyState == 4;
}}
	

//
// IsMsXmlReplaceMode
//
function IsMsXmlReplaceMode()
{
	var strXml = "<?xml version=\"1.0\" encoding=\"UTF-16\"?><cjb></cjb>";
	var strXsl = "<?xml version=\"1.0\" encoding=\"UTF-16\"?><x:stylesheet version=\"1.0\" xmlns:x=\"http://www.w3.org/1999/XSL/Transform\" xmlns:m=\"urn:schemas-microsoft-com:xslt\"><x:template match=\"/\"><x:value-of select=\"system-property('m:version')\" /></x:template></x:stylesheet>";

	try
	{
	    var xmldoc = new ActiveXObject("Microsoft.XMLDOM");  
	    xmldoc.async = false;
	    if (!xmldoc.loadXML(strXml))
		{
			return false;
		}

		var xsldoc = new ActiveXObject("Microsoft.XMLDOM"); 
		xsldoc.async = false;
		if (!xsldoc.loadXML(strXsl))
		{
			return false;
		}

		var op = xmldoc.transformNode(xsldoc);
		if (op.indexOf("stylesheet") == -1)
		{
			return true;
		}
	}
	catch(e)
	{
	}

	return false;
}


var g_strUserDataFile = "DicomViewer";

//
// GetUserDataAttribute
//
function GetUserDataAttribute(oPersist, strAttribute)
{
	try
	{
		oPersist.load(g_strUserDataFile);
		return oPersist.getAttribute(strAttribute);
	}
	catch(e)
	{
		return null;
	}
}


//
// SetUserDataAttribute
//
function SetUserDataAttribute(oPersist, strAttribute, strValue)
{
	try
	{
		oPersist.load(g_strUserDataFile);
	}
	catch(e) 
	{
		// occurs if file does not yet exist, save to continue with saving
	}

	try
	{
		oPersist.setAttribute(strAttribute, strValue);
		oPersist.save(g_strUserDataFile);
	}
	catch(e)
	{
	}
}
