// Rot.js
// one rotten apple spoils the bunch

(() => {
  //Helper functions
  function getPromiseFromEvent(item, event) {
      return new Promise((resolve) => {
          const listener = () => {
              item.removeEventListener(event, listener);
              resolve();
          }
          item.addEventListener(event, listener);
      });
  }

  function sex(sex) {
      return sex // Sex
  }

  function rand(list) {
      return list ? list[list.length * Math.random() | 0] : null;
  }

  //Music functions
  function setMusic(url) {
      if (audio.src == url || (!url && audio.src === window.location.toString()))
          return;

      audio.pause();

      if (url) {
          console.log("Setting music: " + url);
          audio.src = url;
          playMusic();
      } else {
          // This line will cause a lot of errors.
          // Setting src to "" will cause any pending .play()s to fail.
          audio.src = "";
      }
  }

  function playMusic() {
      //Starts playing the music if it isn't muted and isn't already playing
      if (audio.src && audio.src != "" && audio.paused && !audio.muted)
          audio.play().catch(() => getPromiseFromEvent(window, 'click').then(audio.play));
  }

  function volumeSet(number) {
      audio.volume = number; //Set
      localStorage.audiovolume = number; //Save
      updateVolumeLabel();
  }
  function volumeAdd(number) {
      volumeSet(Math.min(1, Math.max(0, audio.volume + number)));
  }
  function updateVolumeLabel() {
      const percentage = `${Math.round(audio.volume * 100)}%`;
      const txtElm = document.querySelector('.volume-percentage');
      if (txtElm)
          txtElm.innerHTML = percentage;

      const slider = document.querySelector('.volume-slider');
      if (slider) {
          const thumb = slider.querySelector('.volume-thumb');
          const fill = slider.querySelector('.volume-fill');
          if (thumb) thumb.style.left = percentage;
          if (fill) fill.style.width = percentage;
      }
  }

  //Registers a mutation observer for an element. Upon triggering and the callback
  //returning true, observation ceases. All observers are disconnected and the array is cleared.
  const observers = [];
  function waitUntilSpecial(selector, callback) {
      const newObserver = new MutationObserver((mutationRecords, observer) => {
          // Query for elements matching the specified selector
          Array.from(document.querySelectorAll(selector)).forEach((element) => {
              //Callback
              if (!callback(element))
                  return;

              //Clean up
              console.log("Cleaning up");
              for (const o of observers)
                  o.disconnect();
              observers.length = 0;
          });
      });
      observers.push(newObserver);
      newObserver.observe(document.documentElement, {
          childList: true,
          subtree: true
      });
  }

  //Theme application
  function applyMainTheme() {
      console.log("Applying main theme");
      setMusic(null);

      //<meta name="pageMusic" content="/assets/music.ogg">
      waitUntilSpecial('meta[name="pageMusic"]', (pageMusic) => {
          setMusic(pageMusic.content);
          playMusic();
          return true;
      });

      //<a id="pageMusic" href="/assets/music.ogg"></a>
      waitUntilSpecial("#pageMusic", (pageMusic) => {
          setMusic(pageMusic.getAttribute("href"));
          playMusic();
          return true;
      });
  }

  function applyUserTheme() {
      console.log("Applying user theme");
      setMusic(null);

      //Configure by post
      waitUntilSpecial(".pin", (pinnedPost) => {
          let p = pinnedPost.nextElementSibling;
          if (!p)
              return false;

          let statusBody = p.querySelector(".StatusBody");
          if (!statusBody)
              return false;

          let txtElm = statusBody.querySelector(".text");
          if (!txtElm)
              return false;

          if (txtElm.innerHTML.replace(/(<([^>]+)>)/ig, '').search(/profile them/ig) == -1)
              return false;

          const ptp = pinnedPost.nextElementSibling.querySelector(".StatusBody");
          if (!ptp)
              return false;

          const musicContainer = ptp.querySelector(".audio-container");
          if (musicContainer) {
              setMusic(rand(musicContainer.children).src);
              return true;
          } else {
              const placeholderContainer = ptp.querySelector(".placeholder-container");
              if (placeholderContainer && placeholderContainer.href.endsWith(".m4a")) {
                  setMusic(placeholderContainer.href);
                  return true;
              } else {
                  setMusic(null);
                  return false;
              }
          }
      });

      //Configure by fields
      waitUntilSpecial(".user-profile-field-name", () => {
          const fields = [...document.getElementsByClassName("user-profile-field-name")]
          if (fields.length == 0)
              return false;

          const musicFields = fields.filter((x) => x.title.toLowerCase().replace(/\s+/g, '') == "music")
          if (musicFields.length > 0) {
              setMusic(rand(musicFields).nextElementSibling.title);
              return true;
          } else {
              setMusic(null);
              return false;
          }
      });
  }

  //Switch-based monkey patching router bullshit
  let lastPath = null;
  function updateRot() {
      try {
          let newPath = window.location.pathname;
          if (lastPath == newPath)
              return;
          lastPath = newPath;

          let pathSpl = newPath.split("/");
          switch (pathSpl.length) {
              case 1:
                  applyMainTheme(); //Root
                  break;

              case 2:
                  switch (pathSpl[1]) {
                      case "about":
                      case "announcements":
                      case "lists":
                      case "bookmarks":
                          applyMainTheme();
                          break;

                      default:
                          applyUserTheme();
                          break;
                  }
                  break;

              case 3:
                  switch (pathSpl[1]) {
                      case "main":
                          applyMainTheme(); //Main timelines
                          break;

                      case "users":
                          applyUserTheme();
                          break;

                      case "notice":
                          applyMainTheme(); //Statuses
                          break;

                      default:
                          applyMainTheme();
                          break;
                  }
                  break;

              default:
                  applyMainTheme();
                  break;
          }
      } catch (e) {
          console.error(e);
      }
  }

  //Rot music player
  const audio = document.createElement("audio");
  audio.loop = true;
  audio.id = "user-music";
  audio.style = "display:none;";
  if (localStorage.audiovolume && localStorage.audiovolume >= 0 && localStorage.audiovolume <= 1)
      audio.volume = localStorage.audiovolume; //Load volume
  else
      audio.volume = 0.2; //Default volume

  //Monkey patches and event listeners
  const oldPushState = history.pushState;
  history.pushState = function pushState() {
      const ret = oldPushState.apply(this, arguments);
      updateRot();
      return ret;
  };

  const oldReplaceState = history.replaceState;
  history.replaceState = function replaceState() {
      const ret = oldReplaceState.apply(this, arguments);
      updateRot();
      return ret;
  };

  addEventListener('locationchange', updateRot);
  addEventListener('popstate', updateRot);

  function createAudioControls() {
      let NavPanel = document.querySelector('.NavPanel');
      let sideDrawer = document.querySelector('.side-drawer');
      if (!NavPanel && !sideDrawer)
          return;

      if (document.querySelector('.audioControl'))
          return;

      //Initialize audio controls and event listeners
      console.log("Adding music controls");
      const panel = document.createElement("div");
      panel.className = "panel panel-default";

          const panelHeading = document.createElement("div");
          panelHeading.className = "panel-heading";
          panel.appendChild(panelHeading);

              const title = document.createElement("div");
              title.className = "title";
              title.innerText = "Music Controls";
              panelHeading.appendChild(title);

          const panelBody = document.createElement("div");
          panelBody.className = "panel-body";
          panel.appendChild(panelBody);

              const audioControl = document.createElement("div");
              audioControl.className = "audioControl";
              panelBody.appendChild(audioControl);

                  const mutebutton = document.createElement("label");
                  mutebutton.className = "mutebutton";
                  audioControl.appendChild(mutebutton);

                  const musicmute = document.createElement("input");
                  musicmute.className = "music-mute";
                  musicmute.setAttribute("type", "checkbox");
                  musicmute.checked = true;
                  mutebutton.appendChild(musicmute);

                  const mutecheck = document.createElement("span");
                  mutecheck.className = "mutecheck";
                  mutecheck.title = "Mute music";
                  mutebutton.appendChild(mutecheck);

              const volumeSlider = document.createElement("div");
              volumeSlider.className = "volume-slider";
              audioControl.appendChild(volumeSlider);

                  const volumeTrack = document.createElement("div");
                  volumeTrack.className = "volume-track";
                  volumeSlider.appendChild(volumeTrack);

                      const volumeFill = document.createElement("div");
                      volumeFill.className = "volume-fill";
                      volumeTrack.appendChild(volumeFill);

                  const volumeThumb = document.createElement("div");
                  volumeThumb.className = "volume-thumb";
                  volumeSlider.appendChild(volumeThumb);

              const volumePercentage = document.createElement("div");
              volumePercentage.className = "volume-percentage";
              audioControl.appendChild(volumePercentage);

      if (NavPanel)
          NavPanel.insertAdjacentElement('afterend', panel);
      else if (sideDrawer)
          sideDrawer.insertAdjacentElement('beforeend', audioControl);

      {
          let isDragging = false;

          function updateVolume(e) {
              const rect = volumeSlider.getBoundingClientRect();
              let x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
              let percentage = (x / rect.width) * 100;

              // Update actual volume
              volumeSet(percentage / 100);
          }

          function onMouseDown(e) {
              isDragging = true;
              updateVolume(e);
              document.addEventListener('mousemove', onMouseMove);
              document.addEventListener('mouseup', onMouseUp);
          }

          function onMouseMove(e) {
              if (isDragging) {
                  updateVolume(e);
              }
          }

          function onMouseUp() {
              isDragging = false;
              document.removeEventListener('mousemove', onMouseMove);
              document.removeEventListener('mouseup', onMouseUp);
          }

          volumeSlider.addEventListener('mousedown', onMouseDown);
      }

      audio.muted = musicmute.checked = localStorage.audiomuted === "true";
      musicmute.addEventListener('click', () => {
          localStorage.audiomuted = audio.muted = musicmute.checked;
          playMusic();
      })

      updateVolumeLabel();
  }

  createAudioControls();
  window.addEventListener('resize', createAudioControls);

  new MutationObserver((mutationRecords, observer) => {
      createAudioControls();
  }).observe(document.body, {
      childList: true,
      subtree: true
  });
})();

console.log("rot.js loaded");
