// Define version.
var POPUP_JS = 1.1; /* 2008-04-16 */

/*
    Argument-properties.
    -------------------------------------------------------------------
    PopupElementId: If we created a popup-element (HTML) and don't want to auto-create one we specified the element id here. Note: Can only be set in the constructor.
    PopupElement: If we created a popup-element (HTML) and don't want to auto-create one we specified the element here. Note: Can only be set in the constructor.

    Content: The content of the popup.
    OffsetX: The x offset of the popup (useful when Position is Popup.PositionAnchor).
    OffsetY: The y offset of the popup (useful when Position is Popup.PositionAnchor).
    ShowDelay: The number of milliseconds before the popup is showed.
    HideDelay: The number of milliseconds before the popup is hidden.
    PopupWidth: The width of the popup. Note that this might not be applicable if the popup-element is userdefined.
    PopupHeight: The height of the popup. Note that this might not be applicable if the popup-element is userdefined.
    PopupClassName: The CSS classname on the popup.
    OverlayColor: The color of the overlay.
    OverlayOpacityPercentage: The percentage (0-100) of opacity for the overlay.
    Position: The position of the popup {Popup.PositionAnchor, Popup.PositionTopLeft, Popup.PositionCenter, Popup.PositionNotSpecified}.
    CalculatePopupSize: Should the popup size be auto-calculated? This is used with Popup.PositionCenter to calculate the offset of the popup (note: you need to specify the width / height of the popup in order for this to work). An alternative way to do this is to set CalculatePopupSize=false, and set the OffsetX and OffsetY to negative values (half of the popup-size).
    AnchorElementId: When Position=Popup.PositionAnchor is specified we can specify an anchor element id here.
    AnchorElement: When Position=Popup.PositionAnchor is specified we can specify an anchor element here.
    ManualClosing: Are manual closing required?
    ShowOverlay: Should we should the overlay when the popup is shown?
    KeepCurrentSettings: Should the current settings be cleared before the specified settings are applied?
    
    Examples of different ways to get a centered popup dialog.
    -------------------------------------------------------------------
    Automatically calculating the offset:
        Popup.getDefaultPopup().showPopup({'Content':'<div style="border: 1px solid black;">Hello world!</div>', 'Position':Popup.PositionCenter, 'PopupWidth':300, 'PopupHeight':50, 'CalculatePopupSize':true, 'ManualClosing':true}, true);
    
    Manually specifying the offset:
        Popup.getDefaultPopup().showPopup({'Content':'<div style="width:360px; height:50px; border: 1px solid black;">Hello world!</div>', 'Position':Popup.PositionCenter, 'OffsetX':-180, 'OffsetY':-25, 'CalculatePopupSize':false, 'ManualClosing':true}, true);
    
    Using CSS:
        CSS     .popupContainer { position: absolute; top: 50%; left: 0px; width: 100%; height: 1px; overflow: visible; display: block; z-index: 10001; }
        CSS     .popupContainer DIV { border: 1px solid black; position: absolute; z-index: 10001; left: 50%; top: -20px; margin-left: -180px; width: 360px; height: 40px; }
        HTML    <div id="popupContainer" class="popupContainer" style="visibility: hidden;"><div>Hello world!</div></div>
        JS      Popup.getDefaultPopup().showPopup({'Position':Popup.PositionNotSpecified, 'PopupElementId':'popupContainer', 'OffsetX':0, 'OffsetY':0, 'CalculatePopupSize':false, 'ManualClosing':true}, true);
    -------------------------------------------------------------------
    
    Example of how to initialize the popup once on demand (using a custom property 'IsInitialized').
    Note: This could also be done (not on demand) by calling the popup-initialization on i.e. onLoad.
    -------------------------------------------------------------------
        var defaultPopup = Popup.getDefaultPopup();
        // Should we initialize = is this the first call?
        if(!defaultPopup.IsInitialized) {
            defaultPopup.setSettings({INITIAL SETTINGS});
            defaultPopup.IsInitialized = true;
        }
        
        defaultPopup.showPopup({SPECIALIZED SETTINGS}, true);
    -------------------------------------------------------------------
    
    Notes / known limitations:
    -------------------------------------------------------------------
    In situations where you want to show a popup on mouseover and hide it on mouseout showing the overlay will
    force mouseout on the anchor directly since the overlay is put above the anchor and the anchor loses focus.
*/

// TODO: Verify that all parameters can be specified WITHOUT need of specifying 'px'.

Popup.Popups = new Array(); // [];
Popup.DefaultPopup = null;

Popup.PositionAnchor = 'anchor'; // Offseted from the top-left anchor position.
Popup.PositionCenter = 'center'; // Center of the screen.
Popup.PositionTopLeft = 'topleft'; // Absolute position 0, 0.
Popup.PositionNotSpecified = 'notspecified'; // Not specified, the end user should handle the positioning.
Popup.PositionMouse = 'mouse'; // Follow the mouse pointer.

Popup.registerPopup = function(instance) {
    if(Popup.Popups.push) {
	    Popup.Popups.push(instance);
	} else {
	    Popup.Popups[Popup.Popups.length] = instance;
	}
};

Popup.getPopup = function(id) {
	for(var i=0; i<Popup.Popups.length; i++) {
		if(Popup.Popups[i].Id == id) { return Popup.Popups[i]; }
	}
	return null;
};

Popup.getDefaultPopup = function() {
    if(Popup.DefaultPopup == null) {
        Popup.DefaultPopup = new Popup();
    }
    return Popup.DefaultPopup;
};

Popup.addEventListener = function(element, eventTypeName, callback, useCapture) {
    if(element.addEventListener) {
        element.addEventListener(eventTypeName, callback, useCapture);
        return true;
    } else if(element.attachEvent) {
        return element.attachEvent('on' + eventTypeName, callback);
    } else {
        element['on' + eventTypeName] = callback;
        return true;
    }
};

Popup.removeEventListener = function(element, eventTypeName, callback, useCapture) {
    if(element.removeEventListener) {
        element.removeEventListener(eventTypeName, callback, useCapture);
        return true;
    } else if(element.detachEvent) {
        return element.detachEvent('on' + eventTypeName, callback);
    } else {
        element['on' + eventTypeName] = null;
        return true;
    }
};

Popup.newGuid = function() {
	var result, i, j;
	result = '';
	for(j=0; j<32; j++) {
		if(j == 8 || j == 12|| j == 16|| j == 20) result = result + '-';
		i = Math.floor(Math.random()*16).toString(16).toUpperCase();
		result = result + i;
	}
	
	return result;
};

function Popup(args) {
    args = args ? args : new Object();

    this.Id = args.Id ? args.Id : Popup.newGuid();
    this.PopupTimer = null;
    this.PopupElement = null;
    this.OverlayElement = null;
    this.IsVisible = false;
    this.IsEventsRegistered = false;

/*    
    if(this.PopupElement != null) {
        this.PopupElement.parentNode.removeChild(this.PopupElement);
    }
*/

    if(args.PopupElementId) {
        this.PopupElement = Popup.getElementById(args.PopupElementId);
    } else if(args.PopupElement) {
        this.PopupElement = args.PopupElement;
    }
        
    this.PopupElementInitialized = false;
    this.OverlayElementInitialized = false;
    
    args.KeepCurrentSettings = false;
    this.setSettings(args);

    var instance = this;
    Popup.registerPopup(instance);
}

Popup.prototype.setSettings = function(args) {
    this.clearPopupTimer();
    
    if(args.KeepCurrentSettings == false) {
        this.Content = '';
        this.OffsetX = 16;
        this.OffsetY = 16;
        this.ShowDelay = 300;
        this.HideDelay = 300;
        this.PopupWidth = -1;
        this.PopupHeight = -1;
        this.PopupClassName = '';
        this.OverlayColor = '#787878';
        this.OverlayOpacityPercentage = 60;
        this.Position = Popup.PositionAnchor;
        this.CalculatePopupSize = true;
        this.ShowOverlay = false;
        this.ManualClosing = false;
        this.AnchorElement = null;
    }

    if(args.AnchorElementId) {
        this.AnchorElement = Popup.getElementById(args.AnchorElementId);
    } else if(args.AnchorElement) {
        this.AnchorElement = args.AnchorElement;
    }
    
    this.Content = args.Content ? args.Content : this.Content;
    this.OffsetX = args.OffsetX ? args.OffsetX : this.OffsetX;
    this.OffsetY = args.OffsetY ? args.OffsetY : this.OffsetY;
    this.ShowDelay = args.ShowDelay ? args.ShowDelay : this.ShowDelay;
    this.HideDelay = args.HideDelay ? args.HideDelay : this.HideDelay;
    this.PopupWidth = args.PopupWidth ? args.PopupWidth : this.PopupWidth;
    this.PopupHeight = args.PopupHeight ? args.PopupHeight : this.PopupHeight;    
    this.PopupClassName = args.ClassName ? args.ClassName : this.PopupClassName;
    this.OverlayColor = args.OverlayColor ? args.OverlayColor : this.OverlayColor;
    this.OverlayOpacityPercentage = args.OverlayOpacity ? args.OverlayOpacity : this.OverlayOpacityPercentage;
    this.ShowOverlay = typeof(args.ShowOverlay) != 'undefined' ? args.ShowOverlay : this.ShowOverlay;
    this.Position = args.Position ? args.Position : this.Position;
    this.CalculatePopupSize = typeof(args.CalculatePopupSize) != 'undefined' ? args.CalculatePopupSize : this.CalculatePopupSize;
    this.ManualClosing = typeof(args.ManualClosing) != 'undefined' ? args.ManualClosing : this.ManualClosing;
    
    if(typeof(this.Content) == 'undefined') this.Content = '';
}

// Get the position for the given element. Since the position
// is given relative to the parent element, we need to propagate
// upwards in the element-tree and get the relative positions.
Popup.getElementPosition = function(element) { 
	var x = element.offsetLeft;
	var y = element.offsetTop;
	var node = element;
	while(node.offsetParent && (node.offsetParent != document.body)) {
		node = node.offsetParent;
		x += node.offsetLeft;
		y += node.offsetTop;
	}
	
	return{'x':x, 'y':y};
};

Popup.getElementById = function(id) {
	if(document.getElementById) return document.getElementById(id);
	else if(document.all) return document.all[id];
	else if(document.layers) return document.layers[id];
	return null;
};

Popup.getViewportSize = function() {
    var width = -1, height = -1;

    if(typeof(window.innerWidth) != 'undefined') {
        // The more standards compliant browsers (mozilla/netscape/opera/IE7) use window.innerWidth and window.innerHeight.
        width = window.innerWidth;
        height = window.innerHeight;
    } else if (typeof(document.documentElement) != 'undefined' && typeof(document.documentElement.clientWidth) != 'undefined' && document.documentElement.clientWidth != 0) {
        // IE6 in standards compliant mode (i.e. with a valid doctype as the first line in the document).
        width = document.documentElement.clientWidth;
        height = document.documentElement.clientHeight;
    } else {
        // Older versions of IE.
        width = document.getElementsByTagName('body')[0].clientWidth;
        height = document.getElementsByTagName('body')[0].clientHeight;
    }
    
    width = parseInt(width, 10);
    height = parseInt(height, 10);

    if(isNaN) {
        if(isNaN(width)) width = -1;
        if(isNaN(height)) height = -1;
    }
    
    return {'width':width, 'height':height};
}

/* Gets the full page size, including the parts that doesn't show due to scroll. */
Popup.getPageSize = function() {
    var xScroll = -1, yScroll = -1;
	
	if (window.innerHeight && window.scrollMaxY) {	
		xScroll = window.innerWidth + window.scrollMaxX;
		yScroll = window.innerHeight + window.scrollMaxY;
	} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
		xScroll = document.body.scrollWidth;
		yScroll = document.body.scrollHeight;
	} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
		xScroll = document.body.offsetWidth;
		yScroll = document.body.offsetHeight;
	}
	
	var windowWidth = -1, windowHeight = -1;
	
	if (self.innerHeight) {	// all except Explorer
		if(document.documentElement.clientWidth){
			windowWidth = document.documentElement.clientWidth; 
		} else {
			windowWidth = self.innerWidth;
		}
		windowHeight = self.innerHeight;
	} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
		windowWidth = document.documentElement.clientWidth;
		windowHeight = document.documentElement.clientHeight;
	} else if (document.body) { // other Explorers
		windowWidth = document.body.clientWidth;
		windowHeight = document.body.clientHeight;
	}	
	
	// for small pages with total height less then height of the viewport
	if(yScroll < windowHeight){
		pageHeight = windowHeight;
	} else { 
		pageHeight = yScroll;
	}

	// for small pages with total width less then width of the viewport
	if(xScroll < windowWidth){	
		pageWidth = xScroll;
	} else {
		pageWidth = windowWidth;
	}

    pageWidth = parseInt(pageWidth, 10);
    pageHeight = parseInt(pageHeight, 10);
    
    if(isNaN) {
        if(isNaN(pageWidth)) pageWidth = -1;
        if(isNaN(pageHeight)) pageHeight = -1;
    }

	return {'width':pageWidth, 'height':pageHeight};
};

Popup.showMessage = function(message) {
    alert(message);
};

// anchorElement: either an HTML-element or an id (a string) to the HTML-element.
// content: the content of the popup.
// force: determines wheather or not to show the popup directly or after a specified time (default 300 ms).
// manualClosing: determines wheather or not the popup requires manual closing.
Popup.prototype.showPopup = function(args, force) {
    args = args ? args : new Object();
    /*
    if(anchorElement == null || typeof(anchorElement) == 'undefined') {
        // Do nothing.
        this.CurrentPopupAnchorElementId = '';
    } else if(typeof(anchorElement) == 'string') {
        this.CurrentPopupAnchorElementId = anchorElement;
    } else {
        this.CurrentPopupAnchorElementId = anchorElement.id;
    }
    */
    
	this.clearPopupTimer();
	
	this.setSettings(args);
	
	// if(typeof(content) == 'undefined') content = '';
	// this.Content = content;
	// this.ManualClosing = manualClosing;

    if(force) {
        this.showPopupInternal();
    } else {
	    this.PopupTimer = window.setTimeout("Popup.getPopup('" + this.Id + "').showPopupInternal()", this.ShowDelay);
	}
};

Popup.prototype.hidePopup = function(force) {
    /*if(this.IsVisible == false) return;*/
    
    if(force) {
        this.clearPopupTimer();
        this.hidePopupInternal();
        this.ManualClosing = false;
    } else {
        if(this.ManualClosing) return;
        this.clearPopupTimer();
	    this.PopupTimer = window.setTimeout("Popup.getPopup('" + this.Id + "').hidePopupInternal()", this.HideDelay);
	}
};

Popup.prototype.getPopupElementSize = function() {
    var width = -1, height = -1;
    if(this.PopupElement.style.width != '') {
        width = parseInt(this.PopupElement.style.width, 10);
        height = parseInt(this.PopupElement.style.height, 10);
    } else if(typeof(this.PopupElement.offsetWidth) != 'undefined') {
        width = parseInt(this.PopupElement.offsetWidth, 10);
        height = parseInt(this.PopupElement.offsetHeight, 10);
    } else {
        width = -1;
        height = -1;
    }
    
    width = parseInt(width, 10);
    height = parseInt(height, 10);
    
    if(isNaN) {
        if(isNaN(width)) width = -1;
        if(isNaN(height)) height = -1;
    }
    
    return {'width':width, 'height':height};
};

Popup.prototype.showPopupInternal = function() {
    this.IsVisible = true;
    this.registerEvents();
    
    if(this.ShowOverlay == true) {
        this.showOverlayElement();
    }
	
	var popupLayer = this.getPopupElement();
	if(this.Content != '') {
	    popupLayer.innerHTML = this.Content;
	}
	
    if(this.PopupClassName != '' && this.PopupClassName != this.PopupElement.className) this.PopupElement.className = this.PopupClassName;
    if(this.PopupWidth != -1 && this.PopupWidth != this.PopupElement.style.width) this.PopupElement.style.width = this.PopupWidth;
    if(this.PopupHeight != -1 && this.PopupHeight != this.PopupElement.style.height) this.PopupElement.style.height = this.PopupHeight;

    this.adjustToWindowSize();
    	
	popupLayer.style.visibility = '';
	popupLayer.style.display = '';
	if(this.ShowOverlay == true) popupLayer.style.zIndex = 10000;
	// alert('left=' + popupLayer.style.left + ', top=' + popupLayer.style.top + ', className=' + popupLayer.className + ', width=' + popupLayer.style.width + ', height=' + popupLayer.style.height + ', visibility=' + popupLayer.style.visibility + ', display=' + popupLayer.style.display + ', id=' + popupLayer.id);
};

Popup.prototype.adjustToWindowSize = function() {
    if(this.IsVisible == false) return;
    
    var pageSize = Popup.getPageSize();
    
    if(this.ShowOverlay == true) {
        var overlayLayer = this.getOverlayElement();
        overlayLayer.style.width = pageSize.width + 'px';
        overlayLayer.style.height = pageSize.height + 'px';
    }
    
    this.adjustPopupPosition(null);
};

Popup.prototype.adjustPopupPosition = function(evt) {
    var popupLayer = this.getPopupElement();

    if(this.Position != Popup.PositionNotSpecified) {
        if(this.Position == Popup.PositionAnchor) {
	        if(this.AnchorElement == null) { // this.CurrentPopupAnchorElementId == ''
	            Popup.showMessage('Anchor element missing.');
	            return;
	        }
    	    
	        // var anchorElement = Popup.getElementById(this.CurrentPopupAnchorElementId);
	        var position = Popup.getElementPosition(this.AnchorElement);
	    } else if(this.Position == Popup.PositionCenter) {
	        var x = 0, y = 0;
            var viewPortSize = Popup.getViewportSize();
            x += (viewPortSize.width / 2);
            y += (viewPortSize.height / 2);
            
            if(this.CalculatePopupSize == true) {
	            var size = this.getPopupElementSize();
	            x -= (size.width / 2);
	            y -= (size.height / 2);
	        }
	        
	        // alert(this.CalculatePopupSize + ',' + viewPortSize.width + ',' + viewPortSize.height + ',' + x + ',' + y);
	        
	        var position = {'x':x, 'y':y};
	    } else if(this.Position == Popup.PositionTopLeft) {
	        var position = {'x':0, 'y':0};
	    } else if(this.Position == Popup.PositionMouse) {
	        if(evt == null) return;
	        var position = Popup.getMousePositionFromEvent(evt);
	        // log('Position=' + position.x + ',' + position.y);
	    } else {
	        Popup.showMessage('Unsupported position specified.');
	    }
    	
        position.x += this.OffsetX;
        position.y += this.OffsetY;
    	
        popupLayer.style.position = 'absolute';
	    popupLayer.style.left = position.x + 'px';
	    popupLayer.style.top = position.y + 'px';
    }
};

Popup.getMousePositionFromEvent = function(evt) {
    if(evt == null || !evt) evt = window.event;
    
    var x = 0, y = 0;
    if(evt.pageX) {
        x = evt.pageX;
        y = evt.pageY;
    } else if(evt.clientX) {
        x = evt.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
        y = evt.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);    
    } else {
        // Do nothing.
    }
    
    if(parseInt) {
        x = parseInt(x, 10);
        y = parseInt(y, 10);
    }
    
    return {'x':x, 'y':y};
}
/*
function follow(evt) {
    if(document.getElementById) {
        var obj = document.getElementById(divName).style;
        obj.visibility = 'visible';
        obj.left = (getMousePositionFromEvent(evt).x+offX) + 'px';
        obj.top = (getMousePositionFromEvent(evt).y+offY) + 'px';
    }
}

document.onmousemove = follow;
*/
Popup.prototype.showOverlayElement = function() {
    var overlayLayer = this.getOverlayElement();
	overlayLayer.style.visibility = '';
	overlayLayer.style.display = '';
    var pageSize = Popup.getPageSize();
    overlayLayer.style.width = pageSize.width + 'px';
    overlayLayer.style.height = pageSize.height + 'px';
    
    overlayLayer.style.backgroundColor = this.OverlayColor;
    overlayLayer.style.filter = 'alpha(opacity=' + this.OverlayOpacityPercentage + ')'; /* for IE */
    overlayLayer.style.opacity = (this.OverlayOpacityPercentage / 100); /* for Safari on MacOS */
    overlayLayer.style.MozOpacity = (this.OverlayOpacityPercentage / 100); /* for Mozilla FireFox */
    overlayLayer.style.KhtmlOpacity = (this.OverlayOpacityPercentage / 100);
};

Popup.prototype.hidePopupInternal = function() {
    this.IsVisible = false;
    this.unregisterEvents();
    
	var popupLayer = this.getPopupElement();
	popupLayer.style.visibility = 'hidden';
	popupLayer.style.display = 'none';
	// this.CurrentPopupAnchorElementId = '';
	
	this.hideOverlayElement();
};

Popup.prototype.clearPopupTimer = function() {
	if(this.PopupTimer) {
		window.clearTimeout(this.PopupTimer);
		this.PopupTimer=null;
	}
};

Popup.prototype.hideOverlayElement = function() {
	var overlayLayer = this.getOverlayElement();
	overlayLayer.style.visibility = 'hidden';
	overlayLayer.style.display = 'none';
};

Popup.prototype.getOverlayElement = function() {
    if(this.OverlayElementInitialized == false) {
        this.initializeOverlayElement();
    }
    
    return this.OverlayElement;
};

Popup.prototype.initializeOverlayElement = function() {
    this.OverlayElementInitialized = true;
    
    var created = false;
    if(this.OverlayElement == null) {
        this.OverlayElement = document.createElement('div');
        this.OverlayElement.id = 'overlayElementLayer' + this.Id;
        this.OverlayElement.style.position = 'absolute';
        this.OverlayElement.style.left = 0;
        this.OverlayElement.style.top = 0;
        this.OverlayElement.style.width = '100%';
        this.OverlayElement.style.height = '100%';
        this.OverlayElement.style.zIndex = 9999;
        created = true;
    }
    
    this.OverlayElement.style.visibility = 'hidden';
    this.OverlayElement.style.display = 'none';

    if(created) {
        document.body.appendChild(this.OverlayElement);
    }
        
    return this.OverlayElement;
};

Popup.prototype.getPopupElement = function() {
    if(this.PopupElementInitialized == false) {
        this.initializePopupElement();
    }
    
    return this.PopupElement;
};

Popup.prototype.initializePopupElement = function() {
    this.PopupElementInitialized = true;
    
    var created = false;
    if(this.PopupElement == null) {
        this.PopupElement = document.createElement('div');
        this.PopupElement.id = 'popupElementLayer' + this.Id;
        this.PopupElement.style.position = 'absolute';
        this.PopupElement.style.left = 0;
        this.PopupElement.style.top = 0;
        this.PopupElement.style.zIndex = 10000;
        created = true;
    }
    
    this.PopupElement.style.visibility = 'hidden';
    this.PopupElement.style.display = 'none';

    // this.registerEvents(); // TODO: This should be done in showPopupInternal, and unregisterEvents should be called in hidePopupInternal.

    if(created) {
        document.body.appendChild(this.PopupElement);
    }
        
    return this.PopupElement;
};

Popup.prototype.registerEvents = function() {
    if(this.IsEventsRegistered == true) return;
        
    var popupElement = this.getPopupElement();
    var instance = this;
    Popup.addEventListener(popupElement, 'mouseover', function() {instance.clearPopupTimer();}, false);
    Popup.addEventListener(popupElement, 'mouseout', function() {instance.hidePopup();}, false);
    Popup.addEventListener(window, 'resize', function() {instance.adjustToWindowSize();}, false);
    
    if(this.Position == Popup.PositionMouse) {
        Popup.addEventListener(document, 'mousemove', function(event) {instance.adjustPopupPosition(event);}, false);
    }
    
    this.IsEventsRegistered = true;
};

Popup.prototype.unregisterEvents = function() {
    if(this.IsEventsRegistered == false) return;
    
    var popupElement = this.getPopupElement();
    var instance = this;
    Popup.removeEventListener(popupElement, 'mouseover', function() {instance.clearPopupTimer();}, false);
    Popup.removeEventListener(popupElement, 'mouseout', function() {instance.hidePopup();}, false);
    Popup.removeEventListener(window, 'resize', function() {instance.adjustToWindowSize();}, false);
    Popup.removeEventListener(document, 'mousemove', function(event) {instance.adjustPopupPosition(event);}, false);    
    
    this.IsEventsRegistered = false;
};