const explainerText = "The expected maximum: the expected value of the maximum sample (averaged over many runs).";

const g = x => x.querySelector.bind(x);
const $ = g(document);

const range = n => {
  let res = [];
  for (let i = 1; i <= n; i++) {
    res.push(i);
  }
  return res;
};

const counts = range(9).map(c => `.count-${c}`);

const randShift = _ => shifts[Math.floor(shifts.length * Math.random())];

const parentBoard = x =>
  /\bpegboard\b/.test(x.className) ? x : parentBoard(x.parentNode);

const incrs = count => e => {
  if (e.animationName != "bounceY") return;
  e.target.remove();
  e.target.removeEventListener("animationend", incrs);
  count.innerHTML = +count.innerHTML + 1;
  count.classList.add("hit");

  const max = g(parentBoard(count))(".max");
  const c = +count.nextElementSibling.innerText.match(/.?\d+\.?\d*/)[0];
  max.innerHTML = max.innerHTML === "" ? c : Math.max(max.innerHTML, c);
};

const maxListener = (onRemove, onAdd) => rs => {
  [].forEach.call(rs, r => {
    r.removedNodes.length && onRemove(r.target);
    r.addedNodes[0] &&
      onAdd(r.target, Math.round((+r.addedNodes[0].data * 3) / 2));
  });
};

const colorize = maxListener(
  max => max.classList.remove("positive", "negative"),
  (max, d) =>
    (d > 0 && max.classList.add("positive")) ||
    (d < 0 && max.classList.add("negative"))
);
const colorListener = new MutationObserver(colorize);

const σs = max => [].slice.call(parentBoard(max).querySelectorAll(".sigma"));
const off = max => +parentBoard(max).className.match(/offset(.?\d)/)[1];
const progress = maxListener(
  max => σs(max).forEach(e => e.classList.remove("achieved")),
  (max, d) =>
    σs(max)
      .slice(0, d + 5 - off(max))
      .forEach(e => e.classList.add("achieved"))
);

const progListener = new MutationObserver(progress);
const mutObs = max =>
  [colorListener, progListener].forEach(l =>
    l.observe(max, { childList: true })
  );

const createBall = pb => s => {
  let b = document.createElement("div");
  b.classList.add("ball");
  b.style.animation = `bounceX${s
    .toString()
    .replace(/,/g, "")
    .replace("-", "m")} 1.2s linear forwards`;
  b.addEventListener(
    "animationend",
    incrs(g(pb)(counts[(s[s.length - 1] + 8) / 2]))
  );
  return b;
};

const updateSigmas = (e, offset) => {
  σText = range(9)
    .map(i => ((i + offset - 5) * 2) / 3)
    .map(i => (i === 0 ? "0" : i.toFixed(1) + "σ"));
  [].forEach.call(
    e.querySelectorAll(".sigma"),
, i) =>.innerText = σText[i])
  );
};

const newBoard = (b => {
  const d = document.createElement("div");
  return (c, offset) => {
    var offset = offset || 0;
    d.innerHTML = b;
    const e = d.firstChild;
    e.classList.remove("offset0");
    e.classList.add(`offset${offset}`);
    updateSigmas(e, offset);
    e.id = c;
    mutObs(g(e)(".max"));
    return e;
  };
})($(".pegboard").outerHTML);

const state = {};

const toOffset = x => Math.round((x * 3) / 2);

const toggleVis = e => evt => {
  evt.preventDefault();
  e.classList[e.classList.contains("no-show") ? "remove" : "add"]("no-show");
};

const asterisk = explainer => {
  const e = document.createElement("a");
  e.href = "#";
  e.innerText = "*";
  e.onclick = toggleVis(explainer);
  return e;
};

const createBar = x => {
  const d = document.createElement("div");
  d.classList.add("bar");
  d.style.left = `${x}em`;

  const explainer = document.createElement("div");
  explainer.innerText = explainerText;
  explainer.classList.add("no-show", "explainer");

  const s = document.createElement("span");
  s.innerText = "Expected maximum";
  s.classList.add("bar-text");

  s.appendChild(asterisk(explainer));

  d.append(s, explainer);
  return d;
};

const sdsToEm = sds => ((sds * 3) / 2) * 8 + 35.7;

const addRow = (ss, lor) => {
  $(`#${lor ? "left" : "right"}-runs tbody`).innerHTML += `<tr><td>${ss}</td>${
    lor
      ? ""
      : `<td>${+$("#rboards .pegboard:last-child").id.match(/\d+/)[0]}</td>`
  }<td>${+$(`#${lor ? "l" : "r"}boards .pegboard:last-child .max`)
    .innerHTML}</td></tr>`;
};

const runNext = (brd, es, ss) => {
  const id = brd.id.replace(/\d/, n => +n + 1);
  const n = newBoard(id, toOffset(g(brd)(".max").innerText));
  brd.parentNode.appendChild(n);
  run(`#${id}`, es, ss);
};

const run = (brd, es, ss) => {
  state[brd] = state[brd] || {};
  const s = state[brd];
  s.timeouts = s.timeouts || [];
  s.bs = s.bs || [];
  const pb = $(brd);

  // cleanup
  s.timeouts.forEach(clearTimeout);
  counts.forEach(c => {
    g(pb)(c).innerHTML = 0;
    g(pb)(c).classList.remove("hit");
  });
  s.bs.forEach(el => el.remove());
  g(pb)(".max").innerHTML = "";

  // again
  const shifts = range(es).map(randShift);
  s.bs = shifts.map(createBall(pb));
  s.bs.forEach((c, i) => {
    s.timeouts.push(setTimeout(_ => pb.prepend(c), i * 150));
  });
  const cb = evt => {
    evt.target.removeEventListener("animationend", cb);

    s.timeouts.push(
      setTimeout(
        _ => (ss != null && ss > 1 ? runNext(pb, es, ss - 1) : addRow(es, !ss)),
        0
      )
    );
  };
  s.bs[s.bs.length - 1].addEventListener("animationend", cb);
};

$("#beanputs").addEventListener("submit", e => {
  e.preventDefault();
  $("#rboards").innerHTML = "";
  $("#rboards").appendChild(newBoard("pegboard-right-1"));

  [].forEach.call(document.querySelectorAll(".bar"), e => e.remove());
  $("#lboards").prepend(
    createBar(sdsToEm(snlook[+$("[name=beans]").value + 1]))
  );
  $("#rboards").prepend(
    createBar(
      sdsToEm($("[name=stages]").value * snlook[+$("[name=per]").value + 1])
    )
  );
  run("#pegboard-left", $("[name=beans]").value);
  run("#pegboard-right-1", $("[name=per]").value, $("[name=stages]").value);
});

mutObs($(".max"));