//===== Collection of small utilities

/*
 * Bind/Unbind events
 *
 * Usage:
 *   var el = document.getElementyById('#container');
 *   bnd(el, 'click', function() {
 *     console.log('clicked');
 *   });
 */

var bnd = function(
  d, // a DOM element
  e, // an event name such as "click"
  f  // a handler function
){
  d.addEventListener(e, f, false);
}

/*
 * Create DOM element
 *
 * Usage:
 *   var el = m('<h1>Hello</h1>');
 *   document.body.appendChild(el);
 *
 * Copyright (C) 2011 Jed Schmidt <http://jed.is> - WTFPL
 * More: https://gist.github.com/966233
 */

var m = function(
  a, // an HTML string
  b, // placeholder
  c  // placeholder
){
  b = document;                   // get the document,
  c = b.createElement("p");       // create a container element,
  c.innerHTML = a;                // write the HTML to it, and
  a = b.createDocumentFragment(); // create a fragment.

  while (                         // while
    b = c.firstChild              // the container element has a first child
  ) a.appendChild(b);             // append the child to the fragment,

  return a                        // and then return the fragment.
}

/*
 * DOM selector
 *
 * Usage:
 *   $('div');
 *   $('#name');
 *   $('.name');
 *
 * Copyright (C) 2011 Jed Schmidt <http://jed.is> - WTFPL
 * More: https://gist.github.com/991057
 */

var $ = function(
  a,                         // take a simple selector like "name", "#name", or ".name", and
  b                          // an optional context, and
){
  a = a.match(/^(\W)?(.*)/); // split the selector into name and symbol.
  return(                    // return an element or list, from within the scope of
    b                        // the passed context
    || document              // or document,
  )[
    "getElement" + (         // obtained by the appropriate method calculated by
      a[1]
        ? a[1] == "#"
          ? "ById"           // the node by ID,
          : "sByClassName"   // the nodes by class name, or
        : "sByTagName"       // the nodes by tag name,
    )
  ](
    a[2]                     // called with the name.
  )
}

/*
 * Get cross browser xhr object
 *
 * Copyright (C) 2011 Jed Schmidt <http://jed.is>
 * More: https://gist.github.com/993585
 */

var j = function(
  a // cursor placeholder
){
  for(                     // for all a
    a=0;                   // from 0
    a<4;                   // to 4,
    a++                    // incrementing
  ) try {                  // try
    return a               // returning
      ? new ActiveXObject( // a new ActiveXObject
          [                // reflecting
            ,              // (elided)
            "Msxml2",      // the various
            "Msxml3",      // working
            "Microsoft"    // options
          ][a] +           // for Microsoft implementations, and
          ".XMLHTTP"       // the appropriate suffix,
        )                  // but make sure to
      : new XMLHttpRequest // try the w3c standard first, and
  }

  catch(e){}               // ignore when it fails.
}

// createElement short-hand

e = function(a) { return document.createElement(a); }

// chain onload handlers

function onLoad(f) {
  var old = window.onload;
  if (typeof old != 'function') {
    window.onload = f;
  } else {
    window.onload = function() {
      old();
      f();
    }
  }
}

//===== helpers to add/remove/toggle HTML element classes

function addClass(el, cl) {
  el.className += ' ' + cl;
}
function removeClass(el, cl) {
  var cls = el.className.split(/\s+/),
      l = cls.length;
  for (var i=0; i<l; i++) {
    if (cls[i] === cl) cls.splice(i, 1);
  }
  el.className = cls.join(' ');
  return cls.length != l
}
function toggleClass(el, cl) {
  if (!removeClass(el, cl)) addClass(el, cl);
}

//===== AJAX

function ajaxReq(method, url, ok_cb, err_cb) {
  var xhr = j();
  xhr.open(method, url, true);
  var timeout = setTimeout(function() {
    xhr.abort();
    console.log("XHR abort:", method, url);
    xhr.status = 599;
    xhr.responseText = "request time-out";
  }, 9000);
  xhr.onreadystatechange = function() {
    if (xhr.readyState != 4) { return; }
    clearTimeout(timeout);
    if (xhr.status >= 200 && xhr.status < 300) {
      console.log("XHR done:", method, url, "->", xhr.status);
      ok_cb(xhr.responseText);
    } else {
      console.log("XHR ERR :", method, url, "->", xhr.status, xhr.responseText, xhr);
      err_cb(xhr.status, xhr.responseText);
    }
  }
  console.log("XHR send:", method, url);
  try {
    xhr.send();
  } catch(err) {
    console.log("XHR EXC :", method, url, "->", err);
    err_cb(599, err);
  }
}

function dispatchJson(resp, ok_cb, err_cb) {
  var j;
  try { j = JSON.parse(resp); }
  catch(err) {
    console.log("JSON parse error: " + err + ". In: " + resp);
    err_cb(500, "JSON parse error: " + err);
    return;
  }
  ok_cb(j);
}

function ajaxJson(method, url, ok_cb, err_cb) {
  ajaxReq(method, url, function(resp) { dispatchJson(resp, ok_cb, err_cb); }, err_cb);
}

function ajaxSpin(method, url, ok_cb, err_cb) {
  $("#spinner").removeAttribute('hidden');
  ajaxReq(method, url, function(resp) {
      $("#spinner").setAttribute('hidden', '');
      ok_cb(resp);
    }, function(status, statusText) {
      $("#spinner").setAttribute('hidden', '');
      //showWarning("Error: " + statusText);
      err_cb(status, statusText);
    });
}

function ajaxJsonSpin(method, url, ok_cb, err_cb) {
  ajaxSpin(method, url, function(resp) { dispatchJson(resp, ok_cb, err_cb); }, err_cb);
}

//===== main menu, header spinner and notification boxes

onLoad(function() {
  var l = $("#layout");
  var o = l.childNodes[0];
  // spinner
  l.insertBefore(m('<div id="spinner" class="spinner" hidden></div>'), o);
  // notification boxes
  l.insertBefore(m(
    '<div id="messages"><div id="warning" hidden></div><div id="notification" hidden></div></div>'), o);
  // menu hamburger button
  l.insertBefore(m('<a href="#menu" id="menuLink" class="menu-link"><span></span></a>'), o);
  // menu left-pane
  var mm = m(
   '<div id="menu">\
      <div class="pure-menu">\
        <a class="pure-menu-heading" href="https://github.com/jeelabs/esp-link">\
        <img src="/favicon.ico" height="32">&nbsp;esp-link</a>\
        <ul id="menu-list" class="pure-menu-list"></ul>\
      </div>\
    </div>\
    ');
  l.insertBefore(mm, o);

  // make hamburger button pull out menu
  var ml = $('#menuLink'), mm = $('#menu');
  bnd(ml, 'click', function (e) {
    console.log("hamburger time");
      var active = 'active';
      e.preventDefault();
      toggleClass(l, active);
      toggleClass(mm, active);
      toggleClass(ml, active);
  });

  // populate menu via ajax call
  var getMenu = function() {
    ajaxJson("GET", "/menu", function(data) {
      var html = "", path = window.location.pathname;
      for (var i=0; i<data.menu.length; i+=2) {
        var href = data.menu[i+1];
        html = html.concat(" <li class=\"pure-menu-item" +
            (path === href ? " pure-menu-selected" : "") + "\">" +
            "<a href=\"" + href + "\" class=\"pure-menu-link\">" +
            data.menu[i] + "</a></li>");
      }
      $("#menu-list").innerHTML = html;

      v = $("#version");
      if (v != null) { v.innerHTML = data.version; }
    }, function() { setTimeout(getMenu, 1000); });
  };
  getMenu();
});

//===== Wifi info

function showWifiInfo(data) {
  Object.keys(data).forEach(function(v) {
    el = $("#wifi-" + v);
    if (el != null) {
      if (el.nodeName === "INPUT") el.value = data[v];
      else el.innerHTML = data[v];
    }
  });
  var dhcp = $('#dhcp-r'+data.dhcp);
  if (dhcp) dhcp.click();
  $("#wifi-spinner").setAttribute("hidden", "");
  $("#wifi-table").removeAttribute("hidden");
  currAp = data.ssid;
}

function getWifiInfo() {
  ajaxJson('GET', "/wifi/info", showWifiInfo,
      function(s, st) { window.setTimeout(getWifiInfo, 1000); });
}

//===== Notifications

function showWarning(text) {
  var el = $("#warning");
  el.innerHTML = text;
  el.removeAttribute('hidden');
}
function hideWarning() {
  el = $("#warning").setAttribute('hidden', '');
}
var notifTimeout = null;
function showNotification(text) {
  var el = $("#notification");
  el.innerHTML = text;
  el.removeAttribute('hidden');
  if (notifTimeout != null) clearTimeout(notifTimeout);
  notifTimout = setTimeout(function() {
      el.setAttribute('hidden', '');
      notifTimout = null;
    }, 4000);
}

//===== GPIO Pin mux card

var currPin;
// pin={reset:12, isp:13, LED_conn:0, LED_ser:2}
function createInputForPin(pin) {
  var input = document.createElement("input");
  input.type = "radio";
  input.name = "pins";
  input.data = pin.name;
  input.className = "pin-input";
  input.value= pin.value;
  input.id   = "opt-" + pin.value;
  if (currPin == pin.name) input.checked = "1";

  var descr = m('<label for="opt-'+pin.value+'"><b>'+pin.name+":</b>"+pin.descr+"</label>");
  var div = document.createElement("div");
  div.appendChild(input);
  div.appendChild(descr);
  return div;
}

function displayPins(resp) {
  var po = $("#pin-mux");
  po.innerHTML = "";
  currPin = resp.curr;
  resp.map.forEach(function(v) {
    po.appendChild(createInputForPin(v));
  });
  var i, inputs = $(".pin-input");
  for (i=0; i<inputs.length; i++) {
    inputs[i].onclick = function() { setPins(this.value, this.data) };
  };
}

function fetchPins() {
  ajaxJson("GET", "/pins", displayPins, function() {
    window.setTimeout(fetchPins, 1000);
  });
}

function setPins(v, name) {
  ajaxSpin("POST", "/pins?map="+v, function() {
    showNotification("Pin assignment changed to " + name);
  }, function() {
    showNotification("Pin assignment change failed");
    window.setTimeout(fetchPins, 100);
  });
}

//===== MQTT cards

function changeMqtt(e) {
  e.preventDefault();
  var url = "mqtt?1=1";
  var i, inputs = document.querySelectorAll('#mqtt-form input');
  for (i=0; i<inputs.length; i++) {
    if (inputs[i].type != "checkbox")
      url += "&" + inputs[i].name + "=" + inputs[i].value;
  };

  hideWarning();
  var cb = $("#mqtt-button");
  addClass(cb, 'pure-button-disabled');
  ajaxSpin("POST", url, function(resp) {
      showNotification("MQTT updated");
      removeClass(cb, 'pure-button-disabled');
    }, function(s, st) {
      showWarning("Error: "+st);
      removeClass(cb, 'pure-button-disabled');
      window.setTimeout(fetchMqtt, 100);
    });
}

function displayMqtt(data) {
  Object.keys(data).forEach(function(v) {
    el = $("#" + v);
    if (el != null) {
      if (el.nodeName === "INPUT") el.value = data[v];
      else el.innerHTML = data[v];
      return;
    }
    el = document.querySelector('input[name="' + v + '"]');
    if (el != null) {
      if (el.type == "checkbox") el.checked = data[v] > 0;
      else el.value = data[v];
    }
  });
  $("#mqtt-spinner").setAttribute("hidden", "");
  $("#mqtt-status-spinner").setAttribute("hidden", "");
  $("#mqtt-form").removeAttribute("hidden");
  $("#mqtt-status-form").removeAttribute("hidden");

  var i, inputs = $("input");
  for (i=0; i<inputs.length; i++) {
    if (inputs[i].type == "checkbox")
      inputs[i].onclick = function() { console.log(this); setMqtt(this.name, this.checked) };
  }
}

function fetchMqtt() {
  ajaxJson("GET", "/mqtt", displayMqtt, function() {
    window.setTimeout(fetchMqtt, 1000);
  });
}

function changeMqttStatus(e) {
  e.preventDefault();
  var v = document.querySelector('input[name="mqtt-status-topic"]').value;
  ajaxSpin("POST", "/mqtt?mqtt-status-topic=" + v, function() {
    showNotification("MQTT status settings updated");
    }, function(s, st) {
      showWarning("Error: "+st);
      window.setTimeout(fetchMqtt, 100);
    });
}

function setMqtt(name, v) {
  ajaxSpin("POST", "/mqtt?" + name + "=" + (v ? 1 : 0), function() {
    var n = name.replace("-enable", "");
    showNotification(n + " is now " + (v ? "enabled" : "disabled"));
    }, function() {
      showWarning("Enable/disable failed");
      window.setTimeout(fetchMqtt, 100);
    });
}