var NANOCALENDAR = function () {
  var localization, imagePath, todayDate, lastOpenedCalendar, dropdowns;

  function appendCssClass(node, cssClass)
  {
    var i, cls;

    cls = node.getAttribute("class");
    if (cls === null)
    {
      cls = node.getAttribute("className");

      if (cls === null)
      {
        cls = "";
      }
    }

    cls = cls.split(" ");

    i = 0;
    while (i < cls.length)
    {
      if (cls[i] === cssClass)
      {
        return;
      }

      i = i + 1;
    }

    cls.push(cssClass);

    cls = cls.join(" ").replace(/^\s+|\s+$/g, "");

    node.setAttribute("class", cls);
    node.setAttribute("className", cls);
  }

  function removeCssClass(node, cssClass)
  {
    var i, cls;

    if (cssClass === undefined)
    {
      cls = "";
    } else {
      cls = node.getAttribute("class");
      if (cls === null)
      {
        cls = node.getAttribute("className");

        if (cls === null)
        {
          cls = "";
        }
      }

      cls = cls.split(" ");

      i = 0;
      while (i < cls.length)
      {
        if (cls[i] === cssClass)
        {
          cls.splice(i, 1);
        } else {
          i = i + 1;
        }
      }

      cls = cls.join(" ").replace(/^\s+|\s+$/g, "");
    }

    if (cls === "")
    {
      node.removeAttribute("class");
      node.removeAttribute("className");
    } else {
      node.setAttribute("class", cls);
      node.setAttribute("className", cls);
    }
  }

  function positionTo(node, positionee)
  {
    function offsetSum(node, attrib)
    {
      var sum;

      sum = 0;
      while (node !== null)
      {
        sum += node[attrib];
        node = node.offsetParent;
      }

      return sum;
    }

    positionee.style.left = offsetSum(node, "offsetLeft") + "px";
    positionee.style.top = offsetSum(node, "offsetTop") + "px";

    if (positionee.offsetLeft == 0)
    {
        positionee.style.left = "10px";
        positionee.style.top = "24px";
    }
  }

  function purgeEvents(node, eventPurgeList)
  {
    var i;

    for (i = 0; i < eventPurgeList.length; i += 1)
    {
      if (typeof node[eventPurgeList[i]] === "function")
      {
        node[eventPurgeList[i]] = null;
      }
    }

    node = node.firstChild;
    while (node !== null)
    {
      purgeEvents(node, eventPurgeList);
      node = node.nextSibling;
    }
  }

  function cssHighlightRoutine(ev)
  {
    var eventTarget;

    ev = ev || event;
    eventTarget = ev.target || ev.srcElement;

    appendCssClass(eventTarget, "hovered");
  }

  function cssLowlightRoutine(ev)
  {
    var eventTarget;

    ev = ev || event;
    eventTarget = ev.target || ev.srcElement;

    removeCssClass(eventTarget, "hovered");
  }

  function buildTable(columns, rows, methodToApply)
  {
    var x, y, tableNode, tableBodyNode, tableRowNode, tableCellNode;

    tableNode = document.createElement("table");
    tableNode.setAttribute("cellpadding", "0");
    tableNode.setAttribute("cellspacing", "2");
    tableBodyNode = document.createElement("tbody");
    tableNode.appendChild(tableBodyNode);

    for (y = 0; y < rows; y += 1)
    {
      tableRowNode = document.createElement("tr");
      tableBodyNode.appendChild(tableRowNode);

      for (x = 0; x < columns; x += 1)
      {
        tableCellNode = document.createElement("td");

        if (typeof methodToApply === "function")
        {
          methodToApply(tableCellNode, x, y);
        }

        tableRowNode.appendChild(tableCellNode);
      }
    }

    return tableNode;
  }

  function applyTable(tableNode, methodToApply, parameters)
  {
    var x, y, rowNodes, cellNodes;

    rowNodes = tableNode.getElementsByTagName("tbody")[0].getElementsByTagName("tr");
    for (y = 0; y < rowNodes.length; y += 1)
    {
      cellNodes = rowNodes[y].getElementsByTagName("td");
      for (x = 0; x < cellNodes.length; x += 1)
      {
        methodToApply(cellNodes[x], x, y, parameters);
      }
    }
  }

  function insertDDLValues(node, values)
  {
    var i, optionNode, val;

    values = values.split("|");

    for (i = 0; i < values.length; i += 1)
    {
      optionNode = document.createElement("option");
      val = values[i].split(":");
      optionNode.value = val[0];
      optionNode.text = val[1];

      try
      {
        node.add(optionNode, null);
      } catch (ex) {
        node.add(optionNode); // IE only
      }
    }
  }

  function selectDDLValue(node, value)
  {
    var i;

    value = value.toString();

    for (i = 0; i < node.options.length; i += 1)
    {
      if (node.options[i].value === value)
      {
        node.selectedIndex = i;
        return true;
      }
    }

    return false;
  }

  function clearCell(node)
  {
    purgeEvents(node, [ "onclick", "onmouseover", "onmouseout" ]);
    removeCssClass(node);

    while (node.childNodes.length > 0)
    {
      node.removeChild(node.lastChild);
    }
  }

  function buildCell(node, x, y, parameters)
  {
    var day;

    if (y > 0)
    {
      clearCell(node);

      if (parameters.baseTick === undefined)
      {
        day = new Date();
        day.setDate(1);
        day.setMonth(parameters.month - 1);
        day.setFullYear(parameters.year);

        parameters.baseTick = day.getTime() - ((day.getDay() + localization.baseDayOffset) % 7) * 86400000;
      }

      day = new Date();
      day.setTime(parameters.baseTick + (x + y * 7 - 7) * 86400000);

      if (day.getMonth() + 1 !== parseInt(parameters.month, 10))
      {
        if (!betweenDates(day, parameters.minDate, parameters.maxDate))
        {
          appendCssClass(node, "outofmonth_passed");
        } else {
          appendCssClass(node, "outofmonth");
        }
      } else if (!betweenDates(day, parameters.minDate, parameters.maxDate))
      {
        if (sameDate(day, parameters.selectedDate))
        {
          appendCssClass(node, "selected_passed");
        } else if (sameDate(day, todayDate))
        {
          appendCssClass(node, "today_passed");
        } else if (x === localization.baseDayOffset)
        {
          appendCssClass(node, "holiday_passed");
        } else {
          appendCssClass(node, "passed");
        }
      } else {
        if (sameDate(day, parameters.selectedDate))
        {
          appendCssClass(node, "selected");
        } else if (sameDate(day, todayDate))
        {
          appendCssClass(node, "today");
        } else if (x === localization.baseDayOffset)
        {
          appendCssClass(node, "holiday");
        }

        node.onmouseover = cssHighlightRoutine;
        node.onmouseout = cssLowlightRoutine;
        node.onclick = function (day, deploymentNode, calendarNode, onWriteDate) {
          return function (ev) {
            deploymentNode.value = writeDate(day, localization.dateFormat);
            calendarNode.style.display = "none";
            if (typeof onWriteDate === "function")
            {
              onWriteDate({ target : deploymentNode });
            }
          };
        }(day, parameters.inputNode, parameters.calendarNode, parameters.onWriteDate);
      }

      node.title = localization.dayCaptionGenerator(day);
      node.appendChild(document.createTextNode(day.getDate()));
    }
  }

  function defaultDayCaptionGenerator(day)
  {
    return localization.days[(day.getDay() + 6) % 7] + ", " + day.getDate() + " " + localization.months[day.getMonth()] + " " + day.getFullYear();
  }

  function britishDayCaptionGenerator(day)
  {
    return localization.days[(day.getDay() + 6) % 7] + ", " + localization.months[day.getMonth()] + " " + day.getDate() + " " + day.getFullYear();
  }

  function readDate(dateText, dateFormat)
  {
    var date;

    if (dateText === null)
    {
      return null;
    }

    dateText = dateText.split(dateFormat.charAt(3));

    if (dateText.length !== 3)
    {
      return null;
    }

    date = new Date();
    date.setFullYear(parseInt(dateText[dateFormat.indexOf("y")], 10));
    date.setMonth(parseInt(dateText[dateFormat.indexOf("m")], 10) - 1);
    date.setDate(parseInt(dateText[dateFormat.indexOf("d")], 10));

    return date;
  }

  function writeDate(date, dateFormat)
  {
    var i, dateText, val;

    dateText = [];
    for (i = 0; i < 3; i += 1)
    {
      switch(dateFormat.charAt(i))
      {
      case "d":
        val = date.getDate();
        break;
      case "m":
        val = date.getMonth() + 1;
        break;
      case "y":
        val = date.getFullYear();
        break;
      }

      if (dateFormat.length === 5 && val < 10)
      {
        val = dateFormat.charAt(4) + val;
      }

      dateText.push(val);
    }

    return dateText.join(dateFormat.charAt(3));
  }

  function betweenDates(day0, day1, day2)
  {
    day0 = Math.floor(day0.getTime() / 86400000);
    day1 = Math.floor(day1.getTime() / 86400000);
    day2 = Math.floor(day2.getTime() / 86400000);

    return day1 <= day0 && day0 <= day2;
  }

  function sameDate(day1, day2)
  {
    if (day1 === null || day2 === null)
    {
      return false;
    }

    day1 = Math.floor(day1.getTime() / 86400000);
    day2 = Math.floor(day2.getTime() / 86400000);

    return day1 === day2;
  }

  function insertNodeAfter(node, insertee)
  {
    if (node.nextSibling === null)
    {
      node.parentNode.appendChild(insertee);
    } else {
      node.parentNode.insertBefore(insertee, node.nextSibling);
    }
  }

  function supressEvent(ev)
  {
    ev = ev || event;

    ev.cancelBubble = true; // IE
    if (ev.stopPropagation) // W3C
    {
      ev.stopPropagation();
    }
  }

  return {
    setup : function (locale, imgPath, todayText, dropMode) {
      var i, monthDDList;

      localization = {
        "en-inv" : {
          daySnip : 2,
          days : "Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday",
          months : "January|February|March|April|May|June|July|August|September|October|November|December",
          tooltips : "Previous month|Month|Year|Next month",
          captions : "Select day|Today|Selected day|Close",
          iconTitle : "Open calendar",
          dateFormat : "mdy/0",
          baseDayOffset : 0,
          dayCaptionGenerator : britishDayCaptionGenerator
        },
        "en" : {
          daySnip : 2,
          days : "Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday",
          months : "January|February|March|April|May|June|July|August|September|October|November|December",
          tooltips : "Previous month|Month|Year|Next month",
          captions : "Select day|Today|Selected day|Close",
          iconTitle : "Open calendar",
          dateFormat : "dmy/0",
          baseDayOffset : 6,
          dayCaptionGenerator : defaultDayCaptionGenerator
        },
        "it" : {
          daySnip : 2,
          days : "Lunedì|Martedì|Mercoledì|Giovedì|Venerdì|Sabato|Domenica",
          months : "Gennaio|Febbraio|Marzo|Aprile|Maggio|Giugno|Luglio|Agosto|Settembre|Ottobre|Novembre|Dicembre",
          tooltips : "Mese precedente|Mese|Anno|Mese successivo",
          captions : "Selezionare giorno|Oggi|Giorno selezionato|Chiudi",
          iconTitle : "Apri calendario",
          dateFormat : "dmy/0",
          baseDayOffset : 6,
          dayCaptionGenerator : defaultDayCaptionGenerator
        },
        "de" : {
          daySnip : 2,
          days : "Montag|Dienstag|Mittwoch|Donnerstag|Freitag|Samstag|Sonntag",
          months : "Januar|Februar|März|April|Mai|Juni|Juli|August|September|Oktober|November|Dezember",
          tooltips : "Vorheriger Monat|Monat|Jahr|Nächster Monat",
          captions : "Tag auswählen|Heute|Ausgewählter Tag|Schließen",
          iconTitle : "Kalendar öffnen",
          dateFormat : "dmy/0",
          baseDayOffset : 6,
          dayCaptionGenerator : defaultDayCaptionGenerator
        },
        "es" : {
          daySnip : 2,
          days : "Lunes|Martes|Miércoles|Jueves|Viernes|Sábado|Domingo",
          months : "Enero|Febrero|Marzo|Abril|Mayo|Junio|Julio|Agosto|Septiembre|Octubre|Noviembre|Diciembre",
          tooltips : "Mes anterior|Mes|Año|Mes siguiente",
          captions : "Seleccionar día|Hoy|Día seleccionado|Cerrar",
          iconTitle : "Abrir calendario",
          dateFormat : "dmy/0",
          baseDayOffset : 6,
          dayCaptionGenerator : defaultDayCaptionGenerator
        },
        "fr" : {
          daySnip : 2,
          days : "Lundi|Mardi|Mercredi|Jeudi|Vendredi|Samedi|Dimanche",
          months : "Janvier|Février|Mars|Avril|Mai|Juin|Juillet|Août|Septembre|Octobre|Novembre|Décembre",
          tooltips : "Mois precedent|Mois|Ans|Mois suivant",
          captions : "Sélectionnez jour|Aujourd'hui|Jour sélectionner|Fermez",
          iconTitle : "Ouvrir calendrier",
          dateFormat : "dmy/0",
          baseDayOffset : 6,
          dayCaptionGenerator : defaultDayCaptionGenerator
        }
      }[locale];

      if (typeof localization.days === "string")
      {
        localization.days = localization.days.split("|");
        localization.months = localization.months.split("|");
        localization.tooltips = localization.tooltips.split("|");
        localization.captions = localization.captions.split("|");

        monthDDList = [];
        for (i = 0; i < 12; i += 1)
        {
          monthDDList.push((i + 1) + ":" + localization.months[i]);
        }

        localization.monthDDList = monthDDList.join("|");
      }

      imagePath = imgPath;

      todayDate = readDate(todayText, "dmy/");

      lastOpenedCalendar = null;

      dropdowns = dropMode === undefined ? 2 : dropMode;
    },
    install : function (inputNodeId, minDate, maxDate, writeDateHandler) {
      var i, m, node, months, years, inputNode, calendarNode, tableNode, prevNode, monthNode, yearNode, nextNode, captionNode;

      minDate = readDate(minDate, "dmy/");
      maxDate = readDate(maxDate, "dmy/");

      inputNode = document.getElementById(inputNodeId);

      calendarNode = document.createElement("div");
      appendCssClass(calendarNode, "calendar");

      prevNode = document.createElement("input");
      prevNode.setAttribute("type", "button");
      prevNode.setAttribute("class", "MonthNavigate");
      prevNode.value = "<";
      prevNode.title = localization.tooltips[0];

      nextNode = document.createElement("input");
      nextNode.setAttribute("type", "button");
      nextNode.setAttribute("class", "MonthNavigate");
      nextNode.value = ">";
      nextNode.title = localization.tooltips[3];

      tableNode = buildTable(7, 7, function (node, x, y) {

        if (y === 0)
        {
          appendCssClass(node, "heading");
          node.title = localization.days[(x + (6 - localization.baseDayOffset)) % 7];
          node.appendChild(document.createTextNode(localization.days[(x + (6 - localization.baseDayOffset)) % 7].substring(0, localization.daySnip)));
        }
      });

      switch (dropdowns)
      {
      case 1:
        m = new Date();
        m.setTime(minDate.getTime());
        m.setDate(1);

        months = [];
        while (m.getTime() <= maxDate.getTime())
        {
          months.push((m.getMonth() + 1) + "_" + m.getFullYear() + ":" + localization.months[m.getMonth()] + " " + m.getFullYear());

          if (m.getMonth() === 11)
          {
            m.setFullYear(m.getFullYear() + 1);
            m.setMonth(0);
          } else {
            m.setMonth(m.getMonth() + 1);
          }
        }
        months = months.join("|");

        monthNode = document.createElement("select");
        insertDDLValues(monthNode, months);
        monthNode.title = localization.tooltips[1] + "/" + localization.tooltips[2];

        monthNode.onchange = function (node, lowNode, sourceNode, pNode, nNode) {
          return function () {
            var selectedDate, segs;

            selectedDate = readDate(sourceNode.value, localization.dateFormat);
            segs = lowNode.value.split("_");
            applyTable(node, buildCell, { month : segs[0], year : segs[1], minDate : minDate, maxDate : maxDate, selectedDate : selectedDate, inputNode : inputNode, calendarNode : calendarNode, onWriteDate : writeDateHandler });

            if (lowNode.selectedIndex > 0)
            {
              pNode.removeAttribute("disabled");
            } else {
              pNode.setAttribute("disabled", "disabled");
            }

            if (lowNode.selectedIndex < lowNode.options.length - 1)
            {
              nNode.removeAttribute("disabled");
            } else {
              nNode.setAttribute("disabled", "disabled");
            }
          };
        }(tableNode, monthNode, inputNode, prevNode, nextNode);
        break;
      case 2:
        monthNode = document.createElement("select");
        insertDDLValues(monthNode, localization.monthDDList);
        monthNode.title = localization.tooltips[1];

        years = [];
        for (i = minDate.getFullYear(); i <= maxDate.getFullYear(); i++)
        {
          years.push(i + ":" + i);
        }
        years = years.join("|");

        yearNode = document.createElement("select");
        insertDDLValues(yearNode, years);
        yearNode.title = localization.tooltips[2];

        monthNode.onchange = function (node, lowNode, highNode, sourceNode) {
          return function () {
            var selectedDate;

            selectedDate = readDate(sourceNode.value, localization.dateFormat);
            applyTable(node, buildCell, { month : lowNode.value, year : highNode.value, minDate : minDate, maxDate : maxDate, selectedDate : selectedDate, inputNode : inputNode, calendarNode : calendarNode, onWriteDate : writeDateHandler });
          };
        }(tableNode, monthNode, yearNode, inputNode);

        yearNode.onchange = monthNode.onchange;
        break;
      }

      prevNode.onclick = function (lowNode, highNode) {
        return function () {
          if (lowNode.selectedIndex > 0)
          {
            lowNode.selectedIndex -= 1;
            lowNode.onchange();
          } else {
            if (highNode !== undefined)
            {
              if (highNode.selectedIndex > 0)
              {
                highNode.selectedIndex -= 1;
                lowNode.selectedIndex = lowNode.options.length - 1;
                lowNode.onchange();
              }
            }
          }
        };
      }(monthNode, yearNode);

      nextNode.onclick = function (lowNode, highNode) {
        return function () {
          if (lowNode.selectedIndex < lowNode.options.length - 1)
          {
            lowNode.selectedIndex += 1;
            lowNode.onchange();
          } else {
            if (highNode !== undefined)
            {
              if (highNode.selectedIndex < highNode.options.length - 1)
              {
                highNode.selectedIndex += 1;
                lowNode.selectedIndex = 0;
                lowNode.onchange();
              }
            }
          }
        };
      }(monthNode, yearNode);

      if (document.all !== undefined) // IE only
      {
        prevNode.ondblclick = prevNode.onclick;
        nextNode.ondblclick = nextNode.onclick;
      }

      captionNode = document.createElement("div");
      appendCssClass(captionNode, "header_caption");

      node = document.createElement("div");
      node.title = localization.captions[3];
      node.onclick = function (calendarNode) {
        return function (ev) {
          calendarNode.style.display = "none";
          lastOpenedCalendar = null;
        };
      }(calendarNode);

      node.appendChild(document.createTextNode("x"));
      appendCssClass(node, "cancelbutton");

      captionNode.appendChild(node);
      captionNode.appendChild(document.createTextNode(localization.captions[0]));
      calendarNode.appendChild(captionNode);

      calendarNode.appendChild(prevNode);
      calendarNode.appendChild(monthNode);

      if (yearNode !== undefined)
      {
        calendarNode.appendChild(yearNode);
      }

      calendarNode.appendChild(nextNode);
      calendarNode.appendChild(tableNode);

      if (todayDate !== null)
      {
        captionNode = document.createElement("div");
        appendCssClass(captionNode, "legend_caption");
        node = document.createElement("div");
        appendCssClass(node, "legend_today");
        captionNode.appendChild(node);
        captionNode.appendChild(document.createTextNode(localization.captions[1]));
        calendarNode.appendChild(captionNode);
      }

      captionNode = document.createElement("div");
      appendCssClass(captionNode, "legend_caption");
      node = document.createElement("div");
      appendCssClass(node, "legend_selected");
      captionNode.appendChild(node);
      captionNode.appendChild(document.createTextNode(localization.captions[2]));
      calendarNode.appendChild(captionNode);

      node = document.createElement("img");
      node.src = imagePath;
      node.title = localization.iconTitle;
      appendCssClass(node, "calendarIcon");

      node.onclick = function (lowNode, highNode, sourceNode, calendarNode) {
        return function (ev) {
          var selectedDate;

          if (calendarNode.style.display !== "block")
          {
            selectedDate = readDate(sourceNode.value, localization.dateFormat);

            if (selectedDate === null)
            {
              selectedDate = new Date();
              selectedDate.setTime(Math.floor(selectedDate.getTime() / 86400000) * 86400000);
            }

            if (yearNode !== undefined)
            {
              selectDDLValue(lowNode, selectedDate.getMonth() + 1);
              selectDDLValue(highNode, selectedDate.getFullYear());
            } else {
              selectDDLValue(lowNode, (selectedDate.getMonth() + 1) + "_" + selectedDate.getFullYear());
            }

            lowNode.onchange();

            if (lastOpenedCalendar !== null)
            {
              lastOpenedCalendar.style.display = "none";
            }

            lastOpenedCalendar = calendarNode;

            calendarNode.style.display = "block";
            positionTo(sourceNode, calendarNode);

            supressEvent(ev);
          }
        };
      }(monthNode, yearNode, inputNode, calendarNode);

      calendarNode.style.display = "none";

      calendarNode.onclick = function (ev) {
        supressEvent(ev);
      };

      document.onclick = function () {
        if (lastOpenedCalendar !== null)
        {
          lastOpenedCalendar.style.display = "none";
          lastOpenedCalendar = null;
        }
      };

      insertNodeAfter(inputNode, node);
      insertNodeAfter(inputNode, calendarNode);
    }
  };
}();

