// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { CoreSite } from '@classes/sites/site';
import { CorePath } from './path';
import { CoreText } from './text';

import { CorePlatform } from '@services/platform';
import { CoreConstants } from '../constants';
import { CoreMedia } from './media';
import { CoreLang, CoreLangFormat } from '@services/lang';
import { DomSanitizer } from '@singletons';
import { SafeUrl } from '@angular/platform-browser';

/**
 * Parts contained within a url.
 */
interface UrlParts {

    /**
     * Url protocol.
     */
    protocol?: string;

    /**
     * Url domain.
     */
    domain?: string;

    /**
     * Url port.
     */
    port?: string;

    /**
     * Url credentials: username and password (if any).
     */
    credentials?: string;

    /**
     * Url's username.
     */
    username?: string;

    /**
     * Url's password.
     */
    password?: string;

    /**
     * Url path.
     */
    path?: string;

    /**
     * Url query.
     */
    query?: string;

    /**
     * Url fragment.
     */
    fragment?: string;

}

export const enum CoreUrlPartNames {
    Protocol = 'protocol',
    WWWInDomain = 'www', // Will remove starting www from domain.
    Query = 'query',
    Fragment = 'fragment',
}

/**
 * Singleton with helper functions for urls.
 */
export class CoreUrl {

    // Avoid creating singleton instances.
    private constructor() {
        // Nothing to do.
    }

    /**
     * Given an address as a string, return a URL to open the address in maps.
     *
     * @param address The address.
     * @returns URL to view the address.
     */
    static buildAddressURL(address: string): SafeUrl {
        const parsedUrl = CoreUrl.parse(address);
        if (parsedUrl?.protocol) {
            // It's already a URL, don't convert it.
            return DomSanitizer.bypassSecurityTrustUrl(address);
        }

        return DomSanitizer.bypassSecurityTrustUrl((CorePlatform.isAndroid() ? 'geo:0,0?q=' : 'http://maps.google.com?q=') +
                encodeURIComponent(address));
    }

    /**
     * Parse parts of a url, using an implicit protocol if it is missing from the url.
     *
     * @param url Url.
     * @returns Url parts.
     */
    static parse(url: string): UrlParts | null {
        url = url.trim();
        // Parse url with regular expression taken from RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B.
        const match = url.match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/);

        if (!match) {
            return null;
        }

        const host = match[4] || '';

        // Get the credentials and the port from the host.
        const [domainAndPort, credentials]: string[] = host.split('@').reverse();
        const [domain, port]: string[] = domainAndPort.split(':');
        const [username, password]: string[] = credentials ? credentials.split(':') : [];

        // Prepare parts replacing empty strings with undefined.
        return {
            protocol: match[2] || undefined,
            domain: domain || undefined,
            port: port || undefined,
            credentials: credentials || undefined,
            username: username || undefined,
            password: password || undefined,
            path: match[5] || undefined,
            query: match[7] || undefined,
            fragment: match[9] || undefined,
        };
    }

    /**
     * Given some parts of a URL, returns the URL as a string.
     *
     * @param parts Parts.
     * @returns Assembled URL.
     */
    static assemble(parts: UrlParts): string {
        const protocol = parts.protocol;
        const credentials = parts.credentials ||
            (parts.password ? `${parts.username}:${parts.password}` : parts.username);

        return (protocol ? `${protocol}://` : '') +
            (credentials ? `${credentials}@` : '') +
            (parts.domain ?? '') +
            (parts.port ? `:${parts.port}` : '') +
            (parts.path ?? '') +
            (parts.query ? `?${parts.query}` : '') +
            (parts.fragment ? `#${parts.fragment}` : '');
    }

    /**
     * Guess the Moodle domain from a site url.
     *
     * @param url Site url.
     * @returns Guessed Moodle domain.
     */
    static guessMoodleDomain(url: string): string | null {
        // Add protocol if it was missing. Moodle can only be served through http or https, so this is a fair assumption to make.
        if (!url.match(/^https?:\/\//)) {
            url = `https://${url}`;
        }

        // Match using common suffixes.
        const knownSuffixes = [
            '/my/?',
            '/\\?redirect=0',
            '/index\\.php',
            '/course/view\\.php',
            '\\/login/index\\.php',
            '/mod/page/view\\.php',
        ];
        const match = url.match(new RegExp(`^https?://(.*?)(${knownSuffixes.join('|')})`));

        if (match) {
            return match[1];
        }

        // If nothing else worked, parse the domain.
        const urlParts = CoreUrl.parse(url);

        return urlParts?.domain ? urlParts.domain : null;
    }

    /**
     * Returns the pattern to check if the URL is a valid Moodle Url.
     *
     * @returns Desired RegExp.
     */
    static getValidMoodleUrlPattern(): RegExp {
        // Regular expression based on RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B.
        // Improved to not admit spaces.
        return new RegExp(/^(([^:/?# ]+):)?(\/\/([^/?# ]*))?([^?# ]*)(\?([^#]*))?(#(.*))?$/);
    }

    /**
     * Check if the given url is valid for the app to connect.
     *
     * @param url Url to check.
     * @returns True if valid, false otherwise.
     */
    static isValidMoodleUrl(url: string): boolean {
        const patt = CoreUrl.getValidMoodleUrlPattern();

        return patt.test(url.trim());
    }

    /**
     * Removes protocol from the url.
     *
     * @param url Site url.
     * @returns Url without protocol.
     * @deprecated since 4.5. Use CoreUrl.removeUrlParts(url, CoreUrlPartNames.Protocol) instead.
     */
    static removeProtocol(url: string): string {
        return CoreUrl.removeUrlParts(url, CoreUrlPartNames.Protocol);
    }

    /**
     * Check if two URLs have the same domain and path.
     *
     * @param urlA First URL.
     * @param urlB Second URL.
     * @returns Whether they have same domain and path.
     */
    static sameDomainAndPath(urlA: string, urlB: string): boolean {
        // Add protocol if missing, the parse function requires it.
        if (!urlA.match(/^[^/:.?]*:\/\//)) {
            urlA = `https://${urlA}`;
        }
        if (!urlB.match(/^[^/:.?]*:\/\//)) {
            urlB = `https://${urlB}`;
        }

        const partsA = CoreUrl.parse(urlA);
        const partsB = CoreUrl.parse(urlB);

        partsA && Object.entries(partsA).forEach(([part, value]) => partsA[part] = value?.toLowerCase());
        partsB && Object.entries(partsB).forEach(([part, value]) => partsB[part] = value?.toLowerCase());

        return partsA?.domain === partsB?.domain
            && CoreText.removeEndingSlash(partsA?.path) === CoreText.removeEndingSlash(partsB?.path);
    }

    /**
     * Get the anchor of a URL. If there's more than one they'll all be returned, separated by #.
     * E.g. myurl.com#foo=1#bar=2 will return #foo=1#bar=2.
     *
     * @param url URL.
     * @returns Anchor, undefined if no anchor.
     */
    static getUrlAnchor(url: string): string | undefined {
        const urlParts = CoreUrl.parse(url);

        return urlParts?.fragment ? `#${urlParts.fragment}` : undefined;
    }

    /**
     * Remove the anchor from a URL.
     *
     * @param url URL.
     * @returns URL without anchor if any.
     *
     * @deprecated since 4.5. Use CoreUrl.removeUrlParts(url, CoreUrlPartNames.Fragment) instead.
     */
    static removeUrlAnchor(url: string): string {
        return CoreUrl.removeUrlParts(url, CoreUrlPartNames.Fragment);
    }

    /**
     * Convert a URL to an absolute URL (if it isn't already).
     *
     * @param parentUrl The parent URL.
     * @param url The url to convert.
     * @returns Absolute URL.
     */
    static toAbsoluteURL(parentUrl: string, url: string): string {
        const parsedUrl = CoreUrl.parse(url);

        if (parsedUrl?.protocol) {
            return url; // Already absolute URL.
        }

        const parsedParentUrl = CoreUrl.parse(parentUrl);

        if (url.startsWith('//')) {
            // It only lacks the protocol, add it.
            return (parsedParentUrl?.protocol || 'https') + ':' + url;
        }

        // The URL should be added after the domain (if starts with /) or after the parent path.
        const treatedParentUrl = CoreUrl.assemble({
            protocol: parsedParentUrl?.protocol || 'https',
            domain: parsedParentUrl?.domain,
            port: parsedParentUrl?.port,
            credentials: parsedParentUrl?.credentials,
            path: url.startsWith('/') ? undefined : parsedParentUrl?.path,
        });

        return CorePath.concatenatePaths(treatedParentUrl, url);
    }

    /**
     * Convert a URL to a relative URL (if it isn't already).
     *
     * @param parentUrl The parent URL.
     * @param url The url to convert.
     * @returns Relative URL.
     */
    static toRelativeURL(parentUrl: string, url: string): string {
        parentUrl = CoreUrl.removeUrlParts(parentUrl, CoreUrlPartNames.Protocol);

        if (!url.includes(parentUrl)) {
            return url; // Already relative URL.
        }

        return CoreText.removeStartingSlash(CoreUrl.removeUrlParts(url, CoreUrlPartNames.Protocol).replace(parentUrl, ''));
    }

    /**
     * Returns if URL is a Vimeo video URL.
     *
     * @param url URL.
     * @returns Whether is a Vimeo video URL.
     */
    static isVimeoVideoUrl(url: string): boolean {
        return !!url.match(/https?:\/\/player\.vimeo\.com\/video\/[0-9]+/);
    }

    /**
     * Get the URL to use to play a Vimeo video if the URL supplied is a Vimeo video URL.
     * If it's a Vimeo video, the app will use the site's wsplayer script instead to make restricted videos work.
     *
     * @param url URL to treat.
     * @param site Site that contains the URL.
     * @returns URL, undefined if not a Vimeo video.
     */
    static getVimeoPlayerUrl(
        url: string,
        site: CoreSite,
    ): string | undefined {
        const matches = url.match(/https?:\/\/player\.vimeo\.com\/video\/([0-9]+)([?&]+h=([a-zA-Z0-9]*))?/);
        if (!matches || !matches[1]) {
            // Not a Vimeo video.
            return;
        }

        let newUrl = CorePath.concatenatePaths(site.getURL(), '/media/player/vimeo/wsplayer.php?video=') +
            matches[1] + '&token=' + site.getToken();

        let privacyHash: string | undefined | null = matches[3];
        if (!privacyHash) {
            // No privacy hash using the new format. Check the legacy format.
            const matches = url.match(/https?:\/\/player\.vimeo\.com\/video\/([0-9]+)(\/([a-zA-Z0-9]+))?/);
            privacyHash = matches && matches[3];
        }

        if (privacyHash) {
            newUrl += `&h=${privacyHash}`;
        }

        return newUrl;
    }

    /**
     * Add or remove 'www' from a URL. The url needs to have http or https protocol.
     *
     * @param url URL to modify.
     * @returns Modified URL.
     */
    static addOrRemoveWWW(url: string): string {
        if (url) {
            if (url.match(/http(s)?:\/\/www\./)) {
                // Already has www. Remove it.
                url = url.replace('www.', '');
            } else {
                url = url.replace('https://', 'https://www.');
                url = url.replace('http://', 'http://www.');
            }
        }

        return url;
    }

    /**
     * Add params to a URL.
     *
     * @param url URL to add the params to.
     * @param params Object with the params to add.
     * @param anchor Anchor text if needed.
     * @param boolToNumber Whether to convert bools to 1 or 0.
     * @returns URL with params.
     */
    static addParamsToUrl(url: string, params?: Record<string, unknown>, anchor?: string, boolToNumber?: boolean): string {
        // Remove any existing anchor to add the params before it.
        const urlAndAnchor = url.split('#');
        url = urlAndAnchor[0];

        let separator = url.indexOf('?') !== -1 ? '&' : '?';

        for (const key in params) {
            let value = params[key];

            if (boolToNumber && typeof value === 'boolean') {
                // Convert booleans to 1 or 0.
                value = value ? '1' : '0';
            }

            // Ignore objects and undefined.
            if (typeof value !== 'object' && value !== undefined) {
                url += separator + key + '=' + value;
                separator = '&';
            }
        }

        // Re-add the anchor if any.
        if (urlAndAnchor.length > 1) {
            // Remove the URL from the array.
            urlAndAnchor.shift();

            // Use a join in case there is more than one #.
            url += '#' + urlAndAnchor.join('#');
        }

        if (anchor) {
            url += '#' + anchor;
        }

        return url;
    }

    /**
     * Given a URL and a text, return an HTML link.
     *
     * @param url URL.
     * @param text Text of the link.
     * @returns Link.
     */
    static buildLink(url: string, text: string): string {
        return '<a href="' + url + '">' + text + '</a>';
    }

    /**
     * Check whether we can use tokenpluginfile.php endpoint for a certain URL.
     *
     * @param url URL to check.
     * @param siteUrl The URL of the site the URL belongs to.
     * @param accessKey User access key for tokenpluginfile.
     * @returns Whether tokenpluginfile.php can be used.
     */
    static canUseTokenPluginFile(url: string, siteUrl: string, accessKey?: string): boolean {
        // Do not use tokenpluginfile if site doesn't use slash params, the URL doesn't work.
        // Also, only use it for "core" pluginfile endpoints. Some plugins can implement their own endpoint (like customcert).
        return !CoreConstants.CONFIG.disableTokenFile && !!accessKey && !url.match(/[&?]file=/) && (
            url.indexOf(CorePath.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 ||
            url.indexOf(CorePath.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0) &&
            !CoreMedia.sourceUsesJavascriptPlayer({ src: url });
    }

    /**
     * Same as Javascript's decodeURI, but if an exception is thrown it will return the original URI.
     *
     * @param uri URI to decode.
     * @returns Decoded URI, or original URI if an exception is thrown.
     */
    static decodeURI(uri: string): string {
        try {
            return decodeURI(uri);
        } catch {
            // Error, use the original URI.
        }

        return uri;
    }

    /**
     * Same as Javascript's decodeURIComponent, but if an exception is thrown it will return the original URI.
     *
     * @param uri URI to decode.
     * @returns Decoded URI, or original URI if an exception is thrown.
     */
    static decodeURIComponent(uri: string): string {
        try {
            return decodeURIComponent(uri);
        } catch {
            // Error, use the original URI.
        }

        return uri;
    }

    /**
     * Extracts the parameters from a URL and stores them in an object.
     *
     * @param url URL to treat.
     * @returns Object with the params.
     */
    static extractUrlParams(url: string): CoreUrlParams {
        const regex = /[?&]+([^=&]+)=?([^&]*)?/gi;
        const subParamsPlaceholder = '@@@SUBPARAMS@@@';
        const params: CoreUrlParams = {};
        const urlAndHash = url.split('#');
        const questionMarkSplit = urlAndHash[0].split('?');
        let subParams: string;

        if (questionMarkSplit.length > 2) {
            // There is more than one question mark in the URL. This can happen if any of the params is a URL with params.
            // We only want to treat the first level of params, so we'll remove this second list of params and restore it later.
            questionMarkSplit.splice(0, 2);

            subParams = '?' + questionMarkSplit.join('?');
            urlAndHash[0] = urlAndHash[0].replace(subParams, subParamsPlaceholder);
        }

        urlAndHash[0].replace(regex, (match: string, key: string, value: string): string => {
            params[key] = value !== undefined ? CoreUrl.decodeURIComponent(value) : '';

            if (subParams) {
                params[key] = params[key].replace(subParamsPlaceholder, subParams);
            }

            return match;
        });

        if (urlAndHash.length > 1) {
            // Remove the URL from the array.
            urlAndHash.shift();

            // Add the hash as a param with a special name. Use a join in case there is more than one #.
            params.urlHash = urlAndHash.join('#');
        }

        return params;
    }

    /**
     * Generic function for adding the wstoken to Moodle urls and for pointing to the correct script.
     * For download remote files from Moodle we need to use the special /webservice/pluginfile passing
     * the ws token as a get parameter.
     *
     * @param url The url to be fixed.
     * @param token Token to use.
     * @param siteUrl The URL of the site the URL belongs to.
     * @param accessKey User access key for tokenpluginfile.
     * @returns Fixed URL.
     */
    static fixPluginfileURL(url: string, token: string, siteUrl: string, accessKey?: string): string {
        if (!url) {
            return '';
        }

        url = url.replace(/&amp;/g, '&');

        const canUseTokenPluginFile = accessKey && CoreUrl.canUseTokenPluginFile(url, siteUrl, accessKey);

        // First check if we need to fix this url or is already fixed.
        if (!canUseTokenPluginFile && url.indexOf('token=') != -1) {
            return url;
        }

        // Check if is a valid URL (contains the pluginfile endpoint) and belongs to the site.
        if (!CoreUrl.isPluginFileUrl(url) || url.indexOf(CoreText.addEndingSlash(siteUrl)) !== 0) {
            return url;
        }

        if (canUseTokenPluginFile) {
            // Use tokenpluginfile.php.
            url = url.replace(/(\/webservice)?\/pluginfile\.php/, '/tokenpluginfile.php/' + accessKey);
        } else {
            // Use pluginfile.php. Some webservices returns directly the correct download url, others not.
            if (url.indexOf(CorePath.concatenatePaths(siteUrl, 'pluginfile.php')) === 0) {
                url = url.replace('/pluginfile', '/webservice/pluginfile');
            }

            url = CoreUrl.addParamsToUrl(url, { token });
        }

        // Always send offline=1 (it's for external repositories).
        return CoreUrl.addParamsToUrl(url, { offline: '1', lang: CoreLang.getCurrentLanguageSync(CoreLangFormat.LMS) });
    }

    /**
     * Formats a URL, trim, lowercase, etc...
     *
     * @param url The url to be formatted.
     * @returns Fromatted url.
     */
    static formatURL(url: string): string {
        url = url.trim();

        // Check if the URL starts by http or https.
        if (! /^http(s)?:\/\/.*/i.test(url)) {
            // Test first allways https.
            url = 'https://' + url;
        }

        // http always in lowercase.
        url = url.replace(/^http/i, 'http');
        url = url.replace(/^https/i, 'https');

        // Replace last slash.
        url = url.replace(/\/$/, '');

        return url;
    }

    /**
     * Returns the Youtube Embed Video URL or undefined if not found.
     *
     * @param url URL
     * @returns Youtube Embed Video URL or undefined if not found.
     */
    static getYoutubeEmbedUrl(url?: string): string | void {
        if (!url) {
            return;
        }

        let videoId = '';
        const params: CoreUrlParams = {};

        url = CoreText.decodeHTML(url);

        // Get the video ID.
        let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/);

        if (match && match[2].length === 11) {
            videoId = match[2];
        }

        // No videoId, do not continue.
        if (!videoId) {
            return;
        }

        // Now get the playlist (if any).
        match = url.match(/[?&]list=([^#&?]+)/);

        if (match && match[1]) {
            params.list = match[1];
        }

        // Now get the start time (if any).
        match = url.match(/[?&]start=(\d+)/);

        if (match && match[1]) {
            params.start = parseInt(match[1], 10).toString();
        } else {
            // No start param, but it could have a time param.
            match = url.match(/[?&]t=(\d+h)?(\d+m)?(\d+s)?/);
            if (match) {
                const start = (match[1] ? parseInt(match[1], 10) * 3600 : 0) +
                    (match[2] ? parseInt(match[2], 10) * 60 : 0) +
                    (match[3] ? parseInt(match[3], 10) : 0);
                params.start = start.toString();
            }
        }

        return CoreUrl.addParamsToUrl('https://www.youtube.com/embed/' + videoId, params);
    }

    /**
     * Given a URL, returns what's after the last '/' without params.
     * Example:
     * http://mysite.com/a/course.html?id=1 -> course.html
     *
     * @param url URL to treat.
     * @returns Last file without params.
     */
    static getLastFileWithoutParams(url: string): string {
        const parsedUrl = CoreUrl.parse(url);
        if (!parsedUrl) {
            return '';
        }
        const path = parsedUrl.path ?? '';

        return path.split('/').pop() ?? '';
    }

    /**
     * Return the array of arguments of the pluginfile url.
     *
     * @param url URL to get the args.
     * @returns The args found, undefined if not a pluginfile.
     */
    static getPluginFileArgs(url: string): string[] | undefined {
        if (!CoreUrl.isPluginFileUrl(url)) {
            // Not pluginfile, return.
            return;
        }

        const relativePath = url.substring(url.indexOf('/pluginfile.php') + 16);
        const args = relativePath.split('/');

        if (args.length < 3) {
            // To be a plugin file it should have at least contextId, Component and Filearea.
            return;
        }

        return args;
    }

    /**
     * Get the protocol from a URL.
     * E.g. http://www.google.com returns 'http'.
     *
     * @param url URL to treat.
     * @returns Protocol, undefined if no protocol found.
     */
    static getUrlProtocol(url: string): string | void {
        return CoreUrl.parse(url)?.protocol;
    }

    /**
     * Gets a username from a URL like: user@mysite.com.
     *
     * @param url URL to treat.
     * @returns Username. Undefined if no username found.
     * @todo Use CoreUrl.parse. It cannot use it right now because it won't detect username on custom URL with double protocol.
     */
    static getUsernameFromUrl(url: string): string | undefined {
            if (url.indexOf('@') < 0) {
                return;
            }

            // Get URL without protocol.
            const withoutProtocol = url.replace(/^[^?@/]*:\/\//, '');
            const matches = withoutProtocol.match(/[^@]*/);

            // Make sure that @ is at the start of the URL, not in a param at the end.
            if (matches && matches.length && !matches[0].match(/[/|?]/)) {
                const credentials = matches[0];

                return credentials.split(':')[0];
            }
    }

    /**
     * Returns if a URL has any protocol (not a relative URL).
     *
     * @param url The url to test against the pattern.
     * @returns Whether the url is absolute.
     */
    static isAbsoluteURL(url: string): boolean {
        return /^[^:]{2,}:\/\//i.test(url) || /^(tel:|mailto:|geo:)/.test(url);
    }

    /**
     * Returns if a URL is downloadable: plugin file OR theme/image.php OR gravatar.
     *
     * @param url The URL to test.
     * @returns Whether the URL is downloadable.
     */
    static isDownloadableUrl(url: string): boolean {
        return CoreUrl.isPluginFileUrl(url) ||
            CoreUrl.isTokenPluginFileUrl(url) ||
            CoreUrl.isThemeImageUrl(url) ||
            CoreUrl.isGravatarUrl(url);
    }

    /**
     * Returns if a URL is a gravatar URL.
     *
     * @param url The URL to test.
     * @returns Whether the URL is a gravatar URL.
     */
    static isGravatarUrl(url: string): boolean {
        return url?.indexOf('gravatar.com/avatar') !== -1;
    }

    /**
     * Check if a URL uses http or https protocol.
     *
     * @param url The url to test.
     * @returns Whether the url uses http or https protocol.
     * @todo Use CoreUrl.parse
     */
    static isHttpURL(url: string): boolean {
        return /^https?:\/\/.+/i.test(url);
    }

    /**
     * Check whether an URL belongs to a local file.
     *
     * @param url URL to check.
     * @returns Whether the URL belongs to a local file.
     */
    static isLocalFileUrl(url: string): boolean {
        const urlParts = CoreUrl.parse(url);

        return CoreUrl.isLocalFileUrlScheme(urlParts?.protocol || '', urlParts?.domain || '');
    }

    /**
     * Check whether a URL scheme belongs to a local file.
     *
     * @param scheme Scheme to check.
     * @returns Whether the scheme belongs to a local file.
     */
    static isLocalFileUrlScheme(scheme: string, domain: string): boolean {
        if (!scheme) {
            return false;
        }
        scheme = scheme.toLowerCase();

        return scheme === 'cdvfile' ||
                scheme === 'file' ||
                scheme === 'filesystem' ||
                scheme === CoreConstants.CONFIG.ioswebviewscheme ||
                (CorePlatform.isMobile() && scheme === 'http' && domain === 'localhost'); // @todo Get served domain from ENV.
    }

    /**
     * Returns if a URL is a pluginfile URL.
     *
     * @param url The URL to test.
     * @returns Whether the URL is a pluginfile URL.
     */
    static isPluginFileUrl(url: string): boolean {
        return url.indexOf('/pluginfile.php') !== -1;
    }

    /**
     * Returns if a URL is a tokenpluginfile URL.
     *
     * @param url The URL to test.
     * @returns Whether the URL is a tokenpluginfile URL.
     */
    static isTokenPluginFileUrl(url: string): boolean {
        return url.indexOf('/tokenpluginfile.php') !== -1;
    }

    /**
     * Returns if a URL is a theme image URL.
     *
     * @param imageUrl The URL to test.
     * @param siteUrl The Site Url.
     * @returns Whether the URL is a theme image URL.
     */
    static isThemeImageUrl(imageUrl: string, siteUrl?: string): boolean {
        if (siteUrl) {
            return imageUrl.startsWith(`${siteUrl}/theme/image.php`);
        }

        return imageUrl?.indexOf('/theme/image.php') !== -1;
    }

    /**
     * Returns an specific param from an image URL.
     *
     * @param imageUrl Image Url
     * @param param Param to get from the URL.
     * @param siteUrl Site URL.
     * @returns Param from the URL.
     */
    static getThemeImageUrlParam(imageUrl: string, param: string, siteUrl?: string): string {
        if (!CoreUrl.isThemeImageUrl(imageUrl, siteUrl)) {
            // Cannot be guessed.
            return '';
        }

        const matches = imageUrl.match('/theme/image.php/(.*)');
        if (matches?.[1]) {
            // Slash arguments found.
            const slasharguments = matches[1].split('/');

            if (slasharguments.length < 4) {
                // Image not found, malformed URL.
                return '';
            }

            // Join from the third element to the end.
            const image = slasharguments.slice(3).join('/');
            switch (param) {
                case 'theme':
                    return slasharguments[0];
                case 'component':
                    return slasharguments[1];
                case 'rev':
                    return slasharguments[2];
                case 'image':
                    // Remove possible url params.
                    return CoreUrl.removeUrlParts(image, [CoreUrlPartNames.Query, CoreUrlPartNames.Fragment]);
                default:
                    return CoreUrl.extractUrlParams(image)[param] || '';
            }

        }

        // URL arguments found.
        const iconParams = CoreUrl.extractUrlParams(imageUrl);

        switch (param) {
            case 'theme':
                return iconParams[param] || 'standard';
            case 'component':
                return iconParams[param] || 'core';
            case 'rev':
                return iconParams[param] || '-1';
            case 'svg':
                return iconParams[param] || '1';
            case 'image':
            default:
                return iconParams[param] || '';
        }
    }

    /**
     * Returns the URL without the desired parts.
     *
     * @param url URL to treat.
     * @param parts Parts to remove.
     * @returns URL without the parts.
     */
    static removeUrlParts(url: string, parts: CoreUrlPartNames | CoreUrlPartNames[]): string {
        if (!url) {
            return url;
        }

        if (!Array.isArray(parts)) {
            parts = [parts];
        }

        parts.forEach((part) => {
            switch (part) {
                case CoreUrlPartNames.WWWInDomain:
                    // Remove www, no protocol.
                    url = url.replace(/^www./, '');
                    // Remove www, with protocol.
                    url = url.replace(/\/\/www./, '//');
                    break;
                case CoreUrlPartNames.Protocol:
                    // Remove the protocol from url
                    url = url.replace(/^.*?:\/\//, '');
                    break;
                case CoreUrlPartNames.Query:
                    url = url.match(/^[^?]+/)?.[0] || '';
                    break;
                case CoreUrlPartNames.Fragment:
                    url = url.split('#')[0];
                    break;
            }
        });

        return url;
    }

    /**
     * Modifies a pluginfile URL to use the default pluginfile script instead of the webservice one.
     *
     * @param url The url to be fixed.
     * @param siteUrl The URL of the site the URL belongs to.
     * @returns Modified URL.
     */
    static unfixPluginfileURL(url: string, siteUrl?: string): string {
        if (!url) {
            return '';
        }

        url = url.replace(/&amp;/g, '&');

        // It site URL is supplied, check if the URL belongs to the site.
        if (siteUrl && url.indexOf(CoreText.addEndingSlash(siteUrl)) !== 0) {
            return url;
        }

        // Check tokenpluginfile first.
        url = url.replace(/\/tokenpluginfile\.php\/[^/]+\//, '/pluginfile.php/');

        // Treat webservice/pluginfile case.
        url = url.replace(/\/webservice\/pluginfile\.php\//, '/pluginfile.php/');

        // Make sure the URL doesn't contain the token.
        url = url.replace(/([?&])token=[^&]*&?/, '$1');

        return url;
    }

}

export type CoreUrlParams = {[key: string]: string};
