/********************************************************************
 * @author:      Kaven
 * @email:       kaven@wuwenkai.com
 * @website:     http://kavenblog.com
 * @file:        [Kaven-Basic] /src/libs/KavenBasic.ts
 * @create:      2018-08-30 16:08:33.658
 * @modify:      2019-08-03 21:25:23.365
 * @version:     1.1.22
 * @times:       121
 * @lines:       1594
 * @copyright:   Copyright © 2018 Kaven. All Rights Reserved.
 * @description: [description]
 * @license:
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 ********************************************************************/
import moment from "moment";
import { DateTimeFormat } from "./Enums";
import { KavenCRC } from "./hashing_algorithms/KavenCRC";
import { KavenMD5 } from "./hashing_algorithms/KavenMD5";
import { KavenSHA } from "./hashing_algorithms/KavenSHA";
import { KavenSHA3 } from "./hashing_algorithms/KavenSHA3";
import { MemoryCache } from "./MemoryCache";
import { MIME } from "./MIME";
import { StringPosition } from "./StringPosition";
import { Strings } from "./Strings";
export class KavenBasic {
    constructor() {
        this.memoryCacheCollection = [];
        this.memoryCollection = {};
    }
    get SHA3() {
        const name = Strings.Names.SHA3Instance;
        let sha3 = this.GetValue(name, undefined);
        if (sha3 === undefined) {
            sha3 = new KavenSHA3();
            this.SetValue(name, sha3);
        }
        return sha3;
    }
    //#region MemoryCache
    /**
     *
     * @param name
     * @param value
     * @param absoluteExpiration
     * @version 1.0.0
     * @since 1.0.5
     */
    SetCacheWithDate(name, value, absoluteExpiration) {
        let item = this.memoryCacheCollection.find(p => p.name === name);
        if (item === undefined) {
            item = new MemoryCache(name, value, absoluteExpiration);
        }
        else {
            item.value = value;
            item.absoluteExpiration = absoluteExpiration;
        }
        console.info(`Set Cache: ${name}`);
        this.memoryCacheCollection.push(item);
    }
    /**
     *
     * @param name
     * @param value
     * @param millisecond
     * @version 1.0.0
     * @since 1.0.5
     */
    SetCacheWithMilliseconds(name, value, millisecond) {
        this.SetCacheWithDate(name, value, moment()
            .add(millisecond, "milliseconds")
            .toDate());
    }
    /**
     *
     * @param name
     * @param value
     * @param minute
     * @version 1.0.0
     * @since 1.0.5
     */
    SetCacheWithMinute(name, value, minute) {
        const milliseconds = moment
            .duration({ minutes: minute })
            .asMilliseconds();
        this.SetCacheWithMilliseconds(name, value, milliseconds);
    }
    /**
     *
     * @param name
     * @param value
     * @param hour
     * @version 1.0.0
     * @since 1.0.5
     */
    SetCacheWithHour(name, value, hour) {
        const milliseconds = moment.duration({ hours: hour }).asMilliseconds();
        this.SetCacheWithMilliseconds(name, value, milliseconds);
    }
    /**
     *
     * @param name
     * @since 1.0.5
     * @version 2018-11-02
     */
    GetCache(name) {
        const index = this.memoryCacheCollection.findIndex(p => p.name === name);
        if (index === -1) {
            return undefined;
        }
        const item = this.memoryCacheCollection[index];
        if (item.IsExpired()) {
            this.memoryCacheCollection.Remove(index);
            return undefined;
        }
        return item.value;
    }
    /**
     *
     * @param name
     * @param removeExpired
     * @since 1.0.5
     * @version 2018-11-02
     */
    RemoveCache(name, removeExpired = true) {
        if (removeExpired) {
            const arr = [];
            for (let i = 0; i < this.memoryCacheCollection.length; i++) {
                const item = this.memoryCacheCollection[i];
                if (item.name === name || item.IsExpired()) {
                    arr.push(i);
                }
            }
            arr.forEach(p => this.memoryCacheCollection.Remove(p));
        }
        else {
            const index = this.memoryCacheCollection.findIndex(p => p.name === name);
            if (index !== -1) {
                this.memoryCacheCollection.Remove(index);
            }
        }
    }
    //#endregion
    /**
     *
     * @param dt
     * @param format
     * @version 1.0.0
     * @since 1.0.5
     */
    FormatDate(dt, format) {
        dt = dt || new Date();
        return dt.Format(format);
    }
    /**
     * @version 1.0.0
     * @since 1.0.5
     */
    GetFileNameByDateTime() {
        return this.FormatDate(undefined, DateTimeFormat.ForFileName);
    }
    /**
     * Module wrapper of @substack's `caller.js`
     * @original: https://github.com/substack/node-resolve/blob/master/lib/caller.js
     * @blessings: https://twitter.com/eriktoth/statuses/413719312273125377
     * @see https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
     * @version 1.0.0
     * @since 1.0.5
     */
    GetStackInfo(depth, type = "getFileName") {
        let pst;
        let stack;
        let file;
        let frame;
        pst = Error.prepareStackTrace;
        Error.prepareStackTrace = (_, s) => {
            Error.prepareStackTrace = pst;
            return s;
        };
        stack = new Error().stack;
        if (stack) {
            const temp = 2;
            depth =
                !depth || isNaN(depth)
                    ? 1
                    : depth > stack.length - temp
                        ? stack.length - temp
                        : depth;
            stack = stack.slice(depth + 1);
        }
        if (stack) {
            do {
                // @ts-ignore
                frame = stack.shift();
                file = frame && frame[type](); // .getFileName();
            } while (stack.length && file === "module.js");
        }
        return file;
    }
    /**
     * Returns a random number between min (inclusive) and max (exclusive)
     * @param min
     * @param max
     * @version 1.0.0
     * @since 1.0.5
     */
    GetRandomArbitrary(min, max) {
        return Math.random() * (max - min) + min;
    }
    /**
     * Returns a random integer between min (inclusive) and max (inclusive)
     * Using Math.round() will give you a non-uniform distribution!
     * @param min
     * @param max
     * @version 1.0.0
     * @since 1.0.5
     */
    GetRandomInt(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    /**
     *
     * @param length
     * @version 1.0.0
     * @since 1.0.5
     */
    GenerateNumberString(length) {
        let result = Strings.Empty;
        const max = 9;
        for (let i = 0; i < length; i++) {
            if (result === Strings.Empty) {
                result = this.GetRandomInt(0, max).toString();
            }
            else {
                result += this.GetRandomInt(0, max).toString();
            }
        }
        return result;
    }
    /**
     * just for backward-compatible with earlier versions, recommend use {@link GenerateYearMonthIdAsync} instead
     * @param randomLength
     * @param validFunc
     * @param ignoreIDs
     * @param candidatesGUID
     * @version 1.0.0
     * @since 1.0.1
     * @example
     */
    GenerateYearMonthId(randomLength = 4, validFunc, ignoreIDs, candidatesGUID) {
        let id = Strings.Empty;
        const ym = this.FormatDate(undefined, DateTimeFormat.YearMonth);
        //#region Implementation 1
        if (typeof validFunc === "function") {
            while (true) {
                // tslint:disable-next-line:no-shadowed-variable
                const randomNumberStr = this.GenerateNumberString(randomLength);
                id = ym + randomNumberStr;
                if (validFunc(id)) {
                    return id;
                }
            }
        }
        //#endregion
        //#region Implementation 2
        if (ignoreIDs && ignoreIDs.IsNotEmpty()) {
            let temp = "1";
            for (let i = 0; i < randomLength; i++) {
                temp += "0";
            }
            const max = parseInt(temp, 10) - 1;
            const cacheName = `GenerateYearMonthId_${randomLength}_candidates`;
            let candidates = this.GetCache(cacheName);
            if (candidates === undefined) {
                candidates = Array.apply(undefined, new Array(max)).map((_, index) => index.toString().padStart(randomLength, "0")); // .map(Function.call, Number);
                this.SetCacheWithMinute(cacheName, candidates, 1);
            }
            //#region Old
            // const candidates: string[] = [];
            // for (let i = 0; i <= max; i++) {
            //     const tempID = i.toString().padStart(randomLength, "0");
            //     if (ignoreIDs.includes(tempID)) {
            //         continue;
            //     }
            //     candidates.push(tempID);
            // }
            //#endregion
            if (candidates && candidatesGUID) {
                let callCandidates = this.GetCache(candidatesGUID);
                if (callCandidates === undefined) {
                    callCandidates = candidates.filter(p => !ignoreIDs.includes(p));
                    // tslint:disable-next-line:no-magic-numbers
                    this.SetCacheWithMinute(candidatesGUID, callCandidates, 5);
                }
                const idIndex = this.GetRandomInt(0, callCandidates.length - 1);
                id = ym + callCandidates[idIndex];
                callCandidates.Remove(idIndex);
            }
            return id;
        }
        //#endregion
        const randomNumberStr = this.GenerateNumberString(randomLength);
        id = ym + randomNumberStr;
        return id;
    }
    /**
     *
     * @param randomLength
     * @param validFunc
     * @param ignoreIDs
     * @param candidatesGUID
     * @version 1.0.0
     * @since 1.0.5
     */
    async GenerateYearMonthIdAsync(randomLength = 4, validFunc, ignoreIDs, candidatesGUID) {
        let id = Strings.Empty;
        const ym = this.FormatDate(undefined, DateTimeFormat.YearMonth);
        //#region Implementation 1
        if (typeof validFunc === "function") {
            while (true) {
                // tslint:disable-next-line:no-shadowed-variable
                const randomNumberStr = this.GenerateNumberString(randomLength);
                id = ym + randomNumberStr;
                if (await validFunc(id)) {
                    return id;
                }
            }
        }
        //#endregion
        //#region Implementation 2
        if (ignoreIDs && ignoreIDs.IsNotEmpty()) {
            let temp = "1";
            for (let i = 0; i < randomLength; i++) {
                temp += "0";
            }
            const max = parseInt(temp, 10) - 1;
            const cacheName = `GenerateYearMonthId_${randomLength}_candidates`;
            let candidates = this.GetCache(cacheName);
            if (candidates === undefined) {
                candidates = Array.apply(undefined, new Array(max)).map((_, index) => index.toString().padStart(randomLength, "0")); // .map(Function.call, Number);
                this.SetCacheWithMinute(cacheName, candidates, 1);
            }
            //#region Old
            // const candidates: string[] = [];
            // for (let i = 0; i <= max; i++) {
            //     const tempID = i.toString().padStart(randomLength, "0");
            //     if (ignoreIDs.includes(tempID)) {
            //         continue;
            //     }
            //     candidates.push(tempID);
            // }
            //#endregion
            if (candidatesGUID && candidates) {
                let callCandidates = this.GetCache(candidatesGUID);
                if (callCandidates === undefined) {
                    callCandidates = candidates.filter(p => !ignoreIDs.includes(p));
                    // tslint:disable-next-line:no-magic-numbers
                    this.SetCacheWithMinute(candidatesGUID, callCandidates, 5);
                }
                const idIndex = this.GetRandomInt(0, callCandidates.length - 1);
                id = ym + callCandidates[idIndex];
                callCandidates.Remove(idIndex);
            }
            return id;
        }
        //#endregion
        const randomNumberStr = this.GenerateNumberString(randomLength);
        id = ym + randomNumberStr;
        return id;
    }
    /**
     * @version 1.0.0
     * @since 1.0.5
     */
    GenerateGuid() {
        // cSpell:ignore yxxx
        return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
            // tslint:disable-next-line:one-variable-per-declaration no-bitwise no-magic-numbers
            const r = (Math.random() * 16) | 0, 
            // tslint:disable-next-line:one-variable-per-declaration no-bitwise no-magic-numbers
            v = c === "x" ? r : (r & 0x3) | 0x8;
            // tslint:disable-next-line:no-magic-numbers
            return v.toString(16);
        });
    }
    /**
     *
     * @param length
     * @param validCharacters
     * @version 1.0.0
     * @since 1.0.5
     */
    GenerateRandomString(length, validCharacters) {
        let str = Strings.Empty;
        // tslint:disable-next-line:no-magic-numbers
        if (validCharacters === undefined || validCharacters.length < 2) {
            validCharacters =
                Strings.LowercaseLetters +
                    Strings.UppercaseLetters +
                    Strings.Numbers;
        }
        for (let i = 0; i < length; i++) {
            const char = validCharacters[this.GetRandomInt(0, validCharacters.length - 1)];
            if (str === Strings.Empty) {
                str = char;
            }
            else {
                str += char;
            }
        }
        return str;
    }
    /**
     *
     * @param length
     * @param numbers
     * @param lowercaseLetters
     * @param uppercaseLetters
     * @param specialCharacters
     * @version 1.0.0
     * @since 1.0.5
     */
    GeneratePassword(length, numbers = true, lowercaseLetters = true, uppercaseLetters = true, specialCharacters = true) {
        // tslint:disable-next-line:no-magic-numbers
        if (length < 10) {
            console.warn(`Your Password is Too Damn Short: ${length}`);
        }
        let chars = Strings.Empty;
        if (numbers) {
            chars += Strings.Numbers;
        }
        if (lowercaseLetters) {
            chars += Strings.LowercaseLetters;
        }
        if (uppercaseLetters) {
            chars += Strings.UppercaseLetters;
        }
        if (specialCharacters) {
            chars += Strings.PasswordSpecialCharacters;
        }
        if (chars.IsNotEmpty()) {
            return this.GenerateRandomString(length, chars);
        }
        else {
            throw new Error(`Invalid parameters: ${arguments}`);
        }
    }
    /**
     *
     * @param func
     * @param times
     * @version 1.0.0
     * @since 1.0.5
     */
    RunTest(func, times) {
        while (times-- > 0) {
            func();
        }
    }
    /**
     * @version 1.0.0
     * @since 1.0.5
     */
    RandomBoolean() {
        // tslint:disable-next-line:no-magic-numbers
        return Math.random() >= 0.5;
    }
    /**
     * @version 1.0.0
     * @since 1.0.5
     * @example C:/a/b/c  return C:/a/b
     */
    GetBaseDir(pathStr) {
        if (!pathStr) {
            return Strings.Empty;
        }
        let index = pathStr.lastIndexOf(Strings.Slash);
        if (index !== -1) {
            return pathStr.substring(0, index + 1);
        }
        else {
            index = pathStr.lastIndexOf(Strings.BackSlash);
            if (index !== -1) {
                return pathStr.substring(0, index + 1);
            }
        }
        return pathStr;
    }
    /**
     *
     * @param fileName
     * @version 1.0.0
     * @since 1.0.5
     * @example "c:/a/b.xyz" => "xyz"
     */
    GetFileExtension(fileName) {
        if (fileName && fileName.lastIndexOf(".") !== -1) {
            return fileName.split(".").pop() || Strings.Empty;
        }
        else {
            return Strings.Empty;
        }
    }
    /**
     *
     * @param fileName
     * @since 1.1.7
     * @version 2018-08-31
     * @example "c:/a/b.xyz" => "b"
     */
    GetFileNameWithoutExtension(fileName) {
        return this.GetFileName(fileName)
            .TrimEnd(this.GetFileExtension(fileName))
            .TrimEnd(".");
    }
    /**
     *
     * @param fileName
     * @version 2018-08-31
     * @since 1.0.5
     * @example
     * "c:/a/b.xyz" => "b.xyz"
     * "b.xyz" => "b.xyz"
     */
    GetFileName(fileName) {
        const base = this.GetBaseDir(fileName);
        if (base === fileName) {
            return fileName;
        }
        return fileName.replace(base, Strings.Empty);
    }
    /**
     *
     * @param fileName
     * @param extension
     * @version 2018-08-31
     * @since 1.0.5
     */
    ChangeFileExtension(fileName, extension) {
        let ext = this.GetFileExtension(fileName);
        let root = fileName.TrimEnd(ext); // fileName.substring(0, fileName.length - ext.length);
        ext = extension.TrimStart(".");
        root = root.TrimEnd(".");
        return `${root}.${ext}`;
    }
    //#region Memory Values
    /**
     *
     * @param name
     * @param value
     * @version 1.0.0
     * @since 1.0.5
     */
    SetValue(name, value) {
        this.memoryCollection[name] = value;
    }
    /**
     *
     * @param name
     * @param defaultValueIfNotExist
     * @version 1.0.0
     * @since 1.0.5
     */
    GetValue(name, defaultValueIfNotExist) {
        const val = this.memoryCollection[name];
        if (val === undefined) {
            return defaultValueIfNotExist;
        }
        return val;
    }
    /**
     *
     * @param name
     * @version 1.0.0
     * @since 1.0.5
     */
    RemoveValue(name) {
        this.memoryCollection[name] = undefined;
    }
    //#endregion
    /**
     *
     * @param obj
     * @version 1.0.0
     * @since 1.0.5
     */
    RemoveEmpty(obj) {
        Object.keys(obj).forEach(k => !obj[k] && obj[k] !== undefined && delete obj[k]);
        return obj;
    }
    /**
     *
     * @param obj
     * @version 1.0.0
     * @since 1.0.5
     */
    SortByKey(obj) {
        const newObj = {};
        Object.keys(obj)
            .sort()
            .forEach(key => {
            newObj[key] = obj[key];
        });
        return newObj;
    }
    /**
     *
     * @param length
     * @param start
     * @param step
     * @version 1.0.0
     * @since 1.0.5
     */
    CreateArray(length, start, step) {
        const arr = [];
        for (let i = 0; i < length; i++) {
            if (step !== undefined) {
                arr.push(start + step * i);
            }
            else {
                arr.push(start);
            }
        }
        return arr;
    }
    /**
     *
     *
     * @export
     * @param {string} str
     * @param {string} strStart
     * @param {string} strEnd
     * @returns
     * @version 2018-09-01
     * @since 1.0.6
     */
    GetStringBetween(str, strStart, strEnd, max = -1) {
        return this.GetStringPositionBetween(str, strStart, strEnd, max).map(p => str.substring(p.Start, p.End));
    }
    /**
     * @param {string} str
     * @param {string} strStart
     * @param {string} strEnd
     * @returns
     * @version 2018-09-02
     * @since 1.1.7
     */
    GetStringPositionBetween(str, strStart, strEnd, max = -1, startIndex = 0) {
        const list = [];
        if (str &&
            strStart &&
            strEnd &&
            str.IsNotEmpty() &&
            strStart.IsNotEmpty() &&
            strEnd.IsNotEmpty()) {
            let current = startIndex;
            while (true) {
                const index = str.indexOf(strStart, current);
                if (index !== -1) {
                    const endIndex = str.indexOf(strEnd, index + strStart.length + 1);
                    if (endIndex !== -1) {
                        const pos = new StringPosition(index + strStart.length, endIndex);
                        pos.RelatedString = str.GetSubstring(pos);
                        list.push(
                        // str.substring(index + strStart.length, endIndex)
                        pos);
                        if (max > 0 && list.length >= max) {
                            break;
                        }
                        current = endIndex + strEnd.length + 1;
                    }
                    else {
                        break;
                    }
                }
                else {
                    break;
                }
            }
        }
        return list;
    }
    /**
     *
     * @param replaceMethod
     * @param str
     * @param strStart
     * @param strEnd
     * @since 1.1.7
     * @version 2018-09-01
     */
    ReplaceAllStringBetween(replaceMethod, str, strStart, strEnd) {
        let current = 0;
        while (true) {
            const pos = this.GetStringPositionBetween(str, strStart, strEnd, 1, current);
            if (pos && pos.IsNotEmpty()) {
                const p = pos[0];
                if (p.RelatedString) {
                    str = str.ReplaceAtPos(p, replaceMethod(p.RelatedString));
                }
                if (p.End === undefined) {
                    throw new Error();
                }
                current = p.End + 1;
            }
            else {
                break;
            }
        }
        return str;
    }
    /**
     *
     *
     * @export
     * @param {string} str
     * @returns
     * @version 2019-03-21
     * @since 1.0.8
     */
    SplitStringByNewline(str) {
        if (str && str.IsNotEmpty()) {
            // return str.split(/\r?\n/);
            return str.split(Strings.BREAK_LINE_REGEXP);
        }
        return undefined;
    }
    /**
     *
     * @param pathStr
     * @version 1.0.0
     * @since 1.0.9
     */
    RemoveInvalidCharactersForPath(pathStr) {
        if (pathStr.IsNotEmpty()) {
            return pathStr.replace(/[|&;$%@"<>()+,/]/g, "");
        }
        return pathStr;
    }
    /**
     *
     *
     * @export
     * @param {string} version
     * @returns
     * @version 1.0.0
     * @since 1.0.12
     */
    GetNextVersion(version) {
        if (!version) {
            return undefined;
        }
        let temp = Number(version);
        if (!isNaN(temp)) {
            return temp + 1;
        }
        if (version.OnlyContains(Strings.Numbers, Strings.Dot)) {
            const items = version.split(Strings.Dot);
            const last = items.Last();
            if (last) {
                temp = Number(last);
                if (!isNaN(temp)) {
                    version = version.TrimEnd(last) + (temp + 1).toString();
                    return version;
                }
            }
        }
        return undefined;
    }
    /**
     *
     *
     * @export
     * @param {string} camelCaseStr
     * @version 1.0.0
     * @since 1.1.1
     * @see {@link https://stackoverflow.com/questions/7225407/convert-camelcasetext-to-sentence-case-text}
     */
    SplitCamelCaseString(camelCaseStr) {
        // return camelCaseStr.replace(/([a-z])([A-Z])/g, ' $1 $2');
        return camelCaseStr
            .replace(/([A-Z]+)/g, " $1")
            .replace(/([A-Z][a-z])/g, " $1");
    }
    /**
     *
     * @param action
     * @param timeout
     * @param retryCount
     * @param startImmediately
     * @version 2018-08-19 14:03:00.932
     * @since 1.1.5
     */
    DoUntilSuccess(action, timeout = 500, retryCount = 5, startImmediately = false) {
        if (retryCount < 1) {
            console.warn(`function exec failed.`);
            return;
        }
        if (startImmediately) {
            retryCount--;
            if (action()) {
                return;
            }
        }
        setTimeout(() => {
            this.DoUntilSuccess(action, timeout, retryCount, true);
        }, timeout);
    }
    /**
     *
     * @param action
     * @param count
     * @param interval
     * @param completeAction
     * @version 2018-08-19 15:59:52.369
     * @since 1.1.5
     */
    Countdown(action, count, interval = 1000, runWhenStart = true, completeAction) {
        if (runWhenStart) {
            if (action) {
                action(count);
            }
            // count--;
        }
        const id = setInterval(() => {
            if (count > 0) {
                if (action) {
                    action(count);
                }
                count--;
            }
            else {
                clearInterval(id);
                if (completeAction) {
                    completeAction();
                }
            }
        }, interval);
    }
    /**
     * RFC3986: https://tools.ietf.org/html/rfc3986
     *
     * 字符A~Z、a~z、0~9以及字符 -、_、.、~ 不编码。
     *
     * 其它字符编码成%XY的格式，其中XY是字符对应ASCII码的16进制。示例：半角双引号（"）对应%22。
     *
     * 扩展的UTF-8字符，编码成%XY%ZA…的格式。
     *
     * 空格（ ）编码成 %20，而不是加号（+）。
     *
     * 该编码方式与application/x-www-form-urlencoded MIME格式编码算法相似，但又有所不同。
     *
     * encodeURIComponent:
     * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
     * Not Escaped: A-Z a-z 0-9 - _ . ! ~ * ' ( )
     *
     * !*()'
     * %21 %2A %28 %29 %27
     *
     * @since 2018-08-20
     * @version 1.1.6
     */
    EncodeByRFC3986(str) {
        const result = encodeURIComponent(str)
            .ReplaceAll("!", "%21")
            .ReplaceAll("*", "%2A")
            .ReplaceAll("(", "%28")
            .ReplaceAll(")", "%29")
            .ReplaceAll("'", "%27");
        return result;
    }
    /**
     *
     * @param str
     * @version 1.1.11
     * @since 2018-10-19
     */
    DecodeByRFC3986(str) {
        const temp = str
            .ReplaceAll("%21", "!")
            .ReplaceAll("%2A", "*")
            .ReplaceAll("%28", "(")
            .ReplaceAll("%29", ")")
            .ReplaceAll("%27", "'");
        return decodeURIComponent(temp);
    }
    /**
     *
     * @see
     * {
     * @link
     * https://stackoverflow.com/questions/7640270/adding-modify-query-string-get-variables-in-a-url-with-javascript
     * }
     * @static
     * @param {string} key
     * @param {string} value
     * @param {string} url
     * @returns
     * @memberof KavenBasic
     * @since 1.1.6
     * @version 2018-08-27
     */
    UpdateQueryString(param, value, url) {
        let newURL = url;
        // Using a positive lookahead (?=\=) to find the
        // given parameter, preceded by a ? or &, and followed
        // by a = with a value after than (using a non-greedy selector)
        // and then followed by a & or the end of the string
        const val = new RegExp("(\\?|\\&)" + param + "=.*?(?=(&|$))");
        const parts = url.toString().split("#");
        url = parts[0];
        const hash = parts[1];
        const queryString = /\?.+$/;
        // Check if the parameter exists
        if (val.test(url)) {
            // if it does, replace it, using the captured group
            // to determine & or ? at the beginning
            newURL = url.replace(val, "$1" + param + "=" + value);
        }
        else if (queryString.test(url)) {
            // otherwise, if there is a query string at all
            // add the param to the end of it
            newURL = url + "&" + param + "=" + value;
        }
        else {
            // if there's no query string, add one
            newURL = url + "?" + param + "=" + value;
        }
        if (hash) {
            newURL += "#" + hash;
        }
        return newURL;
    }
    /**
     * @static
     * @param {number} time
     * @returns
     * @memberof KavenBasic
     * @version 1.1.7
     * @since 2018-08-30
     */
    Sleep(time) {
        return new Promise(resolve => setTimeout(resolve, time));
    }
    /**
     * @static
     * @param {string} url
     * @returns
     * @memberof KavenBasic
     * @version 1.1.7
     * @since 2018-08-30
     */
    RemoveHashFromURL(url) {
        const index = url.indexOf("#");
        if (index !== -1) {
            return url.substring(0, index);
        }
        return url;
    }
    /**
     *
     * @param url
     * @since 1.1.7
     * @version 2018-08-30
     */
    RemoveQueriesFromURL(url) {
        return url.split("?")[0];
    }
    /**
     *
     * @param url
     * @param names
     * @since 1.1.17
     * @version 2019-02-19
     */
    RemoveQueryFromURL(url, ...names) {
        let hash = Strings.Empty;
        const index = url.indexOf("#");
        if (index !== -1) {
            hash = url.substring(index);
        }
        for (const name of names) {
            // prefer to use l.search if you have a location/link object
            const urlParts = url.split("?");
            if (urlParts.length >= 2) {
                const prefix = encodeURIComponent(name) + "=";
                const pars = urlParts[1].split(/[&;]/g);
                // reverse iteration as may be destructive
                for (let i = pars.length; i-- > 0;) {
                    // idiom for string.startsWith
                    if (pars[i].lastIndexOf(prefix, 0) !== -1) {
                        pars.splice(i, 1);
                    }
                }
                url =
                    urlParts[0] + (pars.length > 0 ? "?" + pars.join("&") : "");
            }
        }
        return url + hash;
    }
    /**
     *
     * @param url
     * @since 1.1.7
     * @version 2018-08-30
     */
    GetFileNameFromURL(url) {
        url = this.RemoveQueriesFromURL(url);
        return url.substring(url.lastIndexOf(Strings.Slash) + 1);
    }
    /**
     *
     * @param base
     * @param paths
     * @since 1.1.7
     * @version 2018-08-31
     */
    CombinePath(base, ...paths) {
        const result = base.TrimEnd(Strings.Slash).TrimEnd(Strings.BackSlash);
        return paths
            .reduce((accumulator, currentValue) => {
            return (accumulator +
                Strings.Slash +
                currentValue
                    .TrimAll(Strings.Slash)
                    .TrimAll(Strings.BackSlash));
        }, result)
            .TrimEnd(Strings.Slash)
            .TrimEnd(Strings.BackSlash);
    }
    /**
     *
     * @param str
     * @version 1.1.13
     * @since 2018-10-21
     */
    GetMD5(str) {
        const name = Strings.Names.MD5Instance;
        let md5 = this.GetValue(name, undefined);
        if (md5 === undefined) {
            md5 = new KavenMD5();
            this.SetValue(name, md5);
        }
        return md5.Compute(str);
    }
    /**
     *
     * @param fileExtension
     * @version 1.1.14
     * @since 2018-10-26
     */
    GetMIMEByExtension(fileExtension) {
        return MIME.All.filter(p => p.Extension.TrimStart(".") ===
            fileExtension.TrimStart(".").toLowerCase());
    }
    /**
     *
     * @param contentType
     * @version 1.1.14
     * @since 2018-10-26
     */
    GetMIMEByContentType(contentType) {
        return MIME.All.filter(p => p.Type === contentType.toLowerCase());
    }
    /**
     * Assign the enumerable es6 Symbol properties from one or more objects to
     * the first object passed on the arguments.
     * Can be used as a supplement to other extend,
     * assign or merge methods as a polyfill for the Symbols part of the es6 Object.assign method.
     * @param target
     * @param args
     * @since 1.1.15
     * @version 2018-11-02
     * @see https://github.com/jonschlinkert/assign-symbols/blob/master/index.js
     */
    AssignSymbols(target, ...args) {
        function isObject(val) {
            return (typeof val === "function" ||
                toString.call(val) === "[object Object]" ||
                Array.isArray(val));
        }
        const toString = Object.prototype.toString;
        const isEnumerable = Object.prototype.propertyIsEnumerable;
        const getSymbols = Object.getOwnPropertySymbols;
        if (!isObject(target)) {
            throw new TypeError("expected the first argument to be an object");
        }
        if (args.length === 0 // ||
        // typeof Symbol !== "function" ||
        // typeof getSymbols !== "function"
        ) {
            return target;
        }
        for (const arg of args) {
            const names = getSymbols(arg);
            for (const key of names) {
                if (isEnumerable.call(arg, key)) {
                    target[key] = arg[key];
                }
            }
        }
        return target;
    }
    /**
     * Deeply assign the values of all enumerable-own-properties and symbols f
     * rom one or more source objects to a target object. Returns the target object.
     * @param target
     * @param args
     * @since 1.1.15
     * @version 2018-11-02
     * @see https://github.com/jonschlinkert/assign-deep/blob/master/index.js
     */
    AssignDeep(target, ...args) {
        function isObject(val) {
            return (typeof val === "function" ||
                toString.call(val) === "[object Object]");
        }
        function isPrimitive(val) {
            return typeof val === "object"
                ? val === null
                : typeof val !== "function";
        }
        const toString = Object.prototype.toString;
        let i = 0;
        if (isPrimitive(target)) {
            target = args[i++];
        }
        if (!target) {
            target = {};
        }
        for (; i < args.length; i++) {
            if (isObject(args[i])) {
                for (const key of Object.keys(args[i])) {
                    if (isObject(target[key]) && isObject(args[i][key])) {
                        this.AssignDeep(target[key], args[i][key]);
                    }
                    else {
                        target[key] = args[i][key];
                    }
                }
                this.AssignSymbols(target, args[i]);
            }
        }
        return target;
    }
    /**
     *
     * @param url
     * @since 1.1.17
     * @version 2019-02-26
     */
    GetQueryStringFromURL(url) {
        const index = url.indexOf("?");
        if (index !== -1) {
            return url.substring(index + 1);
        }
        return undefined;
    }
    /**
     *
     * @param str
     * @version 1.1.20
     * @version 2019-03-29
     */
    GetCRC32(str) {
        const name = Strings.Names.CRC32Instance;
        let crc = this.GetValue(name, undefined);
        if (crc === undefined) {
            crc = new KavenCRC(KavenCRC.CRC32);
            this.SetValue(name, crc);
        }
        return crc.Compute(str);
    }
    GetCRC64(str) {
        const name = Strings.Names.CRC64Instance;
        let crc = this.GetValue(name, undefined);
        if (crc === undefined) {
            crc = new KavenCRC(KavenCRC.CRC64_ECMA_XZ);
            this.SetValue(name, crc);
        }
        return crc.Compute(str);
    }
    /**
     * Warning: This algorithm is now considered vulnerable and should not be used.
     * @param str
     * @param separator
     * @since 1.1.20
     * @version 2019-03-30
     */
    GetSHA1(str) {
        const name = Strings.Names.SHAInstance;
        let sha = this.GetValue(name, undefined);
        if (sha === undefined) {
            sha = new KavenSHA();
            this.SetValue(name, sha);
        }
        return sha.ComputeSHA1(str);
    }
    GetSHA256(str) {
        const name = Strings.Names.SHAInstance;
        let sha = this.GetValue(name, undefined);
        if (sha === undefined) {
            sha = new KavenSHA();
            this.SetValue(name, sha);
        }
        return sha.ComputeSHA256(str);
    }
    GetSHA224(str) {
        const name = Strings.Names.SHAInstance;
        let sha = this.GetValue(name, undefined);
        if (sha === undefined) {
            sha = new KavenSHA();
            this.SetValue(name, sha);
        }
        return sha.ComputeSHA224(str);
    }
    GetSHA512(str) {
        const name = Strings.Names.SHAInstance;
        let sha = this.GetValue(name, undefined);
        if (sha === undefined) {
            sha = new KavenSHA();
            this.SetValue(name, sha);
        }
        return sha.ComputeSHA512(str);
    }
    GetSHA384(str) {
        const name = Strings.Names.SHAInstance;
        let sha = this.GetValue(name, undefined);
        if (sha === undefined) {
            sha = new KavenSHA();
            this.SetValue(name, sha);
        }
        return sha.ComputeSHA384(str);
    }
    GetSHA512T(str, t) {
        const name = Strings.Names.SHAInstance;
        let sha = this.GetValue(name, undefined);
        if (sha === undefined) {
            sha = new KavenSHA();
            this.SetValue(name, sha);
        }
        return sha.ComputeSHA512T(str, t);
    }
    /**
     * @returns `true` if little endian, `false` if big endian
     */
    CheckEndian() {
        const arrayBuffer = new ArrayBuffer(2);
        const uint8Array = new Uint8Array(arrayBuffer);
        const uint16array = new Uint16Array(arrayBuffer);
        uint8Array[0] = 0xAA; // set first byte
        uint8Array[1] = 0xBB; // set second byte
        if (uint16array[0] === 0xBBAA) {
            // little endian
            return true;
        }
        if (uint16array[0] === 0xAABB) {
            // big endian
            return false;
        }
        else {
            return undefined;
        }
    }
}
export default new KavenBasic();
//# sourceMappingURL=KavenBasic.js.map