/*globals jQuery,$,window*/
/*jslint 
nomen: false
white: false*/

/*
*
* Wijmo Library 1.1.2
* http://wijmo.com/
*
* Copyright(c) GrapeCity, Inc.  All rights reserved.
* 
* Dual licensed under the MIT or GPL Version 2 licenses.
* licensing@wijmo.com
* http://www.wijmo.com/license
*
* * Wijmo Expander widget.
* 
* Depends:
*  jquery.ui.core.js
*  jquery.ui.widget.js
*  jquery.ui.position.js
*  json2.js
*  jquery.wijmo.wijutil.js
*  jquery.wijmo.wijdialog.js
*  jquery.wijmo.wijexpander.js
*  jquery.wijmo.wijreportviewer.js
*  jquery.wijmo.wijlist.js
*  jquery.wijmo.wijcombobox.js
*  jquery.effects.core.js
*  jquery.effects.blind.js
*  jquery.wijmo.wijutil.js
*  jquery.wijmo.wijpopup.js
*  jquery.wijmo.wijinputcore.js
*  jquery.wijmo.wijinputnumber.js
*  jquery.wijmo.wijcalendar.js
*  jquery.mousewheel.js
*/
(function ($) {
	"use strict";

	$.widget("wijmo.wijreportviewer", {
		// widget options
		options: {
			///	<summary>
			///	Use the localization option in order to customize localization.
			/// Default: {
			///		activityGenerating: "Generating...",
			///		activityGeneratingDocument: "Generating document...",
			///		activityUpdatingPages: "Updating pages...",
			///		activityWaitingForInput: "Waiting for input...",
			///		activityLoadingOutline: "Loading outline...",
			///		activityLoading: "Loading...",
			///		activityStatusSearchingText: "Searching text...",
			///		searchNoMatchesFoundFormat: "No Matches Found for '{0}'",
			///		searchMatchesFoundFormat: "{0} Matches Found",
			///		searchResultPageFormat: "Page {0}:"
			///		activityStatusReceived: "documentStatus received, state is {0}",
			///		activityStatusFormat: "Report is currently generating, total page count may change. Current page count: {0}, completed {1}%",
			///		activityStatusReadyFormat: "Report is completely generated. Total page count: {0}",			
			///		messageFileNameNotSpecified: "Report definition file not specified.",
			///		messageGenerateInDisabledState: "Can't execute generate, report viewer is disabled.",
			///		toolNameThumbs: "Thumbs",
			///		toolNameSearch: "Search",
			///		toolNameOutline: "Outline",
			///		outlineStatus: "[no outline]",
			///		buttonPrint: "Print",
			///		buttonPrintTips: "Print Tips",
			///		buttonCancel: "Cancel",
			///		labelPrint: "Print",

			///		activityReportneedsparameters: "Report needs parameters",
			///		buttonSetparameters: "Set parameters",
			///		titleReportparameters: "report parameters"
			
			///		buttonClearAll: "Clear All",
			///		buttonClose: "Close"


			/// }
			/// Type: Object.
			///	</summary>
			localization: null,
			/// <summary>
			/// Name for the exported file.
			/// Default: ""
			/// Type: String.
			/// Code example: $("#report").c1reportviewer("option", "exportedFileName", "fileName");
			/// </summary>
			exportedFileName: "",
			/// <summary>
			/// Enables built-in log console.
			/// Default: false
			/// Type: Boolean.
			/// Code example: $("#report").c1reportviewer({ enableLogs: true });
			/// </summary>
			enableLogs: false,
			/// <summary>
			/// Gets or sets the index of the page being viewed (zero-based).
			/// Default: 0
			/// Type: Number.
			/// Code example: $("#report").c1reportviewer({ pageIndex: 11 });
			/// </summary>
			pageIndex: 0,
			/// <summary>
			/// The URL to the report service.
			/// Default: "reportService.ashx"
			/// Type: String.
			/// Code example: 
			/// $("#report").c1reportviewer({ 
			///		reportServiceUrl: "http://mysite/reportservice.php" 
			///	});
			/// </summary>
			reportServiceUrl: "reportService.ashx",
			/// <summary>
			/// Gets the common information about current report.
			/// Object consists from following fields:
			///		documentKey
			///			String. Unique key identifying a cached document.
			///		exportFormats
			///			Array of export formats available for current document.
			///			The list is a sequence of pairs: export format 
			///			description/default extension for that format.
			///			The description should uniquely identify the format 
			///			within the list.
			///			The extension must be lowercase and should not include 
			///			the dot (e.g. "pdf").
			///			This member contains data only when the document is not 
			///			generating (isGenerating is false).
			///		isGenerating
			///			Indicates whether current report is generating and report 
			///			information is not available.
			///		state
			///			Number. The current report generating state (ready, 
			///				pages are being added, or pages are being updated).
			///			Possible values:
			///			0 - Ready. Report is completely generated.
			///			1 - GeneratingPages. Report is currently generating, total 
			///				page count may change.
			///			2 - UpdatingPages. Report is still generating, individual 
			///				pages may change but total page count is final.
			///			3 - ParametersRequested. Report needs parameters, status 
			///				contains ParamInfos describing the requested parameters.
			///				All other fields in status are not initialized.
			///		percentComplete
			///			Number. If report is generating, gets the approximate 
			///			percent of work complete, from 0 to 100.
			///		name
			///			String. Report name.
			///		created
			///			Date. Date created.
			///		pageCount
			///			Total or current page count depending on generation state.
			///		w
			///			Default page width in pixels for Zoom 100% and 96 DPI.
			///		h
			///			Default page height in pixels for Zoom 100% and 96 DPI.
			///		changedPages
			///			Indices of pages that changed since the last 
			///			document status call (calls are tracked via the cookie field).
			///		pageSizes
			///			Array of intervals of pages with the same size.
			///			The intervals are guaranteed to be sorted by page indices,
			///			and to cover all pages from 0 to pageCount.	
			///		cookie
			///			String. Cookie data that generated by C1Report service.
			///		error
			///			If an error occurred on the server, contains the error 
			///			description. Normally should be null.
			///		reportParams
			///			The array of REQUIRED report parameters, filled in by 
			///			the server. May be null, BUT IF this is non-null on 
			///			return from server, the client must use this info to fill 
			///			the param name/param value list, and pass it to the server 
			///			in order to generate the document.
			///			todo: describe ReportParameterInfo object fields.
			/// Default: null
			/// Type: Object.
			/// Code example: 
			///	var status = $("#report").c1reportviewer("option", "documentStatus");
			/// </summary>
			documentStatus: null,
			/// <summary>
			/// Specifies whether the viewer is disabled.
			/// Default: false
			/// Type: Boolean.
			/// Code example: $("#report").c1reportviewer("option", "disabled", true);
			/// </summary>
			disabled: false,
			/// <summary>
			/// The name of the file with report.
			/// Default: ""
			/// Type: String.
			/// Code example: 
			///	$("#report").c1reportviewer({ fileName: "BarcodeLabels.xml" });
			/// </summary>
			fileName: "",
			/// <summary>
			/// The name of the report to view.
			/// Default: ""
			/// Type: String.
			/// Code example: 
			///        $("#report").c1reportviewer({
			///             fileName: "CommonTasks.xml",
			///				reportName: "01: Alternating Background (Greenbar report)"
			///        });
			/// </summary>
			reportName: "",
			/// <summary>
			/// An array with report parameters.
			/// Default: null
			/// Type: Array.
			/// Code example: 
			///		$("#report").c1reportviewer("option", "reportParameters", [{n: "IntVals", vs: [1, 2, 3]}, {n: "StringVal", vs: ["Some value"]}]
			/// </summary>
			reportParameters: null,
			/// <summary>
			/// Specifies whether the viewer must show document pages individually 
			///	(with scrollbars covering the current page only) or continuously 
			///	(with scrollbars covering the whole document).
			/// Default: true
			/// Type: Boolean
			/// Code example: $("#report").c1reportviewer({ pagedView: false });
			/// </summary>			
			pagedView: true,
			/// <summary>
			/// The page zoom value. Accepts named zoom values like "actual size", 
			///	"fit page", "fit width", "fit height" or value in percentages, e.g. "50%", "70%".
			/// Default: "100%"
			/// Type: String.
			/// Code example: $("#report").c1reportviewer({ zoom: "Fit Page" });
			/// </summary>
			zoom: "100%"
		},

		/*Available Events:
		/// <summary>
		/// Occurs when current(visible) page index has been changed.
		/// Use pageIndex option in order to read visible page index value.
		/// Type: Function
		/// Event type: c1reportviewerpageindexchanged
		/// Code example:
		/// Supply a callback function to handle the pageIndexChanged event
		///	as an option.
		/// $("#report").c1reportviewer({ pageIndexChanged: function (e) {
		///		...
		///    }
		///	});
		/// Bind to the event by type: c1reportviewerpageindexchanged.
		/// $( "#report" ).bind( "c1reportviewerpageindexchanged", function(e) {
		///		...		
		/// });
		/// </summary>
		/// <param name="e" type="Object">jQuery.Event object.</param>
		/// <param name="index" type="Number">The new current page index.</param>
		pageIndexChanged(e, index)

		/// <summary>
		/// Occurs when document status information changed.
		/// Type: Function
		/// Event type: c1reportviewerdocumentstatuschanged
		/// Code example:
		/// Supply a callback function to handle the documentStatusChanged event
		///	as an option.
		/// $("#report").c1reportviewer({ documentStatusChanged: function (e, documentStatus) {
		///		...
		///    }
		///	});
		/// Bind to the event by type: c1reportviewerdocumentstatuschanged.
		/// $( "#report" ).bind( "c1reportviewerdocumentstatuschanged", function(e, documentStatus) {
		///		...		
		/// });
		/// </summary>
		/// <param name="e" type="Object">jQuery.Event object.</param>
		/// <param name="index" type="Object">The new document status object. See options.documentStatus for the object description.</param>
		documentStatusChanged(e, documentStatus)

		/// <summary>
		/// Occurs when report viewer zoom has been changed.
		/// Type: Function
		/// Event type: c1reportviewerzoomchanged
		/// Code example:
		/// Supply a callback function to handle the zoomChanged event
		///	as an option.
		/// $("#report").c1reportviewer({ zoomChanged: function (e, zoom, resolvedZoom) {
		///		...
		///    }
		///	});
		/// Bind to the event by type: c1reportviewerzoomchanged.
		/// $( "#report" ).bind( "c1reportviewerzoomchanged", 
		///   function(e, zoom, resolvedZoom) {
		///		...		
		///   });
		/// </summary>
		/// <param name="e" type="Object">jQuery.Event object.</param>
		/// <param name="zoom" type="String">Zoom value in percentages or zoom name.</param>
		/// <param name="resolvedZoom" type="Number">Resulting zoom value in percentages.</param>
		zoomChanged(e, zoom, resolvedZoom)


		*/


		// handle option changes:
		_setOption: function (key, value) {
			var o = this.options, scrolledPageIndex;
			switch (key) {
				case "disabled":
					if (value) {
						this.element.addClass("ui-state-disabled");
					} else {
						this.element.removeClass("ui-state-disabled");
					}
					break;
				case "enableLogs":
					o.enableLogs = value;
					this._initLogPanel();
					break;
				case "pagedView":
					if (value) {
						this.element.addClass("wijmo-wijreportviewer-paged");
						this.element.find(".C1Page.C1Current").removeClass("C1Current");
						this.element.find(".C1Page" + o.pageIndex).addClass("C1Current");
						this.scrollToY(0);
					} else {
						scrolledPageIndex = o.pageIndex;
						this.element.removeClass("wijmo-wijreportviewer-paged");
						this.element.find(".C1Page.C1Current").removeClass("C1Current");
						this.scrollToPage(scrolledPageIndex);
					}
					//this._loadVisiblePagesData(false);
					break;
				case "zoom":
					this._changeZoom(value);
					break;
				case "pageIndex":
					this.scrollToPage(value);
					break;
				default:
					break;
			}
			$.Widget.prototype._setOption.apply(this, arguments);
			this.log("option " + key + " changed to " + value);
		},


		// zoom implementation:
		_changeZoom: function (value) {
			var o = this.options,
					prevValue = this.resolvedZoom,
					visiblePageIndexes, visiblePageElement, prevTopDiff,
					reportPaneElement = this.element.find(".wijmo-wijreportviewer-reportpane"),
					reportContentElement = this.element.find(".wijmo-wijreportviewer-content");
			this.resolvedZoom = -1;
			o.zoom = value;
			value = this._rezolveZoom();
			this._repairZoomKoef = value / ((isNaN(prevValue)) ? value : prevValue);
			this.log("Changing zoom to " + o.zoom + ", resolved zoom is " + value + " (previous value is " +
				prevValue + ").");
			if (this._repairZoomKoef === 1) {
				return;
			}
			visiblePageIndexes = this.getVisiblePageIndices();
			visiblePageElement = null;
			prevTopDiff = 0;

			if (visiblePageIndexes.length > 0) {
				visiblePageElement = reportContentElement.find('.C1Page' + visiblePageIndexes[0])[0];
				prevTopDiff = reportPaneElement.scrollTop() - visiblePageElement.offsetTop;
			}
			reportContentElement.find(".C1Page").each($.proxy(this._repairZoomedHihlightBoundsCallback, this));
			if (visiblePageIndexes.length > 0) {
				reportPaneElement.scrollTop(visiblePageElement.offsetTop + Math.round(prevTopDiff * this._repairZoomKoef));
			}
			else {
				reportPaneElement.scrollTop(Math.round(reportPaneElement.scrollTop() * this._repairZoomKoef));
			}
			reportPaneElement.scrollLeft(Math.round(reportPaneElement.scrollLeft() * this._repairZoomKoef));
			this._loadVisiblePagesData(true, null);
			//
			this._trigger("zoomChanged", null, [o.zoom.toString(), this.resolvedZoom]);
		},

		_repairZoomedHihlightBoundsCallback: function (indx, pageElement) {
			var height = pageElement.style.height.toString().replace('px', '') * 1;
			var width = pageElement.style.width.toString().replace('px', '') * 1;
			if (height > 0 && width > 0) {
				var newwidth = Math.round(width * this._repairZoomKoef);
				var newheight = Math.round(height * this._repairZoomKoef);
				pageElement.style.width = newwidth + 'px';
				pageElement.style.height = newheight + 'px';
				this._repairZoomedHihlightBounds(pageElement, this._repairZoomKoef);
			}
			return true;
		},

		_repairZoomedHihlightBounds: function (pageElement, zoomKoef) {
			this._pendingElaborateHighlightBounds = true;
			var highlights = jQuery('.C1Highlight', pageElement).get();
			if (highlights) {
				for (var i = 0; i < highlights.length; i++) {
					var element = highlights[i];
					element.style.left = Math.round(element.offsetLeft * zoomKoef) + 'px';
					element.style.top = Math.round(element.offsetTop * zoomKoef) + 'px';
					element.style.width = Math.round(element.offsetWidth * zoomKoef) + 'px';
					element.style.height = Math.round(element.offsetHeight * zoomKoef) + 'px';
				}
			}
		},

		//<-

		//localizeString:
		localizeString: function (key, defaultValue) {
			var o = this.options;
			if (o.localization && o.localization[key]) {
				return o.localization[key];
			}
			return defaultValue;
		},
		//format:
		_formatString: function (fmt) {
			var r, args = arguments, i, funcArgs, self = this;
			if (args.length <= 1) {
				return Globalize.format(args);
			}
			if (typeof fmt === "string" && typeof window[fmt] === "function") {
				fmt = window[fmt];
			}
			if (typeof fmt === "function") {
				funcArgs = [];
				for (i = 1; i < args.length; i += 1) {
					funcArgs[i - 1] = args[i];
				}
				return fmt.apply(this, funcArgs);
			}
			r = new RegExp("\\{(\\d+)(?:,([-+]?\\d+))?(?:\\:" +
					"([^(^}]+)(?:\\(((?:\\\\\\)|[^)])+)\\)){0,1}){0,1}\\}", "g");
			return fmt.replace(r, function (m, num, len, f, params) {
				m = args[Number(num) + 1];
				if (f && window.Globalize) {
					return Globalize.format(m, f, self._getCulture());
				} else {
					return m;
				}
			});
		},
		_getCulture: function (name) {
			return Globalize.findClosestCulture(name || this.options.culture);
		},
		widgetEventPrefix: "wijreportviewer",
		_create: function () {
			if (!this.wijreportnamespacekey) {
				this.wijreportnamespacekey = "wijreportviewer" + new Date().getTime();
			}
			this._initLogPanel();
			var isSafari = navigator.userAgent.indexOf("Safari") !== -1;

			if (!this.element[0].id) {
				this.element[0].id = "wijreportviewer_dynid_" + new Date().getTime();
				// todo: elaborate, id is needed for hyperlink actions.
			}
			this.element.addClass("wijmo-wijreportviewer ui-widget ui-helper-reset");
			if (!$(".wijmo-wijreportviewer-DPI-test").length) {
				$("<div class='wijmo-wijreportviewer-DPI-test'></div>").prependTo(document.body);
			}

			if (this.element.find(".wijmo-wijreportviewer-reportpane").length < 1) {
				this.element.append('<div class="wijmo-wijreportviewer-reportpane"><center class="wijmo-wijreportviewer-content"></center></div>');
			}

			// Notes:
			// todo: test if following problem was fixed in the latest Safari build:
			// space is required in order to allow raise select event from context menu;
			// select event is not work for Safari when textarea is readonly.
			this._selectionAreaElement = $(".wijmo-wijreportviewer-selection-area-" + this.wijreportnamespacekey);
			if (this._selectionAreaElement.length < 1) {
				this._selectionAreaElement =
				$("<textarea class='wijmo-wijreportviewer-selection-area wijmo-wijreportviewer-selection-area-" + this.wijreportnamespacekey + "' " +
					(!isSafari ? "readonly='readonly' " : "") +
					"rows='1' cols='1'> </textarea>").appendTo(document.body);
				// append to document in order to make sure that 
				// textarea does not have relative parents
			}


			//$(".wijmo-wijreportviewer-expander").wijexpander({ allowExpand: false });		
		},
		_init: function () {
			var o = this.options;
			if (o.disabled) {
				this.element.addClass("ui-state-disabled");
			}
			if (o.pagedView) {
				this.element.addClass("wijmo-wijreportviewer-paged");
			}
			this.element.ajaxError(jQuery.proxy(this._onAjaxError, this));
			this.generate();
			this._bindEvents();
		},

		destroy: function () {
			this._unbindEvents();
			this.element.removeClass("wijmo-wijreportviewer ui-widget ui-helper-reset");
			$("wijmo-wijreportviewer-selection-area-" + this.wijreportnamespacekey).remove();
			$.Widget.prototype.destroy.apply(this, arguments);
		},

		/** public methods */

		/// <summary>
		/// Export active report to a file.
		/// </summary>
		/// <param name="exportFormat">
		///	Unique export format description (see documentStatus.exportFormats).
		///	</param>
		/// <param name="exportFormatExt">
		///	The destination file extension(without dot).
		///	</param>
		exportToFile: function (exportFormat, exportFormatExt) {
			var o = this.options,
				docStatus = o.documentStatus;
			if (!docStatus || docStatus.isGenerating) {
				this.status("Export failed. Document is not generated.",
								"error");
				return;
			}
			else {
				this.log("Exporting to " + exportFormat);
				window.open(o.reportServiceUrl +
					"?clientId=" + this.element[0].id +
					"&command=Export&documentKey=" + docStatus.documentKey +
					"&exportFormat=" + exportFormat + "&exportFormatExt=" +
					exportFormatExt + "&exportedFileName=" + o.exportedFileName, "blank");
			}
		},

		/// <summary>
		/// Searches the text.
		/// </summary>
		/// <param name="query">The search query.</param>
		/// <param name="caseSensitive">if set to <c>true</c> search will be case sensitive.</param>
		/// <param name="useRegExp">if set to <c>true</c> query text will be recognized as a regular expression.</param>
		/// <param name="callback">Callback function that will be called when search done.</param>
		searchText: function (query, caseSensitive, useRegExp, callback) {
			var o = this.options, docStatus = o.documentStatus;
			if (!docStatus) {
				this.log("Unable to search, document status is empty.");
				return false;
			}
			this.log("Search: " + query + "," + caseSensitive + "," + useRegExp);
			this.status(this.localizeString("activityStatusSearchingText", "Searching text..."));
			/*
			$.getJSON(o.reportServiceUrl + "?clientId=" + this.element[0].id +
			"&command=search" +
			"&documentKey=" + docStatus.documentKey +
			"&cookie=" + docStatus.cookie +
			"&query=" + query +
			"&caseSensitive=" + caseSensitive +
			"&useRegExp=" + useRegExp,
			callback
			);
			*/
			/*
			fix for #27394 ...Japanese characters in localized report document:
			*/
			$.ajax({
				url: o.reportServiceUrl + "?clientId=" + this.element[0].id +
				"&command=search" +
				"&documentKey=" + docStatus.documentKey +
				"&cookie=" + docStatus.cookie +
				"&caseSensitive=" + caseSensitive +
				"&useRegExp=" + useRegExp +
				"&timestamp=" + new Date().getTime(),
				dataType: "text",
				contentType: "application/json; charset=utf-8",
				type: "POST",
				data: "query=" + query,
				success: callback,
				error: jQuery.proxy(this._onAjaxError, this)
			});


		},

		_jsonParse: function (s) {
			var o, reISO, reMsAjax;
			if (window.__JSONC1) {
				o = window.__JSONC1.parse(s);
			} else if (window.JSON) {
				reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/;
				reMsAjax = /^\/Date\((d|-|.*)\)\/$/;
				o = window.JSON.parse(s,
					function (key, value) {
						if (typeof value === 'string') {
							var a = reISO.exec(value), b;
							if (a) {
								return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3],
					+a[4], +a[5], +a[6]));
							}
							a = reMsAjax.exec(value);
							if (a) {
								b = a[1].split(/[-,.]/);
								return new Date(+b[0]);
							}
						}
						return value;
					});
			} else {
				throw "JSON variable not found.";
			}
			return o;
		},

		/// <summary>
		/// Gets the tree of document outlines.
		/// This method is primarily used by control developers.
		/// </summary>        
		/// <param name="documentKey">Document key.</param>
		/// <param name="callback">
		///	Function which will be called when outline is loaded.
		///	The function receives one parameter - tree of document outline entries.
		///	</param>
		loadOutline: function (callback) {
			var o = this.options;
			this.log("Loading outline...");
			$.getJSON(o.reportServiceUrl + "?clientId=" + this.element[0].id +
				"&command=outline" +
				"&documentKey=" + o.documentStatus.documentKey +
				"&cookie=" + o.documentStatus.cookie,
				callback
				);
		},

		/// <summary>
		/// Scroll view to search entry and highlight text related to SearchEntry.
		/// This method is primarily used by control developers.
		/// </summary>
		/// <param name="searchEntry">The search entry object.</param>
		goToSearchEntry: function (searchEntry) {
			if (isFinite(searchEntry) && this._searchResults) {
				searchEntry = this._searchResults.ses[searchEntry];
			}
			var koef = this._rezolveZoom() / 100,
				pageIndex = searchEntry.p, trMarkups = searchEntry.tr,
				page = this._pages[pageIndex],
				scrolled = false, i, tr, sp, ep;
			if (page) {
				page.clearSelection();
				for (i = 0; i < trMarkups.length; i++) {
					tr = trMarkups[i];
					sp = { x: Math.round(tr.x * koef), y: Math.round(tr.y * koef) };
					ep = { x: Math.round(tr.x * koef + tr.w * koef),
						y: Math.round(tr.y * koef + tr.h * koef)
					};
					page._applyHighLight(Math.round(tr.x * koef), Math.round(tr.y * koef), Math.round(tr.w * koef), Math.round(tr.h * koef), 'red', { tr100z: tr });
					if (!scrolled) {
						this.scrollToPage(pageIndex, Math.round(tr.y * koef - 10));
						scrolled = true;
					}
				}
				this._loadVisiblePagesData(true, null);
			}
			else {
				this.scrollToPage(pageIndex, 0);
				if (!searchEntry.___flag1) {
					this._loadVisiblePagesData(false, $.proxy(function () {
						searchEntry.___flag1 = true;
						this.goToSearchEntry(searchEntry);
					}, this));
				}
			}
		},

		/// <summary>
		/// Scroll current view vertically to specified top position.
		/// </summary>
		/// <param name="y">The top value in pixels.</param>
		scrollToY: function (y) {
			this.element.find(".wijmo-wijreportviewer-reportpane")[0].scrollTop = y;
		},

		/// <summary>
		/// Gets the current display DPI value (pixels per inch).
		/// </summary>
		getDPI: function () {
			if (!this.currentDpiValue) {
				this.currentDpiValue = $(".wijmo-wijreportviewer-DPI-test")[0].offsetWidth;
			}
			return this.currentDpiValue;
		},

		/// <summary>
		/// Call this method in order to generate report according fileName, reportName and reportParameters options.
		/// Code Example: 
		/// $("#report").wijreportviewer();
		///	$("#report").wijreportviewer("option", "fileName", "BarcodeLabels.xml");
		///	$("#report").wijreportviewer("generate");
		///</summary>
		/// <param name="reportParameters">Optional. Array with report parameters.</param>
		generate: function (reportParameters) {
			var o = this.options;
			if (!reportParameters && this.reportParamsPane) {
				this.reportParamsPane.hide();
			}
			if (o.disabled) {
				this.status(
this.localizeString("messageGenerateInDisabledState", "Can't execute generate, report viewer is disabled.")
				);
				return;
			}
			if (!o.fileName) {
				this.status(
this.localizeString("messageFileNameNotSpecified", "Report definition file not specified.")
				);
				return;
			}

			this._isGenerating = true;
			o.reportParameters = reportParameters ? reportParameters : null;
			o.documentStatus = { isGenerating: true, state: 1,
				percentComplete: 0, documentKey: "",
				cookie: "", pageCount: 0
			};
			this._trigger("documentStatusChanged", null, [o.documentStatus]);
			this.clearCache();
			this.reset();
			this.status(
	this.localizeString("activityGeneratingDocument", "Generating document...")
			);

			this._updateDocumentStatus();
		},

		/// <summary>
		/// Clears the client cache. 
		/// This method is called automatically from generate method.
		/// </summary>
		clearCache: function () {
			this._imageReportCache = {};
		},

		/// <summary>
		/// Clears all cached data and remove all previously rendered report pages.
		/// This method is called automatically from generate method.
		/// </summary>
		reset: function () {
			this.log("reset called.");
			this._pageMarkups = [];
			this._pages = [];
			this._selectionTargetPages = [];
			this.element.find(".wijmo-wijreportviewer-content").html("");
		},

		/// <summary>
		/// Scrolls view to the page given by its index.
		/// </summary>
		/// <param name="pageIndex">Index of the page that must be shown.</param>
		/// <param name="topPageOffset">Optional. Additional top page offset.</param>
		scrollToPage: function (pageIndex, topPageOffset) {
			this.log("scrollToPage, pageIndex=" + pageIndex + ", topPageOffset=" + topPageOffset);
			var o = this.options, pageCount, currentPage;
			if (!topPageOffset) {
				topPageOffset = 0;
			}

			if (o.documentStatus && !isNaN(o.documentStatus.pageCount)) {
				pageCount = o.documentStatus.pageCount;
				pageIndex = (pageIndex < 0 ? 0 : (pageIndex >= pageCount ? pageCount - 1 : pageIndex));
				o.pageIndex = pageIndex;
				this._trigger("pageIndexChanged", null, [pageIndex]);
				currentPage = this.element.find(".wijmo-wijreportviewer-content > .C1Page" + pageIndex);
				if (o.pagedView) {
					this.element.find(".wijmo-wijreportviewer-content > .C1Page.C1Current").removeClass("C1Current");
					currentPage.addClass("C1Current");
					if (currentPage.length > 0) {
						this.element.find(".wijmo-wijreportviewer-reportpane").scrollTop(currentPage[0].offsetTop + topPageOffset);
					}
					this._loadVisiblePagesData(false);
				}
				else {
					if (currentPage.length > 0) {
						this.element.find(".wijmo-wijreportviewer-reportpane").scrollTop(currentPage[0].offsetTop + topPageOffset);
					}
				}
				//this._updateCurrentPageLabel(true, false);
			}
		},

		/// <summary>
		/// Returns the indices of the visible pages according current scroll position.
		/// </summary>
		getVisiblePageIndices: function () {
			var reportPaneElem = this.element.find(".wijmo-wijreportviewer-reportpane")[0],
				scrollOffset = reportPaneElem.scrollTop, o = this.options,
				bottomOffset = scrollOffset + reportPaneElem.offsetHeight,
				arr = [], currentPageIndex = o.pageIndex,
				testPageElement, backMoved = false, forwardMoved = false,
				lowBound, highBound,
				currentPageElement = this.element.find(".wijmo-wijreportviewer-content").find(".C1Page" + currentPageIndex)[0];
			if (!o.documentStatus) {
				return arr;
			}
			if (!currentPageElement || !this._isPageElemVisible(currentPageElement, scrollOffset, bottomOffset)) {
				// if currentPageIndex page is not visible then search for actually visible page 
				// using simple divide by 2 search (needed for very long reports, like 1000 pages).
				// Start search from 0:
				lowBound = 0;
				highBound = o.documentStatus.pageCount - 1;
				currentPageIndex = 0;
				while (true) {
					currentPageElement = $(".C1Page" + currentPageIndex, reportPaneElem)[0];
					if (!currentPageElement) {
						return arr;
					}
					if ((highBound - lowBound) === 0) {
						break;
					}
					if (currentPageElement.offsetTop > bottomOffset) {
						highBound = currentPageIndex;
						// Step back
						currentPageIndex = Math.ceil(highBound - (highBound - lowBound) / 2);
					}
					else if ((currentPageElement.offsetTop + currentPageElement.offsetHeight) < scrollOffset) {
						lowBound = currentPageIndex;
						// Step forward
						currentPageIndex = Math.ceil(lowBound + (highBound - lowBound) / 2);
					}
					else {
						// Found
						break;
					}
				}
			}

			testPageElement = currentPageElement
			while (!backMoved || !forwardMoved) {
				if (!backMoved) {
					if (testPageElement &&
						this._isPageElemVisible(testPageElement,
						scrollOffset, bottomOffset)) {
						arr.push(this._getPageIndexFromPageElement(testPageElement));
					}
					else {
						backMoved = true;
						// Set current element to middle page + 1.
						testPageElement = currentPageElement.nextSibling;
						continue;
					}
					// Move to previous
					testPageElement = testPageElement.previousSibling;
				}
				else if (!forwardMoved) {
					if (testPageElement &&
						this._isPageElemVisible(testPageElement,
						scrollOffset, bottomOffset)) {
						arr.push(this._getPageIndexFromPageElement(testPageElement));
					}
					else {
						forwardMoved = true;
						break;
					}
					// Move to next
					testPageElement = testPageElement.nextSibling;
				}
				else {
					break;
				}
			}
			// Sort indexes before return
			arr.sort(function (a, b) {
				if (a < b) {
					return -1;
				}
				if (a > b) {
					return 1;
				}
				return 0;
			});
			return arr;
		},

		/// <summary>
		/// Load html code that can be used in order to print or preview report.
		/// </summary>
		/// <param name="printOptions">
		///	Optional. Print options.
		/// Syntax:
		/// {
		///		pageRanges: [0, pageCount], // array of page ranges to print
		///		reversePages: false, // reverse pages during print
		///		rangeSubset: 0, // // print range subset: 0 - all, 1 - odd, 2 - even
		///		dpi: 150, // page images rendering DPI
		///		zoom: 100 // pages zoom
		///	};
		///	</param>
		/// <param name="callback">
		///	Function handler that will be called when preview html will be loaded.
		///	</param>
		loadPreviewHtml: function (printOptions, callback) {
			var s = "", o = this.options, documentStatus = o.documentStatus,
				pageCount = documentStatus.pageCount,
				pageWidth = documentStatus.w,
				pageHeight = documentStatus.h,
				isOdd, isEven, i, reqPrefix, renderedPagesCount = 0;
			if (!callback) {
				callback = printOptions;
				printOptions = {};
			}
			if (!callback) {
				return;
			}
			if (!printOptions) {
				printOptions = {};
			}

			printOptions = $.extend({},
				{
					pageRanges: [0, pageCount - 1], // array of page ranges to print
					reversePages: false, // reverse pages during print
					rangeSubset: 0, // // print range subset: 0 - all, 1 - odd, 2 - even
					dpi: 150, // page images rendering DPI
					zoom: 100 // pages zoom
				}, printOptions);

			isOdd = printOptions.rangeSubset === 1;
			isEven = printOptions.rangeSubset === 2;

			i = (printOptions.reversePages) ? (printOptions.pageRanges.length - 1) : 0;
			reqPrefix = o.reportServiceUrl + "?clientId=" + this.element[0].id +
						"&documentKey=" +
						documentStatus.documentKey +
						"&dpi=" + printOptions.dpi + "&zoom=" + printOptions.zoom +
						"&printTarget=true&pageIndex=";

			while (true) {
				if (i >= printOptions.pageRanges.length || i < 0) {
					break;
				}
				var start = printOptions.pageRanges[i];
				i = i + ((printOptions.reversePages) ? -1 : 1);
				if (i >= printOptions.pageRanges.length || i < 0) {
					break;
				}
				var end = printOptions.pageRanges[i];
				var pageDivStartStr = '';
				var pageDivEndStr = '';
				var pageImageStyle = 'width:92%;background-color:#FFFFFF;';
				var j = start;
				while (true) {
					if (j > Math.max(start, end) || j < Math.min(start, end)) {
						break;
					}
					if (j < 0 || j >= pageCount) {
						j = j + ((printOptions.reversePages) ? -1 : 1);
						continue;
					}
					if (isOdd && (j % 2)) {
						j = j + ((printOptions.reversePages) ? -1 : 1);
						continue;
					}
					else if (isEven && (j % 2 !== 1)) {
						j = j + ((printOptions.reversePages) ? -1 : 1);
						continue;
					}
					s += '<br />';
					if (renderedPagesCount > 0) {
						s += '<div title="Print Page Break" style="font-size: 1px; page-break-before: always; vertical-align: middle; height: 1px; background-color: #c0c0c0">&nbsp;</div>';
					}
					s += pageDivStartStr;
					s += '<img style="' + pageImageStyle + '" src="' + (reqPrefix + j) + '" />';
					s += pageDivEndStr;
					j = j + ((printOptions.reversePages) ? -1 : 1);
				}
				i = i + ((printOptions.reversePages) ? -1 : 1);
			}
			s = '<!DOCTYPE html PUBLIC \'-//W3C//DTD XHTML 1.0 Transitional//EN\'' +
				' \'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\'>' +
				'<html xmlns=\'http://www.w3.org/1999/xhtml\'>' +
				'<head><title>' + this.options.reportName + '</title>' +
				'</head><body style="margin:0;background-color:#FFFFFF;' +
				'color:#000000; text-align:center;">' + s + '</body></html>';
			callback(s);
		},

		/// <summary>
		/// Navigates first page.
		/// Code Example: 
		///	$("#report").c1reportviewer("firstPage");
		///</summary>
		firstPage: function () {
			this.scrollToPage(0);
			return false;
		},
		/// <summary>
		/// Navigates previous page.
		/// Code Example: 
		///	$("#report").c1reportviewer("previousPage");
		///</summary>
		previousPage: function () {
			this.scrollToPage(this.options.pageIndex - 1);
			return false;
		},
		/// <summary>
		/// Navigates next page.
		/// Code Example: 
		///	$("#report").c1reportviewer("nextPage");
		///</summary>
		nextPage: function () {
			this.scrollToPage(this.options.pageIndex + 1);
			return false;
		},
		/// <summary>
		/// Navigates last page.
		/// Code Example: 
		///	$("#report").c1reportviewer("lastPage");
		///</summary>
		lastPage: function () {
			this.scrollToPage(this.options.documentStatus.pageCount);
			return false;
		},
		/// <summary>
		/// Zoom in view.
		/// Code Example: 
		///	$("#report").c1reportviewer("zoomIn");
		///</summary>
		zoomIn: function () {
			this._changeZoom(this.resolvedZoom + 10);
			return false;
		},
		/// <summary>
		/// Zoom out view.
		/// Code Example: 
		///	$("#report").c1reportviewer("zoomIn");
		///</summary>
		zoomOut: function () {
			this._changeZoom(this.resolvedZoom - 10);
			return false;
		},

		/// <summary>
		/// Sends a log message to built-in log console. Note, enableLogs option should be set to true.
		/// </summary>
		/// <param name="msg" type="String">
		///	Log message
		///	</param>
		/// <param name="className" type="String">
		/// Optional. CSS class name that will be applied to the destination message.
		/// Few predefined classes are available: 
		///	"error", "warning", "information", "status"
		///	</param>
		log: function (msg, className) {
		},

		/// <summary>
		/// Returns text currently selected by user.
		/// </summary>
		getSelectedText: function () {
			if (this._selectionTargetPages) {
				var s = "", i;
				for (i = 0; i < this._selectionTargetPages.length; i++) {
					if (this._selectionTargetPages[i]) {
						s += this._selectionTargetPages[i].getSelectedText();
					}
				}
				return s;
			}
			return '';
		},

		/// <summary>
		/// Selects all text for visible pages.
		/// </summary>
		selectAll: function () {
			var currentZoom = this._rezolveZoom(), targetPages = [], d, k, n;
			this._ensureSelectionTargetPagesStored();
			for (k in this._selectionTargetPages) {
				targetPages[targetPages.length] = this._selectionTargetPages[k];
			}
			if (targetPages.length > 0) {
				for (n = 0; n < targetPages.length; n++) {
					targetPages[n].selectRegion({ x: -1, y: -1 },
									{ x: 50000, y: 50000 }, currentZoom);
				}
			}
		},

		/// <summary>
		/// Forced load of visible pages text data.
		/// This method is primarily used by control developers.
		/// </summary>
		/// <param name="skipFastCalls" type="Boolean">
		///	Optional. If true then data will be loaded 
		///	after timeout in order to avoid frequently calls
		///	(e.g. during document scrolling).
		///	</param>
		/// <param name="completeCallback" type="Function">
		///	Optional. Function which will be called when data is loaded.
		///	</param>
		loadVisiblePagesData: function (skipFastCalls, completeCallback) {
			this._loadVisiblePagesData(skipFastCalls, completeCallback);
		},

		/// <summary>
		/// Update page image by its index. 
		///	This method is primarily used by control developers.
		/// </summary>
		/// <param name="pageIndex" type="Number">
		///	Page index which need to load image.
		///	</param>
		/// <param name="forceUpdate" type="Boolean">
		///	Optional. Set this parameter to true if you want to 
		///	update already loaded image.
		///	</param>
		updatePageImage: function (pageIndex, forceUpdate) {
			this._updatePageImage(pageIndex, forceUpdate);
		},

		/// <summary>
		/// Moves focus to the report viewer document.
		/// </summary>
		setFocus: function () {
			try {
				var textArea = this._selectionAreaElement, textAreaBounds;
				if (!this._selectionAreaFocused) {
					textAreaBounds = this._getElemBounds(textArea);
					this._setSelectionAreaLocation(textAreaBounds.x, textAreaBounds.y, textAreaBounds.w, textAreaBounds.h);
				}
				textArea.focus();
				this._selectionAreaFocused = true;
			}
			catch ($e1) {
				this.log("[e199191] setFocus() failed.", "error");
			}
		},

		/*----*/

		_log: function (msg, className) {
			var imgHtml;
			if (this.logPanel) {
				var dt = new Date();
				this.logPanel.prepend($('<span class="' +
										(className ? className : 'information') + '">' +
					'[' + dt.getHours() + ':' + dt.getMinutes() + ':' +
						dt.getSeconds() + '] ' + msg + '</span><br/>'));
			}
		},

		_initLogPanel: function () {
			if (this.options.enableLogs) {
				this._createLogPanel();
				this.log = this._log;
			} else {
				this.log = function () { };
			}
		},

		_createLogPanel: function () {
			if (!this.logPanel) {
				this.logDialog = $('<div title="Log"><div class="wijmo-wijreportviewer-log"></div></div>');
				this.logDialog.appendTo(this.element);

				var btnsHash = {};
				btnsHash[this.localizeString("buttonClearAll", "Clear All")] = function () {
					$(this).find(".wijmo-wijreportviewer-log").html("");
				};
				btnsHash[this.localizeString("buttonClose", "Close")] = function () {
					$(this).wijdialog("close");
				};
				this.logPanel = this.logDialog.wijdialog({
					captionButtons: {
						/*pin: { visible: false },
						refresh: { visible: false },
						toggle: { visible: false },
						minimize: { visible: false }*/
					},
					buttons: btnsHash,
					width: 500, height: 220,
					position: ["right", "top"]
				}).wijdialog("open").find(".wijmo-wijreportviewer-log");
			}
		},

		/// <summary>
		/// Changes status label text.
		/// </summary>
		/// <param name="txt" type="String">
		///	The new status text.
		///	</param>
		status: function (txt, className) {
			this.element.find(".c1-c1reportviewer-statusbar").html("<span class='" + (className ? className : "status") + "'>" + txt + "</span>");
			this.log(txt, className ? className : "status");
		},


		/** Private methods */


		_onReportPaneScroll: function () {
			if (!this.options.pagedView) {
				if (this._onScrollTimerId) {
					clearTimeout(this._onScrollTimerId);
					this._onScrollTimerId = null;
				}
				this._onScrollTimerId = setTimeout($.proxy(this._onReportPaneScrollCallback, this), 50);
			}
		},

		_onReportPaneScrollCallback: function () {
			this._onScrollTimerId = null;
			this._loadVisiblePagesData(true);
			//this._updateCurrentPageLabel(true, true);
		},

		// helper methods:

		// Returns number value. Indicates zoom value in percentages.
		_rezolveZoom: function () {

			if (this.resolvedZoom > 0 && !this._repeatResolveZoom) {
				return this.resolvedZoom;
			}
			var o = this.options, docStatus = o.documentStatus,
				zoomName = o.zoom.toString(), resultZoom = 100,
				visiblePages = this.getVisiblePageIndices(),
				originalPagePxW, originalPagePxH, neededPagePxW, neededPagePxH,
				resultZoomW, resultZoomH;

			switch (zoomName.toString().toLowerCase()) {
				case "actual size":
					resultZoom = 100;
					break;
				case "fit page":
					//todo:
					if (docStatus) {
						originalPagePxW = this._calcPxByDPI(visiblePages.length > 0 ?
		this._determinePageSize(visiblePages[0], 100).w : docStatus.w, 100);
						originalPagePxH = this._calcPxByDPI(visiblePages.length > 0 ?
		this._determinePageSize(visiblePages[0], 100).h : docStatus.h, 100);

						neededPagePxW = this._determineFitPageWidth();
						neededPagePxH = this._determineFitPageHeight();
						resultZoomW = Math.round(neededPagePxW * 100 / originalPagePxW);
						resultZoomH = Math.round(neededPagePxH * 100 / originalPagePxH);
						resultZoom = Math.min(resultZoomW, resultZoomH);
					}
					break;
				case "fit width":
					if (docStatus) {
						originalPagePxW = this._calcPxByDPI(visiblePages.length > 0 ?
                            this._determinePageSize(visiblePages[0], 100).w :
																 docStatus.w, 100);
						neededPagePxW = this._determineFitPageWidth();
						resultZoom = Math.round(neededPagePxW * 100 / originalPagePxW);
					}
					break;
				case "fit height":
					if (docStatus) {
						originalPagePxH = this._calcPxByDPI(visiblePages.Length > 0 ?
		this._determinePageSize(visiblePages[0], 100).h : docStatus.h, 100);
						neededPagePxH = this._determineFitPageHeight();
						resultZoom = Math.round(neededPagePxH * 100 / originalPagePxH);
					}
					break;
				default:
					try {
						resultZoom = parseInt(zoomName.toString().replace("%", ""));
					}
					catch (e) {
						resultZoom = 100;
						this.log("Unknown zoom option value: " +
							zoomName + " (" + e + ")", "warning");
					}
					break;
			}
			if (resultZoom < 1 || !isFinite(resultZoom)) {
				if (this._repeatResolveZoom) {
					this._repeatResolveZoom = false;
				} else {
					// fix for 27359: //qq: why docStatus.zoom is undefined?
					this._repeatResolveZoom = true;
				}
				this.resolvedZoom = 100;
			} else {
				this.resolvedZoom = resultZoom;
				this._repeatResolveZoom = false;
			}
			return this.resolvedZoom;
		},

		/// <summary>
		/// Determines the size of the page.
		/// </summary>
		/// <param name="pageIndex">Number. Index of the page.</param>
		/// <param name="zoom">Number. The zoom value.</param>
		/// <returns>Object with width and height. Sample {w: 200, h: 300} </returns>
		_determinePageSize: function (pageIndex, zoom) {
			var w = -1, h = -1,
				docStatus = this.options.documentStatus,
				ranges = docStatus.pageSizes,
				r, i;
			for (i = 0; i < ranges.Length; i++) {
				r = ranges[i];
				if (pageIndex >= r.s && pageIndex <= r.e) {
					w = r.w;
					h = r.h;
					break;
				}
			}
			if (w < 0) {
				w = docStatus.w;
			}
			if (h < 0) {
				h = docStatus.h;
			}
			return { w: this._calcPxByDPI(w, zoom), h: this._calcPxByDPI(h, zoom) };
		},

		_determineFitPageWidth: function () {
			var reportPaneWidth = this.element.find(".wijmo-wijreportviewer-reportpane")[0].offsetWidth;
			reportPaneWidth = Math.round(reportPaneWidth) - 30;
			return reportPaneWidth;
		},

		_determineFitPageHeight: function () {
			var reportPaneHeight = this.element.find(".wijmo-wijreportviewer-reportpane")[0].offsetHeight;
			reportPaneHeight = Math.round(reportPaneHeight) - 60;
			return reportPaneHeight;
		},

		_calcPxByDPI: function (pixelValue96Dpi100Zoom, neededZoom) {
			return Math.round((pixelValue96Dpi100Zoom * this.getDPI() / 96) * (neededZoom / 100));
		},


		/// <summary>
		/// Forces to update document status from server.
		/// Do not call this method if report is not generated.
		/// </summary>
		_updateDocumentStatus: function () {
			var o = this.options, k, paramsStr = "";
			this.log("Updating document status...");
			$.getJSON(o.reportServiceUrl + "?clientId=" + this.element[0].id +
				"&command=status" +
				"&fileName=" + encodeURIComponent(o.fileName) +
				"&ieCacheFix=" + new Date().getTime() +
				"&reportName=" + encodeURIComponent(o.reportName) +
				"&cookie=" + (o.documentStatus ? o.documentStatus.cookie : ""),
				o.reportParameters ? { reportParams: JSON.stringify({ reportParameters: o.reportParameters })} : null,
				$.proxy(this._onDocumentStatusReceived, this)
				);
		},
		_onDocumentStatusReceived: function (docStatus) {
			var o = this.options;
			this.status(
this._formatString(this.localizeString("activityStatusReceived", "documentStatus received, state is {0}"),
															(docStatus ? docStatus.state : "null"))
);

			o.documentStatus = docStatus;
			if (docStatus && this._handleCommonResponseFlags(docStatus)) {
				switch (docStatus.state) {
					case 0: //Ready

						this.status(
this._formatString(this.localizeString("activityStatusReadyFormat", "Report is completely generated. Total page count: {0}"),
															docStatus.pageCount)
);

						this._updatePageLayout(docStatus);
						this._isGenerating = false;
						//ensure visible pages data loaded
						this._loadVisiblePagesData(true);
						break;
					case 1: //GeneratingPages
						this.status(
this._formatString(this.localizeString("activityStatusFormat", "Report is currently generating, total page count may change. Current page count: {0}, completed {1}%"),
docStatus.pageCount, docStatus.percentComplete)
);

						this._updatePageLayout(docStatus);

						if (docStatus.pageCount > 0) {
							// fix for 
							// [19971] [Forum][C1ReportViewer] 
							//	Performance issue on loading report for the first time
							setTimeout($.proxy(this._loadVisiblePagesData, this), 500);
						}

						if (this._isGenerating) {
							setTimeout($.proxy(this._updateDocumentStatus, this), 200);
						}
						break;
					case 2: //UpdatingPages
						this.status("Report is still generating, individual pages may change but total page count is final. Total page count: " +
										docStatus.pageCount + ", completed " + docStatus.percentComplete + "%");
						this._updatePageLayout(docStatus);
						if (this._isGenerating) {
							setTimeout($.proxy(this._updateDocumentStatus, this), 200);
						}
						break;
					case 3: //ParametersRequested
						this.status(this.localizeString("activityReportneedsparameters", "Report needs parameters"));
						this._isGenerating = false;
						this.showReportParametersPane();
						break;
				}
				this._trigger("documentStatusChanged", null, [docStatus]);
			}

		},


		// report parameters>>
		showReportParametersPane: function () {
			if (!this.reportParamsPane) {
				this.reportParamsPane = $("<div class=\"wijmo-wijreportviewer-paramspane\"><div>" +
					this.localizeString("titleReportparameters", "report parameters") +
					"</div><div class=\"wijmo-wijreportviewer-paramspane-content\"></div></div>");
				this.reportParamsPane.insertBefore(this.element);
				this.reportParamsPane.wijexpander();
				this.reportParamsContent = this.reportParamsPane
								.find(".wijmo-wijreportviewer-paramspane-content");
			}
			var o = this.options, paramsConfig = o.documentStatus.reportParams;
			this._renderReportParamsUI(paramsConfig);
			this.reportParamsPane.show();
			this.reportParamsPane.wijexpander("expand");
		},

		_renderReportParamsUI: function (paramsConfig) {
			var s, i, info, count, self = this;
			if (!paramsConfig) {
				this.reportParamsContent.html("No parameters");
			}
			else {
				s = "";
				for (i = 0, count = paramsConfig.length; i < count; i++) {
					info = paramsConfig[i];
					s += "<div class=\"parametercontainer\">";
					if (!info.mv) {
						s += "<label class=\"simpleparameter\" for=\"param_" +
								this._convertParamToClassName(info.n) + "\">";
						s += "<span class=\"title\">";
						s += ((info.p) ? info.p : info.n);
						s += "</span>";
						s += '<span class=\"parameter-control\">';
						s += this._renderSimpleParameterControl(info);
						s += '</span>';
						s += '</label>';
					}
					else {
						s += '<label class=\"multipleparameters\">';
						s += ((info.p) ? info.p : info.n);
						s += '</label>';
						s += this._renderMultipleCheckBoxes(info);
					}
					s += "</div>";
				}
				s += '<input type=\"button\" class=\"button-submitparameters\" value=\"' +
			this.localizeString("buttonSetparameters", "Set parameters") + '\" />';
				this.reportParamsContent.html(s);

				this.reportParamsContent.find(".input-datetime").wijinputdate(
			{
				culture: self.options.culture,
				showTrigger: true,
				dateFormat: "g"
			});
				this.reportParamsContent.find(".input-integer").each(function (ind, el) {
					$(el).wijinputnumber({
						culture: self.options.culture,
						type: "numeric",
						decimalPlaces: 0,
						minValue: -100000000, /* Number.MIN_VALUE*/
						maxValue: 100000000, /* Number.MAX_VALUE*/
						value: el.value,
						showSpinner: true
					});
				});

				this.reportParamsContent.find(".button-submitparameters").button({
					text: true,
					icons: {
						primary: "ui-icon-zoomout"
					}
				}).click($.proxy(this._onSubmitReportParameters, this));
				//this._bindContentFormEvents(this.get_contentElement());
			}
		},

		_onSubmitReportParameters: function () {
			if (this._readAndValidateReportParamsInput()) {
				if (this.reportParamsPane) {
					this.reportParamsPane.wijexpander("collapse");
				}
			} else {
				this.status("Please, fill report parameters correctly.");
			}
		},
		// render report parameter controls >>
		_renderSimpleParameterControl: function (info) {
			var initialVal = (info.vs && info.vs.length > 0) ? info.vs[0] : '';
			var s = '';
			if (info.vx) {
				s += '<select class=\"param_' + this._convertParamToClassName(info.n) + '\">';
				if (info.pvs) {
					for (var j = 0, jcount = info.pvs.length; j < jcount; j++) {
						s += '<option' + ((initialVal === info.pvs[j].v) ? ' selected' : '') +
						' value=\"' + this._convertToStringValue(info.pvs[j].v, info) + '\">';
						s += (info.pvs[j].l) ? info.pvs[j].l : info.pvs[j].v.toString();
						s += '</option>';
					}
				}
				s += '</select>';
			}
			else {
				//this.log("info.t.toLowerCase()=" + info.t.toLowerCase());
				switch (info.t.toLowerCase()) {
					case 'bool':
						s += '<input type=\"checkbox\" class=\"param_' + this._convertParamToClassName(info.n) + '\" checked=\"' + initialVal + '\" />';
						break;
					case 'datetime':
						s += '<input type=\"text\" class=\"input-datetime param_' + this._convertParamToClassName(info.n) + '\" value=\"' + this._dateTimeToString(initialVal) + '\" />';
						break;
					case "int":
					case "integer":
						s += '<input type=\"text\" class=\"input-integer param_' + this._convertParamToClassName(info.n) + '\" value=\"' + initialVal + '\" />';
						break;
					default:
						s += '<input type=\"text\" class=\"param_' + this._convertParamToClassName(info.n) + '\" value=\"' + initialVal + '\" />';
						break;
				}
			}
			return s;
		},

		_renderMultipleCheckBoxes: function (info) {
			var s = '';
			if (info.pvs) {
				for (var j = 0, jcount = info.pvs.length; j < jcount; j++) {
					s += '<label class=\"C1Label-MultipleParameter\">';
					s += '<span class=\"C1Text-Title\">';
					s += (info.pvs[j].l) ? info.pvs[j].l : info.pvs[j].v.toString();
					s += '</span>';
					s += '<span class=\"C1Container-Parameter-Control\">';
					s += '<input type=\"checkbox\" class=\"param_' + this._convertParamToClassName(info.n) + '\" value=\"' + this._convertToStringValue(info.pvs[j].v, info) + '\" />';
					s += '</span>';
					s += '</label>';
				}
			}
			return s;
		},

		_convertParamToClassName: function (s) {
			var r = new RegExp('\\W', 'g');
			return s.replace(r, '_');
		},

		_convertToStringValue: function (o, info) {
			if (!o && isNaN(o)) {
				return '';
			}
			switch (info.t.toLowerCase()) {
				case 'bool':
					return o.toString();
					break;
				case 'datetime':
					return this._dateTimeToString(o);
					break;
				default:
					return o.toString();
					break;
			}
		},

		_dateTimeToString: function (o) {
			return o.toString();
		},
		//<< render parameter controls

		// read parameters from UI>>

		_readAndValidateReportParamsInput: function () {
			var validated = true,
			paramsConfig = this.options.documentStatus.reportParams;
			if (paramsConfig) {
				this.reportParamsConfig = paramsConfig;
			} else {
				// params configuration can be cleared from status, todo: create separate option for parameters config
				paramsConfig = this.reportParamsConfig;
			}
			if (!paramsConfig) {
				this.status("Report parameters configuration was not found.", "error");
				return false;
			}
			try {
				var container = this.reportParamsContent;
				var jContainer = jQuery(container);
				var inputParams = new Array(0);

				for (var i = 0, count = paramsConfig.length; i < count; i++) {
					var info = paramsConfig[i];
					var inputParam = inputParams[i] = {};
					inputParam.n = info.n;
					inputParam.vs = this._readParameterValues(info, jContainer.find('.param_' + this._convertParamToClassName(info.n)));
				}
				if (validated) {
					this.generate(inputParams);
				}
			}
			catch (ex) {
				validated = false;
				alert('Incorrect input: ' + (ex.message ? ex.message : ex));
			}
			return validated;
		},

		_readParameterValues: function (info, jParamControls) {
			var vs = new Array(0);
			if (info.mv) {
				jParamControls.each($.proxy(function (i, elem) {
					if ((elem).checked) {
						vs[vs.length] = this._readSingleParameterValue(info, elem);
					}
					return true;
				}, this));
			}
			else {
				vs[0] = this._readSingleParameterValue(info, jParamControls.get(0));
			}
			return vs;
		},

		_readSingleParameterValue: function (info, dOMElement) {
			var resultval = null;
			var jParamControl = jQuery(dOMElement);
			var val = jParamControl.val();
			switch (info.t.toLowerCase()) {
				case 'datetime':
					if (info.vx) {
						if (!val && !isFinite(val)) {
							if (info.rq) {
								throw "Parameter \'" + info.n + "\' is required.";
							}
							else if (!info.nu) {
								throw "Parameter \'" + info.n + "\' can not be null.";
							}
							else {
								resultval = null;
							}
						}
						else {
							resultval = this._stringToDateTime(val);
							if (!(resultval instanceof Date)) {
								throw "Unable parse date:" + val;
							}
						}
					}
					else {
						if (!val && !isFinite(val)) {
							if (info.rq) {
								throw 'Parameter \'' + info.n + '\' is required.';
							}
							else if (!info.nu) {
								throw 'Parameter \'' + info.n + '\' can not be null.';
							}
							else {
								resultval = null;
							}
						}
						else {
							if (jParamControl.hasClass("wijmo-wijinput-date")) {
								resultval = jParamControl.wijinputdate("option", "date");
							} else {
								resultval = this._stringToDateTime(val);
							}
							if (!(resultval instanceof Date)) {
								throw "Unable parse date:" + val;
							}
						}
					}
					break;
				case 'int':
					var int_val = parseInt(val);
					if (!isFinite(int_val)) {
						throw "Unable to parse int value: " + val;
					}
					resultval = int_val;
					break;
				case 'bool':
					resultval = jParamControl.attr('checked');
					break;
				case 'float':
					var float_val = parseFloat(val);
					if (!isFinite(float_val)) {
						throw "Unable to parse float value: " + val;
					}
					resultval = float_val;
					break;
				default:
					if (info.vx) {
						resultval = val;
					}
					else {
						if (info.rq && !val && !isFinite(val)) {
							throw 'Parameter \'' + info.n + '\' is required.';
						}
						resultval = val;
					}
					break;
			}
			return resultval;
		},

		_stringToDateTime: function (s) {
			return new Date(s);
		},
		//<<

		_onAjaxError: function (event, jqXHR, ajaxSettings, thrownError) {
			if (jqXHR) {
				this.status("Ajax error " + jqXHR.status +
				" (" + jqXHR.statusText + ")",
				"error");
				this.log("Error, requested url: " + ajaxSettings.url, "error");
				if (jqXHR.responseText) {
					this.log("Error, response text: " + jqXHR.responseText, "error");
				}
			} else {
				this.status("Ajax error detected.", "error");
				this.log("Error, requested url: " + ajaxSettings.url, "error");
			}
		},


		// Bind/unbind internal events

		_bindEvents: function () {
			this.element.find(".wijmo-wijreportviewer-reportpane")
				.bind("scroll." + this.wijreportnamespacekey,
									$.proxy(this._onReportPaneScroll, this));

			this.element.find(".wijmo-wijreportviewer-content")
				.bind("dblclick." + this.wijreportnamespacekey,
										$.proxy(this._onReportDoubleClick, this))
				.bind("mousedown." + this.wijreportnamespacekey,
										$.proxy(this._onReportMouseDown, this))
				.bind("mouseup." + this.wijreportnamespacekey,
										$.proxy(this._onDocumentMouseUp, this))
				.bind("dragstart." + this.wijreportnamespacekey,
								function (e) { e.preventDefault(); /*e.stopPropagation();*/ });

			//
			$(document.documentElement).bind("mousemove." + this.wijreportnamespacekey,
										$.proxy(this._onReportMouseMove, this));
			$(document).bind("keydown." + this.wijreportnamespacekey,
								$.proxy(this._onDocumentKeyDown, this))
								.bind("keyup." + this.wijreportnamespacekey,
								$.proxy(this._onDocumentKeyUp, this));
			this._selectionAreaElement.bind("blur." + this.wijreportnamespacekey,
									$.proxy(this._onSelectionAreaElementBlur, this))
									.bind("select." + this.wijreportnamespacekey,
									$.proxy(this._onSelectionAreaElementSelect, this))
									.bind("keydown." + this.wijreportnamespacekey,
									$.proxy(this._onSelectionAreaElementKeyDown, this));
		},
		_unbindEvents: function () {
			this.element.find(".wijmo-wijreportviewer-reportpane")
										.unbind(this.wijreportnamespacekey);
			this.element.find('.wijmo-wijreportviewer-content')
										.unbind(this.wijreportnamespacekey);
			$(document.documentElement).unbind(this.wijreportnamespacekey);
			$(document).unbind(this.wijreportnamespacekey);
		},

		// Selection area ->
		_onSelectionAreaElementBlur: function (e) {
			this._selectionAreaFocused = false;
			return true;
		},
		_onSelectionAreaElementSelect: function (e) {
			if (this._pendingSelectedTextContextMenuActions) {
				this._pendingSelectedTextContextMenuActions = false;
				this.selectAll();
			}
			return true;
		},

		_prepareSelectionAreaElementForContextMenuActions: function (skipUpdatingText) {
			this._pendingSelectedTextContextMenuActions = false;
			if (!skipUpdatingText) {
				var s = this.getSelectedText();
				if (s === "") {
					s = " ";
				}
				this._selectionAreaElement.val(s);
			}
			this._selectionAreaElement.select();
			if ($.browser.webkit) {
				window.setTimeout($.proxy(function () {
					this._pendingSelectedTextContextMenuActions = true;
				}, this), 2);
			}
			else {
				this._pendingSelectedTextContextMenuActions = true;
			}
		},

		_ensureSelectionTargetPagesStored: function () {
			var visiblePageIndexes = this.getVisiblePageIndices(), i, page;
			for (i = 0; i < visiblePageIndexes.length; i++) {
				page = this._pages[visiblePageIndexes[i]]; // this._getPageByIndex(visiblePageIndexes[i]);
				if (page) {
					this._selectionTargetPages[page.index] = page;
				}
			}
		},
		_getWindowBounds: function () {
			var $w = $(window);
			return { x: $w.scrollLeft(), y: $w.scrollTop(),
				h: $w.height(), w: $w.width()
			};
		},
		_setSelectionAreaLocation: function (x, y, preferedWidth, preferedHeight) {
			var selectionAreaElement = this._selectionAreaElement,
				windowClientAreaBounds = this._getWindowBounds();
			if (x < windowClientAreaBounds.x) {
				x = windowClientAreaBounds.x;
			}
			if (y < windowClientAreaBounds.y) {
				y = windowClientAreaBounds.y;
			}
			if ((x + preferedWidth) > (windowClientAreaBounds.x - 6 + windowClientAreaBounds.width)) {
				preferedWidth = preferedWidth - ((x + preferedWidth) - (windowClientAreaBounds.x - 6 + windowClientAreaBounds.width));
				if (preferedWidth < 0) {
					preferedWidth = 0;
				}
			}
			if ((y + preferedHeight) > (windowClientAreaBounds.y + windowClientAreaBounds.height - 6)) {
				preferedHeight = preferedHeight - ((y + preferedHeight) - (windowClientAreaBounds.y + windowClientAreaBounds.height - 6));
				if (preferedHeight < 0) {
					preferedHeight = 0;
				}
			}
			selectionAreaElement.width(0);
			selectionAreaElement.height(0);
			selectionAreaElement.css("left", x);
			selectionAreaElement.css("top", y);
			selectionAreaElement.width(preferedWidth);
			selectionAreaElement.height(preferedHeight);
		},

		_resetSelectionAreaPosition: function () {
			var selectionElement = this._selectionAreaElement,
				reportPaneElementBounds =
		this._getElemBounds(this.element.find(".wijmo-wijreportviewer-reportpane")[0]);
			this._resetSelectionAreaPositionTimeoutId = null;
			this._setSelectionAreaLocation(reportPaneElementBounds.x, reportPaneElementBounds.y, 5, 5);
		},

		_getElemBounds: function (el) {
			var $el = $(el), offset = $el.offset();
			return { x: Math.round(offset.left), y: Math.round(offset.top),
				w: $el.outerWidth(true), h: $el.outerHeight(true)
			};

		},

		_applySelection: function (currentMousePoint) {
			var currentZoom = this._rezolveZoom(), i, pageElement,
				startPagePoint, endPagePoint;
			for (i = 0; i < this._selectionTargetPages.length; i++) {
				if (this._selectionTargetPages[i]) {
					pageElement = this._selectionTargetPages[i].element;
					startPagePoint = this._getPointPositionWithinPage(this._selectionStartPoint, pageElement);
					endPagePoint = this._getPointPositionWithinPage(currentMousePoint, pageElement);
					this._selectionTargetPages[i].selectRegion(startPagePoint, endPagePoint, currentZoom);
				}
			}
		},

		_getPointPositionWithinPage: function (mousePoint, targetPageElement) {
			var pageOffset = $(targetPageElement).offset();
			//this.log("tp.x=" + tp.x + ",targetPageElement=" + targetPageElement);
			return { x: mousePoint.x - pageOffset.left, y: mousePoint.y - pageOffset.top };
		},
		// <--

		_onReportDoubleClick: function (e) {
			this._onReportMouseDown(e);
			this._onReportMouseMove(e);
			this._onDocumentMouseUp(e);
			return true;
		},

		_onReportMouseDown: function (e) {

			if (e) {
				if ($(e.target).hasClass('C1Hyperlink')) {
					return true;
				}
			}
			this._pendingSelectedTextContextMenuActions = false;

			var currentMousePoint = { x: e.pageX, y: e.pageY }, i,
				reportPaneElement;

			if (e.button === 2) {
				if (this._resetSelectionAreaPositionTimeoutId) {
					try {
						window.clearTimeout(this._resetSelectionAreaPositionTimeoutId);
					}
					finally {
						this._resetSelectionAreaPositionTimeoutId = null;
					}
				}
				this._setSelectionAreaLocation(currentMousePoint.x - 50, currentMousePoint.y - 50, 100, 100);
				this._selectionAreaElement.one('mousedown, mouseup', {}, $.proxy(function () {
					this._prepareSelectionAreaElementForContextMenuActions(true);
					setTimeout($.proxy(function () {
						this._resetSelectionAreaPosition();
						this.setFocus();
					}, this), 100);
					return true;
				}, this));
				this.setFocus();
				this._prepareSelectionAreaElementForContextMenuActions(false);
				return true;
			}
			if (this._selectionTargetPages) {
				if (!e.shiftKey && !e.ctrlKey) {
					for (i = 0; i < this._selectionTargetPages.length; i++) {
						if (this._selectionTargetPages[i]) {
							this._selectionTargetPages[i].clearSelection();
						}
					}
					this._selectionTargetPages = [];
				}
			}
			else {
				this._selectionTargetPages = [];
			}
			reportPaneElement = this.element.find('.wijmo-wijreportviewer-reportpane');
			this._reportPaneElementBounds = this._getElemBounds(reportPaneElement);
			this._ensureSelectionTargetPagesStored();
			this._selectionActivated = true;
			this.setFocus();
			this._prevMousePointPosition = currentMousePoint;
			if (e.shiftKey && this._selectionStartPoint && this._selectionStartPoint) {
				this._applySelection(currentMousePoint);
			}
			else {
				this._selectionStartPoint = currentMousePoint;
			}
			e.preventDefault();
			return false;
		},

		_onReportMouseMove: function (e) {
			var scrollTopChange, currentMousePoint, reportPaneElement;
			if (!this._selectionActivated) {
				return true;
			}
			if (!this._selectionAreaFocused) {
				this.setFocus();
			}
			reportPaneElement = this.element.find('.wijmo-wijreportviewer-reportpane');
			currentMousePoint = { x: e.pageX, y: e.pageY };
			if (currentMousePoint.y < this._reportPaneElementBounds.y) {
				scrollTopChange = reportPaneElement.scrollTop();
				reportPaneElement.scrollTop(reportPaneElement.scrollTop() - (Math.abs(this._prevMousePointPosition.y - currentMousePoint.y) + Math.abs(this._prevMousePointPosition.x - currentMousePoint.x)));
				this._ensureSelectionTargetPagesStored();
				scrollTopChange = Math.min(Math.max(scrollTopChange - reportPaneElement.scrollTop(), 0), reportPaneElement[0].scrollHeight);
				this._selectionStartPoint.y += scrollTopChange;
			}
			else if (currentMousePoint.y > (this._reportPaneElementBounds.y + this._reportPaneElementBounds.height)) {
				scrollTopChange = reportPaneElement.scrollTop();
				reportPaneElement.scrollTop(reportPaneElement.scrollTop() + (Math.abs(this._prevMousePointPosition.y - currentMousePoint.y) + Math.abs(this._prevMousePointPosition.x - currentMousePoint.x)));
				this._ensureSelectionTargetPagesStored();
				scrollTopChange = Math.min(Math.min(scrollTopChange - reportPaneElement.scrollTop(), 0), reportPaneElement[0].scrollHeight);
				this._selectionStartPoint.y += scrollTopChange;
			}
			this._prevMousePointPosition = currentMousePoint;
			this._applySelection(currentMousePoint);
			e.preventDefault();
			return false;
		},

		_onDocumentMouseUp: function (e) {
			this._selectionActivated = false;
			if (!this._resetSelectionAreaPositionTimeoutId) {
				this._resetSelectionAreaPositionTimeoutId = window.setTimeout($.proxy(this._resetSelectionAreaPosition, this), 100);
			}
			return true;
		},

		// PageImages View worker

		_dataReceivedHandlers: [], //_pendingImagesDataReceivedOptionalHandlers
		_imageReportCache: {},
		_pagesToLoadIndices: null,
		_activeCallbacksCount: 0,
		_selectionTargetPages: [],
		_pages: [],
		_pendingElaborateHighlightBounds: false,

		_getPageIndexFromPageElement: function (elem) {
			return parseInt(new RegExp('C1Page([0-9]+)', 'g').exec(elem.className)[1]);
		},

		_isPageElemVisible: function (elem, scrollOffset, bottomOffset) {
			if (elem.offsetHeight > 0) {
				if ((elem.offsetTop > scrollOffset && elem.offsetTop < bottomOffset) ||
				(elem.offsetTop < bottomOffset && (elem.offsetTop + elem.offsetHeight) > scrollOffset)) {
					return true;
				}
			}
			return false;
		},

		_updatePageLayout: function (docStatus) {
			var realPageCount = $(".C1Page", this.element).length,
				pendingPageCount = docStatus.pageCount,
				pageElement, pageSize,
				contentElem = this.element.find(".wijmo-wijreportviewer-content"),
				rezolvedZoom = this._rezolveZoom(), i;
			this._onPageImagesChanged(docStatus.changedPages);
			if (pendingPageCount > realPageCount) {
				// Add layout for missed pages:
				for (i = realPageCount; i < pendingPageCount; i++) {
					pageSize = this._determinePageSize(i, rezolvedZoom);
					$("<div class='C1Page C1Page" + i +
						" C1NotLoaded' style='width:" + pageSize.w + "px;height:" +
						pageSize.h + "px'><img class='C1Image' /></div>")
						.appendTo(contentElem);
				}

				if (realPageCount === 0) {
					// Load and show First page.
					$(".C1Page0", contentElem).addClass("C1Current");
					this._loadVisiblePagesData(false);
				}
			}
			else if (pendingPageCount < realPageCount) {
				// Remove excess page elements:
				$(".C1Page" + (pendingPageCount - 1), contentElem)
							.jLastPage.next(".C1Page").remove();
			}
		},

		_onPageImagesChanged: function (changedPages) {
			if (changedPages) {
				var d = this._imageReportCache, k, k2, zoomPagesCache;
				for (k in d) {
					zoomPagesCache = d[k];
					if (zoomPagesCache) {
						for (k2 = 0; k2 < changedPages.length; k2++) {
							zoomPagesCache[changedPages[k2]] = null;
						}
					}
				}
				for (k = 0; k < changedPages.length; k++) {
					this._updatePageImage(changedPages[k], true);
				}
			}
		},

		_loadVisiblePagesData: function (skipFastCalls, completeCallback) {
			if (completeCallback) {
				this._dataReceivedHandlers[this._dataReceivedHandlers.length] = completeCallback;
			}
			if (this._visiblePagesTimerId) {
				window.clearTimeout(this._visiblePagesTimerId);
				this._visiblePagesTimerId = null;
			}
			if (skipFastCalls) {
				this._visiblePagesTimerId = window.setTimeout($.proxy(this._loadVisiblePagesDataCallback, this), 250);
			}
			else {
				this._loadVisiblePagesDataCallback();
			}
		},

		_loadVisiblePagesDataCallback: function () {
			var documentStatus = this.options.documentStatus,
				pageCount, contentElem, notLoadedPagesIdx = [], visiblePages,
				cachedMarkup, k, ind;
			if (!documentStatus) {
				return;
			}
			pageCount = documentStatus.pageCount;
			contentElem = this.element.find(".wijmo-wijreportviewer-content");
			visiblePages = this.getVisiblePageIndices();
			cachedMarkup = this._loadFromCache(visiblePages, notLoadedPagesIdx, true);
			this._visiblePagesTimerId = null;

			for (k = 0; k < visiblePages.length; k++) {
				ind = visiblePages[k];
				$('.C1Page' + ind + ' .C1Image', contentElem).each($.proxy(function (ind, targetImageElem) {
					this._updatePageImage(ind, false);
					return true;
				}, this));
			}
			if (cachedMarkup) {
				this._loadPageImagesMarkup(cachedMarkup);
				if (notLoadedPagesIdx.length > 0) {
					this._loadPages(notLoadedPagesIdx);
				}
			}
			else {
				this._loadPages(visiblePages);
			}
		},

		_updatePageImage: function (pageIndex, forceUpdate) {
			var o = this.options,
				contentElem = this.element.find(".wijmo-wijreportviewer-content"),
				pageImage = $('.C1Page' + pageIndex + ' .C1Image', contentElem)[0],
				ver;
			if (!pageImage || forceUpdate && !pageImage.src) {
				return;
			}
			ver = pageImage.__c1imgver;
			if (!ver) {
				ver = new Date().getTime();
			}
			if (forceUpdate) {
				ver = ver + 1;
			}
			if (pageImage.__c1imgver === ver) {
				return;
			}
			pageImage.__c1imgver = ver;
			pageImage.src = o.reportServiceUrl + "?clientId=" + this.element[0].id +
						"&documentKey=" + o.documentStatus.documentKey +
						"&dpi=" + this.getDPI() +
						"&printTarget=false&zoom=" + this._rezolveZoom() +
						"&pageIndex=" + pageIndex +
						"&imageVersion=" + ver;
		},

		_loadFromCache: function (pagesToLoadIndx, notLoadedPageIndices, lockNotLoadedPages) {
			var rezolvedZoom = this._rezolveZoom(),
				receivedZoomKey = 'z' + rezolvedZoom, data, arr, k, ii;
			if (this._imageReportCache[receivedZoomKey]) {
				data = {};
				data.zoom = rezolvedZoom;
				data.pages = [];
				arr = this._imageReportCache[receivedZoomKey];
				k = 0;
				for (ii = 0; ii < pagesToLoadIndx.length; ii++) {
					var ind = pagesToLoadIndx[ii];
					if (!arr[ind] || isFinite(arr[ind]) &&
						((new Date().getTime() - arr[ind]) > 30000)) {
						if (lockNotLoadedPages) {
							arr[ind] = new Date().getTime();
						}
						notLoadedPageIndices.push(ind);
						continue;
					}
					else if (isNaN(arr[ind])) {
						data.pages[k] = arr[ind];
						k++;
					}
				}
				return data;
			}
			return null;
		},

		_saveToCache: function C1_Web_UI_Controls_C1Report_C1ReportViewer$_saveToCache(data) {
			var receivedZoomKey = "z" + this._rezolveZoom(), arr, i;
			if (!this._imageReportCache[receivedZoomKey]) {
				this._imageReportCache[receivedZoomKey] = [];
			}
			arr = data.pages;
			for (i = 0; i < arr.length; i++) {
				if (arr[i]) {
					(this._imageReportCache[receivedZoomKey])[arr[i].idx] = arr[i];
				}
			}
		},

		_loadPages: function (pagesToLoadIndices) {
			var o, i, len, indicesJsonStr;
			if (!pagesToLoadIndices || pagesToLoadIndices.length < 1) {
				return;
			}
			this._pagesToLoadIndices = pagesToLoadIndices;
			o = this.options;


			indicesJsonStr = "[";
			for (i = 0, len = pagesToLoadIndices.length - 1; i < len; i++) {
				indicesJsonStr += pagesToLoadIndices[i] + ",";
			}
			indicesJsonStr += pagesToLoadIndices[len] + "]";
			this.log("Loading markup data for visible pages: " + indicesJsonStr);
			$.getJSON(o.reportServiceUrl + "?clientId=" + this.element[0].id +
				"&command=markup" +
				"&documentKey=" + o.documentStatus.documentKey +
				"&dpi=" + this.getDPI() +
				"&zoom=" + this._rezolveZoom() +
				"&pageIndices=" + indicesJsonStr +
				"&getImagesOnly=false" +
				(o.documentStatus.cookie ? ("&cookie=" + o.documentStatus.cookie) : ""),
				$.proxy(this._onLoadPagesDataReceived, this)
				);

		},

		_onLoadPagesDataReceived: function (data) {
			var k, arr, c;
			this.log("Pages markup data received. Resolved zoom is " +
					this.resolvedZoom + ", data.zoom is " + data.zoom);
			if (data.zoom != this.resolvedZoom) {
				this.log("Ignoring incorrect markup data. Resolved zoom is " +
					this.resolvedZoom + ", received data zoom is " + data.zoom, "warning");
				return;
			}
			if (data.documentKey !== this.options.documentStatus.documentKey) {
				this.log("Ignoring incorrect markup data. Active documentKey is " +
					this.options.documentStatus.documentKey +
					", received documentKey is " + data.documentKey, "warning");
				return;
			}
			this._loadPageImagesMarkup(data);
			if (this._activeCallbacksCount < 1) {
				window.setTimeout($.proxy(this._preloadMorePagesCallback, this), 100);
			}
			arr = this._dataReceivedHandlers;
			this._dataReceivedHandlers = [];
			for (k = 0; k < arr.length; k++) {
				c = arr[k];
				c();
			}
		},

		_preloadMorePagesCallback: function () {
			if (this._activeCallbacksCount < 1) {
				var pageCount = this.options.documentStatus.pageCount,
					contentElem = this.element.find(".wijmo-wijreportviewer-content"),
					h, k, notLoadedPagesIdx, cachedMarkup, ind, preloadCount,
					firtsLoadedIndex, lastLoadedIndex, pagesToLoad;
				if (this._pagesToLoadIndices && this._pagesToLoadIndices.length > 0 && pageCount > 0) {

					preloadCount = 2;
					firtsLoadedIndex = this._pagesToLoadIndices[0];
					lastLoadedIndex = this._pagesToLoadIndices[this._pagesToLoadIndices.length - 1];
					pagesToLoad = [];
					for (h = 1; h <= preloadCount; h++) {
						k = lastLoadedIndex - h;
						if (k < 0) {
							break;
						}
						pagesToLoad[pagesToLoad.length] = k;
					}
					for (h = 1; h <= preloadCount; h++) {
						k = lastLoadedIndex + h;
						if (k >= pageCount) {
							break;
						}
						pagesToLoad[pagesToLoad.length] = k;
					}
					pagesToLoad[pagesToLoad.length] = pageCount - 1;
					if (pageCount > 1) {
						pagesToLoad[pagesToLoad.length] = pageCount - 2;
					}
					notLoadedPagesIdx = [];
					cachedMarkup = this._loadFromCache(pagesToLoad, notLoadedPagesIdx, false);
					for (k = 0; k < notLoadedPagesIdx.length; k++) {
						ind = notLoadedPagesIdx[k];
						$('.C1Page' + ind + ' .C1Image', contentElem).each($.proxy(function (index, targetImageElem) {
							this._updatePageImage(ind, false);
							return true;
						}, this));
					}
				}
			}
		},



		_loadPageImagesMarkup: function (data) {
			this._handleCommonResponseFlags(data);
			this._saveToCache(data);
			this._setPagesData(data);
			//this.UpdateCurrentPageLabel(false);
		},

		_setPagesData: function (pagesData) {
			var receivedZoom = pagesData.zoom, pendingZoom = this._rezolveZoom(),
				dataCount = pagesData.pages.length, o = this.options,
			jReportContent = this.element.find(".wijmo-wijreportviewer-content"),
			imgUrlFmt, i, pageIndex, page;
			for (i = 0; i < dataCount; i++) {
				var pageData = pagesData.pages[i];
				if (!pageData) {
					continue;
				}
				pageIndex = pageData.idx;
				page = this._pages[pageIndex];
				if (!page) {
					var pageElement = jReportContent.find('.C1Page' + pageIndex).get(0);
					if (!pageElement) {
						this.log("Page element is not created for page with index " + pageIndex, "warning");
						continue;
					}
					page = new C1ImagePageInternal(pageElement, this);
					this._pages[pageIndex] = page;
				}
				if (receivedZoom === pendingZoom) {
					imgUrlFmt = o.reportServiceUrl + "?clientId=" + this.element[0].id +
						"&documentKey=" + o.documentStatus.documentKey +
						"&dpi=" + this.getDPI() +
						"&printTarget=false&zoom=" + pendingZoom +
						"&pageIndex={0}" +
						"&imageVersion={1}";
					page._setData(pageData, this._pendingElaborateHighlightBounds,
									pendingZoom, imgUrlFmt);
					this._pendingElaborateHighlightBounds = false;
				}
			}
		},

		// returns false if error occurred
		_handleCommonResponseFlags: function (answer) {
			if (answer) {
				if (answer.error) {
					this.status(answer.error, "error");
					//jQuery('.C1ProgressActivityIndicator', this.element).hide();
					answer.error = null;
					this._onReportViewerStatusChanged(-1);
					return false;
				}
				else if (answer.isGenerating) {
					this._onReportViewerStatusChanged(1);
					//todo:
					/*var isGenerating = answer.isGenerating;
					if (!this.get_documentStatus().isGenerating && isGenerating) {
					this.get_documentStatus().isGenerating = true;
					this._ensureReportIsGenerated$3();
					}*/
				}
			}
			return true;
		},

		/// <summary>
		/// Sends a log message to log console.
		/// </summary>
		/// <param name="status" type="String">
		///	Number. Indicates new report viewer status.
		///		Possible values:
		///		   -1 - Error occurred.
		///			0 - Ready. Report is completely generated.
		///			1 - GeneratingPages. Report is currently generating, total 
		///				page count may change.
		///			2 - UpdatingPages. Report is still generating, individual 
		///				pages may change but total page count is final.
		///			3 - ParametersRequested. Report needs parameters, status 
		///				contains ParamInfos describing the requested parameters.
		///				All other fields in status are not initialized.
		///	</param>
		_onReportViewerStatusChanged: function (status) {
			//this._reportViewerStatus = status;
			switch (status) {
				case 1:
				case 2:
					//UpdateToolBarButtonsEnabledState(new Dictionary("Print", false, "ExportDropDown", false));
					break;
				case 0:
					//UpdateToolBarButtonsEnabledState(new Dictionary("Print", true));
					// Export enabled later if export formats available.
					break;
				case -1:
					//$(".C1ProgressActivityIndicator", this.element).hide();
					break;
			}
			////Print,ExportDropDown,ZoomOut,ZoomIn,ContinuesView
		}

	});










} (jQuery));


////////////////////////////////////////////////////////////////////////////////
// C1ImagePageInternal
C1ImagePageInternal = function (element, reportViewer) {
	this._oldHighLights$2 = {};
	this._currentHighLights$2 = {};
	this._highLightsDetails$2 = {};
	this.element = element;
	this._jObj = $(element);
	this.reportViewer = reportViewer;
};
C1ImagePageInternal.createStubPageData = function (pageElement) {
	var stub = {};
	stub.h = pageElement.offsetHeight;
	stub.w = pageElement.offsetWidth;
	stub.idx = -1;
	stub.texts = [];
	return stub;
};
C1ImagePageInternal.prototype = {
	_pageData$2: null,
	_currentDataZoom$2: 100,
	index: 0,

	_setData: function (pageData, elaborateHighlightBounds, zoom, imgUrlFmt) {
		var img = jQuery('.C1Image', this.element).get(0),
			imageVer = img.__c1imgver;
		this._currentDataZoom$2 = zoom;
		this._pageData$2 = pageData;
		this.index = pageData.idx;
		this.element.style.width = pageData.w + 'px';
		this.element.style.height = pageData.h + 'px';
		if (!imageVer) {
			img.__c1imgver = imageVer = new Date().getTime();
		}
		this._displayHyperlinks(pageData.links);
		img.src = imgUrlFmt.replace("{0}", this.index).replace("{1}", imageVer);
		$(this.element).removeClass('C1NotLoaded');
		if (elaborateHighlightBounds) {
			this._elaborateHighlightBounds$2();
		}
	},

	get_pageData: function () {
		if (!this._pageData$2) {
			this._pageData$2 = C1ImagePageInternal.createStubPageData(this.element);
		}
		return this._pageData$2;
	},

	getSelectedText: function () {
		var s = '', wordIndex, textSliceStartIndex, textSliceEndIndex, details,
			detail, trIndx, tr, i,
			trimRegexp = new RegExp("^[\\s\\n\\r\\t]*|[\\s\\n\\r\\t]*$");

		if (this._highLightsDetails$2) {
			details = this._getSortedArrayOfDetais$2(this._highLightsDetails$2);
			for (i = 0; i < details.length; i++) {
				detail = details[i];
				trIndx = detail.trIndx;
				if (trIndx === -1 || isNaN(trIndx)) {
					continue;
				}
				tr = this._pageData$2.texts[trIndx];
				if (typeof (detail.pi) === 'undefined' || !tr.ix || (tr.ix.length === 1 && tr.ix[0] === -1)) {
					s += tr.text;
				}
				else {
					wordIndex = detail.pi;
					textSliceStartIndex = tr.ix[wordIndex];
					textSliceEndIndex = (wordIndex >= (tr.ix.length - 1)) ? tr.text.length : tr.ix[wordIndex + 1];
					s += tr.text.substring(textSliceStartIndex, textSliceEndIndex);
				}
			}
		}
		s = (s + "").replace(trimRegexp, "");
		return s;
	},

	_elaborateHighlightBounds$2: function () {
		if (this._highLightsDetails$2) {
			var $dict1 = this._highLightsDetails$2;
			for (var $key2 in $dict1) {
				var en = { key: $key2, value: $dict1[$key2] };
				var detail = en.value;
				var highlight = this._currentHighLights$2[en.key];
				var trIndx = detail.trIndx;
				if (trIndx === -1 || isNaN(trIndx)) {
					continue;
				}
				var tr = this._pageData$2.texts[trIndx];
				if (!tr) {
					continue;
				}
				if (tr) {
					highlight.style.top = tr.y + 'px';
					highlight.style.height = tr.h + 'px';
					if (typeof (detail.pi) === 'undefined' || !tr.ix || (tr.ix.length === 1 && tr.ix[0] === -1)) {
						highlight.style.left = tr.x + 'px';
						highlight.style.width = tr.w + 'px';
					}
					else {
						var wordIndex = detail.pi;
						var leftOffset = tr.x + tr.p[wordIndex];
						highlight.style.left = leftOffset + 'px';
						var width = ((wordIndex >= (tr.p.length - 1)) ? tr.w : tr.p[wordIndex + 1]) - tr.p[wordIndex];
						highlight.style.width = width + 'px';
					}
				}
			}
		}
	},

	selectRegion: function (startPoint, endPoint, zoom) {
		if (!this._startHighLightsUpdate()) {
			return false;
		}
		var spx = startPoint.x;
		var spy = startPoint.y;
		var epx = endPoint.x;
		var epy = endPoint.y;
		var tmpVal = 0;
		if (spy > epy) {
			tmpVal = epy;
			epy = spy;
			spy = tmpVal;
			tmpVal = epx;
			epx = spx;
			spx = tmpVal;
		}
		var textRuns = this._pageData$2.texts;
		var textRunsFoundCount = 0;
		var textRunsCount = textRuns.length;
		var zoomKoef = zoom / this._currentDataZoom$2;
		for (var k = 0; k < textRunsCount; k++) {
			var textRun = textRuns[k];
			var trL = textRun.x;
			var trT = textRun.y;
			var trW = textRun.w;
			var trH = textRun.h;
			var p = textRun.p;
			if (zoomKoef !== 1) {
				trL = Math.round(trL * zoomKoef);
				trT = Math.round(trT * zoomKoef);
				trW = Math.round(textRun.w * zoomKoef);
				trH = Math.round(trH * zoomKoef);
				if (p) {
					var pp = [];
					for (var kk = 0; kk < p.length; kk++) {
						pp[kk] = Math.round(p[kk] * zoomKoef);
					}
				}
			}
			var testAllWords = (trT <= spy && (trT + trH) >= epy);
			var testWordsOnlyFromLeft = !testAllWords && (trT < spy && (trT + trH) > spy);
			var testWordsOnlyFromRight = !testAllWords && (trT < epy && (trT + trH) > epy);
			if (spy < (trT + trH) && epy > trT) {
				textRunsFoundCount++;
				if (!p || (!testAllWords && !testWordsOnlyFromLeft && !testWordsOnlyFromRight)) {
					this._applyHighLight(trL, trT, trW, trH, null, { trIndx: k });
				}
				else {
					for (var i = 0; i < p.length; i++) {
						var absWordL = trL + p[i];
						var wordW = (i < (p.length - 1)) ? (p[i + 1] - p[i]) : (trW - p[i]);
						if (wordW < 0) {
							break;
						}
						if (((spx < (absWordL + wordW) || testWordsOnlyFromRight) && (epx > absWordL || testWordsOnlyFromLeft)) && (spy < (trT + trH) && epy > trT)) {
							this._applyHighLight(trL + p[i], trT, wordW, trH, null, { trIndx: k, pi: i });
						}
					}
				}
			}
		}
		this._endHighlightsUpdate();
		return true;
	},

	_displayHyperlinks: function (links) {
		this._jObj.find('.C1Hyperlink').remove();
		if (links) {
			for (var i = 0, count = links.length; i < count; i++) {
				if (links[i].a) {
					this._createHyperlink(links[i]);
				}
			}
		}
	},

	_createHyperlink: function (link) {
		var trs = link.tr, j, hyperlinkElem;
		for (j = 0, jcount = trs.length; j < jcount; j++) {
			hyperlinkElem = document.createElement('DIV');
			hyperlinkElem.className = 'C1Hyperlink';
			hyperlinkElem.style.width = link.tr[j].w + 'px';
			hyperlinkElem.style.height = link.tr[j].h + 'px';
			hyperlinkElem.style.left = link.tr[j].x + 'px';
			hyperlinkElem.style.top = (link.tr[j].y - 1) + 'px';
			hyperlinkElem.title = link.text;
			$(hyperlinkElem).bind("click", $.proxy(function (e) {
				e.preventDefault();
				this._execHyperlinkAction(link.a, e);
				return false;
			}, this));
			this.element.appendChild(hyperlinkElem);
		}
	},

	_execHyperlinkAction: function (action, e) {
		var actionName = action.substring(0, action.indexOf(':'));
		var actionVal = action.substr(action.indexOf(':') + 1);
		switch (actionName) {
			case "exec":
				window.__w$jReportViewerTmpRef1$29 = this.reportViewer;
				eval("window.__w$jReportViewerTmpRef1$29." + actionVal);
				window.__w$jReportViewerTmpRef1$29 = null;
				break;
			case 'javascript':
				eval(actionVal);
				break;
			default:
				if (e && e.ctrlKey) {
					window.open(action, '_blank');
				}
				else {
					window.location.href = action;
				}
				break;
		}
	},

	_applyHighLight: function (highLightX, highLightY, highLightW, highLightH, specificColor, details) {
		var highLight;
		if (!specificColor) {
			specificColor = "";
		}
		var highlightKey = highLightY + 'x' + highLightX + 'w' + highLightW + 'h' + highLightH;
		details['xy'] = { x: highLightX, y: highLightY };
		if (this._oldHighLights$2[highlightKey]) {
			this._currentHighLights$2[highlightKey] = this._oldHighLights$2[highlightKey];
			this._highLightsDetails$2[highlightKey] = details;
			(this._currentHighLights$2[highlightKey]).style.backgroundColor = specificColor;
			delete this._oldHighLights$2[highlightKey];
			return;
		}
		highLight = document.createElement('DIV');
		highLight.style.backgroundColor = specificColor;
		this._currentHighLights$2[highlightKey] = highLight;
		this._highLightsDetails$2[highlightKey] = details;
		highLight.className = 'C1Highlight';
		highLight.style.width = highLightW + 'px';
		highLight.style.height = highLightH + 'px';
		highLight.style.left = highLightX + 'px';
		highLight.style.top = (highLightY - 1) + 'px';
		this.element.appendChild(highLight);
	},

	clearSelection: function C1_Web_UI_Controls_C1Report_C1ImagePage$clearSelection() {
		var oldHighLights = this._currentHighLights$2;
		this._currentHighLights$2 = {};
		this._highLightsDetails$2 = {};
		var $dict1 = oldHighLights;
		for (var $key2 in $dict1) {
			var d = { key: $key2, value: $dict1[$key2] };
			var highlight = d.value;
			if (highlight && highlight.parentNode) {
				highlight.parentNode.removeChild(highlight);
			}
		}
	},

	_highlightLock$2: false,

	_startHighLightsUpdate: function C1_Web_UI_Controls_C1Report_C1ImagePage$_startHighLightsUpdate() {
		if (this._highlightLock$2) {
			return false;
		}
		this._highlightLock$2 = true;
		this._oldHighLights$2 = this._currentHighLights$2;
		this._currentHighLights$2 = {};
		return true;
	},

	_endHighlightsUpdate: function C1_Web_UI_Controls_C1Report_C1ImagePage$_endHighlightsUpdate() {
		var $dict1 = this._oldHighLights$2;
		for (var $key2 in $dict1) {
			var d = { key: $key2, value: $dict1[$key2] };
			delete this._highLightsDetails$2[d.key];
			var highlight = d.value;
			if (highlight && highlight.parentNode) {
				highlight.parentNode.removeChild(highlight);
			}
		}
		this._oldHighLights$2 = {};
		this._highlightLock$2 = false;
	},

	_getSortedArrayOfDetais$2: function (aInput) {
		var arr = new Array(0), k;
		for (k in aInput) {
			arr.push(aInput[k]);
		}
		arr.sort(this._sortDetaisCallback);
		return arr;
	},

	_sortDetaisCallback: function (o1, o2) {
		var key1 = (o1)['xy'];
		var key2 = (o2)['xy'];
		if (key1['y'] < key2['y']) {
			return -1;
		}
		if (key1['y'] > key2['y']) {
			return 1;
		}
		if (key1['x'] < key2['x']) {
			return -1;
		}
		if (key1['x'] > key2['x']) {
			return 1;
		}
		return 0;
	}
}