window.matrixLogin = {
    endpoint: location.origin + "/_matrix/client/r0/login",
    serverAcceptsPassword: false,
    serverAcceptsSso: false,
};

// Titles get updated through the process to give users feedback.
const TITLE_PRE_AUTH = "Log in with one of the following methods";
const TITLE_POST_AUTH = "Logging in...";

// The cookie used to store the original query parameters when using SSO.
const COOKIE_KEY = "synapse_login_fallback_qs";

/*
 * Submit a login request.
 *
 * type: The login type as a string (e.g. "m.login.foo").
 * data: An object of data specific to the login type.
 * extra: (Optional) An object to search for extra information to send with the
 *     login request, e.g. device_id.
 * callback: (Optional) Function to call on successful login.
 */
function submitLogin(type, data, extra, callback) {
    console.log("Logging in with " + type);
    setTitle(TITLE_POST_AUTH);

    // Add the login type.
    data.type = type;

    // Add the device information, if it was provided.
    if (extra.device_id) {
        data.device_id = extra.device_id;
    }
    if (extra.initial_device_display_name) {
        data.initial_device_display_name = extra.initial_device_display_name;
    }

    $.post(matrixLogin.endpoint, JSON.stringify(data), function(response) {
        if (callback) {
            callback();
        }
        matrixLogin.onLogin(response);
    }).fail(errorFunc);
}

/*
 * Display an error to the user and show the login form again.
 */
function errorFunc(err) {
    // We want to show the error to the user rather than redirecting immediately to the
    // SSO portal (if SSO is the only login option), so we inhibit the redirect.
    showLogin(true);

    if (err.responseJSON && err.responseJSON.error) {
        setFeedbackString(err.responseJSON.error + " (" + err.responseJSON.errcode + ")");
    }
    else {
        setFeedbackString("Request failed: " + err.status);
    }
}

/*
 * Display an error to the user.
 */
function setFeedbackString(text) {
    $("#feedback").text(text);
}

/*
 * (Maybe) Show the login forms.
 *
 * This actually does a few unrelated functions:
 *
 * * Configures the SSO redirect URL to come back to this page.
 * * Configures and shows the SSO form, if the server supports SSO.
 * * Otherwise, shows the password form.
 */
function showLogin(inhibitRedirect) {
    setTitle(TITLE_PRE_AUTH);

    // If inhibitRedirect is false, and SSO is the only supported login method,
    // we can redirect straight to the SSO page.
    if (matrixLogin.serverAcceptsSso) {
        // Set the redirect to come back to this page, a login token will get
        // added as a query parameter and handled after the redirect.
        $("#sso_redirect_url").val(window.location.origin + window.location.pathname);

        // Before submitting SSO, set the current query parameters into a cookie
        // for retrieval later.
        var qs = parseQsFromUrl();
        setCookie(COOKIE_KEY, JSON.stringify(qs));

        // If password is not supported and redirects are allowed, then submit
        // the form (redirecting to the SSO provider).
        if (!inhibitRedirect && !matrixLogin.serverAcceptsPassword) {
            $("#sso_form").submit();
            return;
        }

        // Otherwise, show the SSO form
        $("#sso_flow").show();
    }

    if (matrixLogin.serverAcceptsPassword) {
        $("#password_flow").show();
    }

    // If neither password or SSO are supported, show an error to the user.
    if (!matrixLogin.serverAcceptsPassword && !matrixLogin.serverAcceptsSso) {
        $("#no_login_types").show();
    }

    $("#loading").hide();
}

/*
 * Hides the forms and shows a loading throbber.
 */
function showSpinner() {
    $("#password_flow").hide();
    $("#sso_flow").hide();
    $("#no_login_types").hide();
    $("#loading").show();
}

/*
 * Helper to show the page's main title.
 */
function setTitle(title) {
    $("#title").text(title);
}

/*
 * Query the login endpoint for the homeserver's supported flows.
 *
 * This populates matrixLogin.serverAccepts* variables.
 */
function fetchLoginFlows(cb) {
    $.get(matrixLogin.endpoint, function(response) {
        for (var i = 0; i < response.flows.length; i++) {
            var flow = response.flows[i];
            if ("m.login.sso" === flow.type) {
                matrixLogin.serverAcceptsSso = true;
                console.log("Server accepts SSO");
            }
            if ("m.login.password" === flow.type) {
                matrixLogin.serverAcceptsPassword = true;
                console.log("Server accepts password");
            }
        }

        cb();
    }).fail(errorFunc);
}

/*
 * Called on load to fetch login flows and attempt SSO login (if a token is available).
 */
matrixLogin.onLoad = function() {
    fetchLoginFlows(function() {
        // (Maybe) attempt logging in via SSO if a token is available.
        if (!tryTokenLogin()) {
            showLogin(false);
        }
    });
};

/*
 * Submit simple user & password login.
 */
matrixLogin.passwordLogin = function() {
    var user = $("#user_id").val();
    var pwd = $("#password").val();

    setFeedbackString("");

    showSpinner();
    submitLogin(
        "m.login.password",
        {user: user, password: pwd},
        parseQsFromUrl());
};

/*
 * The onLogin function gets called after a succesful login.
 *
 * It is expected that implementations override this to be notified when the
 * login is complete. The response to the login call is provided as the single
 * parameter.
 */
matrixLogin.onLogin = function(response) {
    // clobber this function
    console.warn("onLogin - This function should be replaced to proceed.");
};

/*
 * Process the query parameters from the current URL into an object.
 */
function parseQsFromUrl() {
    var pos = window.location.href.indexOf("?");
    if (pos == -1) {
        return {};
    }
    var query = window.location.href.substr(pos + 1);

    var result = {};
    query.split("&").forEach(function(part) {
        var item = part.split("=");
        var key = item[0];
        var val = item[1];

        if (val) {
            val = decodeURIComponent(val);
        }
        result[key] = val;
    });
    return result;
}

/*
 * Process the cookies and return an object.
 */
function parseCookies() {
    var allCookies = document.cookie;
    var result = {};
    allCookies.split(";").forEach(function(part) {
        var item = part.split("=");
        // Cookies might have arbitrary whitespace between them.
        var key = item[0].trim();
        // You can end up with a broken cookie that doesn't have an equals sign
        // in it. Set to an empty value.
        var val = (item[1] || "").trim();
        // Values might be URI encoded.
        if (val) {
            val = decodeURIComponent(val);
        }
        result[key] = val;
    });
    return result;
}

/*
 * Set a cookie that is valid for 1 hour.
 */
function setCookie(key, value) {
    // The maximum age is set in seconds.
    var maxAge = 60 * 60;
    // Set the cookie, this defaults to the current domain and path.
    document.cookie = key + "=" + encodeURIComponent(value) + ";max-age=" + maxAge + ";sameSite=lax";
}

/*
 * Removes a cookie by key.
 */
function deleteCookie(key) {
    // Delete a cookie by setting the expiration to 0. (Note that the value
    // doesn't matter.)
    document.cookie = key + "=deleted;expires=0";
}

/*
 * Submits the login token if one is found in the query parameters. Returns a
 * boolean of whether the login token was found or not.
 */
function tryTokenLogin() {
    // Check if the login token is in the query parameters.
    var qs = parseQsFromUrl();

    var loginToken = qs.loginToken;
    if (!loginToken) {
        return false;
    }

    // Retrieve the original query parameters (from before the SSO redirect).
    // They are stored as JSON in a cookie.
    var cookies = parseCookies();
    var originalQueryParams = JSON.parse(cookies[COOKIE_KEY] || "{}")

    // If the login is successful, delete the cookie.
    function callback() {
        deleteCookie(COOKIE_KEY);
    }

    submitLogin(
        "m.login.token",
        {token: loginToken},
        originalQueryParams,
        callback);

    return true;
}
