// BigTextScroller JavaScript file.
// Original source for scrolling div: http://www.leigeber.com/2008/05/ultimate-javascript-scroller-and-slider/
// Simplified and modified to scroll horizontally.

// global defaults
var MIN_LEADER_LENGTH = 480;  // pixels
var MAX_LEADER_LENGTH = 640;  // pixels
var SCROLLER_DIV_NAME = "scroller";
var SCROLL_TIMER = 10;  // milliseconds
var DEFAULT_BACKGROUND_COLOUR = "#fff";
var DEFAULT_TEXT_COLOUR = "#000";

// scroller instance vars
var isSafari = false;
var isMobileSafari = false;
var theForm;
var scrollerDiv;
var scrollSpeed;
var scrollHeight;
var fontSize;
var fontFamily;
var invert = false;  // false for black writing on white background; true for white writing on black background
var invertTrueValues = ["1", "true", "yes"];
var scrollingFlag = false;
var leaderLength = MIN_LEADER_LENGTH;
var savedOffsetWidth;

// define some examples
var exampleScrolls = [
  "text=Newsflash: Eat breakfast every day!&size=150&font=monospace&speed=3",
  "text=Please read this carefully&size=300&font=serif&speed=2&invert=true",
  "text=I hope you can read this quick little message&size=40&font=sans-serif&speed=5",
  "text=Supercalafragelisticexpialadocious&size=75&font=serif&speed=1&invert=yes"
];

// called when page loads
function init()
{
  isSafari = navigator.userAgent.indexOf("Safari") > -1;
  theForm = document.parametersForm;
  scrollerDiv = document.getElementById(SCROLLER_DIV_NAME);
  scrollerDiv.style.color = DEFAULT_TEXT_COLOUR;
  scrollerDiv.style.backgroundColor = DEFAULT_BACKGROUND_COLOUR;
  parseURLParameterString(document.location.search);
  restartScroller();
  populateExamples();
  populateURLParamInfo();
}

// called when page loads - special case for Mobile Safari
function initMobileSafari()
{
  isMobileSafari = true;
  SCROLL_TIMER = 3;
  init();
}

// parses any parameters passed in on the url, in the format:
//    "text=the%20message&size=150&font=serif&speed=3&invert=true"
function parseURLParameterString(paramString)
{
  if (paramString.length > 0)
  {
    var arguments = extractFieldValues(paramString);
    if (arguments["text"] != undefined)
    {
      theForm.text.value = arguments["text"];
    }
    // note that browsers can set the appropriate option based on the value assigned (invalid options are ignored)
    theForm.size.value = arguments["size"];
    theForm.font.value = arguments["font"];
    if (arguments["invert"] && validFieldValue(arguments["invert"], invertTrueValues))
    {
      theForm.invert.checked = true;
      if (isMobileSafari)
      {
        invertToggleDiv = document.getElementById("invertToggle");
        invertToggleDiv.setAttribute("toggled", "true");
      }
      invertColours();
    }
    else
    {
      theForm.invert.checked = false;
      if (isMobileSafari)
      {
        invertToggleDiv = document.getElementById("invertToggle");
        invertToggleDiv.setAttribute("toggled", "false");
      }
    }
    theForm.speed.value = arguments["speed"];
  }
}

// extracts arguments from search string, returning a psuedo-associative array
function extractFieldValues(encodedSearch)
{
  var modifiedSearch = unescape(encodedSearch.substr(1, encodedSearch.length-1));
  var arguments = new Object;
  if (modifiedSearch.indexOf("=") > -1)
  {
    var fields = modifiedSearch.split("&");
    for (var i in fields)
    {
      var name = fields[i].substr(0, fields[i].indexOf("="));
      var value = fields[i].substr(fields[i].indexOf("=")+1, fields[i].length-1);
      arguments[name] = value;
    }
  }
  return arguments;
}

// tests that value is in the set of valid values
function validFieldValue(value, arrayOfValues)
{
  for (var i in arrayOfValues)
  {
    if (value.toLowerCase() == arrayOfValues[i].toLowerCase()) return true;
  }
  return false;
}

// restarts the scroller, using the current text input by the user
function restartScroller()
{
  // update the scroller text
  var textToScroll = theForm.text.value;
  if (textToScroll.replace(/\s/g, "").length == 0)
  {
    textToScroll = "Please enter some text to scroll";
  }
  // need to replace newlines with spaces (they cause problems for Firefox, and are not relevant here anyway)
  textToScroll = textToScroll.replace(/[\n\r]+/g, " ");
  scrollerDiv.innerHTML = "<span id=\"leader\">&nbsp;</span>";
  // to facilitate smooth scrolling, each character is wrapped in a dummy tag
  scrollerDiv.innerHTML += textToScroll.split("").map(processCharacter).join("<k> </k>");
  scrollerDiv.innerHTML += " <span id=\"trailer\"> </span>";

  // update other scroller settings
  changeSpeed(theForm.speed);
  changeFont(theForm.font);
  changeSize(theForm.size);

  // update relevant styles (need to be done _after_ scroller has some content - in particular, tags)
  if (!isMobileSafari)
  {
    var windowWidth = window.innerWidth;
    if (windowWidth > MIN_LEADER_LENGTH)
    {
      leaderLength = Math.min(windowWidth, MAX_LEADER_LENGTH);
    }
    document.getElementById("leader").style.paddingRight = leaderLength + "px";
    document.getElementById("rightMaskingEdge").style.marginLeft = leaderLength + "px";
  }
  document.getElementById("container").onclick = function() { toggleScrolling(); };

  // start scrolling
  resetMessageToStart();
  scrollContent();
}

// convenience function to replace spaces with special tag combo
var processCharacter = function(c) { return (c == " ") ? "<s>.</s>" : c; };

// changes the speed setting based on user's input
function changeSpeed(selectObj)
{
  scrollSpeed = new Number(selectObj.options[selectObj.selectedIndex].value);
  if (isMobileSafari) scrollSpeed = 2 * scrollSpeed - 1;
}

// changes the size setting based on user's input
function changeSize(selectObj)
{
  scrollHeight = new Number(selectObj.options[selectObj.selectedIndex].value);
  fontSize = (isSafari ? Math.round(scrollHeight / 1.15) : scrollHeight);
  document.getElementById("container").style.height = scrollHeight + "px";
  document.getElementById("rightMaskingEdge").style.height = scrollHeight + "px";
  scrollerDiv.style.height = scrollHeight + "px";
  scrollerDiv.style.fontSize = fontSize + "px";
}

// changes the font setting based on user's input
function changeFont(selectObj)
{
  fontFamily = selectObj.options[selectObj.selectedIndex].value;
  scrollerDiv.style.fontFamily = fontFamily;
}

// swaps the text and background colours
function invertColours()
{
  invert = !invert;
  var previousColor = scrollerDiv.style.color;
  scrollerDiv.style.color = scrollerDiv.style.backgroundColor;
  scrollerDiv.style.backgroundColor = previousColor;
  if (theForm.invert.checked != invert) theForm.invert.checked = invert;
}

// triggers scrolling of the content
function scrollContent()
{
  clearInterval(scrollerDiv.timer);
  scrollerDiv.timer = setInterval(function() { scrollAnimate(); }, SCROLL_TIMER);
}

// performs the scrolling by one "frame"
function scrollAnimate()
{
  scrollingFlag = true;
  updatePauseOrResumeButton();
  var limit;
  if (savedOffsetWidth)
  {
    if (savedOffsetWidth < scrollerDiv.offsetWidth)
    {
      savedOffsetWidth = scrollerDiv.offsetWidth;
      limit = savedOffsetWidth + scrollSpeed;
    }
    else
    {
      limit = savedOffsetWidth;
      var left = scrollerDiv.style.left.replace('px', '');
      if (Math.abs(left) - limit <= scrollSpeed)
      {
        // reached end of message, so loop back to start
        resetMessageToStart();
      }
    }
  }
  else
  {
    savedOffsetWidth = scrollerDiv.offsetWidth;
    limit = savedOffsetWidth + scrollSpeed;
  }
  scrollerDiv.style.left = scrollerDiv.style.left || '0px';
  var left = scrollerDiv.style.left.replace('px', '');
  if (limit - Math.abs(left) <= scrollSpeed)
  {
    cancelScroll();
  }
  else
  {
    scrollerDiv.style.left = left - scrollSpeed + 'px';
  }
}

// toggles the scrolling state
function toggleScrolling()
{
  if (scrollingFlag)
  {
    cancelScroll();
  }
  else
  {
    resumeScroll();
  }
}

// cancels the scrolling
function cancelScroll()
{
  clearTimeout(scrollerDiv.timer);
  scrollingFlag = false;
  updatePauseOrResumeButton();
}

// resumes the scrolling
function resumeScroll()
{
  scrollContent();
}

// updates the label of the pause/resume button
function updatePauseOrResumeButton()
{
  // TODO: update button(s) in Mobile Safari
  if (isMobileSafari)
  {
    var pauseOrResumeButton = document.getElementById("pauseOrResumeButton");
    if (scrollingFlag)
    {
      pauseOrResumeButton.innerHTML = "Pause";
    }
    else
    {
      pauseOrResumeButton.innerHTML = "Resume";
    }
  }
  else
  {
    var pauseOrResumeButton = theForm.pauseOrResumeButton;
    if (scrollingFlag)
    {
      pauseOrResumeButton.value = "Pause";
      pauseOrResumeButton.onclick = function() { cancelScroll(); };
    }
    else
    {
      pauseOrResumeButton.value = "Resume";
      pauseOrResumeButton.onclick = function() { resumeScroll(); };
    }
  }
}

// resets the scroller's positioning to the start of the message (including the leader)
function resetMessageToStart()
{
  scrollerDiv.style.left = '0px';
  savedOffsetWidth = scrollerDiv.offsetWidth;
}

// populates the URL parameter info bits of the page
function populateURLParamInfo()
{
  var parameterNames = ["size", "font", "speed"];
  for (var i in parameterNames)
  {
    var valueAndTextPairs = collectOptionValues(theForm[parameterNames[i]]);
    var infoSpan = document.getElementById(parameterNames[i] + "ParamInfo");
    if (infoSpan) infoSpan.innerHTML = "Accepted values: " + valueAndTextPairs.join(", ");
  }
  var invertInfoSpan = document.getElementById("invertParamInfo");
  if (invertInfoSpan) invertInfoSpan.innerHTML = "Accepted values: <code>" + invertTrueValues.join(", ") + "</code> (all other values treated as false)";
}

// collects an array of formatted text/value pairs for a given <select> object's options
function collectOptionValues(selectObj)
{
  var optionArguments = new Array();
  for (var i in selectObj.options)
  {
    if (typeof selectObj.options[i].value == "string")
    {
      optionArguments.push("<code>" + selectObj.options[i].value + "</code> = <i>" + selectObj.options[i].text + "</i>");
    }
  }
  return optionArguments;
}

// populates the lists of examples
function populateExamples()
{
  // first, the in-page examples list
  var ul = document.getElementById("examplesList");
  for (var i in exampleScrolls)
  {
    var arguments = extractFieldValues("?" + exampleScrolls[i])
    var li = document.createElement("li");
    var exampleLink = document.createElement("a");
    exampleLink.i = i;
    if (isMobileSafari)
    {
      exampleLink.className = "exampleLink";
      exampleLink.type = "cancel";
      exampleLink.onclick = function() {
        if (invert) invertColours();
        parseURLParameterString("?" + exampleScrolls[this.i]);
      };
    }
    else
    {
      exampleLink.onclick = function() {
        if (invert) invertColours();
        parseURLParameterString("?" + exampleScrolls[this.i]);
        restartScroller();
      };
      exampleLink.href = "#";
    }
    exampleLink.appendChild(document.createTextNode(arguments["text"]));
    li.appendChild(exampleLink);
    ul.appendChild(li);
  }
  // next, the example URLs list
  var ul = document.getElementById("exampleURLsList");
  var pathElements = window.location.pathname.split("/");
  var filename = pathElements[pathElements.length-1];
  for (var i in exampleScrolls)
  {
    var li = document.createElement("li");
    var linkWrapper = document.createElement("nobr");
    var exampleLink = document.createElement("a");
    if (isMobileSafari)
    {
      exampleLink.className = "exampleLink";
      exampleLink.type = "cancel";
      exampleLink.href = filename + "?" + exampleScrolls[i] + "#_home";
      exampleLink.onclick = function() {
        window.location = this.href;
      };
      exampleLink.appendChild(document.createTextNode(exampleLink.href));
      li.appendChild(exampleLink);
    }
    else
    {
      exampleLink.href = "?" + exampleScrolls[i];
      exampleLink.appendChild(document.createTextNode(filename + "?" + exampleScrolls[i]));
      linkWrapper.appendChild(exampleLink);
      li.appendChild(linkWrapper);
    }
    ul.appendChild(li);
  }
}

// called when user changes orientation of Mobile device
function handleOrientationChange()
{
  var newWidth = window.innerWidth;
  document.getElementById("leader").style.paddingRight = newWidth + "px";
  document.getElementById("rightMaskingEdge").style.marginLeft = newWidth + "px";
}
