'use strict';
/**
 * @author Marco Lehmann <marco.lehmann@kiwigrid.com>
 * @copyright Kiwigrid GmbH 2014-2015
 * @module keta.services.AccessToken
 * @description AccessToken Factory
 */
angular.module('keta.services.AccessToken',
	[
		'keta.services.AppContext'
	])
/**
 * @class ketaAccessTokenConstants
 * @propertyOf keta.services.AccessToken
 * @description Access Token Constants
 */
	.constant('ketaAccessTokenConstants', {
		// session types
		SESSION_TYPE: {
			NORMAL: 'normal',
			IMPERSONATED: 'impersonated'
		},
		// json web token regular expression
		JWT_REGULAR_EXPRESSION: new RegExp('^(\\w+)\\.(\\w+)\\.([\x00-\x7F]+)$') // eslint-disable-line no-control-regex
	})
	/**
	 * @class ketaAccessToken
	 * @propertyOf keta.services.AccessToken
	 * @description Access Token Factory
	 */
	.factory('ketaAccessToken', function AccessTokenFactory(
		$http, $q,
		ketaAppContext, ketaAccessTokenConstants
	) {
		/**
		 * @private
		 * @description True, if the current authentication provider is a Keycloak.
		 */
		var isKeycloak = ketaAppContext.get('common.keycloak');
		/**
		 * @private
		 * @description keycloak-js representation
		 */
		var keycloak = isKeycloak ? window.auth.authz : {};
		/**
		 * @private
		 * @description Internal representation of access token which was injected by web server into context.js.
		 */
		var accessToken = isKeycloak
			? keycloak.token
			: ketaAppContext.get('oauth.accessToken');
		/**
		 * @private
		 * @description Decoded access token.
		 */
		var decodedAccessToken = null;
		/**
		 * @private
		 * @description Refresh promise.
		 */
		var refreshPromise = null;
		/**
		 * @private
		 * @description Flag if refresh call is currently in progress.
		 */
		var refreshInProgress = false;
		/*eslint-disable no-magic-numbers */
		var Base64 = {
			keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
			decode: function(input) {
				var output = '';
				var chr1, chr2, chr3;
				var enc1, enc2, enc3, enc4;
				var i = 0;
				input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
				while (i < input.length) {
					enc1 = this.keyStr.indexOf(input.charAt(i++));
					enc2 = this.keyStr.indexOf(input.charAt(i++));
					enc3 = this.keyStr.indexOf(input.charAt(i++));
					enc4 = this.keyStr.indexOf(input.charAt(i++));
					chr1 = enc1 << 2 | enc2 >> 4;
					chr2 = (enc2 & 15) << 4 | enc3 >> 2;
					chr3 = (enc3 & 3) << 6 | enc4;
					output += String.fromCharCode(chr1);
					if (enc3 !== 64) {
						output += String.fromCharCode(chr2);
					}
					if (enc4 !== 64) {
						output += String.fromCharCode(chr3);
					}
				}
				return Base64.utf8Decode(output);
			},
			encode: function(input) {
				var output = '';
				var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
				var i = 0;
				input = Base64.utf8Encode(input);
				while (i < input.length) {
					chr1 = input.charCodeAt(i++);
					chr2 = input.charCodeAt(i++);
					chr3 = input.charCodeAt(i++);
					enc1 = chr1 >> 2;
					enc2 = (chr1 & 3) << 4 | chr2 >> 4;
					enc3 = (chr2 & 15) << 2 | chr3 >> 6;
					enc4 = chr3 & 63;
					if (isNaN(chr2)) {
						enc3 = enc4 = 64;
					} else if (isNaN(chr3)) {
						enc4 = 64;
					}
					output = output +
						this.keyStr.charAt(enc1) +
						this.keyStr.charAt(enc2) +
						this.keyStr.charAt(enc3) +
						this.keyStr.charAt(enc4);
				}
				return output;
			},
			utf8Decode: function(utfText) {
				var string = '';
				var i = 0;
				var c = 0, c2 = 0, c3 = 0;
				while (i < utfText.length) {
					c = utfText.charCodeAt(i);
					if (c < 128) {
						string += String.fromCharCode(c);
						i++;
					} else if (c > 191 && c < 224) {
						c2 = utfText.charCodeAt(i + 1);
						string += String.fromCharCode((c & 31) << 6 | c2 & 63);
						i += 2;
					} else {
						c2 = utfText.charCodeAt(i + 1);
						c3 = utfText.charCodeAt(i + 2);
						string += String.fromCharCode((c & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
						i += 3;
					}
				}
				return string;
			},
			utf8Encode: function(string) {
				string = string.replace(/\r\n/g, '\n');
				var utfText = '';
				for (var n = 0; n < string.length; n++) {
					var c = string.charCodeAt(n);
					if (c < 128) {
						utfText += String.fromCharCode(c);
					} else if (c > 127 && c < 2048) {
						utfText += String.fromCharCode(c >> 6 | 192);
						utfText += String.fromCharCode(c & 63 | 128);
					} else {
						utfText += String.fromCharCode(c >> 12 | 224);
						utfText += String.fromCharCode(c >> 6 & 63 | 128);
						utfText += String.fromCharCode(c & 63 | 128);
					}
				}
				return utfText;
			}
		};
		/*eslint-enable no-magic-numbers */
		/**
		 * @private
		 * @param {string} property property to extract from token
		 * @returns {*} property value
		 */
		var getProperty = function(property) {
			return decodedAccessToken !== null &&
			angular.isDefined(decodedAccessToken[property]) ?
				decodedAccessToken[property] : null;
		};
		var api = {
			/**
			 * @name get
			 * @function
			 * @description Get access token.
			 * @param {boolean} decoded Return in decoded or raw format.
			 * @returns {string} access token
			 * @example
			 * angular.module('exampleApp', ['keta.services.AccessToken'])
			 *     .controller('ExampleController', function(ketaAccessToken) {
			 *         var accessToken = ketaAccessToken.get();
			 *     });
			 */
			get: function(decoded) {
				if (accessToken !== null && decodedAccessToken === null) {
					decodedAccessToken = api.decode(accessToken);
				}
				return angular.isDefined(decoded) && decoded === true ? decodedAccessToken : accessToken;
			},
			/**
			 * @name set
			 * @function
			 * @description Set access token.
			 * @param {string} token new access token
			 * @returns {void} returns nothing
			 * @example
			 * angular.module('exampleApp', ['keta.services.AccessToken'])
			 *     .controller('ExampleController', function(ketaAccessToken) {
			 *         ketaAccessToken.set('new-token');
			 *     });
			 */
			set: function(token) {
				if (angular.isDefined(token) && angular.isString(token)) {
					accessToken = token;
					decodedAccessToken = api.decode(token);
				}
			},
			/**
			 * @name decode
			 * @function
			 * @description Decode access token.
			 * @param {string} token access token to decode (jwt or legacy)
			 * @returns {Object} access token properties
			 * @example
			 * angular.module('exampleApp', ['keta.services.AccessToken'])
			 *     .controller('ExampleController', function(ketaAccessToken) {
			 *         var accessTokenProps = ketaAccessToken.decode(AccessToken.get());
			 *     });
			 */
			decode: function(token) {
				var props = {};
				try {
					var decoded;
					// check if token is a JSON Web Token
					if (ketaAccessTokenConstants.JWT_REGULAR_EXPRESSION.test(token)) {
						// take payload from token
						var payload = token.split('.')[1];
						decoded = Base64.decode(payload);
					} else {
						decoded = Base64.decode(token);
						// strip away everything after }.
						if (decoded.indexOf('}.') !== -1) {
							decoded = decoded.substr(0, decoded.indexOf('}.') + 1);
						}
					}
					props = JSON.parse(decoded);
				} catch (e) {
					return null;
				}
				return props;
			},
			/**
			 * @name encode
			 * @function
			 * @description Encode access token properties.
			 * @param {Object} props access token properties to encode
			 * @returns {string} access token
			 * @example
			 * angular.module('exampleApp', ['keta.services.AccessToken'])
			 *     .controller('ExampleController', function(ketaAccessToken) {
			 *         var accessTokenProps = ketaAccessToken.decode(ketaAccessToken.get());
			 *         accessTokenProps.loaded = true;
			 *         var accessToken = ketaAccessToken.encode(accessTokenProps);
			 *     });
			 */
			encode: function(props) {
				return Base64.encode(JSON.stringify(props));
			},
			/**
			 * @name refresh
			 * @function
			 * @description Refresh access token by requesting backend.
			 * @returns {promise} Promise which is resolved when query is returned
			 * @example
			 * angular.module('exampleApp', [])
			 *     .controller('ExampleController', function(ketaAccessToken) {
			 *         ketaAccessToken.refresh().then(
			 *             function(response) {
			 *                 if (angular.isDefined(response.data.accessToken)) {
			 *                     ketaAccessToken.set(response.data.accessToken);
			 *                 }
			 *             },
			 *             function(message) {
			 *                 console.error(message);
			 *             }
			 *         );
			 *     });
			 */
			refresh: function() {
				if (refreshPromise === null || !refreshInProgress) {
					refreshPromise = $q.defer();
					refreshInProgress = true;
					var refreshUrl = ketaAppContext.get('oauth.refreshTokenPath') || '/refreshAccessToken';
					var promise = isKeycloak
						? keycloak.updateToken()
						: $http({method: 'GET', url: refreshUrl});
					promise.then(
						function(response) {
							refreshPromise.resolve(response);
							refreshInProgress = false;
						},
						function() {
							refreshPromise.reject('Could not refresh access token');
							refreshInProgress = false;
						}
					);
				}
				return refreshPromise.promise;
			},
			/**
			 * @name hasPermission
			 * @function
			 * @description Checks if current user has a certain permission.
			 * @param {string} permission permission to check
			 * @returns {boolean} result
			 */
			hasPermission: function(permission) {
				var has = false;
				var decoded = api.get(true);
				if (decoded !== null &&
					angular.isArray(decoded.scope)) {
					has = decoded.scope.indexOf(permission) !== -1;
				}
				return has;
			},
			/**
			 * @name isType
			 * @function
			 * @description Checks if session is of a certain type.
			 * @param {string} type session type (use ketaAccessTokenConstants.SESSION_TYPE)
			 * @returns {boolean} result
			 */
			isType: function(type) {
				var decoded = api.get(true);
				return decoded !== null &&
					angular.isDefined(decoded.session) &&
					angular.isDefined(decoded.session.type) &&
					decoded.session.type === type;
			},
			/**
			 * @name getBackUrl
			 * @function
			 * @description Returns back URL for an impersonated session.
			 * @returns {string} back URL
			 */
			getBackUrl: function() {
				var backUrl = null;
				if (api.isType(ketaAccessTokenConstants.SESSION_TYPE.IMPERSONATED)) {
					var decoded = api.get(true);
					if (decoded !== null &&
						angular.isDefined(decoded.session) &&
						angular.isDefined(decoded.session.backUrl)) {
						backUrl = decoded.session.backUrl;
					}
				}
				return backUrl;
			},
			/**
			 * @name getUserId
			 * @function
			 * @description Get user id from token.
			 * @returns {string} user id
			 */
			getUserId: function() {
				return getProperty('user_id');
			},
			/**
			 * @name getChannel
			 * @function
			 * @description Get channel from token.
			 * @returns {string} channel
			 */
			getChannel: function() {
				return getProperty('channel');
			}
		};
		return api;
	});