
function init() {

  jokes.editDiv = document.getElementById('editDiv');
  jokes.editForm = document.getElementById('editForm');

  // Add event handlers to stars and containers
  var js = getElementsByClass('jstars',document,'div');
  for (var j=0; j<js.length; j++) (function(j){   // anon fn binds vars properly
    var c = js[j].className;    // save original
    js[j].onmouseout = function() { starsOut(js[j],c) };
    js[j].onclick = function() { starsClick(js[j],c) };
    var divs = getChildrenByTagName(js[j], 'div');
    for (var d=0; d<divs.length; d++) (function(d){
      divs[d].onmouseover = function() { starOver(divs[d],d) };
    })(d);
  })(j);
}

function hasClass (e,c) {
  var pat = new RegExp("(^|\\s)"+c+"(\\s|$)");
  var rc = pat.test(e.className);
  return rc;
}
function setClass (e,c) {
  e.className = c;
}
function chgClass (e,cOld,cNew) {
  var pat = new RegExp("(^|\\s)"+cOld+"(\\s|$)");
  e.className = e.className.replace(pat,' '+cNew+' ');
}
function trim(s) {
  return s.replace(/^\s+|\s+$/g,'');
}

// Args: classname[, start node, tag filter]
function getElementsByClass (c,node,tag) {
  var ee = [];
  if ( node == null ) node = document;
  var els = node.getElementsByTagName(tag?tag:'*');
  var elsLen = els.length;
  var pat = new RegExp("(^|\\s)"+c+"(\\s|$)");
  for (i = 0; i < elsLen; i++)
    if (pat.test(els[i].className)) ee.push(els[i]);
  return ee;
}

function getChildrenByTagName (node, tag) {
  var out = [];
  if (node) {
    var c = node.childNodes;
    tag = tag.toUpperCase();
    for (var i = 0; i < c.length; i++)
      if (c[i].nodeName.toUpperCase() == tag)
        out.push(c[i]);
  }
  return out;
}

function firstDescendant(e) {
  e = e.firstChild;
  while (e && e.nodeType != 1) e = e.nextSibling; // skip text
  return e;
}

function getSib(e) {
  var sib = e.nextSibling;
  while (sib && sib.nodeType != 1) sib = sib.nextSibling;
  return sib;
}
function getStyle (e,s) {
  if (e.currentStyle)
    return e.currentStyle[s];
  else if (window.getComputedStyle)
    return document.defaultView.getComputedStyle(e,null).getPropertyValue(s);
}

function ratingStr(jok) {
  return jok.n + ' rating' + (parseInt(jok.n,10) == 1 ? '' : 's');
}
function starOver(e,n) {
  var jid = e.parentNode.parentNode.id;
  if (jokes[jid].pending) {
    clearTimeout(jokes[jid].pending); // cancel mouseout on parent
    jokes[jid].pending = null;
  }
  setClass(e.parentNode,'jstars s'+(n+1));
  var sib = getSib(e.parentNode);
  sib.innerHTML = 'Click to rate';
}
function starsClick(e) {
  // remove handlers
  e.onclick = jokes.nullFunc;
  e.onmouseout = jokes.nullFunc;
  var divs = getChildrenByTagName(e, 'div');
  for (var d=0; d<divs.length; d++) {
    divs[d].onmouseover = jokes.nullFunc;
    divs[d].style.cursor = 'auto';
  }

  // calculate my rating, and update rating/raters in jokes[]
  var ridx = e.className.split(' ')[1].substr(1); //'jstars s3' -> '3'
  var rating = ridx / 2;
  var j = jokes[e.parentNode.id];
  j.r = (j.n*j.r + rating) / (j.n + 1);
  j.n++;

  // Show msg and set timeout to update display
  var sib = getSib(e);  // msg
  sib.innerHTML = 'Thank you!';
  setTimeout(function() {
    sib.innerHTML = ratingStr(j);
    setClass(e, 'jstars s'+parseInt(2*j.r,10));
    }, 1500);
  var jokeId = e.parentNode.id.substr(1);  //j123 -> 123

  sendVote(jokeId,rating);
}
function starsOut(e,origClass) {   // 'jstars' div, class
  if (jokes[e.parentNode.id].pending) return;
  var t = setTimeout(function() {
      setClass(e,origClass);
      getSib(e).innerHTML = ratingStr(jokes[e.parentNode.id]);
    }, 200);
  jokes[e.parentNode.id].pending = t;
}
function sendVote(id,rating) {
  sendRequest('api.php', null, 'j='+id+'&r='+rating);
}
function sendDelete(id) {
  sendRequest('api.php', null, 'd='+id);
}
function addNew() {
  if (jokes.editForm.parentNode != jokes.editDiv) return; // busy
  var tr = document.createElement('tr');
  var td = document.createElement('td');
  setClass(td,'trank');
  td.id = 'j-1';
  tr.appendChild(td);
  td = document.createElement('td');
  setClass(td,'jcon');
  var sp = document.createElement('span');
  setClass(sp,'jjcon');
  td.appendChild(sp);
  sp = document.createElement('span');
  setClass(sp,'jjattr');
  td.appendChild(sp);
  tr.appendChild(td);
  td = document.createElement('td');
  tr.appendChild(td);
  var tab = document.getElementById('jtable');
  tab = tab.getElementsByTagName('tbody')[0];
  tab.insertBefore(tr,tab.firstChild);
  showEditForm('j-1');
}
function showEditForm(td) {
  if (jokes.editForm.parentNode != jokes.editDiv) return; // busy
  var jcon = getSib(document.getElementById(td));
  jokes.editForm.style.display = 'none';
  jokes.editForm.parentNode.removeChild(jokes.editForm);

  // get and copy content to edit form
  var ins = jokes.editForm.getElementsByTagName('input');
  var tas = jokes.editForm.getElementsByTagName('textarea');
  var jspans = jcon.getElementsByTagName('span');
  tas[0].value = jspans[0].innerHTML;  // body of joke
  ins[0].value = td.substr(1);         // hidden joke ID
  var s = jspans[1].innerHTML;         // attribution
  ins[1].value = s.replace(/^\s*\(|\)\s*$/g,"");        // trim '()'
  ins[3].onclick = function() { cancelEditForm(jcon) }; // cancel button

  jspans[0].style.color = '#aaa';  // grey original content
  jspans[1].style.color = '#aaa';

  jcon.appendChild(jokes.editForm);   // show edit form
  jokes.editForm.style.display = 'block';
}
function cancelEditForm(jcon) {
  if (jokes.editForm.jid.value == '-1') {
    var tab = document.getElementById('jtable');
    tab = tab.getElementsByTagName('tbody')[0];
    tab.removeChild(tab.firstChild);  // remove first,blank row
  }
  jokes.editForm.style.display = 'none';
  jokes.editForm.parentNode.removeChild(jokes.editForm);
  jokes.editDiv.appendChild(jokes.editForm);
  var jspans = jcon.getElementsByTagName('span');
  jspans[0].style.color = '#000';   // restore content color
  jspans[1].style.color = '#000';
}
function deleteJoke(td) {
  var jcon = getSib(document.getElementById(td));
  var oldBG = getStyle(jcon,'backgroundColor');
  jcon.style.backgroundColor = '#ffbbbb';  // visual indication
  if (confirm('Really delete this joke?')) {
    var f = getChildrenByTagName(jcon,'FORM');  // remove form if there
    if (f.length > 0) {
      jokes.editForm.style.display = 'none';
      jokes.editForm.parentNode.removeChild(jokes.editForm);
      jokes.editDiv.appendChild(jokes.editForm);
    }
    var tds = jcon.parentNode.getElementsByTagName('td'); // mark deleted
    if (tds.length >= 2) {
      tds[0].innerHTML = "deleted";
      tds[1].innerHTML = "deleted";
    }
    sendDelete(td.substr(1));
  } else {
    jcon.style.backgroundColor = oldBG;
  }
}

function sendRequest(url,callback,postData) {
  var req = createXMLHTTPObject();
  if (!req) return;
  var method = (postData) ? "POST" : "GET";
  req.open(method,url,true);
  req.setRequestHeader('X-Requested-With','XMLHttpRequest');
  if (postData)
    req.setRequestHeader('Content-type','application/x-www-form-urlencoded');
  req.onreadystatechange = function () {
    if (req.readyState == 4 &&
        (req.status == 200 || req.status == 304)) {
      if (callback)
        callback(req);
      req.onreadystatechange = jokes.nullFunc;
    }
    return;
  }
  if (req.readyState == 4) return;
  req.send(postData);
}

var XMLHttpFactories = [
  function () {return new XMLHttpRequest()},
  function () {return new ActiveXObject("MSXML2.XMLHTTP.6.0")},
  function () {return new ActiveXObject("MSXML2.XMLHTTP.3.0")},
  function () {return new ActiveXObject("MSXML2.XMLHTTP")},
  function () {return new ActiveXObject("Microsoft.XMLHTTP")}
];

function createXMLHTTPObject() {
  for (var i=0;i<XMLHttpFactories.length;i++) {
    try { var xmlhttp = XMLHttpFactories[i](); return xmlhttp; }
    catch (e) { }
  }
  return null;
}


