dojo.provide("ues.core.webui.Lightbox");
dojo.require("dijit._Widget");
dojo.require("dojox.image.Lightbox");
dojo.require("dojo.dnd.Moveable");
dojo.require("dojox.fx._base");
dojo.require("ues.core.webui.Dialog");


(function () {

// TODO Move panner & panable to separate files (they're reusable)
var Panner = dojo.declare(dojo.dnd.Mover, {
  invertX: true,
  invertY: true,
  constructor: function (node, e, host, params) {
    dojo.mixin(this, params);
    this._scrollOrigin = { x: this.node.scrollLeft, y: this.node.scrollTop };
    this._pageOrigin = { x: e.pageX, y: e.pageY };
    this.scrollBox = {};
    if (this.invertX) this.scrollBox.l = this._scrollOrigin.x + this._pageOrigin.x;
    else this.scrollBox.l = this._scrollOrigin.x - this._pageOrigin.x;
    if (this.invertY) this.scrollBox.t = this._scrollOrigin.y + this._pageOrigin.y;
    else this.scrollBox.t = this._scrollOrigin.y - this._pageOrigin.y;
  },
  onMouseMove: function (e) {
    var sb = this.scrollBox;
    var lt = {};
    if (this.invertX) lt.l = sb.l - e.pageX;
    else lt.l = sb.l + e.pageX;
    if (this.invertY) lt.t = sb.t - e.pageY;
    else lt.t = sb.t + e.pageY;
    this.host.onMove(this, lt);
    dojo.stopEvent(e);
  },
  onFirstMove: function () {
    var h = this.host;
    if (h && h.onFirstMove) h.onFirstMove(this);
    dojo.disconnect(this.events.pop());
  }
});

var Panable = dojo.declare(dojo.dnd.Moveable, {
  _moverParams: null,
  constructor: function (node, params) {
    if (!params.mover) this.mover = Panner;
    var p = dojo.mixin({ invertX: true, invertY: true }, params);
    this._moverParams = { invertX: p.invertX, invertY: p.invertY };
  },
  onDragDetected: function(/* Event */ e){
    // summary:
    //    called when the drag is detected;
    //    responsible for creation of the mover
    new this.mover(this.node, e, this, this._moverParams);
  },
  onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop) {
    mover.node.scrollLeft = leftTop.l;
    mover.node.scrollTop = leftTop.t;
  }
});

var LB = dojo.declare("ues.core.webui.Lightbox", dijit._Widget, {
  // CSS selector marking elements to use for the lightbox group (e.g. "a[href$=png]")
  selector: "",
  // Name of attribute used on selected HTML elements that will be used for filtering elements to lightbox groups.
  groupBy: "",
  // if true, lightbox dialog won't be closed when underlay is clicked
  modal: true,

  _lb: null, // array of lightbox items in the group
  _groupId: null, // automatically generated group id
  _groupIdPool: { last: 0 }, // static counter for group IDs

  postCreate: function () {
    this._lb = [];
    this._groupId = "g" + (++this._groupIdPool.last);
    if (!this.selector) return;
    var that = this;

    // create LightboxItem for every element matching the selector when the document is ready
    dojo.addOnLoad(function () {
      dojo.query(that.selector).forEach(function (aElem, aInd) {
      	var imgRef = null;
      	var connectOnClick = true;
      	var isImgWithPreview = false;
      	var addToGallery = false;
        if (aElem.tagName == "IMG") {
        	// full size image
        	imgRef = dojo.attr(aElem, "src");
        	connectOnClick = !aElem.getAttribute("usemap") && dojo.query(aElem).parents("a").length == 0;
        } else if (aElem.tagName == "A") {
        	// image with preview
        	// skip elements with JS-like hrefs
        	if (aElem.href.match(/^javascript:/i)) return;
        	imgRef = dojo.attr(aElem, "href");
        	isImgWithPreview = true;
        }

        var groupId = that._groupId;
        if (that.groupBy) {
        	// get group with galleryYes/No prefix and set groupId
        	var group = dojo.string.trim(dojo.attr(aElem, that.groupBy));
        	addToGallery = group.match(/^galleryYes_/i);
        	if (addToGallery) groupId = group.replace(/^galleryYes_/, "");
        	else groupId = group.replace(/^galleryNo_/, "");
        }
        
        var lb = null;
        // add image with preview to its group
        if (connectOnClick && isImgWithPreview) {
        	lb = new ues.core.webui.LightboxItem({ title: aElem.title, href: imgRef, group: groupId, modal: that.modal, connectOnClick: connectOnClick }, aElem);
        	that._lb.push(lb);
        	lb.startup();
        }
        // add image to gallery group
        if (addToGallery) {
	        lb = new ues.core.webui.LightboxItem({ title: aElem.title, href: imgRef, group: "ues-lb-gallery", modal: that.modal, connectOnClick: false }, null);
	        that._lb.push(lb);
	        lb.startup();
        }
      });
    });
  },
  destroy: function () {
    dojo.forEach(this._lb, function (aVal) {
      aVal.destroyRecursive();
    });
    delete this._lb;
    this.inherited(arguments);
  }
});

LB.show = function (aParams) {
	var id = 'ues-core-LightboxDialog';
	var group = 'ues-lb-gallery';
	var fullscreen = true;
	
  if (aParams && aParams.group) group = aParams.group;
  if (aParams && aParams.fullscreen) fullscreen = aParams.fullscreen;
  
	var lbDialog = dijit.byId(id);
	if (lbDialog != null) {
		lbDialog.show({group: group, modal: true, fullscreen: fullscreen });
	} else {
		var PREF = "ues_v5.core_v1.appc_v1.visc_v1.pagecomponents.";
		var message = UES.localize(PREF + "LLightbox_No_Images_BODY");
		var title = UES.localize(PREF + "LLightbox_No_Images_TITLE");
		new ues.core.webui.InformationDialog({ message: message, title: title }).show();
	}
};

dojo.declare("ues.core.webui.LightboxItem", dojox.image.Lightbox, {
  _DIALOG_ID: "ues-core-LightboxDialog",
  connectOnClick: true,
  _origScrollY: 0,

  startup: function () {
    // fix title for IE
    this.title = this.title || "";

    // NOTE Don't call superclass (we don't want to create new instance of dojox.image.LightboxDialog).
    this._started = true;
    if (!this._attachedDialog) {
      var dlg = dijit.byId(this._DIALOG_ID);
      if (!dlg) {
        dlg = new ues.core.webui.LightboxDialog({ id: this._DIALOG_ID });
        dlg.startup();
      }
      this._attachedDialog = dlg;
    }
    if (!this.store) {
      // FIXME: full store support lacking, have to manually call this._attachedDialog.addImage(imgage,group) as it stands
      this._addSelf();
      if (this.connectOnClick) {
        this.connect(this.domNode, "onclick", "_handleClick");
        dojo.style(this.domNode, "cursor", "pointer");
      }
    }
  },

  _handleClick : function(e) {
  	this._origScrollY = getScrollY(e.target);
  	this._offsetParent = e.target.offsetParent;
  	this.inherited(arguments);
  }
});


dojo.declare("ues.core.webui.LightboxDialog", ues.core.webui.Dialog, {
	title: "",
	inGroup: null,
	imgUrl: dijit._Widget.prototype._blankGif,
	errorMessage : "",
	adjust: true,
	modal: false,
	resizeY: false,
	isFullScreen: false,
	shadow: false,
	refocus: false,
	_groups: {
		XnoGroupX: []
	},
	_origState: null,
	duration: dijit.defaultDuration,
	dragHandleClass: "drag-obj_lightbox",
	errorImg: dojo.moduleUrl("ues.core", "webui/images/lightbox_warning.gif"),

	templateString: dojo.cache("ues.core","resources/templates/LightboxDialog.html", "<div class=\"ues-core-lightbox dojoxLightbox\" dojoAttachPoint=\"containerNode\">\n    <div style=\"position:relative\">\n        <div class=\"ues-core-lightbox_bar\" dojoAttachPoint=\"titleNode\">\n            <div class=\"ues-core-lightbox_headerWrap\">\n                <div class=\"dijitInline ues-core-lightbox_navigation\" dojoAttachPoint=\"titleTextNode\">\n                <span class=\"dijitInline ues-core-lightbox_prev\" dojoAttachPoint=\"prevNode\"></span>\n                <span class=\"dijitInline ues-core-lightbox_next\" dojoAttachPoint=\"nextNode\"></span>\n                    <span dojoAttachPoint=\"groupCount\" class=\"dojoxLightboxGroupText\"></span>\n                    <span class=\"ues-core-lightbox_shrink\" dojoAttachPoint=\"_shrinkNode\" dojoAttachEvent=\"onclick: _onShrinkClick\"></span>\n                <span class=\"dijitInline\"></span>\n                </div>\n                    <div class=\"dijitInline title\" dojoAttachPoint=\"textNode\">${title}</div>\n            </div>\n                    <div class=\"dijitInline ues-core-lightbox_view\" dojoAttachPoint=\"_fullscreenNode\" dojoAttachEvent=\"onclick: _onFullScreenClick\"></div>\n            <div class=\"dijitInline ues-core-lightbox_close\" dojoAttachPoint=\"closeNode\"></div>\n        </div>\n        <div class=\"ues-core-lightbox_body\"><div class=\"ues-core-lightbox_imgContWrap\" dojoAttachPoint=\"_imgContWrapNode\">\n            <div class=\"ues-core-lightbox_navIn ues-core-lightbox_navInPrev\" dojoAttachEvent=\"onclick: _prevImage\"><div></div></div>\n            <div class=\"ues-core-lightbox_navIn ues-core-lightbox_navInNext\" dojoAttachEvent=\"onclick: _nextImage\"><div></div></div>\n            <div class=\"ues-core-lightbox_imgCont\" dojoAttachPoint=\"imageContainer\" dojoAttachEvent=\"onclick: _onImageClick\">\n                <img dojoAttachPoint=\"imgNode\" src=\"${imgUrl}\" class=\"dojoxLightboxImage\" alt=\"${title}\">\n            <div dojoAttachPoint=\"errorNode\" class=\"ues-core-lightbox_errorNode\"></div>\n            </div></div>\n        </div>\n    </div>\n</div>"),

  shrinkToViewport: true,
  inImageNavigation: true, // whether to add navigation arrows over the image
  zoomFactor: 0.1,
  isZooming: false,

  _shrinkNode: null, // node for "shrink to viewport" functionality
  _imgContWrapNode: null,
  _imgContWrapExt: null, // extents for _imgContWrapNode
  _adjustSize: null, // delta between image size and window content size (excluding titleNode)
  _usingPan: false, // whether panning is switched on
  _padBorderExt: null, // extents for domNode (computed just once)
  _SCROLL_H_SIZE: 19, // FIXME Measure exactly & add to global constants
  _SCROLL_V_SIZE: 19, // FIXME Measure exactly & add to global constants
  _IMG_FADEOUT_DURATION: 175,
  _CSS_BASE: "ues-core-lightbox",
  _CSS_PAN: "ues-core-lightbox_withPan",
  _CSS_LOADING: "ues-core-lightbox_loading",
  _CSS_WITH_NAV_IN: "ues-core-lightbox_withNavIn",
  _CSS_SHRINK_ON: "ues-core-lightbox_shrinkOn",
  _CSS_SHRINK_OFF: "ues-core-lightbox_shrinkOff",
  _IN_IMAGE_NAV_WIDTH_DIALOG: 0.1, // percentage width of the in-image navigation (given as decimal)
  _IN_IMAGE_NAV_MIN_WIDTH_DIALOG: 100,
  _IN_IMAGE_NAV_WIDTH_FULLSCREEN: 0.1, // percentage width of the in-image navigation (given as decimal)
  _IN_IMAGE_NAV_MIN_WIDTH_FULLSCREEN: 100,
  _MIN_IMG_CONT_SIZE: { w: 250, h: 250 }, // minimal size of image container
  _BOUNDARIES: { l: 0, t: 0, w: 0, h: 0 }, // boundaries from viewport (right=w-l, bottom=h-t)
  _CSS_FULL_SCREEN: "fullscreen_mode",
  _CSS_LIGHTBOX_SHOWN: "ues-lightbox-on",

  // general flow (1st display):
  // 1. show => _ready (sets image src) => _imageLoaded => resizeTo =>
  //    sizing animation (_sizeAnim) => _showImage => fade-in animation (_showImageAnim) =>
  //    _finishLoading => _showNav => bar fade-in animation (_showNavAnim)
  //
  // general flow (navigation):
  // 2. _prevImage|_nextImage => _loadImage => fade-out animation (_loadingAnim) =>
  //    _prepNodes => 1.

  startup: function() {
		this.inherited(arguments);
		this._animConnects = [];
		this.connect(this.nextNode, "onclick", "_nextImage");
		this.connect(this.prevNode, "onclick", "_prevImage");
		this.connect(this.closeNode, "onclick", "hide");
		this._makeAnims();
		this._vp = dijit.getViewport();
		this.errorMessage = UES.localize("ues_v5.core_v1.appc_v1.visc_v1.pagecomponents.LLightbox_Loading_Error");
		return this;
	},

  postMixInProperties: function () {
    this.inherited(arguments);
    this["class"] += " " + this._CSS_BASE; // required in order to set the right class to dialog underlay
  },

  _lazyInit: function () {
    if (this._padBorderExt) return;
    this._padBorderExt = dojo._getPadBorderExtents(this.domNode);
    this._imgContWrapExt = dojo._getPadBorderExtents(this._imgContWrapNode);
    this._adjustSize = { w: this._imgContWrapExt.w, h: this._imgContWrapExt.h };
    this._lastSize = { w: 200, h: 200 }; // for cases when a resize event is triggerred before image is loaded (during 1st display)
    this._currentSize = { w: this._lastSize.w, h: this._lastSize.h };
    this._updateShrinkNode(); // switch button to correct state & add localized texts
    if (dojo.isIE && dojo.isIE <= 6) {
      // TODO Remember the connection (this is memory leak if the app is heavy-client).
      var toggleHovered = function () { dojo.toggleClass(this, "ues-core-lightbox_navInOver"); };
      dojo.query(".ues-core-lightbox_navIn", this.domNode).onmouseenter(toggleHovered).onmouseleave(toggleHovered);
    }
    dojo.query(".ues-core-lightbox_navIn", this.domNode).style("width", this._getImageNavWidth());
  },

  show: function (/* Object */groupData) {
    // do some initialization before super-call
    var wasOpen = this.open;
    this._lazyInit();
    this._switchPanning(false); // reset panning (image node will be destroyed)
    dojo.addClass(dojo.doc.documentElement, this._CSS_LIGHTBOX_SHOWN);
    if (dojo.isOpera && groupData._offsetParent) {
    	// Fix for Opera bug: Scrollbars on page scroll to top when changing overflow on documentElement.
    	groupData._offsetParent.scrollTop = groupData._origScrollY;
    }
    if (this.isFullScreen && this.switched === null && !groupData.fullscreen) {
			this.shrinkToViewport = true;
			this.switched = true;
    }
    if (dojo.isFF) this.imageContainer.scrollTop = 0; // NOTE This fixes flicker in FF when scrollbar isn't at the top and we're moving to the next image.
    
    var that = this;
    
		this._lastGroup = groupData;
		if (!that.open) {
			that.inherited(arguments);
			this._modalconnects.push(
					dojo.connect(dojo.global, "onscroll", this, "_position"),
					dojo.connect(dojo.global, "onresize", this, "_position"),
					dojo.connect(dojo.global, "onresize", this, "_shortenTextNode"),
					dojo.connect(dojo.body(), "onkeypress", this, "_handleKey")
			);
			if (!groupData.modal) {
				this._modalconnects.push(dojo.connect(
						dijit._underlay.domNode, "onclick", this, "onCancel"));
			}
			if (groupData.title) {
				this.title = groupData.title;
			}
		}
		if (this._wasStyled) {
			dojo.destroy(that.imgNode);
			that.imgNode = dojo.create("img", null, that.imageContainer,
					"first");
			that._makeAnims();
			that._wasStyled = false;
		}
		dojo.style(that.imgNode, "opacity", "0");
		dojo.style(that.titleNode, "opacity", "0");
		var href = groupData.href;
		if ((groupData.group && groupData !== "XnoGroupX") || that.inGroup) {
			if (!that.inGroup) {
				that.inGroup = that._groups[(groupData.group)];
				dojo.forEach(that.inGroup, function(g, i) {
					if (g.href == groupData.href) {
						that._index = i;
					}
				}, that);
			}
			if (!that._index) {
				that._index = 0;
				href = that.inGroup[that._index].href;
			}
			that.groupCount.innerHTML = " (" + (that._index + 1) + " of "
					+ that.inGroup.length + ")";
			that.prevNode.style.visibility = "visible";
			that.nextNode.style.visibility = "visible";
		} else {
			that.groupCount.innerHTML = "";
			that.prevNode.style.visibility = "hidden";
			that.nextNode.style.visibility = "hidden";
		}
		if (!groupData.leaveTitle) {
			that.textNode.innerHTML = groupData.title;
			that.title = groupData.title;
		}		
		if (groupData.fullscreen) {
			that.title = that.inGroup[this._index].title;
		}
		
		this._titleSize = dojo.marginBox(this.titleNode);
		
		that._ready(href);
		
    this._updateImageContainer(false, true);
    this._wasStyled = true; // make superclass always re-create image node

    // remap key-handling to the document root instead of body
    // (this fixes problem where keys were swallowed up by the grand-superclass _onKey
    // handler in FF)
    // NOTE IE might need this unremapped (untested) due to arrow key handling
    // (arrow keys were adjusting body scrollbars).
    if (dojo.isFF && !wasOpen) {
      var ind = this._modalconnects.length - 1 + (!groupData.modal ? -1 : 0);
      dojo.disconnect(this._modalconnects[ind]);
      this._modalconnects.splice(ind, 1);
      this._modalconnects.push(dojo.connect(dojo.doc.documentElement, "onkeypress", this, "_handleKey"));
    }
    // FIXME Disconnecting connects by using their indices is nasty. Do in another way.
    if (UES.isHandheld && !wasOpen) this._removeConnects(0, 1, 3, 4); // Dialog (onscroll, onresize), Lightbox (onscroll, onresize)

    // fix opacity - use it on image container so that the (possible) scrollbar
    // fades too
    dojo.style(this._imgContWrapNode, "opacity", "0");
    dojo.style(this.imgNode, "opacity", "1");

    // fix group count text
    this._updateBar();
  },

  hide: function () {
    // hide scrollbars to prevent bad background bug
    dojo.removeClass(dojo.doc.documentElement, this._CSS_LIGHTBOX_SHOWN);
    if (dojo.isOpera && this._lastGroup._offsetParent) {
    	// set position of scrollbar on offsetParent element of opened image to original state
    	this._lastGroup._offsetParent.scrollTop = this._lastGroup._origScrollY;
    }
    if (dojo.isChrome) this._updateImageContainer(false, false);
    dojo.fadeOut( {
			node : this.titleNode,
			duration : 200,
			onEnd : dojo.hitch(this, function() {
				this.imgNode.src = this._blankGif;
			})
		}).play(5);
		this.inherited(arguments);
		this.inGroup = null;
		this._index = null;
		this.switched = null;
    if (this._lastGroup.fullscreen) {
    	this.isFullScreen = false;
    	dojo.removeClass(this.domNode, this._CSS_FULL_SCREEN);
    	if (this._origState != null) {
    		this.attr("shadow", this._origState.shadow);
    		this.attr("draggable", this._origState.draggable);
    		this._origState = null;
    	}
    }
  },

  _updateBar: function () {
    var text = "";
    var display = "none";
    if (this.inGroup && this.inGroup.length > 1) {
      text = (this._index + 1) + " / " + this.inGroup.length;
      display = (dojo.isIE && dojo.isIE<=7 ? "inline" : "inline-block");
    }
    this.groupCount.innerHTML = text;
    // force title to redraw in IE (text wasn't redrawn sometimes when mouse
    // was outside of the IE)
    if (dojo.isIE) {
      this.groupCount.style.display = "none";
      this.groupCount.offsetHeight; // must access offsetHeight to redraw
    }
    this.groupCount.style.display = display;
    this.prevNode.style.display = display;
    this.nextNode.style.display = display;
  },

  _makeAnims: function () {
    // summary: make and cleanup animation and animation connections

    // use image container for fading in & out
    dojo.forEach(this._animConnects, dojo.disconnect);
    this._animConnects = [];
    this._showImageAnim = dojo.fadeIn({
      node: this._imgContWrapNode,
      duration: this.duration
    });
    this._animConnects.push(dojo.connect(this._showImageAnim, "onEnd", this, "_finishLoading"));
    this._loadingAnim = dojo.fx.combine([
      dojo.fadeOut({ node: this._imgContWrapNode, duration: this._IMG_FADEOUT_DURATION }),
      dojo.fadeOut({ node: this.titleNode, duration: this._IMG_FADEOUT_DURATION })
    ]);
    this._animConnects.push(dojo.connect(this._loadingAnim, "onEnd", this, "_prepNodes"));
    this._showNavAnim = dojo.fadeIn({ node: this.titleNode, duration:225 });
  },

  _loadImage: function () {
    // NOTE Chrome has bug - scrollbars with opacity are shown in black instead of given
    // opaque level => hide scrollbar before manipulating the opacity.
    if (dojo.isChrome) this._updateImageContainer(false, false);
    dojo.addClass(this.domNode, this._CSS_LOADING);

    this._loadingAnim.play(1);
  },
  _ready: function (aSrc) {
    // listen for 404's:
    var t = this;
    t._imgError = dojo.connect(t.imgNode, "error", t, function (e) {
      dojo.disconnect(t._imgError);
      t._imgError = null;
      // NOTE Timeout must be used because IE sometimes runs this code sooner than
      // current stack of JS methods finishes.
      setTimeout(function () { t._imageLoadError(e); }, 0);
    });

    // connect to the onload of the image
    t._imgConnect = dojo.connect(t.imgNode, "load", t, function (e) {
      // cleanup
      dojo.disconnect(t._imgConnect);
      if (t._imgError) {
        dojo.disconnect(t._imgError);
      }
      // hide error message
      dojo.style(this.errorNode, "display", "none");
      // process load
      // NOTE Timeout must be used because IE sometimes runs this code sooner than
      // current stack of JS methods finishes.
      setTimeout(function () { t._imageLoaded(e); }, 0);
    });

    t._operaSizeFix = 0;
    t.imgNode.src = aSrc;    
  },

  _imageLoadError: function (e) {
  	var t = this;
  	if (t.imgNode.src != t.errorImg) {
  		t.imgNode.src = t.errorImg; // this triggers "load" handler above
  		t.textNode.innerHTML = t.errorMessage;
  	}
  },

  _updateErrorMessage: function () {
  	var lastX = 0;
    var lastY = 0;
    this.errorNode.innerHTML = this.errorMessage;
    var contNodeInfo = dojo.position(this.imageContainer);
    lastX += (contNodeInfo.w - dojo.style(this.errorNode, "width")) / 2;
    lastY += contNodeInfo.h / 2;
    dojo.style(this.errorNode, {
    	  left: lastX + "px",
        top: lastY + "px"
    });
    dojo.style(this.errorNode, "display", "block");
  },  
  
  _imageLoaded: function (e) {
    // Opera sometimes report that the image is 1x1 and needs some time before adjusting
    // the size correctly
    if (dojo.isOpera && (this.imgNode.width <= 1 || this.imgNode.height <= 1)) {
      if (this._operaSizeFix < 2) { // prevent loop, allow 2 times only
        ++this._operaSizeFix;
        setTimeout(dojo.hitch(this, "_imageLoaded"), 500);
        return;
      }
      this._operaSizeFix = 0;
    }

    // resize to the image size
    var t = this;
    t._lastSize = {
      w: t.imgNode.width,
      h: t.imgNode.height,
      duration: t.duration
    };

    this._setMinZoomSize(this._lastSize);
    this._setMaxZoomSize(this._lastSize);

    this.scaleUp = this.isImageScaleUpAllowed();

    if (this._lastGroup.fullscreen === true) {
    	this._setFullScreen(true);
		} else {
			t.resizeTo(t._lastSize);
		}
    
    // add pre-loading
    if (this.inGroup) {
      // preload next & previous images
      var len = this.inGroup.length;
      var imgArray = [];
      if (len > 1) imgArray.push(this.inGroup[(this._index+1)%len].href);
      if (len > 2) imgArray.push(this.inGroup[(this._index+len-1)%len].href);
      if (imgArray.length > 0) dojox.image.preload(imgArray);
    }
  },

  isImageScaleUpAllowed: function () {
  	this._containerSize = this._getSizeForContainer();
  	return (this.isFullScreen && !this.shrinkToViewport && (this._lastSize.h < this._containerSize.h && this._lastSize.w < this._containerSize.w));
  },

  resizeTo: function(/* Object */size, aImmediate){
    // summary: Resize our dialog container, and fire _showImage

    // For dialog mode resize image container according to the image. For fullscreen mode resize
    // image container according to the viewport size.
    var imageLimitSize = this._getImageLimitSize();
  	if (!this._isError()) {
	  	if (this.isZooming || (this.scaleUp && this.isFullScreen) || size.h > imageLimitSize.h || size.w > imageLimitSize.w) {
	      size = this._scaleToFit(size, imageLimitSize);
	    }
  	}

    var isAdjusted = (size.w != this._lastSize.w || size.h != this._lastSize.h);
    this._currentSize = size;
    var containerSize = this._getSizeForContainer(size);
    this._containerSize = containerSize;
    this._setImageSize(size, containerSize);

    var isImgShort, isScrollXShown, isScrollYShown = false;
    if (this.isFullScreen) {
    	isImgShort = ((containerSize.w - this._currentSize.w) / 2) > Math.max(containerSize.w * this._IN_IMAGE_NAV_WIDTH_FULLSCREEN, this._IN_IMAGE_NAV_MIN_WIDTH_FULLSCREEN);
    	isScrollXShown = this._currentSize.w > containerSize.w;
    	isScrollYShown = this._currentSize.h > containerSize.h;
    }

    // turn on / off panning & adjust shrink-control button
    if (this.isZooming && (isScrollXShown || isScrollYShown)) {
    	this._switchPanning(true)
    } else {
    	this._switchPanning((isAdjusted || isScrollXShown || isScrollYShown) && !this.shrinkToViewport && !this.scaleUp);
    }

    if (this.inImageNavigation && (!isAdjusted || this.shrinkToViewport || this.scaleUp || isImgShort) &&
        this.inGroup && this.inGroup.length > 1 && !isScrollXShown)
    	dojo.addClass(this.domNode, this._CSS_WITH_NAV_IN);
    else
    	dojo.removeClass(this.domNode, this._CSS_WITH_NAV_IN);
  	dojo.query(".ues-core-lightbox_navInNext", this.domNode).style({ right: (isScrollYShown ? this._SCROLL_H_SIZE + "px" : 0) });

    this._updateShrinkNode();

    var dlgSize = { w:0, h:0};
    // do the resize
    if (!this.isFullScreen) {
    	dlgSize = { w: containerSize.w + this._adjustSize.w, h: containerSize.h + this._titleSize.h + this._adjustSize.h };
    } else {
    	dlgSize = { w: containerSize.w, h: containerSize.h + this._titleSize.h };
    }
  	if (!aImmediate) {
      var _sizeAnim = dojox.fx.sizeTo({
        node: this.containerNode,
        duration: size.duration || this.duration,
        width: dlgSize.w,
        height: dlgSize.h
      });
      this.connect(_sizeAnim, "onEnd", "_showImage");
      _sizeAnim.play(15);
    } else {
      dojo.style(this.containerNode, { width: dlgSize.w + "px", height: dlgSize.h + "px" });
      this._showImage();
    }
  },
  _getImageLimitSize: function () {
		return { w: Math.max(this._vp.w - this._adjustSize.w - this._BOUNDARIES.w - this._padBorderExt.w, 1),
             h: Math.max(this._vp.h - this._adjustSize.h - this._titleSize.h - this._BOUNDARIES.h - this._padBorderExt.h, 1) };
  },
  _getSizeForContainer: function (aImgSize) {
//    // for handhelds return always the real size of the image
//    if (UES.isHandheld) return { w: Math.max(this._lastSize.w, this._MIN_IMG_CONT_SIZE.w), h: Math.max(this._lastSize.h, this._MIN_IMG_CONT_SIZE.h) };
    if (this.isFullScreen || !aImgSize) {
    	return { w: getWindowWidth() - this._padBorderExt.w, h: getWindowHeight() - this._titleSize.h - this._padBorderExt.w };
    } else {
    	return { w: Math.max(aImgSize.w, this._MIN_IMG_CONT_SIZE.w), h: Math.max(aImgSize.h, this._MIN_IMG_CONT_SIZE.h) };
    }
  },

  _getScaledDimension: function (/* Object */size, aBoundary) {
  	var ns = { w: size.w, h: size.h };
  	var vw = aBoundary.w;
  	var vh = aBoundary.h;
  	if (vh * size.w > vw * size.h) { // DOJOFIX Fixed condition to be actually correct.
      ns.w = vw;
      ns.h = ns.w * (size.h / size.w);
    } else {
      ns.h = vh;
      ns.w = ns.h * (size.w / size.h);
    }
  	return ns;
  },

  _setMinZoomSize: function (aSize) {
  	var size = { w: aSize.w, h: aSize.h };
  	if (aSize.w <= this._vp.w && aSize.h <= this._vp.h) {
  		this._minZoomSize = { w: size.w, h: size.h };
  	} else {
  		var imageLimitSize = this._getImageLimitSize();
  		var scaledDimension = this._getScaledDimension(size, imageLimitSize);
  		this._minZoomSize = { w: Math.round(scaledDimension.w), h: Math.round(scaledDimension.h) };
  	}
  },

  _setMaxZoomSize: function (aSize) {
  	var size = { w: aSize.w, h: aSize.h };
  	if (aSize.w <= this._vp.w && aSize.h <= this._vp.h) {
  		var imageLimitSize = this._getImageLimitSize();
  		var scaledDimension = this._getScaledDimension(size, imageLimitSize);
  		this._maxZoomSize = { w: Math.round(scaledDimension.w), h: Math.round(scaledDimension.h) };
  	} else {
  		this._maxZoomSize = { w: size.w, h: size.h };
  	}
  },

  _zoomIn: function () {
  	if (!this.isFullScreen) return;
  	if (this._currentSize.w >= this._maxZoomSize.w || this._currentSize.h >= this._maxZoomSize.h) return;
  	this.isZooming = true;
  	var width = this.imgNode.width + ((this.imgNode.width/100) * (this.zoomFactor*100));
  	var height = this.imgNode.height + ((this.imgNode.height/100) * (this.zoomFactor*100));
  	this.resizeTo({w: Math.min(Math.round(width), this._maxZoomSize.w), h: Math.min(Math.round(height), this._maxZoomSize.h)}, true);
  	this.isZooming = false;
  },

  _zoomOut: function () {
  	if (!this.isFullScreen) return;
  	if (this._currentSize.w <= this._minZoomSize.w || this._currentSize.h <= this._minZoomSize.h) return;
		this.isZooming = true;
  	var width = this.imgNode.width - ((this.imgNode.width/100) * (this.zoomFactor*100));
  	var height = this.imgNode.height - ((this.imgNode.height/100) * (this.zoomFactor*100));
  	this.resizeTo({w: Math.max(Math.round(width), this._minZoomSize.w), h: Math.max(Math.round(height), this._minZoomSize.h)}, true);
  	this.isZooming = false;
  },

  _scaleToFit: function(/* Object */size, aBoundary){
    // summary: resize an image to fit within the bounds of the viewport
    // size: Object
    //    The 'size' object passed around for this image
    var ns = { h: size.h, w: size.w };
  	ns.duration = size.duration;

    var vw = aBoundary.w;
    var vh = aBoundary.h;

    if ((!this.isFullScreen && !this.shrinkToViewport) || (this.isFullScreen && !this.shrinkToViewport && !this.scaleUp) || this.isZooming) {
    	if (this.isZooming) {
    		ns.w = Math.min(Math.round(ns.w), Math.max(this._lastSize.w, vw));
    		ns.h = Math.min(Math.round(ns.h), Math.max(this._lastSize.h, vh));
    	}
      // find out required size of image container (+ count in scrollbars)
      var added = false;
      if (ns.w > vw) { ns.h += this._SCROLL_H_SIZE; added = true; }
      if (ns.h > vh) ns.w += this._SCROLL_V_SIZE; // this might result in displaying horiz. scrollbar => check again
      if (!added && ns.w > vw) ns.h += this._SCROLL_H_SIZE;
      if (this.isZooming) {
      	ns.w = Math.round(ns.w - (added ? this._SCROLL_V_SIZE : 0));
      	ns.h = Math.round(ns.h - (added ? this._SCROLL_H_SIZE : 0));
      	return ns;
      }
    } else {
  		// one of the dimensions is too big/small, go with the smaller/bigger viewport edge:
    	var scaledSize = this._getScaledDimension(ns, aBoundary);
  		ns.w = scaledSize.w;
  		ns.h = scaledSize.h;
    }

    if (!this.shrinkToViewport && this.isFullScreen && !this.scaleUp) {
    	ns.w = Math.round(ns.w);
    	ns.h = Math.round(ns.h);
    } else {
    	ns.w = Math.min(Math.round(ns.w), vw);
    	ns.h = Math.min(Math.round(ns.h), vh);
    }
    return ns; // Object
  },

  _position: function(/* Event */e){
    // summary: we want to know the viewport size any time it changes
    if (e && e.type == "resize") {
      // use immediate resizing (instead of animated) when resizing browser window
      // + do the resize before calling superclass (domNode must have new size already)
      this._vp = dijit.getViewport();
      this._setMinZoomSize(this._lastSize);
      this._setMaxZoomSize(this._lastSize);
      this.resizeTo(this._lastSize, true);
      this.scaleUp = this.isImageScaleUpAllowed();

      this._vp = dijit.getViewport();
      this.inherited(arguments, [null]);
      this._position0(arguments, [null]);
      if (this._isError()) {
				this._updateErrorMessage();
			}
    } else {
    	this._vp = dijit.getViewport();
    	this.inherited(arguments);
    }
  },

  _position0: function(/* Event */e) {
		if (e && e.type == "resize") {
			if (this._wasStyled) {
				this._setImageSize(this._lastSize);
				this.resizeTo(this._lastSize);
			} else {
				if (this.imgNode.height + 80 > this._vp.h
						|| this.imgNode.width + 60 > this._vp.h) {
					this.resizeTo( {
						w : this.imgNode.width,
						h : this.imgNode.height
					});
				}
			}
		}
	},

  _setImageSize: function (aSize, aContainerSize) {
    // summary: Reset the image size to some actual size.

    if (!this.isFullScreen) {
	    // NOTE Scrollbar will be shown in _showImage.
	    dojo.style(this.imageContainer, { width: aContainerSize.w + "px", height: aContainerSize.h + "px" });
    } else {
    	dojo.style(this.imageContainer, { width: "100%", height: "100%" });
    	dojo.style(this._imgContWrapNode, { height: aContainerSize.h + "px" });
    }
		this._updateInImageNav(aContainerSize);
		var imgSize = (this.shrinkToViewport || this.scaleUp || this.isZooming ? aSize : this._lastSize);
		var s = this.imgNode;
		s.height = imgSize.h;
		s.width = imgSize.w;

		if (UES.isHandheld) dojo.style(s, { width: imgSize.w + "px", height: imgSize.h + "px" });

		// center the image if it's smaller than minimal size of the image container
		s.style.marginTop = Math.floor(Math.max(0, (this.isFullScreen ? aContainerSize.h - imgSize.h : this._MIN_IMG_CONT_SIZE.h - imgSize.h) / 2)) + "px";
		s.style.marginLeft = (this.isFullScreen ? "auto" : Math.floor(Math.max(0, this._MIN_IMG_CONT_SIZE.w - imgSize.w) / 2) + "px");
	},

  _showImage: function () {
    if (dojo.isSafari && this._currentSize.w == this._lastSize.w && this._currentSize.h == this._lastSize.h) {
      // Safari displays scrollbar even though the image fits fine and "overflow: auto" is used
      // => use "overflow: hidden"
      this._updateImageContainer(false, false);
    } else if (!dojo.isChrome) {
      // Chrome has bug - scrollbars with opacity are shown in black instead of given
      // opaque level => wait with scrollbar displaying after the animation is played
      this._updateImageContainer(true, false);
    }
    this._position(); // fixes +-1px displacement at the end of the previous sizing animation
    this._showImageAnim.play(1);
  },

  _showNav: function () {
    // summary: Fade in the bar.
    this._showNavAnim.play(1);
  	this._shortenTextNode();
  },

  _shortenTextNode: function () {
  	var lightboxHeader = dojo.query(".ues-core-lightbox_headerWrap", this.domNode)[0];
  	var that = this;
  	dojo.query(".title", this.domNode).forEach(
  			function(aNode) {
  				aNode.innerHTML = that.title;
					var shInfo = new UES.Util.NodeShortener(lightboxHeader, 1, aNode, 20, 5).execute();
					// Show title with complete text in case it's not fully visible.
					// In case of default lightbox view add title to the drag object. In case full screen mode add it to the text node.
					// Title is removed from lightbox textNode when switching from fullscreen mode to default mode.
					var titleHolder = dojo.query(".drag-obj_lightbox", this.domNode)[0];
					if (titleHolder == null) titleHolder = that.textNode;
					if (titleHolder) {
						if (shInfo.visibleChars < shInfo.totalChars) {
							titleHolder.setAttribute("title", that.title);
						} else {
							titleHolder.removeAttribute("title");
						}
					}
  			}
  	);
  },

  _finishLoading: function () {
    dojo.removeClass(this.domNode, this._CSS_LOADING);
    // Chrome has bug - scrollbars with opacity are shown in black instead of given
    // opaque level => show scrollbars after the fade-in.
    if (dojo.isChrome) this._updateImageContainer(true, false);
    if (this._isError()) this._updateErrorMessage();
    this._showNav();
    this._setDragHandlePosition();
  },

  _handleKey: function (/* Event */e) {
    // summary: Handle keyboard navigation internally
    if(!this.open){ return; }

    // DOJOFIX Ignore keys if Ctrl / Alt / Shift is pressed.
    if (e.ctrlKey || e.altKey || e.shiftKey) return;
    // DOJOFIX Block event once it's processed. In IE this fixes problem where arrow keys
    // were navigating & moving scrollbar on layout at the same time.
    var dk = dojo.keys;
    var processed = false;
    switch(e.charOrCode){
      case dk.ESCAPE:
        this.hide();
        processed = true;
        break;

      case dk.SPACE:
      case ' ':
      	if (!this.isFullScreen) break;
      case dk.UP_ARROW:
      case dk.RIGHT_ARROW:
      case 78: // key "n"
        this._nextImage();
        processed = true;
        break;

      case dk.DOWN_ARROW:
      case dk.LEFT_ARROW:
      case 80: // key "p"
        this._prevImage();
        processed = true;
        break;
      case dk.NUMPAD_PLUS:
      case "+":
      	this._zoomIn();
      	processed = true;
      	break;
      case dk.NUMPAD_MINUS:
      case "-":
      	this._zoomOut();
      	processed = true;
      	break;
    }
    if (processed) dojo.stopEvent(e);
  },

  _nextImage: function (e) {
    // DOJOFIX Don't reload image if it's alone in the group.
    if (!this.inGroup || this.inGroup.length <= 1) return;
    if (e && this._skipClick && e.type == "click") return;
    if (e) dojo.stopEvent(e);

		if (this._index + 1 < this.inGroup.length) {
			this._index++;
		} else {
			this._index = 0;
		}
		this._loadImage();
  },
  _prevImage: function (e) {
    // DOJOFIX Don't reload image if it's alone in the group.
    if (!this.inGroup || this.inGroup.length <= 1) return;
    if (e && this._skipClick && e.type == "click") return;
    if (e) dojo.stopEvent(e);

		if (this._index == 0) {
			this._index = this.inGroup.length - 1;
		} else {
			this._index--;
		}
		this._loadImage();
  },

  _updateImageContainer: function (aShowOverflow, aResetScroll) {
    var showOverflow = aShowOverflow && (!this.shrinkToViewport || this.isZooming);
    var o = (showOverflow ? "auto" : "hidden");
    if (this.imageContainer.style.overflow != o) dojo.style(this.imageContainer, "overflow", o);
    if (aResetScroll) {
      this.imageContainer.scrollTop = 0;
      this.imageContainer.scrollLeft = 0;
    }
  },

  // arguments must be sorted increasingly
  _removeConnects: function (/* aIndices... */) {
    for (var i=arguments.length-1; i>=0; --i) {
      var ind = arguments[i];
      dojo.disconnect(this._modalconnects[ind]);
      this._modalconnects.splice(ind, 1);
    }
  },

  _onShrinkClick: function (aEvent) {
    var isAdjusted = (this._lastSize.w != this._currentSize.w || this._lastSize.h != this._currentSize.h);
    if (!isAdjusted && !this.isFullScreen) return;

    this.shrinkToViewport = !this.shrinkToViewport;

    this._updateShrinkNode();
    // we will play standard animations (fade-out, resize, fade-in) because otherwise
    // we would have to do resize-To on a shrinking image (more work, risk of choppy animation)
    this._loadImage();
  },

  _onFullScreenClick: function (aEvent) {
  	if (this.isFullScreen) {
  		this.textNode.removeAttribute("title"); 
  	}
  	this._setFullScreen(!this.isFullScreen);
  },

  _updateShrinkNode: function () {
    var isAdjusted = (this._lastSize.w != this._currentSize.w || this._lastSize.h != this._currentSize.h);

    dojo.removeClass(this.domNode, this._CSS_SHRINK_ON + " " + this._CSS_SHRINK_OFF);
    if (isAdjusted || this.isFullScreen) {
    	if (!this.shrinkToViewport) {
      	dojo.addClass(this.domNode, this._CSS_SHRINK_OFF);
      } else {
      	dojo.addClass(this.domNode, this._CSS_SHRINK_ON);
      }
    }
  },

  _updateInImageNav: function (aContainerSize) {
    if (!this.inImageNavigation) return;

    dojo.query(".ues-core-lightbox_navIn", this.domNode).style({
      height: aContainerSize.h + "px",
      width: this._getImageNavWidth()
    });
  },

  _onImageClick: function (e) {
    if (this.inImageNavigation && this._usingPan) {
      if ((this._panEndTime && new Date().getTime() - this._panEndTime < 500) || this._panMoveDone) {
        // ignore (panning was done)
        this._panEndTime = null;
        dojo.stopEvent(e);
        return;
      }
    }
    if (e && e.target == this.imgNode) {
			this.onClick(this._lastGroup);
			if (this._lastGroup.declaredClass) {
				this._lastGroup.onClick(this._lastGroup);
			}
		}

    // we want to do navigation on click even if panning cursor is shown
    // => check which side of the image has been clicked
    // NOTE We probably can't do the click-checking better. E.g. by overlaying the image with DIV
    // would mousewheel for scrolling stop working.
    if (this.inImageNavigation && this._usingPan) {
      var p = dojo.position(this.imageContainer, true);
      var imageNavWidth = (this.isFullScreen ? this._IN_IMAGE_NAV_WIDTH_FULLSCREEN : this._IN_IMAGE_NAV_WIDTH_DIALOG);
      if (p.x <= e.pageX && e.pageX < p.x + imageNavWidth*p.w) this._prevImage(e);
      else if (p.x + (1-imageNavWidth)*p.w < e.pageX && e.pageX < p.x + p.w) this._nextImage(e);
    }
  },

  addImage : function(_f, _10) {
  	var g = _10;
  	if (!_f.href) {
  		return;
  	}
  	if (g) {
  		if (!this._groups[g]) {
  			this._groups[g] = [];
  		}
  		this._groups[g].push(_f);
  	} else {
  		this._groups["XnoGroupX"].push(_f);
  	}
  },

  onClick: function(_11) {
  },

  _size: function() {
  },

  _prepNodes: function() {
  	this._imageReady = false;
  	this.show( {
  		href : this.inGroup[this._index].href,
  		title : this.inGroup[this._index].title
  	});
  },

  _setDragHandlePosition: function () {
  	var navWidth = this.titleTextNode.offsetWidth;
  	var titlePos = dojo.position(this.titleTextNode, true);
  	var cPos = dojo.position(this.containerNode, true);
  	var pos = { x: titlePos.x - cPos.x + navWidth, y: titlePos.y - cPos.y};
  	var header = dojo.query(".ues-core-lightbox_headerWrap", this.domNode)[0];
  	var hWidth = dojo.style(header, 'width');
  	var hHeight = dojo.style(header, 'height');

  	dojo.query("."+this.dragHandleClass, this.domNode).style({
      left: pos.x - this._adjustSize.w + "px",
      top: pos.y - this._adjustSize.h + "px",
      width: hWidth - navWidth + "px",
      height: hHeight + "px"
  	});
  },

  _getImageNavWidth: function () {
  	if (this.isFullScreen) {
  		var width = (this._imgContWrapNode.offsetWidth / 100) * this._IN_IMAGE_NAV_WIDTH_FULLSCREEN * 100;
  		return ((width && width > this._IN_IMAGE_NAV_MIN_WIDTH_FULLSCREEN) ? this._IN_IMAGE_NAV_WIDTH_FULLSCREEN * 100 + "%" : this._IN_IMAGE_NAV_MIN_WIDTH_FULLSCREEN + "px");
  	} else {
  		var width = (this._imgContWrapNode.offsetWidth / 100) * this._IN_IMAGE_NAV_WIDTH_DIALOG * 100;
  		return ((width && width > this._IN_IMAGE_NAV_MIN_WIDTH_DIALOG) ? this._IN_IMAGE_NAV_WIDTH_DIALOG * 100 + "%" : this._IN_IMAGE_NAV_MIN_WIDTH_DIALOG + "px");
  	}
  },

  _setFullScreen: function (aFull) {
  	this.isFullScreen = aFull;
  	
  	if (aFull) {
  		// store the basic state we have to restore later
  		var domNode = this.domNode;
  		this._origState = {
				top: dojo.style(domNode, "top") || "0",
				left: dojo.style(domNode, "left") || "0",
				draggable: this.draggable || false,
				shadow: this.shadow || false
  		};

  		this.attr("shadow", false);
  		this.attr("draggable", false);
  		dojo.addClass(this.domNode, this._CSS_FULL_SCREEN);

  		// resize lightbox dialog
  		this._vp = dijit.getViewport();
  		dojo.style(this.domNode, {
	  		position: "absolute",
	  		top: "0",
	  		left: "0",
	  		width: this._vp.w - this._adjustSize.w + "px",
	  		height: this._vp.h - this._adjustSize.h + "px"
  		});

  		// shrink image to viewport after the first switch to fullscreen mode
  		if (!this.switched)
  			this.shrinkToViewport = true;
  		
 			this.switched = true;

  		this.resizeTo( {
  			w : this.imgNode.width,
  			h : this.imgNode.height
  		});
  	} else {
			dojo.removeClass(this.domNode, this._CSS_FULL_SCREEN);
			this.attr("shadow", this._origState.shadow);
			this.attr("draggable", this._origState.draggable);
			this._origState = null;
			this._loadImage();
  	}
  	
  	if (this._isError()) this._updateErrorMessage();
  },
  
  _isError: function () {
  	return (this.imgNode.src.indexOf(this.errorImg) != -1);
  },

  /////////// panning functionality ////////////////
  _switchPanning: function (aUsePan) {
    if (aUsePan === this._usingPan) return;
    this._usingPan = aUsePan;
    if (!this._usingPan) { // remove panning
      this.disconnect(this._panable, "onMoveStart", "_panStart");
      this.disconnect(this._panable, "onFirstMove", "_panFirstMove");
      this.disconnect(this._panable, "onMoveStop", "_panStop");
      this._panable.destroy();
      dojo.removeClass(this.domNode, this._CSS_PAN);
      delete this._panable;
    } else { // add panning
      this._panable = new Panable(this.imageContainer, { handle: this.imgNode });
      dojo.addClass(this.domNode, this._CSS_PAN);
      this.connect(this._panable, "onMoveStart", "_panStart");
      this.connect(this._panable, "onFirstMove", "_panFirstMove");
      this.connect(this._panable, "onMoveStop", "_panStop");
    }
  },

  // following 3 functions are required so that the in-image navigation works
  // even when panning is turned on
  _panStart: function () {
    this._panMoveDone = false;
  },
  _panFirstMove: function () {
    this._panMoveDone = true;
  },
  _panStop: function () {
    if (this._panMoveDone) this._panEndTime = new Date().getTime();
    else this._panEndTime = null;
    this._panMoveDone = false;
  }
});

})();

