coreFramework.provider('Auth', function () {
    var config = {
        timePaddingFactor: 0.9, // cut down expires to 90% of its value to give time for a new token request
        storageItemId: 'cwAuthData'
    };
    var authTimeout;
    var dataCallback = function (token) {};

    this.setConfig = function (params) {
        angular.extend(config, params);
    };

    this.$get = ['$q', '$interval', '$timeout', function ($q, $interval, $timeout) {
        function generateUuid() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            });
        }

        function queryStringToObject(string) {
            var queryString = string.replace(/[\?\#]+/, '');
            var queryStringArray = [];
            var queryStringObject = {};

            if (queryString.length) {
                queryStringArray = queryString.split('&');
                for (var i = 0, len = queryStringArray.length; i < len; i += 1) {
                    queryStringObject[queryStringArray[i].split('=')[0]] = queryStringArray[i].split('=')[1];
                }
            }

            return queryStringObject;
        }

        function requestAuth() {
            var deferred = $q.defer();
            if (!config.url || !config.clientId) {
                // console.error('Auth: API url or clientId missing');
                deferred.reject('Auth: API url or clientId missing');
                return deferred.promise;
            }
            
            var uuid = generateUuid();
            var iframeEl = document.createElement('iframe');

            function destroyFrame() {
                document.body.removeChild(iframeEl);
            }

            iframeEl.style.display = 'none';
            iframeEl.onload = function () {
                try {
                    if (iframeEl.contentWindow && iframeEl.contentWindow.location.hash) {
                        var response = queryStringToObject(iframeEl.contentWindow.location.hash);

                        if (response.access_token && (response.state === uuid)) {
                            // console.log('Auth: success');
                            response.obtained_at = Date.now();
                            deferred.resolve(response);
                        } else {
                            // console.log('Auth: API error');
                            deferred.reject('Auth: API error');
                        }
                    } else {
                        // console.log('Auth: request error');
                        deferred.reject('Auth: request error');
                    }
                } catch(e) {
                    console.error('Auth: Permission denied fetching access token for client id "' + config.clientId + '" (Invalid client url location. Must be: ' + config.url + ').');
                    deferred.reject('Auth: request error');
                }
                destroyFrame();
            };
            iframeEl.onerror = function (e) {
                // console.log('Auth: request error');
                deferred.reject('Auth: request error');
                destroyFrame();
            };
            iframeEl.setAttribute('src', config.url + '?client_id=' + config.clientId + '&response_type=token&state=' + uuid);

            deferred.notify('Auth: about to authorize');
            document.body.appendChild(iframeEl);

            return deferred.promise;
        }

        function startTimer(timeout) {
            if (typeof timeout === 'number' && timeout > 0) {
                $timeout.cancel(authTimeout);
                authTimeout = $timeout(function () {
                    console.log('Auth: renewing token');
                    init(true);
                }, timeout, false);
            } else {
                throw new Error('Auth: timeout must be a positive integer');
            }
        }

        function setTimer(timestamp, expires) {
            var elapsed = Math.floor(Date.now() - timestamp);
            expires = Math.floor(parseInt(expires, 10) * 1000 * config.timePaddingFactor);

            console.log('Auth: timeout in', Math.floor((expires - elapsed) / 1000 / 60), 'minutes');
            startTimer(expires - elapsed);
        }

        function isStale(timestamp, expires) {
            var elapsed = Math.floor(Date.now() - timestamp);
            expires = Math.floor(parseInt(expires, 10) * 1000 * config.timePaddingFactor);

            return elapsed > expires ? true : false;
        }

        function setData(data) {
            dataCallback(data.access_token);
            localStorage.setItem(config.storageItemId, JSON.stringify(data));
        }

        function getData() {
            var data = localStorage.getItem(config.storageItemId);

            return data ? JSON.parse(data) : false;
        }

        function removeData() {
            localStorage.removeItem(config.storageItemId);
        }

        function init(isForced) {
            var deferred = $q.defer();
            var data = getData();

            if (data && !isForced && !isStale(data.obtained_at, data.expires_in)) {
                console.log('Auth: data found in localStorage');
                setTimer(data.obtained_at, data.expires_in);
                deferred.resolve(data);
            } else {
                console.log('Auth: requesting new data');
                requestAuth().then(function (response) {
                    setData(response);
                    setTimer(response.obtained_at, response.expires_in);
                    deferred.resolve(response);
                }, function (error) {
                    deferred.reject(error);
                }, function (progress) {
                    deferred.notify(progress);
                });
            }

            return deferred.promise;
        }

        return {
            authorize: init,
            revoke: removeData,
            getData: function () {
                return getData();
            },
            insertDataCallback: function (callback) {
                dataCallback = callback;
            },
            getToken: function () {
                var data = getData();
                return data.access_token || false;
            },
            getConfig: function () {
                return config;
            }
        };
    }];
});
