String.prototype.rightChars = function (n) {
    if (n <= 0) {
        return "";
    } else if (n > this.length) {
        return this;
    } else {
        return this.substring(this.length, this.length - n);
    }
};

(function ($) {
    var options = {
            highlightSpeed: 20,
            typeSpeed: 200,
            clearDelay: 1500,
            typeDelay: 200,
            clearOnHighlight: true,
            typerDataAttr: 'data-typer-targets',
            typerInterval: 1000
        },
        highlight,
        clearText,
        backspace,
        type,
        spanWithColor,
        clearDelay,
        typeDelay,
        clearData,
        isNumber,
        typeWithAttribute,
        getHighlightInterval,
        getTypeInterval,
        typerInterval;

    spanWithColor = function (color, backgroundColor) {
        if (color === 'rgba(0, 0, 0, 0)') {
            color = 'rgb(255, 255, 255)';
        }

        return $('<span></span>')
            .css('color', color)
            .css('background-color', backgroundColor);
    };

    isNumber = function (n) {
        return !isNaN(parseFloat(n)) && isFinite(n);
    };

    clearData = function ($e) {
        $e.removeData([
            'typePosition',
            'highlightPosition',
            'leftStop',
            'rightStop',
            'primaryColor',
            'backgroundColor',
            'text',
            'typing'
        ]);
    };

    type = function ($e) {
        var
            text = $e.data('text'),
            oldLeft = $e.data('oldLeft'),
            oldRight = $e.data('oldRight');

        if (!text || text.length === 0) {
            clearData($e);
            return;
        }


        $e.text(
            oldLeft +
            text.charAt(0) +
            oldRight
        ).data({
            oldLeft: oldLeft + text.charAt(0),
            text: text.substring(1)
        });

        setTimeout(function () {
            type($e);
        }, getTypeInterval());
    };

    clearText = function ($e) {
        $e.find('span').remove();

        setTimeout(function () {
            type($e);
        }, typeDelay());
    };

    highlight = function ($e) {
        var
            position = $e.data('highlightPosition'),
            leftText,
            highlightedText,
            rightText;

        if (!isNumber(position)) {
            position = $e.data('rightStop') + 1;
        }

        if (position <= $e.data('leftStop')) {
            setTimeout(function () {
                clearText($e);
            }, clearDelay());
            return;
        }

        leftText = $e.text().substring(0, position - 1);
        highlightedText = $e.text().substring(position - 1, $e.data('rightStop') + 1);
        rightText = $e.text().substring($e.data('rightStop') + 1);

        $e.html(leftText)
            .append(
                spanWithColor(
                    $e.data('backgroundColor'),
                    $e.data('primaryColor')
                )
                .append(highlightedText)
            )
            .append(rightText);

        $e.data('highlightPosition', position - 1);

        setTimeout(function () {
            return highlight($e);
        }, getHighlightInterval());
    };

    typeWithAttribute = function ($e) {
        var targets;

        if ($e.data('typing')) {
            return;
        }

        try {
            targets = JSON.parse($e.attr($.typer.options.typerDataAttr)).targets;
        } catch (e) {}

        if (typeof targets === "undefined") {
            targets = $.map($e.attr($.typer.options.typerDataAttr).split(','), function (e) {
                return $.trim(e);
            });
        }

        $e.typeTo(targets[Math.floor(Math.random() * targets.length)]);
    };

    // Expose our options to the world.
    $.typer = (function () {
        return {
            options: options
        };
    })();

    $.extend($.typer, {
        options: options
    });

    //-- Methods to attach to jQuery sets

    $.fn.typer = function () {
        var $elements = $(this);

        return $elements.each(function () {
            var $e = $(this);

            if (typeof $e.attr($.typer.options.typerDataAttr) === "undefined") {
                return;
            }

            typeWithAttribute($e);
            setInterval(function () {
                typeWithAttribute($e);
            }, typerInterval());
        });
    };

    $.fn.typeTo = function (newString) {
        var
            $e = $(this),
            currentText = $e.text(),
            i = 0,
            j = 0;

        if (currentText === newString) {
            // console.log("Our strings our equal, nothing to type");
            return $e;
        }

        if (currentText !== $e.html()) {
            // console.error("Typer does not work on elements with child elements.");
            return $e;
        }

        $e.data('typing', true);

        while (currentText.charAt(i) === newString.charAt(i)) {
            i++;
        }

        while (currentText.rightChars(j) === newString.rightChars(j)) {
            j++;
        }

        newString = newString.substring(i, newString.length - j + 1);

        $e.data({
            oldLeft: currentText.substring(0, i),
            oldRight: currentText.rightChars(j - 1),
            leftStop: i,
            rightStop: currentText.length - j,
            primaryColor: $e.css('color'),
            backgroundColor: $e.css('background-color'),
            text: newString
        });

        highlight($e);

        return $e;
    };

    //-- Helper methods. These can one day be customized further to include things like ranges of delays.

    getHighlightInterval = function () {
        return $.typer.options.highlightSpeed;
    };

    getTypeInterval = function () {
            return $.typer.options.typeSpeed;
        },

        clearDelay = function () {
            return $.typer.options.clearDelay;
        },

        typeDelay = function () {
            return $.typer.options.typeDelay;
        };

    typerInterval = function () {
        return $.typer.options.typerInterval;
    };
})(jQuery);

$('.type-once').typeTo("Testing Typer.js jQuery Plugin with a 1 time text string.");

$('[data-typer-targets]').typer({
    highlightSpeed: 2660,
    typeSpeed: 8600,
    clearDelay: 6500,
    typeDelay: 6200,
    clearOnHighlight: false,
    typerDataAttr: 'data-typer-targets',
    typerInterval: 2000
});
