/**
* Services
*/
app.service('appServices', function($rootScope, $resource, $http, $log, $uibModal, FileSaver) {

    var NOTIFICATION_TIMEOUT = 4000;

    // Create Base64 Object
    var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9+/=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/rn/g,"n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}}

	//IE-hez kell
	if (!String.prototype.startsWith) {
		String.prototype.startsWith = function(searchString, position) {
			position = position || 0;
			return this.indexOf(searchString, position) === position;
		};
	}
    

    //a validaciohoz szukseges nestelt objektumokat stringgel elero fuggveny
    function objectByString(o, s) {
        s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
        var a = s.split('.');
        for (var i = 0, n = a.length; i < n; ++i) {
            var k = a[i];
            if (k in o) {
                o = o[k];
            } else {
                return;
            }
        }
        return o;
    }

    function clone(obj) {
        var copy;

        // Handle the 3 simple types, and null or undefined
        if (null == obj || "object" != typeof obj) return obj;

        // Handle Date
        if (obj instanceof Date) {
            copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }

        // Handle Array
        if (obj instanceof Array) {
            copy = [];
            for (var i = 0, len = obj.length; i < len; i++) {
                copy[i] = clone(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            copy = {};
            for (var attr in obj) {
                if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
            }
            return copy;
        }

        throw new Error("Unable to copy obj! Its type isn't supported.");
    }

    function filter(sourceArray, filterArray) {
        var filteredArray = sourceArray.filter(function (obj) {
            var talalat = false;
            angular.forEach(filterArray, function (filterObj) {
                if(filterObj.id === obj.id) {
                    talalat = true;
                }
            });
            return talalat;
        });
        // TODO: Nem tudom, hogy miért csak így működik elemduplázódás mentesen
        return angular.copy(filteredArray);
    }
    
    function submitLogin(data) {
            return $resource('login').save(data);
	}

	function getMenu() {
		return $resource(webUtilService.makeOperationUrl('api/config/menu'), {
			rnd: Math.random()
		}).query();
	}

	function getLocale() {
		return $resource(webUtilService.makeOperationUrl('api/config/getlocale'), {
			rnd: Math.random()
		}).get();
	}

	function getOrgProperties() {
	    return $resource(webUtilService.makeOperationUrl('api/config/getorgproperties'), {
	        rnd: Math.random()
	    }).get();
	}

	// initFormSave-et átneveztük, mert nem csak save-nél 
	function initFormSubmit (scope, data, transformDictionary) { 

	    //Itt kapjuk el a gomb kattintáskor kiváltott eseményt
        var unbindEvent = scope.$on('formSubmittedAndFinishTask', function (evt, transformName, callParam, taskId, nextOperation) {
            initFormSubmitPriv(scope, data, transformDictionary, evt, transformName, callParam, taskId, nextOperation);
        });

        //Itt kapjuk el a gomb kattintáskor kiváltott eseményt
	    var unbindEvent = scope.$on('formSubmitted', function (evt, transformName, callParam) {
            initFormSubmitPriv(scope, data, transformDictionary, evt, transformName, callParam);
		});

		scope.$on('$destroy', function () {
			unbindEvent();
		});
	}

    function initFormSubmitPriv(scope, data, transformDictionary, evt, transformName, callParam, taskId, nextOperation){
        
			if (evt.stopPropagation){
				evt.stopPropagation();
			}
			if (!transformDictionary[transformName]) {
				//$log.info('NR: transformDictionary');
				//$log.info(transformDictionary);
				if (transformName != 'cancelForm') {
                    console.error('Can\'t find transform ' + transformName + ' on controller!');
                }
				return;
			}
			
		    // Ez itt a visszahívás a controller-ben implemetált transzformációs blokkra
            // A nextOperation paramétert is átadjuk hátha a transzformációs blokk ez alapján el akar ágazni
			var transformOutput = transformDictionary[transformName](data, callParam, nextOperation);

			if (transformOutput.customAction) {
			    transformOutput.customAction();
			    if ($rootScope.modalidsindex > -1) {
			        $rootScope.$broadcast('closeOnSuccess' + $rootScope.modalids[$rootScope.modalidsindex]);
			    }
			    return;
			}

			if (transformOutput.cancel) {
				    return;
			}

			if (!transformOutput.endpoint) {
				console.error('Nincs megadva endpoint!');
				return;
			}
			
			var endpoint = transformOutput.endpoint;
			if (taskId){
				endpoint = endpoint + '?taskId='+taskId+'&nextOperation='+nextOperation;
			}
			if (transformOutput.procedure) {
			    if (taskId) {
			        endpoint = endpoint + '&';
			    } else {
			        endpoint = endpoint + '?';
			    }
				endpoint = endpoint + 'proc=' + transformOutput.procedure;
			}
			
			if (!transformOutput.data) {
				transformOutput.data = data;
			}
			var validateForms = transformOutput.validateForms;
            if (validateForms) {
                if (typeof validateForms === 'string') validateForms = [validateForms];
                var formsAreValid = true;
                validateForms.forEach(function (formName) {
                    if ($rootScope.formval[formName].$invalid) {
                        formsAreValid = false;
                    }
                });
                if (!formsAreValid) {
					if(validateForms.length === 1 && validateForms[0] === "loginform"){
						showErrors(checkErrors($rootScope.formval[validateForms[0]].$error));
						//toastr.warning('Felhasználónév és jelszó megadása kötelező.', 'Hiba', {timeOut: NOTIFICATION_TIMEOUT});
					} else {
						toastr.warning('Hibásan töltötte ki az űrlapot. Kérem javítsa!', 'Hiba', {timeOut: NOTIFICATION_TIMEOUT});
					}
					return;
                }
            }

			var urlencode = transformOutput.urlencode || false;

			submitForm(endpoint, transformOutput.data, urlencode, transformOutput.response, scope, transformOutput, taskId, nextOperation, transformOutput.toastrSuccess, transformOutput.errorCallback);
        
    }

	function checkErrors(errorArray){
		var errorMessages = [];
		if(errorArray.required.length > 0){
			angular.forEach(errorArray.required, function(error){
				if(error.$error.required){
					if(error.$name == "username"){
						errorMessages.push("A Felhasználónév megadása kötelező.");
					}
					if(error.$name == "password"){
						errorMessages.push("A Jelszó megadása kötelező.");
					}
					if(error.$name == "ugyfelExtId"){
						errorMessages.push("A Munkáltató azonosító megadása kötelező.");
					}
				}
			});
			return errorMessages;
		}
	}

    function submitForm(endpoint, data, urlencode, successCallback, scope, confirm, taskId, nextOperation, toastrSuccess, errorCallback, customAction) {
        if (confirm && confirm.confirm) {
            $rootScope.confirmMessage = confirm.confirmMessage;
            $rootScope.confirmMessageCode = confirm.confirmMessageCode;
			$rootScope.confirmSubmitButton = confirm.confirmSubmitButton ? confirm.confirmSubmitButton : 'OK';
			$rootScope.confirmCancelButton = confirm.confirmCancelButton ? confirm.confirmCancelButton : 'CANCEL';
			$('#saveModal').modal();
			if($rootScope.modalUnbind) $rootScope.modalUnbind();
            $rootScope.modalUnbind = scope.$on('confirmSave', function() {
                $rootScope.modalUnbind();
                customAction ? customAction(data,scope) : submitFormPriv(endpoint, data, urlencode, successCallback, taskId, nextOperation, null, toastrSuccess, errorCallback);
            });
      } else {
			customAction ? customAction(data,scope) : submitFormPriv(endpoint, data, urlencode, successCallback, taskId, nextOperation, null, toastrSuccess, errorCallback);
      }
    }

	function b64toBlob(b64Data, contentType, sliceSize) {
		contentType = contentType || '';
		sliceSize = sliceSize || 512;

		var byteCharacters = atob(b64Data);
		var byteArrays = [];

		for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
			var slice = byteCharacters.slice(offset, offset + sliceSize);

			var byteNumbers = new Array(slice.length);
			for (var i = 0; i < slice.length; i++) {
				byteNumbers[i] = slice.charCodeAt(i);
			}

			var byteArray = new Uint8Array(byteNumbers);

			byteArrays.push(byteArray);
		}
		return new Blob(byteArrays, {type: contentType});
	}

    function submitFormPriv(endpoint, data, urlencode, successCallback, taskId, nextOperation, toFile, toastrSuccess, errorCallback) {
        var callResolved = false;
		var loadingModalOpen = false;
		var onSuccess = function (res) {
			callResolved = true;
			if (loadingModalOpen) {
				$('#loading-modal').modal('hide');
			}
//			console.log(res);
            // Egyelőre a szerveren nincs megoldva, hogy ne kétféleképpen jöjjön vissza a válasz
            var resData = res.hasOwnProperty("data") ? res.data : res;
			var success = resData.hasOwnProperty("success") ? Boolean(resData.success) : true;
			var exceptionType = resData.exceptionType;
			if (success) {
				if (resData.redirectUrl) {
                    window.location.href = resData.redirectUrl;
                }
                // Ha fájlba kell menteni a szerver válaszát
                if (toFile) {
                    FileSaver.saveAs(
						b64toBlob(resData.fileContent, 'text/csv'), resData.fileName
                        //new Blob([Base64.decode(resData.fileContent)], { type: 'text/csv' }),
                        //resData.fileName
                    );                    
                }
                // Ha van callback function, akkor meghívjuk a szervertől visszakapott adattal
                if (successCallback) {
                    successCallback(resData);
				} 
                if (toastrSuccess){
					showSuccess(resData.title ? resData.title : '',resData.message ? resData.message : 'Sikeres mentés!');
                }
			    // Ha van operation kód akkor a backend hívja meg a finishTask-ot.
			    // TODO: Valószínűleg jobb lenne, ha egységesen minden esetben a backend híná a finishTask-ot.
                // Remélem az updateprocessvariable-t csak hiánypótlásnál hívjuk
                
                if (endpoint.startsWith(webUtilService.makeOperationUrl('api/case/updateprocessvariable')) ||( taskId && !nextOperation)) {
                    finishTask(taskId);
                }

                if($rootScope.modalidsindex > -1) {
					$rootScope.$broadcast('closeOnSuccess'+$rootScope.modalids[$rootScope.modalidsindex]);
				}
                
			} else if (exceptionType === 'USER') {
                // Ha van error callback function, akkor meghívjuk a szervertől visszakapott adattal
                if (errorCallback) {
                    errorCallback(resData);
                }
				toastr.error(resData.message, resData.title ? resData.title : 'Hiba');
				if($rootScope.modalidsindex > -1) {
					$rootScope.$broadcast('onError'+$rootScope.modalids[$rootScope.modalidsindex]);
				}
			} else {
			    // Ha van error callback function, akkor meghívjuk a szervertől visszakapott adattal
			    if (errorCallback) {
			        errorCallback(resData);
			    }
				toastr.error(resData.message + '<br/><br/>' +
						'<i class="fa fa-angle-up fa-2x"></i>' +
						'<i class="fa fa-angle-down fa-2x"></i>' +
						'<div class="stack-trace">' + resData.stack + '</div>', 'Hiba',
					{ onclick: function (evt) {
						var $target = angular.element(evt.target);
						if ($target.is('.fa')) {
							$target.closest('.toast-error').toggleClass('expanded');
					}
				}});
				if($rootScope.modalidsindex > -1) {
					$rootScope.$broadcast('onError'+$rootScope.modalids[$rootScope.modalidsindex]);
				}
			}
		};
        var onError = function () {
			toastr.error($rootScope.envConfig.internalServerError, $rootScope.envConfig.error + ":", $rootScope.toastrOptions);
            console.error('Unable to reach server');
            callResolved = true;
			if (loadingModalOpen) {
				$('#loading-modal').modal('hide');
			}
			if($rootScope.modalidsindex > -1) {
				$rootScope.$broadcast('onError'+$rootScope.modalids[$rootScope.modalidsindex]);
			}
		};  
        
		setTimeout(function () {
			if (!callResolved) {
				$('#loading-modal').modal({
				    backdrop: false //'static'
				});
				loadingModalOpen = true;
				//$('.modal-backdrop').attr('style', 'opacity: 0.04 !important');
			}
		}, 1500);
		if (!urlencode) {
			$http.post(webUtilService.makeOperationUrl(endpoint), data, {headers: {'VsId': $rootScope.vsId}}).then(onSuccess, onError);
		} else {
			$http({
				method: 'POST',
				url: webUtilService.makeOperationUrl(endpoint),
				headers: {'Content-Type': 'application/x-www-form-urlencoded',
					      'VsId': $rootScope.vsId},
				transformRequest: function(obj) {
					var str = [];
					for(var p in obj)
						str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
					return str.join("&");
				},
				data: data
			}).success(onSuccess).error(onError);
		}
	}

	function getCaptions(codes, languageId, codeGroup) {
		var value = [];
		angular.forEach(codes, function(code){
			value.push({
				kod: code,
				kodCsoport: codeGroup ? codeGroup : "APP_CAPTIONS", // IDEIGLENES: BA_BANK_NEV
				nev: "",
				leiras: ""
			});
		});
		return $http(
			{
				url: webUtilService.makeOperationUrl('api/config/getcaptions'),
				method: 'POST',
				data: {
					value: value,
					languageId: languageId,
					safe: 1
				},
				headers: {
					'Accept': 'application/json',
					'Content-Type': 'application/json'
				},
				dataType: 'json'
			}
		);
	}

	function getLoginCaptions(languageId) {
		/** A value paramétert a backend-en töltjük fel, mert ez nem védett eljárás*/
		return $http(
				{
					url: webUtilService.makeOperationUrl('api/config/getlogincaptions'),
					method: 'POST',
					data: {
						value: null,
						languageId: languageId,
						safe: 1
					},
					headers: {
						'Accept': 'application/json',
						'Content-Type': 'application/json'
					},
					dataType: 'json'
				}
		);
	}

	function getInfo(code, codeGroup) {
		var value = [];
		value.push({
			kod: code,
			kodCsoport: codeGroup,
			nev: "",
			leiras: ""
		});
		return $http(
			{
				url: webUtilService.makeOperationUrl('api/config/getcaptions'),
				method: 'POST',
				data: {
					value: value,
					safe: 1
				},
				headers: {
					'Accept': 'application/json',
					'Content-Type': 'application/json'
				},
				dataType: 'json'
			}
		);
	}

	function getComboItems(url, initParam) {

		return $http(
				{
					url: webUtilService.makeOperationUrl('api/' + url),
					method: 'POST',
					data: initParam,
					headers: {
						'Accept': 'application/json',
						'Content-Type': 'application/json'
					},
					dataType: 'json'
				}
		).then(function(result){
					return result;
				});
	}

    function initLogin(login, errorFunction) {
        return $http(
            {
                url: webUtilService.makeOperationUrl('api/config/initlogin'),
                method: 'POST',
                data: login,
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
                dataType: 'json'
            }
        ).error(errorFunction);
    }

    function colorLuminance(hex, lum) {
        // validate hex string
        hex = String(hex).replace(/[^0-9a-f]/gi, '');
        if (hex.length < 6) {
            hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
        }
        lum = lum || 0;
        // convert to decimal and change luminosity
        var rgb = "#", c, i;
        for (i = 0; i < 3; i++) {
            c = parseInt(hex.substr(i * 2, 2), 16);
            c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
            rgb += ("00" + c).substr(c.length);
        }
        return rgb;
    }

    function setInitModel(initModel) {
        if ($rootScope.modalidsindex > -1) {
            $rootScope['initModel' + $rootScope.modalids[$rootScope.modalidsindex]] = initModel;
        } else {
            $rootScope['initModel'] = initModel;
        }
    }
    
    function getInitModel() {
        if ($rootScope.modalidsindex > -1) { 
            return $rootScope['initModel' + $rootScope.modalids[$rootScope.modalidsindex]];
        } else {
            return $rootScope['initModel'];
        }
    }
    
    function popUp(modalid, template, model, taskId, footerButtons, size) {
        if (!template || !modalid) {
            console.log('A (template) és (modalid) paraméterek megadása kötelező!');
            return;
        }
        modalid = modalid.replace(/\//g, '');

        //var clonedModel = clone(model);
        $rootScope["popupmodel" + modalid] = {model: model};

        if (!footerButtons) {
            footerButtons = ['CANCEL', 'SAVE']; // Ha nincs megadva, akkor alapértelmezett érték
        }
        var footerButtonCodes = [];
        var customButtons = [];
        angular.forEach(footerButtons, function (button) {
            if (typeof button === 'object') {
                customButtons.push(button);
                footerButtonCodes.push(button.captionCode);
            } else {
                footerButtonCodes.push(button);
            }
        });

        getCaptions(footerButtonCodes).then(function (res) {

            var cancelCaption = '', closeCaption = '', finishTaskCaption = '', saveCaption = '';
            var modalInstance;

            angular.forEach(res.data, function (item) {
                switch (item.Code) {
                    case "CANCEL":
                        cancelCaption = item.Name;
                        break;
                    case "CLOSE":
                        closeCaption = item.Name;
                        break;
                    case "FINISHTASK":
                        finishTaskCaption = item.Name;
                        break;
                    case "SAVE":
                        saveCaption = item.Name;
                        break;
                    default: 
                        // Egyedi nyomógombok
                        angular.forEach(customButtons, function (customButton) {
                            if (customButton.captionCode === item.Code){
                                customButton.caption = item.Name;
                            }
                        });
                }
            });

            $rootScope["popupmodel" + modalid].taskId = taskId;
            $rootScope["popupmodel" + modalid].modalInstance = {};
            $rootScope["popupmodel" + modalid].customButtons = customButtons;
            modalInstance = $uibModal.open({
                template: '<popup modalid="' + modalid + '"' +
                                ' template="' + template + '"' +
                                ' popupmodel="popupmodel' + modalid + '"' +
                                ' hasfooter="' + (footerButtons ? "true" : "false") + '"' +
                                ' cancelcaption="' + cancelCaption + '"' +
                                ' closecaption="' + closeCaption + '"' +
                                ' finishtaskcaption="' + finishTaskCaption + '"' +
                                ' savecaption="' + saveCaption + '"' +
                                ' />',
                size: size ? size : 'lg',
                animation: true,
                backdrop: 'static' // true, false, 'static'
            });
            
            modalInstance.closed.then(function () {
                $rootScope.modalids.splice($rootScope.modalidsindex, 1);
                $rootScope.modalidsindex = $rootScope.modalidsindex - 1;
                //console.log('closed!!: ' + $rootScope.modalidsindex);
            });

            $rootScope["popupmodel" + modalid].modalInstance = modalInstance;
        });

    }    
    
    
    function exportFile(endpoint, searchData) {
        if (!endpoint) {
            console.error('Nincs megadva végpont!');
            return;
        }
        submitFormPriv(endpoint, searchData, null, null, null, null, true, false);
    }

	function downloadBinaryFile(endpoint, searchData, fileName) {
		if (!endpoint) {
			console.error('Nincs megadva végpont!');
			return;
		}

		$http(
				{
					url: webUtilService.makeOperationUrl(endpoint),
					method: 'POST',
					responseType: 'arraybuffer',
					data: searchData,
					headers: {
						'Content-type': 'application/json',
						'Accept': 'text/csv'
					}
					/*
					 headers: {
					 'Accept': 'application/json',
					 'Content-Type': 'application/json'
					 },
					 dataType: 'json'        */
				}
		).success(
				function(data) {
					FileSaver.saveAs(
							new Blob([data], { type: 'application/octet-stream' }),
							fileName ? fileName : 'file.jpg'
					);
				});
	}

	function downloadFile(endpoint, searchData) {
		if (!endpoint) {
			console.error('Nincs megadva végpont!');
			return;
		}

		$.ajax(
				{
					url: webUtilService.makeOperationUrl(endpoint),
					method: 'GET',
					responseType: 'arraybuffer',
					data: searchData,
					headers: {
						'Content-type': 'application/json',
						'Accept': 'text/csv'
					}
				}
		).success(
				function(result) {
					var byteData = toByteArray(result.Content);
					var blobData = new Blob([byteData], {type: result.ContentType});
					FileSaver.saveAs(blobData, result.FileName, true);
				});
	}
//new Blob([data], {type: xhr.getResponseHeader('Content-Type')}), fileName);
	function getHtmlBlock(code) {
		return $http(
			{
				url: webUtilService.makeOperationUrl('api/config/gethtmlblock'),
				method: 'POST',
				data: {code:code},
				headers: {
					'Accept': 'application/json',
					'Content-Type': 'application/json'
				},
				dataType: 'json'
			}
		);
	}
	function showErrors(errorArray, errorTitle) {
		removeErrors();
		var errorString = '<ul style="-webkit-padding-start:16px; margin-top:5px">' + errorArray.map(function(e){return '<li>' + e + '</li>';}).join("") + "</ul>";
		toastr.error(errorString, errorTitle);
	}

	function removeErrors(){
		if($('#toast-container').length > 0){
			var divArray=$('#toast-container')[0].children;
			//Error toastr-ek törlése
			for(var i=divArray.length-1; i>=0;i--){
				if(divArray.item(i).className == 'toast toast-error'){
					divArray.item(i).remove();
				}
			}
		}
	}

	function showSuccess(title, msg) {
		removeErrors();
		toastr.success(title, msg,{ timeOut: NOTIFICATION_TIMEOUT, closeButton: false, progressBar: true });
	}


    // --- UnivBase64 ---

	var lookup = [];
	var revLookup = [];
	var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array;

	function univBase64Init() {
	    var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
	    for (var i = 0, len = code.length; i < len; ++i) {
	        lookup[i] = code[i];
	        revLookup[code.charCodeAt(i)] = i;
	    }

	    revLookup['-'.charCodeAt(0)] = 62;
	    revLookup['_'.charCodeAt(0)] = 63;
	}

	univBase64Init();

	function toByteArray(b64) {
	    var i, j, l, tmp, placeHolders, arr;
	    var len = b64.length;

	    if (len % 4 > 0) {
	        throw new Error('Invalid string. Length must be a multiple of 4');
	    }

	    // the number of equal signs (place holders)
	    // if there are two placeholders, than the two characters before it
	    // represent one byte
	    // if there is only one, then the three characters before it represent 2 bytes
	    // this is just a cheap hack to not do indexOf twice
	    placeHolders = b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0;

	    // base64 is 4/3 + up to two characters of the original data
	    arr = new Arr(len * 3 / 4 - placeHolders);

	    // if there are placeholders, only get up to the last complete 4 chars
	    l = placeHolders > 0 ? len - 4 : len;

	    var L = 0;

	    for (i = 0, j = 0; i < l; i += 4, j += 3) {
	        tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)];
	        arr[L++] = (tmp >> 16) & 0xFF;
	        arr[L++] = (tmp >> 8) & 0xFF;
	        arr[L++] = tmp & 0xFF;
	    }

	    if (placeHolders === 2) {
	        tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4);
	        arr[L++] = tmp & 0xFF;
	    } else if (placeHolders === 1) {
	        tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2);
	        arr[L++] = (tmp >> 8) & 0xFF;
	        arr[L++] = tmp & 0xFF;
	    }

	    return arr;
	}

	function tripletToBase64(num) {
	    return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F];
	}

	function encodeChunk(uint8, start, end) {
	    var tmp;
	    var output = [];
	    for (var i = start; i < end; i += 3) {
	        tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]);
	        output.push(tripletToBase64(tmp));
	    }
	    return output.join('');
	}

	function fromByteArray(uint8) {
	    var tmp;
	    var len = uint8.length;
	    var extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
	    var output = '';
	    var parts = [];
	    var maxChunkLength = 16383; // must be multiple of 3

	    // go through the array every three bytes, we'll deal with trailing stuff later
	    for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
	        parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)));
	    }

	    // pad the end with zeros, but make sure to not forget the extra bytes
	    if (extraBytes === 1) {
	        tmp = uint8[len - 1];
	        output += lookup[tmp >> 2];
	        output += lookup[(tmp << 4) & 0x3F];
	        output += '==';
	    } else if (extraBytes === 2) {
	        tmp = (uint8[len - 2] << 8) + (uint8[len - 1]);
	        output += lookup[tmp >> 10];
	        output += lookup[(tmp >> 4) & 0x3F];
	        output += lookup[(tmp << 2) & 0x3F];
	        output += '=';
	    }

	    parts.push(output);

	    return parts.join('');
	}

    // --- UnivBase64 end ---


    return {
        clone: clone,
        filter: filter,
		submitLogin: submitLogin,
		getMenu: getMenu,
		getLocale: getLocale,
		getOrgProperties: getOrgProperties,
		initFormSubmit: initFormSubmit,
		getCaptions: getCaptions,
		getLoginCaptions:getLoginCaptions,
		getInfo: getInfo,
        objectByString: objectByString,
		getComboItems: getComboItems,
		initLogin: initLogin,
		submitForm: submitForm,
		colorLuminance: colorLuminance,
        setInitModel: setInitModel,
        getInitModel: getInitModel,
        popUp: popUp,
        exportFile: exportFile,
		downloadBinaryFile: downloadBinaryFile,
		getHtmlBlock: getHtmlBlock,
		showErrors: showErrors,
		removeErrors: removeErrors,
		showSuccess: showSuccess,
		Base64: Base64,
		UnivBase64: { toByteArray: toByteArray, fromByteArray: fromByteArray },
		downloadFile: downloadFile
	};
});
