/// <reference path="../references.js" />

/*jshint -W106 */
rwthapp.tokenManager = (function () {
    var clientId = rwthapp.options.getOauthClientId();
    var scopes = rwthapp.options.getOauthScope();
    var expirationFactor = 0.45;
    var pollingFactor = 1.1;
    var userLoggedInDefault = true;
    var loginPersonalizedDefault = false;

    var pollingPromise = null;
    var accessTokenRequestPromise = null;

    // Initialise persistent storage
    var storage = new rwthapp.Storage('tokenManager');
    storage.addAttribute('userIsLoggedIn');
    storage.addAttribute('userLoginIsPersonalized');
    storage.addAttribute('userId');
    storage.addAttribute('streamIds');

    var oauthPersistentStorage = new rwthapp.Storage('oauth.persistent');
    oauthPersistentStorage.addAttribute('refreshToken');
    oauthPersistentStorage.addAttribute('accessToken');
    oauthPersistentStorage.addAttribute('accessTokenExpirationTime');
    oauthPersistentStorage.addAttribute('authorizedScopes'); // TODO track changes in scopes

    var oauthAuthorizationStorage = new rwthapp.Storage('oauth.authorization');
    oauthAuthorizationStorage.addAttribute('deviceCode');
    oauthAuthorizationStorage.addAttribute('pollingInterval');
    oauthAuthorizationStorage.addAttribute('authorizationPending');
    oauthAuthorizationStorage.addAttribute('authorizationUrl');

    // Will be initialized from localStorage in initialize method
    var userLoggedIn = userLoggedInDefault;
    var loginPersonalized = loginPersonalizedDefault;

    var getUserId = function () {
        var userId = storage.getUserId();
        if (!userId) {
            userId = rwthapp.util.generateRandomId(30);
            storage.setUserId(userId);
        }
        return userId;
    };

    var getStreamId = function () {
        return rwthapp.util.generateRandomId(30);
    }

    var getStreamIds = function () {
        return storage.getStreamIds();
    }

    var setStreamIds = function (data) {
        storage.setStreamIds(data);
    }

    var getAnonymousAccessToken = function () {
        return new Promise(function (resolve, reject) {
            if (oauthPersistentStorage.getAccessTokenExpirationTime() > Date.now()) {
                resolve(oauthPersistentStorage.getAccessToken());
            } else {
                rwthapp.connection.oauth.getAccessTokenAnonymous(clientId).then(function (data) {
                    if (data.status === "ok") {
                        oauthPersistentStorage.setAccessToken(data.access_token);
                        oauthPersistentStorage.setAccessTokenExpirationTime(Date.now() + data.expires_in * 1000 * expirationFactor);
                        resolve(data.access_token);
                    } else {
                        reject(new rwthapp.error.Error("state invalid.", 20, rwthapp.localization.getLocalizedString("errors.oauth.general"), false));
                    }
                }).catch(function () {
                    reject(new rwthapp.error.Error("request failed.", 20, rwthapp.localization.getLocalizedString("errors.oauth.general"), false));
                });
            }
        });
    };
        
    var authorize = function () {
        // Check if authorization is already in progress
        if (pollingPromise !== null) {
            // If authorization URL already set, open in browser (if user closed browser tab)
            if (oauthAuthorizationStorage.getAuthorizationUrl() !== null) {
                window.open(oauthAuthorizationStorage.getAuthorizationUrl(), "_system", "");
            }

            return pollingPromise;
        } else {
            return new Promise(function (resolve, reject) {
                
                rwthapp.connection.oauth.startAuthentication(clientId, scopes).then(function (data) {
                    if (data.status === "ok") {
                        oauthAuthorizationStorage.setAuthorizationPending(true);
                        oauthAuthorizationStorage.setDeviceCode(data.device_code);
                        oauthAuthorizationStorage.setPollingInterval(data.interval * 1000 * pollingFactor);

                        var url = data.verification_url + "?q=verify&d=" + data.user_code;
                        oauthAuthorizationStorage.setAuthorizationUrl(url);

                        window.open(url, "_system", "");

                        startPolling().then(function (token) {
                            resolve(token);
                        }).catch(function (error) {
                            // Cancel authorization
                            oauthAuthorizationStorage.clear();

                            reject(error);
                        });
                    } else {
                        // Cancel authorization
                        oauthAuthorizationStorage.clear();

                        reject(new rwthapp.error.Error("authentication start failed.", 23, rwthapp.localization.getLocalizedString("errors.oauth.authenticationStartFailed"), true));
                    }

                }).catch(function (error) {
                    // Cancel authorization
                    oauthAuthorizationStorage.clear();

                    reject(error);
                });
            });
        }
    };

    var startPolling = function () {
        pollingPromise = new Promise(function (resolve, reject) {
            var interval = oauthAuthorizationStorage.getPollingInterval(5500); // Default 5.5s
            var poll = function () {
                rwthapp.connection.oauth.checkAuthorization(clientId, oauthAuthorizationStorage.getDeviceCode(), device.platform, device.model).then(function (data) {
                    if (data.status === "ok") {
                        // Authorization successful
                        oauthAuthorizationStorage.clear();

                        oauthPersistentStorage.setAccessToken(data.access_token);
                        oauthPersistentStorage.setAccessTokenExpirationTime(Date.now() + data.expires_in * 1000 * expirationFactor);
                        oauthPersistentStorage.setRefreshToken(data.refresh_token);

                        resolve(data.access_token);
                    } else if (data.status === "error: authorization pending.") {
                        setTimeout(poll, interval);
                    } else {
                        // TODO Timeout, cancel authorization
                        oauthAuthorizationStorage.clear();

                        reject(new rwthapp.error.Error("authentication failed.", 24, rwthapp.localization.getLocalizedString("errors.oauth.authenticationFailed"), true));
                    }
                }).catch(function () {
                    // Try again?
                    setTimeout(poll, interval);
                });
            };

            rwthapp.navigator.gotoPage("login", { state: "polling" });
            poll();
        }).then(function () {
            pollingPromise = null;
        }).catch(function (error) {
            pollingPromise = null;
            throw error;
        });

        return pollingPromise;
    };

    var getPersonalizedAccessToken = function () {
        // TODO check state (refresh token available etc)
        return new Promise(function (resolve, reject) {
            if (oauthPersistentStorage.getAccessTokenExpirationTime() > Date.now()) {
                resolve(oauthPersistentStorage.getAccessToken());
            } else {
                rwthapp.connection.oauth.getAccessTokenPersonalized(clientId, oauthPersistentStorage.getRefreshToken()).then(function (data) {
                    if (data.status === "ok") {
                        oauthPersistentStorage.setAccessToken(data.access_token);
                        oauthPersistentStorage.setAccessTokenExpirationTime(Date.now() + data.expires_in * 1000 * expirationFactor);
                        resolve(data.access_token);
                    } else if (data.status === "error: refresh token invalid.") {
                        reject(new rwthapp.error.Error("refresh token invalid.", 22, rwthapp.localization.getLocalizedString("errors.oauth.refreshTokenInvalid"), false));
                    } else {
                        reject(new rwthapp.error.Error("state invalid.", 20, rwthapp.localization.getLocalizedString("errors.oauth.general"), false));
                    }
                }).catch(function () {
                    reject(new rwthapp.error.Error("request failed.", 20, rwthapp.localization.getLocalizedString("errors.oauth.general"), false));
                });
            }
        });
    };

    var invalidateRefreshToken = function () {
        return new Promise(function (resolve, reject) {
            if (userLoggedIn && loginPersonalized) {
                rwthapp.connection.oauth.invalidateRefreshToken(clientId, oauthPersistentStorage.getRefreshToken()).then(function () {
                    oauthPersistentStorage.clear();

                    resolve();
                }).catch(function () {
                    // Ignore problems
                    oauthPersistentStorage.clear();

                    resolve();
                });
            } else {
                // Nothing to be done
                resolve();
            }
        });
    };

    var getToken = function () {
        // Only create new request if there is currently no other active request
        if (accessTokenRequestPromise === null) {
            accessTokenRequestPromise = new Promise(function (resolve, reject) {
                if (userLoggedIn) {
                    if (loginPersonalized) {
                        getPersonalizedAccessToken().then(function (token) {
                            resolve(token);
                        }).catch(function (error) {
                            if (error.code === 22) {
                                // TODO Refresh token expired, show notification
                                setLoginStatusToDefault();
                            }
                            reject(error);
                        });
                    } else {
                        getAnonymousAccessToken().then(function (token) {
                            resolve(token);
                        }).catch(function (error) {
                            reject(error);
                        });
                    }
                } else {
                    // TODO Redirect to login template?
                    reject(new rwthapp.error.Error("Not logged in.", 21, rwthapp.localization.getLocalizedString("errors.oauth.notLoggedIn"), false));
                }
            }).then(function (token) {
                // Reset promise on success...
                accessTokenRequestPromise = null;
                return token;
            }).catch(function (error) {
                // ... and on failure as well
                accessTokenRequestPromise = null;
                throw error;
            });  
        }

        return accessTokenRequestPromise;
    };

    var persistLoginInformation = function () {
        storage.setUserIsLoggedIn(userLoggedIn);
        storage.setUserLoginIsPersonalized(loginPersonalized);
    };

    var isUserLoggedIn = function () {
        return userLoggedIn;
    };

    var isLoginPersonalized = function () {
        return loginPersonalized;
    };

    var login = function () {
        // TODO invalidate token if still valid
        return authorize().then(function (token) {
            userLoggedIn = true;
            loginPersonalized = true;
            persistLoginInformation();

            return token;
        });
    };

    var useAnonymous = function () {
        userLoggedIn = true;
        loginPersonalized = false;
        persistLoginInformation();
    };

    var setLoginStatusToDefault = function () {
        userLoggedIn = userLoggedInDefault;
        loginPersonalized = loginPersonalizedDefault;
        persistLoginInformation();
    };

    var logout = function () {
        return invalidateRefreshToken().then(function () {
            setLoginStatusToDefault();
        });
    };

    // Initialise
    var initialize = function () {
        userLoggedIn = storage.getUserIsLoggedIn(userLoggedInDefault);
        loginPersonalized = storage.getUserLoginIsPersonalized(loginPersonalizedDefault);

        var me = this;
        return new Promise(function (resolve, reject) {
            if (oauthAuthorizationStorage.getAuthorizationPending(false) === true) {
                startPolling().then(function () {
                    userLoggedIn = true;
                    loginPersonalized = true;
                    persistLoginInformation();

                    rwthapp.navigator.gotoPage("home");
                }).catch(function (error) {
                    rwthapp.navigator.gotoPage("home");
                });
                // Continue with initialization
                resolve();
            } else if (userLoggedIn) {
                if (loginPersonalized) {
                    getPersonalizedAccessToken().then(function () {
                        resolve();
                    }).catch(function (error) {
                        if (error.code === 22) {
                            // Refresh token invalid
                            reject(error);
                        } else {
                            // Validity of token could not be checked, continue anyways
                            resolve();
                        }
                    });
                } else {
                    resolve();
                }
            } else {

                //resolve("login");
                me.useAnonymous();
                resolve("home");
            }
        }).catch(function () {
            // Refresh token invalid or authorization timed out
            // TODO message to user
            setLoginStatusToDefault();
            return "home";
        }).then(function (targetPage) {
            return targetPage;
        });
    };

    return {
        initialize: initialize,
        isUserLoggedIn: isUserLoggedIn,
        isLoginPersonalized: isLoginPersonalized,
        login: login,
        useAnonymous: useAnonymous,
        logout: logout,
        getToken: getToken,
        getUserId: getUserId,
        getStreamId: getStreamId,
        getStreamIds: getStreamIds,
        setStreamIds: setStreamIds
    };

})();
/*jshint +W106 */