String.prototype.toTitleCase = function () {
    console.debug("[Info] String.prototype has been extended with a method: toTitleCase()");
    return function toTitleCase() {
        let words = this.split(" ");
        words = words.map(word => word[0].toUpperCase() + word.slice(1));
        let str = words.join(" ");
        return str;
    }
}();

String.snakeCase2CamelCase = function () {
    console.debug("[Info] String has been extended with a method: snakeCase2CamelCase()");
    return function snakeCase2CamelCase(snakeCaseStr) {
        let str   = snakeCaseStr;
        let words = str.split("_");
        let first = words.shift();
        words     = words.map(word => word.toTitleCase());
        str       = first + words.join("");
        return str;
    }
}();

/**
 * Very basic string formatting.
 * 
 * Expects the string to include special placeholders.
 * 
 * Placeholders might be in the following forms:
 *  * {something}: something is a key that needs to be in the values object.
 *  * {2}: replace values are inserted using their indices. A key/index can be re-used.
 *  * {}: replace values are inserted using their indices with no control.
 * 
 * A mix of {key} and {} is not allowed. Method will fail and you'll get a warning.
 */
String.prototype.format = function formatString(values) {
    
    let string = this.valueOf();
    
    let placeholderPattern = /{[^{]*}/g;
    
    let placeholders = string.match(placeholderPattern);
    
    if (placeholders.length == 0) {
        return string;
    }
    
    if (typeof values == "undefined") {
        console.warn("Format operation aborted. Missing replace values.");
        return string;
    }
    else if (!(values instanceof Array) || typeof values != "object") {
        console.warn("Format operation aborted. Unexpected replace values type: " + typeof values);
        return string;
    }
    
    let useArrayIndices = placeholders.every(ph => ph == "{}");
    
    if (!useArrayIndices) {
        let indexKeyMix = placeholders.some(ph => ph == "{}");
        if (indexKeyMix) {
            console.warn("Format operation aborted. String includes inconsistent placeholders.");
            return string;
        }
    }
    
    if (useArrayIndices) {
        values = Array.from(values);
        let index = 0;
        string = string.replace(placeholderPattern, function (ph) {
            let value = ph;
            if (index in values) {
                value = values[index];
                index++;
            }
            return value;
        });
    } else {
        string = string.replace(placeholderPattern, function (ph) {
            let key = ph.slice(1, -1);
            if (key in values) {
                return values[key];
            }
            return ph;
        });
    }
    
    return string;
};


String.random = function () {
    console.debug("[Info] String has been extended with a method: random()");
    return function random(length = 8) {
        let randomStr = parseInt(Math.random().toString().slice(2)).toString(36);
        while (randomStr.length < length) {
            randomStr += String.random();
        }
        randomStr = randomStr.slice(-length);
        return randomStr;
    }
}();


String.ellipsis = function (string, maxLength, ellipsis = "...") {
    if (!string || !maxLength || !ellipsis) {
        return string;
    }
    maxLength -= ellipsis.length;
    if (string.length > maxLength) {
        string = string.substr(0, maxLength) + ellipsis;
    }
    return string;
};