/********************************************************************
 * @author:      Kaven
 * @email:       kaven@wuwenkai.com
 * @website:     http://kavenblog.com
 * @file:        [Kaven-Basic] /src/libs/Extensions.ts
 * @create:      2019-04-25 12:45:36.310
 * @modify:      2019-04-25 12:45:45.659
 * @version:     1.1.21
 * @times:       2
 * @lines:       1179
 * @copyright:   Copyright © 2019 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, TimeFactor, TimeUnit } from "./Enums";
import { Strings } from "./Strings";
//#endregion
//#region Extend Methods
Date.prototype.Format = function (format = DateTimeFormat.FullDataTime) {
    return moment(this).format(format);
};
Date.Create = () => {
    return new Date();
};
// https://momentjs.com/docs/#/manipulating/
//#region Add
Date.prototype.AddMilliseconds = function () {
    return moment(this)
        .add(arguments[0], "milliseconds")
        .toDate();
};
Date.prototype.AddSeconds = function () {
    return moment(this)
        .add(arguments[0], "seconds")
        .toDate();
};
Date.prototype.AddMinutes = function () {
    return moment(this)
        .add(arguments[0], "minutes")
        .toDate();
};
Date.prototype.AddHours = function () {
    return moment(this)
        .add(arguments[0], "hours")
        .toDate();
};
Date.prototype.AddDays = function () {
    return moment(this)
        .add(arguments[0], "days")
        .toDate();
};
Date.prototype.AddWeeks = function () {
    return moment(this)
        .add(arguments[0], "weeks")
        .toDate();
};
Date.prototype.AddMonths = function () {
    return moment(this)
        .add(arguments[0], "months")
        .toDate();
};
Date.prototype.AddQuarters = function () {
    return moment(this)
        .add(arguments[0], "quarters")
        .toDate();
};
Date.prototype.AddYears = function () {
    return moment(this)
        .add(arguments[0], "years")
        .toDate();
};
//#endregion
//#region Subtract
Date.prototype.SubtractMilliseconds = function () {
    return moment(this)
        .subtract(arguments[0], "milliseconds")
        .toDate();
};
Date.prototype.SubtractSeconds = function () {
    return moment(this)
        .subtract(arguments[0], "seconds")
        .toDate();
};
Date.prototype.SubtractMinutes = function () {
    return moment(this)
        .subtract(arguments[0], "minutes")
        .toDate();
};
Date.prototype.SubtractHours = function () {
    return moment(this)
        .subtract(arguments[0], "hours")
        .toDate();
};
Date.prototype.SubtractDays = function () {
    return moment(this)
        .subtract(arguments[0], "days")
        .toDate();
};
Date.prototype.SubtractWeeks = function () {
    return moment(this)
        .subtract(arguments[0], "weeks")
        .toDate();
};
Date.prototype.SubtractMonths = function () {
    return moment(this)
        .subtract(arguments[0], "months")
        .toDate();
};
Date.prototype.SubtractQuarters = function () {
    return moment(this)
        .subtract(arguments[0], "quarters")
        .toDate();
};
Date.prototype.SubtractYears = function () {
    return moment(this)
        .subtract(arguments[0], "years")
        .toDate();
};
//#endregion
Date.prototype.DiffMilliseconds = function (anotherDate) {
    return moment(this).diff(moment(anotherDate), TimeUnit.milliseconds, true);
};
Date.prototype.DiffMonths = function (anotherDate) {
    return moment(this).diff(moment(anotherDate), TimeUnit.months, true);
};
Date.prototype.DiffYears = function (anotherDate) {
    return moment(this).diff(moment(anotherDate), TimeUnit.years, true);
};
String.prototype.ReplaceAll = function (old, rep) {
    // return this.replace(new RegExp(old, 'gm'), rep);
    return this.split(old).join(rep);
};
String.prototype.ReplaceAt = function (index, rep, length = 1) {
    return (String.prototype.substring.call(this, 0, index) +
        rep +
        String.prototype.substring.call(this, index + length));
};
String.prototype.ReplaceAtPos = function (pos, rep) {
    if (pos.Length) {
        return this.ReplaceAt(pos.Start, rep, pos.Length);
    }
    return String.prototype.substring.call(this, 0, pos.Start) + rep;
};
if (!String.prototype.endsWith) {
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
    String.prototype.endsWith = function (search, length) {
        if (length > this.length) {
            length = this.length;
        }
        return this.substring(length - search.length, length) === search;
    };
}
String.prototype.IsNotEmpty = function () {
    return this.length > 0;
};
String.prototype.IsEmpty = function () {
    return !this.IsNotEmpty();
};
String.prototype.TrimStart = function (trim, max = 1) {
    let result = this;
    while (max-- > 0) {
        if (trim.IsNotEmpty()) {
            if (String.prototype.startsWith.call(result, trim)) {
                result = String.prototype.substring.call(result, trim.length);
            }
            else {
                return result;
            }
        }
    }
    return result;
};
String.prototype.TrimEnd = function (trim, max = 1) {
    let result = this;
    while (max-- > 0) {
        if (trim.IsNotEmpty()) {
            if (String.prototype.endsWith.call(result, trim)) {
                result = String.prototype.slice.call(this, 0, -trim.length);
            }
            else {
                return result;
            }
        }
    }
    return result;
};
String.prototype.TrimAll = function (trim, max = 1) {
    return this.TrimStart(trim, max).TrimEnd(trim, max);
};
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
String.prototype.padStart = function (targetLength, padString) {
    // tslint:disable-next-line:no-bitwise
    targetLength = targetLength >> 0; // truncate if number or convert non-number to 0;
    // padString = String(typeof padString !== 'undefined' ? padString : ' ')
    if (this.length > targetLength) {
        return String(this);
    }
    else {
        targetLength = targetLength - this.length;
        if (targetLength > padString.length) {
            // append to original to ensure we are longer than needed
            padString += padString.repeat(targetLength / padString.length);
        }
        return padString.slice(0, targetLength) + String(this);
    }
};
String.prototype.GetIndent = function () {
    const str = this;
    if (str.IsNotEmpty()) {
        let result = Strings.Empty;
        for (const c of str) {
            if (c === Strings.WhiteSpace || c === Strings.Tab) {
                result += c;
                continue;
            }
            break;
        }
        return result;
    }
    return Strings.Empty;
};
String.prototype.OnlyContains = function (...characters) {
    const str = this;
    if (str.IsNotEmpty()) {
        const charactersStr = characters.join(Strings.Empty).Distinct();
        for (const val of str) {
            if (!charactersStr.includes(val)) {
                return false;
            }
        }
    }
    return true;
};
String.prototype.ContainsSubstringOnlyOnce = function (...substrings) {
    for (const substring of substrings) {
        if (!this.includes(substring)) {
            return false;
        }
        if (this.split(substring).length > 2) {
            return false;
        }
    }
    return true;
};
String.prototype.CharactersCount = function (...characters) {
    let count = 0;
    const str = this;
    if (str.IsNotEmpty()) {
        const charactersStr = characters.join(Strings.Empty).Distinct();
        for (const val of str) {
            if (charactersStr.includes(val)) {
                count++;
            }
        }
    }
    return count;
};
String.prototype.ToArray = function () {
    const str = this;
    if (str.IsNotEmpty()) {
        // tslint:disable-next-line:max-line-length
        // https://stackoverflow.com/questions/6484670/how-do-i-split-a-string-into-an-array-of-characters/38901550#38901550
        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split#Syntax
        // return str.split(Strings.Empty); // 2019-04-03
        return Array.from(str);
    }
    return [];
};
String.prototype.Distinct = function () {
    const str = this;
    if (str.IsNotEmpty()) {
        return str
            .ToArray()
            .Distinct()
            .join(Strings.Empty);
    }
    return this;
};
String.prototype.GetSubstring = function (pos) {
    return this.substring(pos.Start, pos.End);
};
String.prototype.CapitalizeSentences = function () {
    function LeftTrim(str, chars) {
        chars = chars || "\\s";
        return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
    }
    let result = this;
    let tempArray = this.split(".");
    let tempStr = "";
    for (let i = 0; i < tempArray.length; i++) {
        tempArray[i] = LeftTrim(tempArray[i], " ");
        tempStr =
            tempStr +
                tempArray[i].substring(0, 1).toUpperCase() +
                tempArray[i].slice(1).toLowerCase();
        if (i < tempArray.length - 1) {
            tempStr = tempStr + ". ";
        }
    }
    result = tempStr;
    tempArray = result.split("?");
    tempStr = "";
    for (let i = 0; i < tempArray.length; i++) {
        tempArray[i] = LeftTrim(tempArray[i], " ");
        tempStr =
            tempStr +
                tempArray[i].substring(0, 1).toUpperCase() +
                tempArray[i].slice(1);
        if (i < tempArray.length - 1) {
            tempStr = tempStr + "? ";
        }
    }
    result = tempStr;
    tempArray = result.split("!");
    tempStr = "";
    for (let i = 0; i < tempArray.length; i++) {
        tempArray[i] = LeftTrim(tempArray[i], " ");
        tempStr =
            tempStr +
                tempArray[i].substring(0, 1).toUpperCase() +
                tempArray[i].slice(1);
        if (i < tempArray.length - 1) {
            tempStr = tempStr + "! ";
        }
    }
    result = tempStr;
    return result;
};
String.prototype.CapitalizeWords = function () {
    return this.replace(/^(.)|\s(.)/g, $1 => {
        return $1.toUpperCase();
    });
};
String.prototype.UTF8Encode = function () {
    // const imp1 = () => {
    //     return new TextEncoder()
    //     .encode(this)
    //     .reduce(
    //         (prev, curr) => prev + String.fromCharCode(curr),
    //         Strings.Empty
    //     );
    // };
    const imp2 = () => {
        return unescape(encodeURIComponent(this));
    };
    const imp3 = () => {
        return this.ToUTF8ByteArray().reduce((prev, curr) => prev + String.fromCharCode(curr), Strings.Empty);
    };
    // try {
    //     return imp1();
    // } catch (ex) {
    //     console.error(ex);
    // }
    try {
        return imp2();
    }
    catch (ex) {
        console.error(ex);
    }
    try {
        return imp3();
    }
    catch (ex) {
        console.error(ex);
    }
    return this;
};
String.prototype.ToUTF8ByteArray = function () {
    // tslint:disable-next-line: max-line-length
    // https://github.com/google/closure-library/blob/8598d87242af59aac233270742c8984e2b2bdbe0/closure/goog/crypt/crypt.js#L117-L143
    const out = [];
    let p = 0;
    for (let i = 0; i < this.length; i++) {
        // tslint:disable: no-bitwise
        let c = this.charCodeAt(i);
        if (c < 128) {
            out[p++] = c;
        }
        else if (c < 2048) {
            out[p++] = (c >> 6) | 192;
            out[p++] = (c & 63) | 128;
        }
        else if (((c & 0xFC00) === 0xD800) && (i + 1) < this.length &&
            ((this.charCodeAt(i + 1) & 0xFC00) === 0xDC00)) {
            // Surrogate Pair
            c = 0x10000 + ((c & 0x03FF) << 10) + (this.charCodeAt(++i) & 0x03FF);
            out[p++] = (c >> 18) | 240;
            out[p++] = ((c >> 12) & 63) | 128;
            out[p++] = ((c >> 6) & 63) | 128;
            out[p++] = (c & 63) | 128;
        }
        else {
            out[p++] = (c >> 12) | 224;
            out[p++] = ((c >> 6) & 63) | 128;
            out[p++] = (c & 63) | 128;
        }
        // tslint:enable: no-bitwise
    }
    return out;
};
String.prototype.SplitByN = function (n, fromLeft) {
    const reg = fromLeft !== true ? `.{1,${n}}(?=(.{${n}})+(?!.))|.{1,${n}}$` : `.{1,${n}}`;
    return this.match(new RegExp(reg, "g"));
};
String.prototype.Reverse = function () {
    return this.ToArray().reverse().join(Strings.Empty);
};
Array.prototype.IsNotEmpty = function () {
    return Array.isArray(this) && this.length > 0;
};
Array.prototype.Remove = function (index) {
    return Array.prototype.splice.call(this, index, 1);
};
Array.prototype.Distinct = function () {
    // https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates
    return Array.from(new Set(this));
};
Array.prototype.First = function () {
    const arr = this;
    if (arr.IsNotEmpty()) {
        return arr[0];
    }
    return undefined;
};
Array.prototype.Last = function () {
    const arr = this;
    if (arr && arr.IsNotEmpty()) {
        return arr[arr.length - 1];
    }
    return undefined;
};
Array.prototype.Split = function (size, fromLeft) {
    const array = new Array(0);
    if (fromLeft === true) {
        for (let i = 0; i < this.length; i += size) {
            array.push(this.slice(i, i + size));
        }
    }
    else {
        const remainder = this.length % size;
        if (remainder > 0) {
            array.push(this.slice(0, remainder));
        }
        for (let i = remainder; i < this.length; i += size) {
            array.push(this.slice(i, i + size));
        }
    }
    return array;
};
Array.prototype.ToHEX = function () {
    return this.map(p => p.ToHEX()).join(Strings.Empty);
};
Number.prototype.ToMilliseconds = function (currentUnit) {
    switch (currentUnit) {
        case TimeUnit.milliseconds:
        case TimeUnit.seconds:
        case TimeUnit.minutes:
        case TimeUnit.hours:
        case TimeUnit.days:
        case TimeUnit.weeks:
        case TimeUnit.months:
        case TimeUnit.years:
            return this * TimeFactor[currentUnit];
        default:
            console.warn(`Unexpected time unit: ${currentUnit}.`);
            return this;
    }
};
Number.prototype.ToSeconds = function (currentUnit) {
    return this.ToMilliseconds(currentUnit) / TimeFactor.seconds;
};
Number.prototype.ToMinutes = function (currentUnit) {
    return this.ToMilliseconds(currentUnit) / TimeFactor.minutes;
};
Number.prototype.ToHours = function (currentUnit) {
    return this.ToMilliseconds(currentUnit) / TimeFactor.hours;
};
Number.prototype.ToDays = function (currentUnit) {
    return this.ToMilliseconds(currentUnit) / TimeFactor.days;
};
Number.prototype.ToWeeks = function (currentUnit) {
    return this.ToMilliseconds(currentUnit) / TimeFactor.weeks;
};
Number.prototype.ToMonths = function (currentUnit) {
    return this.ToMilliseconds(currentUnit) / TimeFactor.months;
};
Number.prototype.ToYears = function (currentUnit) {
    return this.ToMilliseconds(currentUnit) / TimeFactor.years;
};
Number.prototype.LeftRotate = function (n, bits) {
    if (bits === undefined) {
        bits = 32;
    }
    // tslint:disable-next-line: no-bitwise
    return (this << n) | (this >>> (bits - n));
};
Number.prototype.RightRotate = function (n, bits) {
    if (bits === undefined) {
        bits = 32;
    }
    // tslint:disable-next-line: no-bitwise
    return (this >>> n) | (this << (bits - n));
};
Number.prototype.ToUInt32 = function () {
    // note '>>> 0' for 'addition modulo 2^32'
    // The reason >> works is because it operates only on 32-bit integers, so the value is truncated.
    // tslint:disable-next-line: no-bitwise
    return this >>> 0;
    // same to:
    // return this & 0xffffffff;
};
Number.prototype.Swap32 = function () {
    // tslint:disable-next-line: no-bitwise
    return ((this & 0xFF) << 24) | ((this & 0xFF00) << 8) | ((this >> 8) & 0xFF00) | ((this >> 24) & 0xFF);
};
Number.prototype.ToHEX = function () {
    return this.toString(16).padStart(8, "0");
};
Number.prototype.GetBaseLog = function (baseNumber) {
    return Math.log(this) / Math.log(baseNumber);
};
Number.prototype.Mod = function (n) {
    // https://stackoverflow.com/questions/4467539/javascript-modulo-gives-a-negative-result-for-negative-numbers
    return ((this % n) + n) % n;
};
//# sourceMappingURL=Extensions.js.map