// ==UserScript==
// @name        Flag Randomizer
// @namespace   http://ponepaste.org/5555
// @match       https://boards.4channel.org/mlp/thread/*
// @match       https://boards.4chan.org/mlp/thread/*
// @grant       none
// @version     0.2.3
// @author      Fillyanon, ScriptFilly, RandomSchmuck, Marker
// @description Adds a flag randomizer to the Board.
// ==/UserScript==

"use strict";

// Possible values are: Submit, Keystroke, Timer
const changeFlagOn = "Submit";

const flags = Object.freeze({
  MISC: ["4CC", "AN"],

  G4: [
    "AJ",
    "ANF",
    "APB",
    "AU",
    "BB",
    "BM",
    "BP",
    "BS",
    "CB",
    "CG",
    "CHE",
    "CL",
    "CO",
    "DAY",
    "DD",
    "DER",
    "DIS",
    "DT",
    "FAU",
    "FL",
    "FLE",
    "GI",
    "LI",
    "LT",
    "LY",
    "MA",
    "MAU",
    "MIN",
    "NI",
    "NUR",
    "OCT",
    "PAR",
    "PC",
    "PCE",
    "PI",
    "PLU",
    "PM",
    "QC",
    "RAR",
    "RD",
    "RLU",
    "S1L",
    "SCO",
    "SHI",
    "SIL",
    "SP",
    "SPI",
    "STA",
    "STL",
    "SUN",
    "SUS",
    "SWB",
    "TS",
    "TWI",
    "TX",
    "VS",
    "ZE",
  ],

  G5: ["HT", "IZ", "PP", "SPT", "ZS", "SS"],

  EQG: [
    "ADA",
    "AB",
    "SON",
    "EQA",
    "EQF",
    "EQP",
    "EQR",
    "EQT",
    "EQI",
    "EQS",
    "ERA",
  ],

  TFH: ["TFA", "TFO", "TFP", "TFS", "TFT", "TFV", "TP"],
});

const allFlags = Object.values(flags).flat();

const getRand = (coll) => {
  return coll[Math.floor(Math.random() * coll.length)];
};

const getPost = () => {
  const use4chanX = document.getElementById("qr");
  const quickReply = document.getElementById("qrForm");
  const qrForm = use4chanX ?? quickReply;
  return qrForm ?? document.forms.post;
};

const changeFlag = () => {
  if (selector.value == "OFF") return;
  const post = getPost();
  const flagSelector = post.querySelector(".flagSelector");
  const origValue = flagSelector.value;
  flagSelector.value = (selector.value != "ALL") ? getRand(flags[selector.value]) : getRand(allFlags);

  // Workaround for 4chanX issue where its flag selector is 3 missing flags.
  // Attempting to assign a Selector to an option that doesn't exist would
  // reset it's value to "".
  // When this happens, we restore it to its initial value.
  if (flagSelector.value == "") flagSelector.value = origValue;
};

const makeOpt = (option) => {
  const opt = document.createElement("option");
  opt.value = opt.innerText = option;
  selector.appendChild(opt);
};

const selector = document.createElement('select');
const botLine = document.querySelector('.navLinksBot');
selector.style.marginLeft = '1rem';
Object.keys(flags).forEach(key => makeOpt(key));
makeOpt('ALL');
makeOpt('OFF');
selector.addEventListener('change', () => {
  window.localStorage.setItem("flagGroup", selector.value);
});
selector.value = window.localStorage.getItem("flagGroup") ?? "G4";
botLine.appendChild(selector);

// Setup Randomizer Functions

const creationObserver = (function () {
    const observedNodes = [];
    const callbacks = [];
    const executeCallback = (fn, node) => {
        if (observedNodes.includes(node))
            return;
        observedNodes.push(node);
        fn(node);
    };
    const obs = new MutationObserver(mutationRecords => {
        mutationRecords.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                if (!(node instanceof HTMLElement))
                    return;
                callbacks.forEach(([selector, fn]) => {
                    if (node.matches(selector))
                        executeCallback(fn, node);
                    node.querySelectorAll(selector).forEach(childNode => executeCallback(fn, childNode));
                });
            });
        });
    });
    obs.observe(document.body, { childList: true, subtree: true });
    return function (selector, fn) {
        document.querySelectorAll(selector).forEach(node => executeCallback(fn, node));
        callbacks.push([selector, fn]);
    };
})();

const selectors = [
    'form[name="post"]',   // 4chan default post
    'form[name="qrPost"]', // 4chanNX quick reply form
    'div#qr form',         // 4chanX quick reply form
].join(', ');

// Keystroke
function debounce(func, wait, immediate) {
  let timeout;
  return function () {
    const context = this, args = arguments;
    const later = function () {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };
    const callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func.apply(context, args);
  };
}
const changeAfterKeystroke = debounce(changeFlag, 250, false);
const config = {attributes: true, childList: true, subtree: true};

// Randomize the flag
if (changeFlagOn === "Keystroke") {
  const keystrokeHandler = function() {
    const is4chanXQrActive = document.getElementById("qr");
    const post = getPost();
                                                           
    // update the flag
    const reply = (is4chanXQrActive)
      ? post.querySelector("textarea[data-name=\"com\"]")
      : post.querySelector("textarea[name=\"com\"]");
    reply.addEventListener("input", changeAfterKeystroke);
  }
  creationObserver(selectors, form => {
      if (!(form instanceof HTMLFormElement))
          return;
      keystrokeHandler();
  });
} else if (changeFlagOn === "Timer") {
  setInterval(() => {
    changeFlag();
  }, 500);
} else if (changeFlagOn === "Submit") {
  const submitHandler = (event) => {
      const target = event.target;
      if (!target.matches('input[type="submit"]'))
          return;
      const form = event.currentTarget;
      changeFlag(form);
  }
  creationObserver(selectors, form => {
      if (!(form instanceof HTMLFormElement))
          return;
      form.addEventListener('click', submitHandler, { capture: true });
  });
}