// For reference:
// expanded quotes:  qctoggled = true
// collapsed quotes: qctoggled = false

var QuoteCollapse = {
  oMsgBody: null,
  quoteLevelsRequested: 0,
  quoteLevelsInMessage: 0,

  _onLoad: function() {
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_onLoad]");

    QuoteCollapse._getQCPrefs();
    QuoteCollapse._generateStyleBlock();

    // the following commands API listener works in 3-pane-window, 
    // even when the focus is not explicitly in message pane
    browser.runtime.onMessage.addListener(command => {
      QuoteCollapse._onCommand(command);
      // return Promise.resolve({response: "Hi from content script"});
    });
  },

  _generateStyleBlock: function() {
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_generateStyleBlock]");
    
    if (this.bPrefEnableQuoteCollapse) {
      this.oMsgBody = document.body;
      consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_generateStyleBlock] document.body" +  document.body);
      if (!this.oMsgBody.getElementsByTagName("blockquote").item(0))
        return; // nothing to be done

      consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_generateStyleBlock]: oMsgBody is existing");
      this.oMsgBody.addEventListener("click", QuoteCollapse._onClick, false);

      // In theory the following command listener should not be necessary, when using the commands API. 
      // There are 2 blocking issues:
      // 1.) The commands API doesn't find standalone message windows (circumvented by another approach).
      // 2.) The browser.runtime messaging system fails for standalone windows (Bug 1768468).
      // Therefore the follwing listener is additionally necessary. If the messaging system will be working 
      // as intended in future, the following can be removed and a config option could be implemented in the 
      // options dialog, to redifine the commandkey by the user.

      /* ************ no longer necessary ********************************** */
      /*
      // This listener is working only, when the focus _is_ in message pane
      this.oMsgBody.addEventListener('keydown', function (event) {
        if (event.altKey && event.key === 'q') {
          event.preventDefault();
          consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] event.altKey + event.key=q");
          QuoteCollapse._onCommand("");
        }
      });
      */
      /* ******************************************************************* */

      var sStyleContent = '\
        blockquote[type="cite"] {\n\
          background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAB3RJTUUH1AsREDArtdKZsQAAAAlwSFlzAABOIAAATiABFn2Z3gAAAARnQU1BAACxjwv8YQUAAAC7SURBVHjabZBPCoJAFMbfG1wFQW2SFt2pW1RnyCN4AC/QqmNItBfaS2BYCwvBCdSZrxlzLKLf6vHx/RmGg/CwJNIRMfv0A4hyBq0oCOP8fCnh0BpoFCBb4JQ+sA3jTLBgfzEfv5MmKgSTJ4hqBZpORmQWZmKoNgajD8hnS5Vsu9v7bnAwcx/Ex2QbnGAN7nZ0psZoVY1uwnFMCpL9nIDGNc3K4Q2uxRqKu7QTN9OE9W6fRPzvn4Bckdq8AE3sc/7yC/yTAAAAAElFTkSuQmCC");\n\
          background-repeat: no-repeat;\n\
          background-position: top left;\n\
          max-height: 1.4em; /* 1lh */\n\
          padding: 1ex !important;\n\
          overflow: hidden;\n\
        }\n\
        \n\
        blockquote[type="cite"][qctoggled="true"] {\n\
          background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAB3RJTUUH1AsREDAbkwupHQAAAAlwSFlzAABOIAAATiABFn2Z3gAAAARnQU1BAACxjwv8YQUAAACpSURBVHjaY6zpORrAwPBvBgMjozgDGvjPwPCC8T9DOgtIgZ62rLggPycDOzszAwcHDLMwfPj4TWLjtuvTWUAmYFMAohX4BRiA8mJMIGOxKeAAirGzgaUZWEAESNBMW5ABw03//0MUMUIVwQSwAaCbGMBWPH73C24FGxMjAyvQJiaoIqb///6//PTpO1YFj559Atn5Cuim/xnrt16bwYgtnP7/f/GX4W8mAH/oMd/d5kSRAAAAAElFTkSuQmCC");\n\
          max-height: none;\n\
          overflow: visible;\n\
        }\n\
      ';
      if (this.bPrefQuoteCollapseShowFewLines == true) {
        consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_generateStyleBlock] bPrefQuoteCollapseShowFewLines == true");

        sStyleContent += '\
        \n\
        /* Establish predictable line-height */\n\
        :root, html, html > body {\n\
          line-height: 1.4;\n\
        }\n\
        \n\
        /* Show a few extra lines of the first collapsed quote level */\n\
        blockquote[type="cite"]:not([qctoggled="true"]) {\n\
          max-height: 7em !important;\n\
        }\n\
        \n\
        /* Nested collapsed quotes show just one line of text */\n\
          blockquote[type="cite"]:not([qctoggled="true"])\n\
          blockquote[type="cite"]:not([qctoggled="true"]) {\n\
          max-height: 1.4em !important; /* 1lh */\n\
        }\n\
      ';
      }

      var qrStyle = document.createElement('style');
      qrStyle.classList.add('quotecollapse_msgDisplay');
      //  qrStyle.media = 'screen';
      qrStyle.textContent = sStyleContent;
      if (document.head) {
        document.head.append(qrStyle);
      } else {
        var root = document.documentElement;
        root.append(qrStyle);
        var observer = new MutationObserver(() => {
          if (document.head) {
            observer.disconnect();
            if (qrStyle.isConnected) {
              document.head.append(qrStyle);
            }
          }
        });
        observer.observe(root, {
          childList: true
        });
      }

      // Collapse all quotes, when option is enabled
      consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js]: bPrefQuoteCollapseByDefault = " + this.bPrefQuoteCollapseByDefault + ", bPrefQuoteCollapseOnlySubquotes = " + this.bPrefQuoteCollapseOnlySubquotes);
      /*
      for (let quote of QuoteCollapse._getQuoteRoots(this.oMsgBody)) {
        QuoteCollapse._toggleFullyVisible(quote);
      }
      */

      // Use the following lines instead of the lines above, to respect the collapse default setting
      QuoteCollapse._setSubTree(this.oMsgBody, !this.bPrefQuoteCollapseByDefault, this.bPrefQuoteCollapseOnlySubquotes, 1, 0);

      if (!this.bPrefQuoteCollapseByDefault)
        this.quoteLevelsRequested = this.quoteLevelsInMessage;

      consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js]: _generateStyleBlock this.quoteLevelsInMessage = ", this.quoteLevelsInMessage);
      consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js]: _generateStyleBlock this.quoteLevelsRequested = ", this.quoteLevelsRequested);
    }
  },

  _getQCPrefs : function()
  {
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js]: QuoteCollapse._getQCPrefs");

    // ########################################################################
    // set value from user prefs
    this.bPrefEnableQuoteCollapse = options.enableQuoteCollapse;
    this.bPrefQuoteCollapseShowFewLines = options.quoteCollapseShowFewLines;
    this.bPrefQuoteCollapseByDefault = options.quoteCollapseByDefault;
    this.bPrefQuoteCollapseOnlySubquotes = options.quoteCollapseOnlySubquotes;
  },

  _toggleFullyVisible: function toggleFullyVisible(quote) {
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_toggleFullyVisible]");

    if (quote.clientHeight < quote.scrollHeight) {
      consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_toggleFullyVisible]: quote.clientHeight < quote.scrollHeight");
      return false;
    }

    for (let nested of QuoteCollapse._getQuoteRoots(quote)) {
      if (!toggleFullyVisible(nested))
        return false;
    }
    quote.setAttribute("qctoggled", "true");
    return true;
},

  _getState: function(node) {
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_getState]");

    let current = node;
    while (current) {
      if (current.nodeName == "BLOCKQUOTE" && current.getAttribute(
          "qctoggled") != "true")
        return false;

      current = current.parentNode
    }
    return true;
  },

  _setState: function(node, state, bubble) {
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_setState]");

    if (state)
      node.setAttribute("qctoggled", "true");
    else
      node.setAttribute("qctoggled", "false");

    if (bubble) {
      var currentParent = node.parentNode;
      while (currentParent) {
        if (currentParent.nodeName == 'BLOCKQUOTE')
          QuoteCollapse._setState(currentParent, state);

        currentParent = currentParent.parentNode;
      }
    }
  },

  _setSubTree: function(node, state, onlySubquotes, depth, quoteDepth) {
    // depth:                     is the DOM element depth
    // quoteDepth:                is the "BLOCKQUOTE" element depth (and thus, the quote level depth)
    // this.quoteLevelsInMessage: is the maximum quote levels depth (by multiple recursions in this function)
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_setSubTree] depth = ", depth);
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_setSubTree] quoteDepth = ", quoteDepth);

    var value = state;
    // If we get here, when the message is loaded, then onlySubquotes should be honored
    if (onlySubquotes && value == false)
      this.quoteLevelsRequested = 1;

    // this.quoteLevelsRequested + 2 is because of the 2 parent elements <html> and <body>
    if (value == false && depth <= (this.quoteLevelsRequested + 2))
      value = true;

    if (node.nodeName == 'BLOCKQUOTE') {
      QuoteCollapse._setState(node, value);
      quoteDepth++;
      if (this.quoteLevelsInMessage <= quoteDepth)
        this.quoteLevelsInMessage = quoteDepth;
    }

    for (var i = 0; i < node.childNodes.length; i++) {
      QuoteCollapse._setSubTree(node.childNodes.item(i), state, onlySubquotes, depth + 1, quoteDepth);
      consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_setSubTree] nodeName = ", node.nodeName);
      consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_setSubTree] depth_inner = ", depth);
    }
  },

  _setSubTreeLevel: function(node, state, level) {
    if (node.nodeName == 'BLOCKQUOTE') {
      if (level <= 0) {
        QuoteCollapse._setState(node, state, state);
        if (state)
          for (let nested of node.querySelectorAll("blockquote")) {
            QuoteCollapse._toggleFullyVisible(nested);
          }

        return; // no need to go deeper
      }
      level--; // only BQs count for the level magic
    }
    for (var i = 0; i < node.childNodes.length; i++) {
      QuoteCollapse._setSubTreeLevel(node.childNodes.item(i), state, level);
    }
  },

  // we could use subtree on BODY, but the following is more efficient
  _setTree: function(doc, newstate) {
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_setTree] newstate = " + newstate);

    var tree = doc.getElementsByTagName("blockquote");
    for (var i = 0; i < tree.length; i++)
      QuoteCollapse._setState(tree.item(i), newstate);
  },

  _setLevel: function(target, newstate) {
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_setLevel]");

    var level = 0;
    var node = target;
    do {
      node = node.parentNode;
      if (node.nodeName == 'BLOCKQUOTE')
        level++;
    } while (node.nodeName != 'BODY');
    QuoteCollapse._setSubTreeLevel(node, newstate,
    level); // node is the BODY element
  },

  // this is called by the commandkey
  _onCommand: function(command) {
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_onCommand]");
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_onCommand] command =", command);

    // var target = document.body;
    var target = QuoteCollapse.oMsgBody;
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_onCommand] document.body:", target)

    if (target) {
      if (!target.getElementsByTagName("blockquote").item(0))
        return; // nothing to be done
  
      switch(command) {
        case "toggle-all-quotes":
          var isCollapsed = !QuoteCollapse._getState(target.getElementsByTagName("blockquote").item(0));
          consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_onCommand] isCollapsed = " + isCollapsed);

          if (isCollapsed === true) {
            for (let quote of target.querySelectorAll("blockquote")) {
              quote.setAttribute("qctoggled", "true");
            }
            // How much levels has this message? We need to detect this to set
            // this.quoteLevelsRequested = the maximum levels (because they are all open);
            this.quoteLevelsRequested = this.quoteLevelsInMessage;
            return;
          } else {
            for (let quote of target.querySelectorAll("blockquote")) {
              quote.setAttribute("qctoggled", "false");
            }
            // All quotes collapsed, so this.quoteLevelsRequested = 0
            this.quoteLevelsRequested = 0;
            return;
          }
          break;
        
        case "expand-one-more-level":
          consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_onCommand] prior quoteLevel = ", this.quoteLevelsRequested);
          this.quoteLevelsRequested++;
          if (this.quoteLevelsRequested >= this.quoteLevelsInMessage)
            this.quoteLevelsRequested = this.quoteLevelsInMessage;
          consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_onCommand] quoteLevelsRequested++ (Level shown)= ", this.quoteLevelsRequested);

          QuoteCollapse._setSubTree(this.oMsgBody, false, false, 1, 0);
          break;
          
        case "collapse-one-more-level":
          consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_onCommand] prior quoteLevel = ", this.quoteLevelsRequested);
          this.quoteLevelsRequested--;
          if (this.quoteLevelsRequested <= 0)
            this.quoteLevelsRequested = 0;
          consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_onCommand] quoteLevelsRequested-- (Level shown) = ", this.quoteLevelsRequested);

          QuoteCollapse._setSubTree(this.oMsgBody, false, false, 1, 0);
          break;

        }
    }
  },

  // this is called by a click event
  _onClick: function(event) {
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_onClick]");

    var target = event.target;
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_onClick] target: ",  event.target);
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_onClick] target.nodeName: ",  event.target.nodeName);

    if (target.nodeName == 'SPAN')
      target = target.parentNode; // cite-tags span?
    if (target.nodeName == 'PRE')
      target = target.parentNode; // PRE inside; don't walk all the way up
    if (target.nodeName != 'BLOCKQUOTE')
      return true;

    var newstate = !QuoteCollapse._getState(target);


    // react only to active spot (leave rest for copy etc.)
    if (event.pageX > target.offsetLeft + 12) return true;

    // What the modifier keys do:
    // Shift key: open/collapse all sub quotes in the tree
    // Ctrl or Meta key: open/collapse all subquotes (not ony the one tree)
    if (event.shiftKey)
      if (event.ctrlKey || event.metaKey)
        QuoteCollapse._setTree(target.ownerDocument, newstate);
      else
        QuoteCollapse._setSubTree(target, newstate, false, 1);
    else if (event.ctrlKey || event.metaKey)
      QuoteCollapse._setLevel(target, newstate);
    else {
      QuoteCollapse._setState(target, newstate, newstate);
      if (newstate)
        for (let nested of target.querySelectorAll("blockquote")) {
          QuoteCollapse._toggleFullyVisible(nested);
        }
    }
    return true;
  },

  _getQuoteRoots: function getQuoteRoots(node, result = []) {
    consoleDebug("[QuoteColors] [quotecollapse_msgDisplay.js] [_getQuoteRoots]");

    for (let childElement of node.children) {
      if (childElement.localName == "blockquote")
        result.push(childElement);
      else
        getQuoteRoots(childElement, result);
    }
    return result;
  },

};

QuoteCollapse._onLoad();
