var nl_months_long = ["januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"];
var nl_months_short = ["jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"];
var nl_weekdays_short = ["zo", "ma", "di", "wo", "do", "vr", "za"];

function chainFor(func, array, c){
  function loop(i){
    if (i == array.length)
      c();
    else
      func(array[i], bind(loop, null, i + 1));
  }
  loop(0);
}

function action(type, href, title){
  return A({"class": "action action-" + (type || "edit"), "href": href}, title);
}

function button(action, label, type){
  return FORM({"class": "button-form", method: "POST", action: action},
              BUTTON({"class": "button button-" + (type || "create"), "title": label}, SPAN(null, label)));
}

function findIf(array, test){
  for (var i = 0; i < array.length; i++){
    var element = array[i];
    if (test(element))
      return element;
  }
  return undefined;
}

function filterFind(array, filter){
  for (var i = 0; i < array.length; i++){
    var result = filter(array[i]);
    if (result !== undefined)
      return result;
  }
  return undefined;
}

function forEach$(){
  var f = arguments[arguments.length - 1];
  var arrays = [];
  for (var i = 0; i < arguments.length - 1; i++)
    arrays.push(arguments[i]);
  for (var i = 0; i < arguments[0].length; i++)
    f.apply(null, map(itemgetter(i), arrays));
}

function remove(element, array){
  for (var i = 0; i < array.length; i++){
    if (array[i] == element)
      array.splice(i--, 1);
  }
}

function last(array){
  return array[array.length - 1];
}

function capitalize(string){
  return string.charAt(0).toUpperCase() + string.slice(1);
}

function getMonthName(date, capitalized){
  var name = nl_months_long[date.getMonth()];
  return capitalized ? capitalize(name) : name;
}

function maximizeWith(array, f){
  if (array.length == 0)
    return undefined;
  else
    return reduce(function(a, b) {return f(a, b) < 0 ? b : a;}, array);
}
function mochiMin(array){
  return maximizeWith(array, compose(operator.neg, compare));
}
function compareBy(accessor){
  return function (a, b){
    return compare(accessor(a), accessor(b));
  }
}
function invert(f){
  return function(){
    return -f.apply(this, arguments);
  }
}

function delayTimeout(time, id, action){
  clearTimeout(id);
  return setTimeout(action, time);
}

function addChildToFront(element, child){
  if (element.firstChild)
    element.insertBefore(child, element.firstChild);
  else
    element.appendChild(child);
}

function nonEmptyString(str){
  return !str.match(/^\s*$/);
}

function padZero(val, size){
  size = size || 2;
  val = "" + val;
  while (val.length < size)
    val = "0" + val;
  return val;
}

function parseDate(date){
  var match = date.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (match)
    return new Date(parseInt(match[1], 10), parseInt(match[2], 10) - 1, parseInt(match[3], 10));
  else
    return false;
}
function writeDate(date){
  return date.getFullYear() + "-" + padZero(date.getMonth() + 1) + "-" + padZero(date.getDate());
}
function displayDate(date){
  return padZero(date.getDate()) + "-" + padZero(date.getMonth() + 1) + "-" + padZero(date.getFullYear()%100);
}

function parseTime(time){
  var match = time.match(/^(\d{1,2}):(\d{2})$/);
  if (match)
    return {hour: parseInt(match[1], 10), min: parseInt(match[2], 10)};
  else
    return false;
}
function writeTime(time){
  return time.hour + ":" + padZero(time.min);
}
function compareTime(a, b){
  var hourcomp = compare(a.hour, b.hour);
  return hourcomp != 0 ? hourcomp : compare(a.min, b.min);
}

function buildTimestamp(date, time){
  // this is not correct when the client is in a different timezone
  var timestamp = cloneDate(date);
  timestamp.setHours(time.hour);
  timestamp.setMinutes(time.min);
  return timestamp;
}
function parseTimestamp(stamp){
  var parts = stamp.split(" ");
  if (parts.length == 2){
    var date = parseDate(parts[0]), time = parseTime(parts[1]);
    return (date && time && new Date(date.getFullYear(), date.getMonth(), date.getDate(), time.hour, time.min));
  }
  else {
    return false;
  }
}
function writeTimestamp(stamp){
  return writeDate(stamp) + " " + writeTime({hour: stamp.getHours(), min: stamp.getMinutes()});
}

function cloneDate(date){
  return new Date(date.getTime()); // Simplest way I could find, hopefully there is a better one
}

// Use things like "FullYear", "Month", or "Date" as unit
function datePlus(date, unit, amount){
  var result = cloneDate(date);
  result["set" + unit](result["get" + unit]() + amount); // Muahaha
  return result;
}

function daysBetween(start, end){
  var days = 0;
  while (compare(start, end) < 0){
    days++;
    start = datePlus(start, "Date", 1);
  }
  return days;
}

function weeksAndDaysBetween(start, end){
  var days = daysBetween(start, end);
  return [Math.floor(days/7), days % 7];
}

function dateAtMidnight(date){
  return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}

Date.todayStart = dateAtMidnight(new Date());
Date.todayEnd = datePlus(Date.todayStart, "Date", 1);

function isPast(date) {
  return (date < Date.todayStart);
}

function isFuture(date) {
  return (date >= Date.todayEnd);
}

function isToday(date) {
  return !isPast() && !isFuture(date);
}

// Constants to make reasoning about weekdays a bit more comprehensive.
Date.sunday = 0;
Date.monday = 1;
Date.tuesday = 2;
Date.wednesday = 3;
Date.thursday = 4;
Date.friday = 5;
Date.saturday = 6;

function roundToWeek(date, day, direction){
  var start_of_week = day || Date.monday;
  while (date.getDay() != start_of_week)
    date = datePlus(date, "Date", direction || -1);
  return date;
}

function roundToMonth(date){
  return new Date(date.getFullYear(), date.getMonth(), 1);
}

// ID generation

function createUniqueDOMID(prefix){
  var id = Math.floor(Math.random() * 1000000000);
  var prefix = prefix ? prefix + "_" : "";
  while($(prefix + id) != null)
    id = Math.floor(Math.random() * 1000000000);
  return prefix + id;
}

// URL generation

function aanvraagViewURL(aanvraag){
  return tagURL("/aanvragen/aanvraag/" + aanvraag.id);
}

function aanvraagEditURL(aanvraag){
  return tagURL("/aanvragen/aanvraag/" + aanvraag.id + "/edit");
}

function aanvraagDelURL(aanvraag){
  return tagURL("/aanvragen/aanvraag/" + aanvraag.id + "/delete");
}

function aanvraagPartCancelURL(part){
  return tagURL("/aanvragen/aanvraag/" + part.aanvraag + "-" + encodeURIComponent(part.product) + "/annuleer");
}

function tagURL(url) {
  var sessionID = document.location.href.match(/;jsessionid=[0-9A-Z]+(#.+)?$/);
  if (sessionID)
    return context_path + url + sessionID[0];
  else
    return context_path + url;
}

// Remote function calling

function doRemoteCall(functionName, argument) {
  var deferred = doXHR(tagURL("/function/" + functionName), {method: "POST", sendContent: serializeJSON(argument)});
  deferred.addCallback(evalJSONRequest);
  return deferred;
}

function doRemoteCallWithRetries(functionName, argument, timeout, retries) {
  var test = function(error) { return (error instanceof CancelledError); };
  var repeatable = function () {
    return cancelWithTimeout(doRemoteCall(functionName, argument), timeout);
  };
  return retryOnError(repeatable, retries, test);
}

function cancelWithTimeout(deferred, timeout){
  var canceller = callLater(timeout, function(){
    deferred.cancel();
  });
  return deferred.addCallback(function (r){
    canceller.cancel();
    return r;
  });
}

function retryOnError(repeatable, retries, test){
  var deferred = new Deferred();
  var retry = function(retries) {
    var attempt = repeatable();
    if(attempt == undefined) {
      deferred.errback("calling repeatable did not result in a deferred");
      return;
    }
    attempt.addCallback(function(result) {
        deferred.callback(result);
      });
    attempt.addErrback(function(error) {
        if(retries == 1 || !test(error))
          deferred.errback(error);
        else
          retry(--retries);
      });
  }
  retry(retries);
  return deferred;
}

// Text formatting

function textToHTML(container, text){
  function nl2br(text, p) {
    var lines = text.split(/\s*\n\s*/);
    if(lines.length > 0) {
      appendChildNodes(p, lines[0]);
      forEach(lines.slice(1), function(line) {
        appendChildNodes(p, BR(), line);
      });
    }
    return p;
  }
  forEach(text.split(/\s*\n\s*\n\s*/), function(paragraph) {
    appendChildNodes(container, nl2br(paragraph, P()));
  });
  return container;
}

// Showing/hiding

function hide(element){
  element.style.display = 'none';
}

function show(element){
  element.style.display = '';
}

// Table row filtering

function fireDelayed(delay, id, fn){
  if(id.delayTimeoutId) {
    clearTimeout(id.delayTimeoutId);
    id.delayTimeoutId = null;
  }
  id.delayTimeoutId = setTimeout(fn, delay);
}

function filterTable(delay, table, fn){
  var tableElement = $(table);
  var rowFilterFunction = fn;
  fireDelayed(delay, tableElement, function() {
    forEach(tableElement.rows, function(row) {
      if(rowFilterFunction(row))
        show(row);
      else
        hide(row);
    })
  });
}

function resetTable(table){
  forEach($(table).rows, show);
}

// Folding

function folded(foldable) {
  return hasElementClass(foldable, "folded");
}

function toggleFolding(foldable, folder) {
  if(folded(foldable)) {
    removeElementClass(folder, "folded");
    removeElementClass(foldable, "folded");
  } else {
    addElementClass(folder, "folded");
    addElementClass(foldable, "folded");
  }
}

