(function() {
  /**
   * Check and set a global guard variable.
   * If this content script is injected into the same page again,
   * it will do nothing next time.
   */
  if (window.hasRun) {
    console.log('already run');
    return;
  }

  window.hasRun = true;

  // defaults
  var options = {
    doThumb: false,
    doMaxWidth: true,
    valMaxWidth: "900px",
    doDetect: true,
    preventPreview: false,
    fullPreview: false,
    doAuthor: false,
    enableCss: false,
    bypassCSP: false,
    customCss: null,
    newTab: true
  };

  let xml_parser = new XMLSerializer();
  let html_parser = new DOMParser();

  function xhrdoc(url, type, cb) {
    let xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);

    xhr.responseType = 'document';
    xhr.overrideMimeType('text/' + type);

    xhr.onload = () => {
      if (xhr.readyState === xhr.DONE) {
        if (xhr.status === 200) {
          let resp = type == 'xml' ? xhr.responseXML : xhr.response;
          cb(resp);
        }
      }
    };

    xhr.send(null);
  }

  function applyxsl(xmlin, xsl, node, doc = document) {
    let xsltProcessor = new XSLTProcessor();
    xsltProcessor.importStylesheet(xsl);
    xsltProcessor.setParameter(null, 'fullPreview', options.fullPreview);
    xsltProcessor.setParameter(null, 'doAuthor', options.doAuthor);
    let fragment = xsltProcessor.transformToFragment(xmlin, doc);
    node.appendChild(fragment);
  }

  function getlang() {
    return browser.i18n.getUILanguage();
  }

  function formatsubtitle() {
    try {
      let feed_desc = document.getElementById('feedSubtitleRaw');

      let html_desc = html_parser.parseFromString(
        '<h2 id="feedSubtitleText">' + feed_desc.innerText + '</h2>',
        'text/html'
      );
      let xml_desc = xml_parser.serializeToString(html_desc.body.firstChild);

      feed_desc.insertAdjacentHTML('afterend', xml_desc);

      feed_desc.parentNode.removeChild(feed_desc);
    } catch (e) {
      console.error(e);
    }
  }

  function formatdescriptions(el = document) {
    // unescapes descriptions to html then to xml
    let tohtml = el.getElementsByClassName('feedRawContent');

    for (let i = 0; i < tohtml.length; i++) {

      try {
        let html_txt = '';
        if (tohtml[i].getAttribute('desctype') == 'text/plain') {
          html_txt = '<div class="feedEntryContent" style="white-space: pre-wrap;" >' + tohtml[i].innerHTML + '</div>';
        }
        else if (tohtml[i].getAttribute('desctype') == 'xhtml') {
          html_txt = '<div class="feedEntryContent">' + tohtml[i].innerHTML + '</div>';
        }
        else {
          html_txt = '<div class="feedEntryContent">' + tohtml[i].textContent + '</div>';
        }

        let html_desc = html_parser.parseFromString(html_txt, 'text/html');
        let xml_desc = xml_parser.serializeToString(
          html_desc.body.firstChild
        );

        tohtml[i].insertAdjacentHTML('afterend', xml_desc);
        tohtml[i].setAttribute('todel', 1);
      } catch (e) {
        console.error(e);
        console.log(tohtml[i]);
      }

    }

    el.querySelectorAll('.feedRawContent').forEach(a => {
      if (a.getAttribute('todel') == '1') {
        a.remove();
      }
    });
  }

  function removeemptyenclosures(el = document) {
    let encs = el.getElementsByClassName('enclosures');

    for (let i = 0; i < encs.length; i++)
      if (!encs[i].firstChild) encs[i].style.display = 'none';
  }

  function formatfilenames(el = document) {
    let encfn = el.getElementsByClassName('enclosureFilename');

    for (let i = 0; i < encfn.length; i++) {
      let url = new URL(encfn[i].innerText);

      if (url) {
        let fn = url.pathname.split('/').pop();

        if (fn != '') encfn[i].innerText = fn;
      }
    }
  }

  function formatfilesizes(el = document) {
    function humanfilesize(size) {
      let i = 0;

      if (size && size != '' && size > 0)
        i = Math.floor(Math.log(size) / Math.log(1024));

      return (
        (size / Math.pow(1024, i)).toFixed(2) * 1 +
        ' ' +
        ['B', 'kB', 'MB', 'GB', 'TB'][i]
      );
    }

    let encsz = el.getElementsByClassName('enclosureSize');
    for (let i = 0; i < encsz.length; i++) {
      let hsize = humanfilesize(encsz[i].innerText);

      if (hsize) encsz[i].innerText = hsize;
    }
  }

  function formattitles(el = document) {
    let et = el.getElementsByClassName('entrytitle');

    for (let i = 0; i < et.length; i++) {
      //basically removes html content if there is some
      //only do it if there's a tag to avoid doing it when text titles cointain a '&'
      //(which can be caught but still displays an error in console, which is annoying)
      if (et[i].innerText.indexOf('<') >= 0 || et[i].innerText.indexOf('&amp;')) {

        let tmp = document.createElement('span');
        try {
          tmp.innerHTML = et[i].innerText;
          et[i].innerText = tmp.textContent;
        } catch (e) {
          // if not parsable, display as text
          console.error(e);
          console.log(et[i].innerText);
        }
      }
    }
  }

  function formatdates(el = document) {
    let lang = getlang();
    if (!lang) return;

    let opts = {
      weekday: 'long',
      year: 'numeric',
      month: 'long',
      day: 'numeric',
    };

    let ed = el.getElementsByClassName('lastUpdated');
    for (let i = 0; i < ed.length; i++) {
      let d = new Date(ed[i].innerText);
      if (isNaN(d)) continue;

      let dstr =
        d.toLocaleDateString(lang, opts) + ' ' + d.toLocaleTimeString(lang);

      ed[i].innerText = dstr;
    }

    let lu = el.getElementById('feedLastUpdate');
    if (lu && lu.innerText.trim() != "") {
      lu.innerText = "Last updated: " + lu.innerText;
    }
  }

  function extensionimages(el = document) {
    let extimgs = el.getElementsByClassName('extImg');

    for (let i = 0; i < extimgs.length; i++)
      extimgs[i].src = chrome.runtime.getURL(
        extimgs[i].attributes['data-src'].nodeValue
      );
  }

  function applysettings() {

    document.querySelectorAll('.mediaThumb').forEach((elem) => {
      elem.style.display = options.doThumb ? "block" : "none";
    });


    document.querySelectorAll('img').forEach((elem) => {
      if (options.doMaxWidth)
        elem.style["max-width"] = options.valMaxWidth;
    });

  }

  function makepreviewhtml() {
    let doc = document.implementation.createHTMLDocument('');
    doc.body.id = "rsspreviewBody";

    let feedBody = doc.createElement('div');
    feedBody.id = 'feedBody';
    doc.body.appendChild(feedBody);

    let css = doc.createElement('link');
    css.setAttribute('rel', 'stylesheet');
    css.setAttribute('href', chrome.runtime.getURL('preview.css'));
    doc.head.appendChild(css);

    if (options.enableCss && options.customCss) {
      let node = doc.createElement('style');
      node.innerHTML = options.customCss;
      doc.head.appendChild(node);
    }

    return doc;
  }

  function detect() {
    let rootNode = document.getRootNode();

    // for chrome
    let d = document.getElementById('webkit-xml-viewer-source-xml');
    if (d && d.firstChild) rootNode = d.firstChild;

    const rootName = rootNode.documentElement.nodeName.toLowerCase();

    let isRSS1 = false;

    if (rootName == 'rdf' || rootName == 'rdf:rdf') {
      if (rootNode.documentElement.attributes['xmlns']) {
        isRSS1 = rootNode.documentElement.attributes['xmlns'].nodeValue.search('rss') > 0;
      }

    }

    if (
      rootName == 'rss' ||
      rootName == 'channel' || // rss2
      rootName == 'feed' || // atom
      isRSS1
    )
      return rootNode;

    return null;
  }

  function main(feedNode) {
    let feed_url = window.location.href;
    let preview = makepreviewhtml();

    xhrdoc(chrome.runtime.getURL('rss.xsl'), 'xml', xsl_xml => {
      applyxsl(feedNode, xsl_xml, preview.getElementById('feedBody'), preview);

      // replace the content with the preview document
      document.replaceChild(
        document.importNode(preview.documentElement, true),
        document.documentElement
      );

      let t0 = performance.now();

      formatsubtitle();

      formatdescriptions();
      removeemptyenclosures();
      formatfilenames();
      formatfilesizes();
      formattitles();
      formatdates();
      extensionimages();
      applysettings();

      let t1 = performance.now();
      //console.log("exec in: " + (t1 - t0) + "ms");

      document.title = document.getElementById('feedTitleText').innerText;
    });
  }


  function onOptions(opts) {
    options = opts;

    let feedRoot = detect();

    if (feedRoot && !options.preventPreview) {

      main(feedRoot);
    }

    else if (options.doDetect) {

      findFeeds();
    }

  }

  function onError(error) {
    console.log(`Error on get options: ${error}`);
  }

  let getting = browser.storage.sync.get(options);
  getting.then(onOptions, onError);


  function registerFeeds(feeds) {
    if (Object.keys(feeds).length > 0) {
      function handleResponse(message) {
      }

      function handleError(error) {
        //console.log(error);
      }

      browser.runtime.sendMessage(feeds).then(handleResponse, handleError);
    }
  }


  function findiTunesPodcastsFeeds() {
    let match = document.URL.match(/id(\d+)/)
    if (match) {
      let feeds = {};
      let itunesid = match[1];

      var xhr = new XMLHttpRequest();
      xhr.open('GET', "https://itunes.apple.com/lookup?id="+itunesid+"&entity=podcast");

      xhr.onload = function () {
        if (xhr.readyState === xhr.DONE) {
          if (xhr.status === 200) {
            let res = JSON.parse(xhr.responseText);

            if ("results" in res) {
              let pod = res["results"][0];
              let title = pod["collectionName"] || document.title;
              let url = pod["feedUrl"];
              if (url) {
                feeds[url] = title;
              }
            }
          }
        }

        registerFeeds(feeds);
      };
      xhr.send();
    }
  }

  function findYouTubeFeeds() {
    // YouTube's canonical channel URLs look like /channel/AlphaNumericID
    // It also supports named channels of the form /c/MyChannelName
    // and handle links of the form /@MyChannelHandle.
    // Match also on '%' to handle non-latin character codes
    // Match on both of these to autodetect channel feeds on either URL
    let idPattern = /channel\/([a-zA-Z0-9%_-]+)/;
    let namePattern = /(?:c|user)\/[a-zA-Z0-9%_-]+/;
    let handlePattern = /@[a-zA-Z0-9%_-]+/;
    let urlPattern = new RegExp(`${idPattern.source}|${namePattern.source}|${handlePattern.source}`);
    if (document.URL.match(urlPattern)) {
      let feeds = {};
      let canonicalUrl = document.querySelector("link[rel='canonical']").href;
      let channelId = canonicalUrl.match(idPattern)[1];
      let url = `https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`;
      let title = document.title;
      feeds[url] = title;
      registerFeeds(feeds);
    }
  }

  // The default function used to find feeds if a domain-specific function doesn't exist.
  // Parse the document's HTML looking for link tags pointing to the feed URL.
  function defaultFindFeeds() {
    let feeds = {};
    document.querySelectorAll("link[rel='alternate']").forEach( (elem) => {
      let type_attr = elem.getAttribute('type');
      if (!type_attr) {
        return;
      }

      let type = type_attr.toLowerCase();
      if (type.includes('rss') || type.includes('atom') || type.includes('feed')) {
        let title = elem.getAttribute('title');
        let url = elem.href;

        if (url) {
          feeds[url] = (title ? title : url);
        }
      }
    });
    registerFeeds(feeds);
  }

  const domainFeedFinders = new Map([
    ["itunes.apple.com", findiTunesPodcastsFeeds],
    ["podcasts.apple.com", findiTunesPodcastsFeeds],
    ["www.youtube.com", findYouTubeFeeds],
  ]);

  function findFeeds() {
    // Look up a feed detection function based on the domain.
    // If a domain-specific function doesn't exist, fall back to a default.
    let finder = domainFeedFinders.get(document.domain) || defaultFindFeeds;
    finder();
  }

})();
