(function() {
  if (
    typeof self === 'undefined' ||
    !self.Prism ||
    !self.document ||
    !document.querySelector
  ) {
    return;
  }

  function $$(expr, con) {
    return Array.prototype.slice.call((con || document).querySelectorAll(expr));
  }

  function hasClass(element, className) {
    className = ' ' + className + ' ';
    return (
      (' ' + element.className + ' ')
        .replace(/[\n\t]/g, ' ')
        .indexOf(className) > -1
    );
  }

  // Some browsers round the line-height, others don't.
  // We need to test for it to position the elements properly.
  var isLineHeightRounded = (function() {
    var res;
    return function() {
      if (typeof res === 'undefined') {
        var d = document.createElement('div');
        d.style.fontSize = '13px';
        d.style.lineHeight = '1.5';
        d.style.padding = 0;
        d.style.border = 0;
        d.innerHTML = '&nbsp;<br />&nbsp;';
        document.body.appendChild(d);
        // Browsers that round the line-height should have offsetHeight === 38
        // The others should have 39.
        res = d.offsetHeight === 38;
        document.body.removeChild(d);
      }
      return res;
    };
  })();

  function highlightLines(pre, lines, classes) {
    lines = typeof lines === 'string' ? lines : pre.getAttribute('data-line');

    var ranges = lines.replace(/\s+/g, '').split(','),
      offset = +pre.getAttribute('data-line-offset') || 0;

    var parseMethod = isLineHeightRounded() ? parseInt : parseFloat;
    var lineHeight = parseMethod(getComputedStyle(pre).lineHeight);
    var hasLineNumbers = hasClass(pre, 'line-numbers');

    for (var i = 0, currentRange; (currentRange = ranges[i++]); ) {
      var range = currentRange.split('-');

      var start = +range[0],
        end = +range[1] || start;

      var line =
        pre.querySelector(
          '.line-highlight[data-range="' + currentRange + '"]'
        ) || document.createElement('div');

      line.setAttribute('aria-hidden', 'true');
      line.setAttribute('data-range', currentRange);
      line.className = (classes || '') + ' line-highlight';

      //if the line-numbers plugin is enabled, then there is no reason for this plugin to display the line numbers
      if (hasLineNumbers && Prism.plugins.lineNumbers) {
        var startNode = Prism.plugins.lineNumbers.getLine(pre, start);
        var endNode = Prism.plugins.lineNumbers.getLine(pre, end);

        if (startNode) {
          line.style.top = startNode.offsetTop + 'px';
        }

        if (endNode) {
          line.style.height =
            endNode.offsetTop -
            startNode.offsetTop +
            endNode.offsetHeight +
            'px';
        }
      } else {
        line.setAttribute('data-start', start);

        if (end > start) {
          line.setAttribute('data-end', end);
        }

        line.style.top = (start - offset - 1) * lineHeight + 'px';

        line.textContent = new Array(end - start + 2).join(' \n');
      }

      //allow this to play nicely with the line-numbers plugin
      if (hasLineNumbers) {
        //need to attack to pre as when line-numbers is enabled, the code tag is relatively which screws up the positioning
        pre.appendChild(line);
      } else {
        (pre.querySelector('code') || pre).appendChild(line);
      }
    }
  }

  function applyHash() {
    var hash = location.hash.slice(1);

    // Remove pre-existing temporary lines
    $$('.temporary.line-highlight').forEach(function(line) {
      line.parentNode.removeChild(line);
    });

    var range = (hash.match(/\.([\d,-]+)$/) || [, ''])[1];

    if (!range || document.getElementById(hash)) {
      return;
    }

    var id = hash.slice(0, hash.lastIndexOf('.')),
      pre = document.getElementById(id);

    if (!pre) {
      return;
    }

    if (!pre.hasAttribute('data-line')) {
      pre.setAttribute('data-line', '');
    }

    highlightLines(pre, range, 'temporary ');

    document.querySelector('.temporary.line-highlight').scrollIntoView();
  }

  var fakeTimer = 0; // Hack to limit the number of times applyHash() runs

  Prism.hooks.add('before-sanity-check', function(env) {
    var pre = env.element.parentNode;
    var lines = pre && pre.getAttribute('data-line');

    if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
      return;
    }

    /*
	* Cleanup for other plugins (e.g. autoloader).
	 *
	 * Sometimes <code> blocks are highlighted multiple times. It is necessary
	 * to cleanup any left-over tags, because the whitespace inside of the <div>
	 * tags change the content of the <code> tag.
	 */
    var num = 0;
    $$('.line-highlight', pre).forEach(function(line) {
      num += line.textContent.length;
      line.parentNode.removeChild(line);
    });
    // Remove extra whitespace
    if (num && /^( \n)+$/.test(env.code.slice(-num))) {
      env.code = env.code.slice(0, -num);
    }
  });

  Prism.hooks.add('complete', function completeHook(env) {
    var pre = env.element.parentNode;
    var lines = pre && pre.getAttribute('data-line');

    if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
      return;
    }

    clearTimeout(fakeTimer);

    var hasLineNumbers = Prism.plugins.lineNumbers;
    var isLineNumbersLoaded = env.plugins && env.plugins.lineNumbers;

    if (
      hasClass(pre, 'line-numbers') &&
      hasLineNumbers &&
      !isLineNumbersLoaded
    ) {
      Prism.hooks.add('line-numbers', completeHook);
    } else {
      highlightLines(pre, lines);
      fakeTimer = setTimeout(applyHash, 1);
    }
  });

  window.addEventListener('hashchange', applyHash);
  window.addEventListener('resize', function() {
    var preElements = document.querySelectorAll('pre[data-line]');
    Array.prototype.forEach.call(preElements, function(pre) {
      highlightLines(pre);
    });
  });
})();
