/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */

import { IUri } from "../IUri";
import { Constants, SSOTypes, ServerHashParamKeys } from "./Constants";
import { ServerRequestParameters } from "../ServerRequestParameters";
import { ScopeSet } from "../ScopeSet";
import { StringUtils } from "./StringUtils";
import { CryptoUtils } from "./CryptoUtils";

/**
 * @hidden
 */
export class UrlUtils {

    /**
     * generates the URL with QueryString Parameters
     * @param scopes
     */
    static createNavigateUrl(serverRequestParams: ServerRequestParameters): string {
        const str = this.createNavigationUrlString(serverRequestParams);
        let authEndpoint: string = serverRequestParams.authorityInstance.AuthorizationEndpoint;
        // if the endpoint already has queryparams, lets add to it, otherwise add the first one
        if (authEndpoint.indexOf("?") < 0) {
            authEndpoint += "?";
        } else {
            authEndpoint += "&";
        }

        const requestUrl: string = `${authEndpoint}${str.join("&")}`;
        return requestUrl;
    }

    /**
     * Generate the array of all QueryStringParams to be sent to the server
     * @param scopes
     */
    static createNavigationUrlString(serverRequestParams: ServerRequestParameters): Array<string> {
        const scopes = ScopeSet.appendDefaultScopes(serverRequestParams.scopes);

        const str: Array<string> = [];
        str.push("response_type=" + serverRequestParams.responseType);
        str.push("scope=" + encodeURIComponent(ScopeSet.parseScope(scopes)));
        str.push("client_id=" + encodeURIComponent(serverRequestParams.clientId));
        str.push("redirect_uri=" + encodeURIComponent(serverRequestParams.redirectUri));

        str.push("state=" + encodeURIComponent(serverRequestParams.state));
        str.push("nonce=" + encodeURIComponent(serverRequestParams.nonce));

        str.push("client_info=1");
        str.push(`x-client-SKU=${serverRequestParams.xClientSku}`);
        str.push(`x-client-Ver=${serverRequestParams.xClientVer}`);
        if (serverRequestParams.promptValue) {
            str.push("prompt=" + encodeURIComponent(serverRequestParams.promptValue));
        }

        if (serverRequestParams.claimsValue) {
            str.push("claims=" + encodeURIComponent(serverRequestParams.claimsValue));
        }

        if (serverRequestParams.queryParameters) {
            str.push(serverRequestParams.queryParameters);
        }

        if (serverRequestParams.extraQueryParameters) {
            str.push(serverRequestParams.extraQueryParameters);
        }

        str.push("client-request-id=" + encodeURIComponent(serverRequestParams.correlationId));
        return str;
    }

    /**
     * Returns current window URL as redirect uri
     */
    static getCurrentUrl(): string {
        return window.location.href.split("?")[0].split("#")[0];
    }

    /**
     * Returns given URL with query string removed
     */
    static removeHashFromUrl(url: string): string {
        return url.split("#")[0];
    }

    /**
     * Given a url like https://a:b/common/d?e=f#g, and a tenantId, returns https://a:b/tenantId/d
     * @param href The url
     * @param tenantId The tenant id to replace
     */
    static replaceTenantPath(url: string, tenantId: string): string {
        const lowerCaseUrl = url.toLowerCase();
        const urlObject = this.GetUrlComponents(lowerCaseUrl);
        const pathArray = urlObject.PathSegments;
        if (tenantId && (pathArray.length !== 0 && (pathArray[0] === Constants.common || pathArray[0] === SSOTypes.ORGANIZATIONS))) {
            pathArray[0] = tenantId;
        }
        return this.constructAuthorityUriFromObject(urlObject, pathArray);
    }

    static constructAuthorityUriFromObject(urlObject: IUri, pathArray: string[]) {
        return this.CanonicalizeUri(urlObject.Protocol + "//" + urlObject.HostNameAndPort + "/" + pathArray.join("/"));
    }
    
    /**
     * Checks if an authority is common (ex. https://a:b/common/)
     * @param url The url
     * @returns true if authority is common and false otherwise 
     */
    static isCommonAuthority(url: string): boolean {
        const authority =  this.CanonicalizeUri(url);
        const pathArray = this.GetUrlComponents(authority).PathSegments;
        return (pathArray.length !== 0 && pathArray[0] === Constants.common);
    }

    /**
     * Checks if an authority is for organizations (ex. https://a:b/organizations/)
     * @param url The url
     * @returns true if authority is for  and false otherwise 
     */
    static isOrganizationsAuthority(url: string): boolean {
        const authority =  this.CanonicalizeUri(url);
        const pathArray = this.GetUrlComponents(authority).PathSegments;
        return (pathArray.length !== 0 && pathArray[0] === SSOTypes.ORGANIZATIONS);
    }

    /**
     * Parses out the components from a url string.
     * @returns An object with the various components. Please cache this value insted of calling this multiple times on the same url.
     */
    static GetUrlComponents(url: string): IUri {
        if (!url) {
            throw "Url required";
        }

        // https://gist.github.com/curtisz/11139b2cfcaef4a261e0
        const regEx = RegExp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?");

        const match = url.match(regEx);

        if (!match || match.length < 6) {
            throw "Valid url required";
        }

        const urlComponents = <IUri>{
            Protocol: match[1],
            HostNameAndPort: match[4],
            AbsolutePath: match[5]
        };

        let pathSegments = urlComponents.AbsolutePath.split("/");
        pathSegments = pathSegments.filter((val) => val && val.length > 0); // remove empty elements
        urlComponents.PathSegments = pathSegments;

        if (match[6]){
            urlComponents.Search = match[6];
        }
        if (match[8]){
            urlComponents.Hash = match[8];
        }
        
        return urlComponents;
    }

    /**
     * Given a url or path, append a trailing slash if one doesnt exist
     *
     * @param url
     */
    static CanonicalizeUri(url: string): string {
        if (url) {
            url = url.toLowerCase();
        }

        if (url && !UrlUtils.endsWith(url, "/")) {
            url += "/";
        }

        return url;
    }

    /**
     * Checks to see if the url ends with the suffix
     * Required because we are compiling for es5 instead of es6
     * @param url
     * @param str
     */
    // TODO: Rename this, not clear what it is supposed to do
    static endsWith(url: string, suffix: string): boolean {
        if (!url || !suffix) {
            return false;
        }

        return url.indexOf(suffix, url.length - suffix.length) !== -1;
    }

    /**
     * Utils function to remove the login_hint and domain_hint from the i/p extraQueryParameters
     * @param url
     * @param name
     */
    static urlRemoveQueryStringParameter(url: string, name: string): string {
        if (StringUtils.isEmpty(url)) {
            return url;
        }

        let regex = new RegExp("(\\&" + name + "=)[^\&]+");
        url = url.replace(regex, "");
        // name=value&
        regex = new RegExp("(" + name + "=)[^\&]+&");
        url = url.replace(regex, "");
        // name=value
        regex = new RegExp("(" + name + "=)[^\&]+");
        url = url.replace(regex, "");
        return url;
    }

    /**
     * @hidden
     * @ignore
     *
     * Returns the anchor part(#) of the URL
     */
    static getHashFromUrl(urlStringOrFragment: string): string {
        const hashIndex1 = urlStringOrFragment.indexOf("#");
        const hashIndex2 = urlStringOrFragment.indexOf("#/");
        if (hashIndex2 > -1) {
            return urlStringOrFragment.substring(hashIndex2 + 2);
        } else if (hashIndex1 > -1) {
            return urlStringOrFragment.substring(hashIndex1 + 1);
        }
        return urlStringOrFragment;
    }

    /**
     * @hidden
     * Check if the url contains a hash with known properties
     * @ignore
     */
    static urlContainsHash(urlString: string): boolean {
        const parameters = UrlUtils.deserializeHash(urlString);
        return (
            parameters.hasOwnProperty(ServerHashParamKeys.ERROR_DESCRIPTION) ||
            parameters.hasOwnProperty(ServerHashParamKeys.ERROR) ||
            parameters.hasOwnProperty(ServerHashParamKeys.ACCESS_TOKEN) ||
            parameters.hasOwnProperty(ServerHashParamKeys.ID_TOKEN)
        );
    }

    /**
     * @hidden
     * Returns deserialized portion of URL hash
     * @ignore
     */
    static deserializeHash(urlFragment: string) {
        const hash = UrlUtils.getHashFromUrl(urlFragment);
        return CryptoUtils.deserialize(hash);
    }

    /**
     * @ignore
     * @param {string} URI
     * @returns {string} host from the URI
     *
     * extract URI from the host
     */
    static getHostFromUri(uri: string): string {
        // remove http:// or https:// from uri
        let extractedUri = String(uri).replace(/^(https?:)\/\//, "");
        extractedUri = extractedUri.split("/")[0];
        return extractedUri;
    }
}
