// Only install the plugin if Rainbow is present and has been loaded
if (window.Rainbow) window.Rainbow.linenumbers = (function(Rainbow) {
    /**
     * Splits up a single element into individual lines
     *
     * @param {HTMLElement} elem
     * @returns {Array}
     */
    function splitElement(elem) {
        if (elem.nodeType === 3) {
            // Just split up the text node
            return elem.nodeValue.split('\n');
        }

        // Otherwise, we need to split up the HTML
        var sourceLines = elem.innerHTML.split('\n');
        var lines = [];
        
        // Wraps each chunk in the parent element. For example:
        // <b>foo\nbar</b> -> [<b>foo</b>, <b>bar</b>]
        for (var i = 0; i < sourceLines.length; i++) {
            // Handles <b>\nbar</b> -> [, <b>bar</b>]
            if (sourceLines[i] === '') {
                lines.push('');
            } else {
                var wrapper = elem.cloneNode(true);
                wrapper.innerHTML = sourceLines[i];

                var div = document.createElement('div');
                div.appendChild(wrapper.cloneNode(true));

                lines.push(div.innerHTML);
            }
        }

        return lines;
    };

    /**
     * Splits up the element containing highlighted source code
     * into an array of lines
     *
     * @param {HTMLElement} block
     * @returns {Array}
     */
    function splitLines(block) {
        var lines = [''];

        for (var i = 0; i < block.childNodes.length; i++) {
            var elemLines = splitElement(block.childNodes[i]);

            // The first element in elemLines is 
            // a continuation of the previous line
            lines[lines.length - 1] += elemLines[0];

            // The remaining elements get their own lines
            for (var j = 1; j < elemLines.length; j++) {
                lines.push(elemLines[j]);
            }
        }

        // Returns the array of lines
        return lines;
    };
    
    // Callback is called when Rainbow has highlighted a block
    Rainbow.onHighlight(function(block) {
        // This addresses an issue when Rainbow.color() is called multiple times.
        // Since code element is replaced with table element below,
        // second pass of Rainbow.color() will result in block.parentNode being null.
        if (!block || !block.parentNode) {
            return;
        }

        // Create a table wrapper
        var table = document.createElement('table');
        table.className = 'rainbow';
        table.setAttribute('data-language', block.getAttribute('data-language'));
        
        // Split up the lines of the block
        var lines = splitLines(block);
        
        // For each line
        for (var i = 0; i < lines.length; i++) {
            var line = lines[i];
            var index = i + 1;
            
            // Create a row
            var row = table.insertRow(-1);
            row.className = 'line line-' + index;
            
            // Create a cell which displays the line number with CSS
            var lineNumber = row.insertCell(-1);
            lineNumber.className = 'line-number';
            lineNumber.setAttribute('data-line-number', index);
            
            // Add in the actual line of source code
            var code = row.insertCell(-1);
            code.className = 'line-code';

            // If the line is blank, add a newline to make it copyable.
            if (line === '') {
                line = '\n';
            }

            code.innerHTML = line;
        }

        // If the block is a <pre> element, its parent is not an element
        // generated by Rainbow (i.e. it could be <body>). We don't want
        // to clear this.
        var parent = (block.nodeName.toLowerCase() === 'pre') ? block : block.parentNode;

        // Clear the parent element and use the table in place of the <code> block
        parent.innerHTML = '';
        parent.appendChild(table);
    });
})(window.Rainbow);