CalendarMonth.from = function (date)
{
    var month = date.getMonth();
    var year = date.getFullYear();
    
    return new CalendarMonth(month, year);
    
}

function CalendarMonth (month, year) 
{
    this.month = month;
    this.year = year;
}

CalendarMonth.prototype = 
{
    month: -1,
    year: -1,
    
    weeks: function ()
    {
        var weeks = [];
        var week = this.firstWeek();
        
        while (week != null)
        {
            weeks.push(week);
            week = week.next();
        }
        
        return weeks;
    },
    
    firstWeek: function ()
    {
        var lastDay = this.lastDay();
        var firstWeekday = this.firstWeekday();
        return new CalendarWeek(firstWeekday, 1, lastDay);
    },
    
    firstWeekday: function ()
    {
        var offsetWithMondayAsFirst = -1;
        var date = new Date(this.year, this.month, 1);
        var weekday = date.getDay() + offsetWithMondayAsFirst;
        
        return weekday < 0 ? weekday + 7 : weekday;
    },
    
    lastDay: function ()
    {
        var date = new Date(this.year, this.month + 1, 0);
        return date.getDate();
    },
    
    next: function ()
    {
        return this.monthByOffset(1);
    },
    
    previous: function ()
    {
        return this.monthByOffset(-1);
    },
    
    monthByOffset: function (offset)
    {
        var date = new Date(this.year, this.month + offset, 1);
        return new CalendarMonth(date.getMonth(), date.getFullYear());
    },
    
    contains: function (date)
    {
        var month = date.getMonth();
        var year = date.getFullYear();
        return this.month === month && this.year === year;
    },
    
    toDate: function (day)
    {
        return new Date(this.year, this.month, day);
    },
    
    toString: function ()
    {
        var names = ["Jan", "Feb", "Mar", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Des"];
        return names[this.month] + " " + this.year;
    }
}
function CalendarRenderer () {}

CalendarRenderer.prototype = 
{
    title: null,
    dayTitles: null,
    previousMonth: null,
    nextMonth: null,
    weeksInMonth: [],
    
    prepare: function (calendar)
    {
        this.prepareTitle(calendar);
        this.prepareMonthSelectors(calendar);
        this.prepareDayTitles();
        this.prepareAllWeeks(calendar);
    },
    
    buildTable: function ()
    {
        var table = Tag.create("table").noBorder().noCellspacing();
        var body = table.child("tBody");
        
        this.appendTitleRowTo(body);
        this.appendDayLabelRowTo(body);
        this.appendWeekRowsTo(body);
        
        return table.node;
    },
    
    appendWeekRowsTo: function (body)
    {
        var weeks = this.weeksInMonth;
        for (var i = 0; i < weeks.length; i++)
        {
            var row = this.dayRow(weeks[i]);
            row.appendTo(body);
        }
    },
    
    dayRow: function (week)
    {
        var row = Tag.create("tr");
        
        for (var i = 0; i < week.length; i++)
        {
            week[i].appendTo(row);
        }
        
        return row;
    },
    
    appendDayLabelRowTo: function (body)
    {
        var row = Tag.create("tr");
        var titles = this.dayTitles;
        for (var i = 0; i < titles.length; i++)
        {
            titles[i].appendTo(row);
        }
        row.appendTo(body);
    },
    
    appendTitleRowTo: function (body)
    {
        var row = Tag.create("tr");
        this.previousMonth.appendTo(row);
        this.title.appendTo(row);
        this.nextMonth.appendTo(row);
        
        row.appendTo(body);
    },
    
    prepareTitle: function (calendar)
    {
        var text = calendar.title();
        this.title = Tag.create("td").style("calendar-title").text(text).colspan(5);
    },
    
    prepareMonthSelectors: function (calendar)
    {
        var template = Tag.create("td").style("calendar-month-selector");
        this.previousMonth = template.clone().text("&lt;");
        this.nextMonth = template.clone().text("&gt;");
        
        calendar.registerPreviousMonthSelector(this.previousMonth.node);
        calendar.registerNextMonthSelector(this.nextMonth.node);
    },
    
    prepareDayTitles: function ()
    {
        var template = Tag.create("td").style("calendar-day-title");
        var days = [];
        
        var names = ["Ma", "Ti", "On", "To", "Fr", "L&oslash;", "S&oslash;"];
        for (var i = 0; i < names.length; i++)
        {
            days.push(template.clone().text(names[i]));
        }
        
        this.dayTitles = days;
    },
    
    prepareAllWeeks: function (calendar)
    {
        var week = calendar.firstWeek();
        
        var weeks = [];
        
        while (week != null)
        {
            weeks.push(this.prepareDaySelectors(week, calendar));
            week = week.next();
        }
        
        this.weeksInMonth = weeks;
    },
    
    prepareDaySelectors: function (week, calendar)
    {
        var selectedDay = calendar.selectedDay();
        var weekdays = [];

        for (var i = 0; i < 7; i++)
        {
            var day = week.dayAt(i);
            var text = week.isEmpty(i) ? "" : day;
            
            var isEmpty = week.isEmpty(i);
            var isSelected = day === selectedDay;
            var className = this.dayStyle(isEmpty, isSelected);

            var tag = Tag.create("td").style(className);
            
            if (isEmpty === false)
            {
                calendar.registerDaySelector(tag.node, day);
            }
            
            weekdays.push(tag.text(text));
        }
        
        return weekdays;
    },
    
    dayStyle: function (isEmpty, isSelected)
    {
        var className;
        if (isEmpty)
        {
            className = "calendar-day-unselectable";
        }
        else if (isSelected)
        {
            className = "calendar-day-selected";
        }
        else
        {
            className = "calendar-day";
        }
        
        return className;
    }
}
function CalendarWeek (firstDayAt, firstDay, lastDay) 
{
    this.firstDayAt = firstDayAt;
    this.firstDay = firstDay;
    this.lastDay = lastDay;
}

CalendarWeek.prototype = 
{
    firstDayAt: -1,
    firstDay: -1,
    lastDay: -1,
    
    isEmpty: function (number)
    {
        return number < this.firstDayAt || this.dayAt(number) > this.lastDay;
    },

    dayAt: function (number)
    {
        return number + this.firstDay - this.firstDayAt;
    },
    
    next: function ()
    {
        var firstDay = this.dayAt(6) + 1;
        var firstDayAt = 0;
        var week = new CalendarWeek(firstDayAt, firstDay, this.lastDay, this.selectedDay);
        
        return week.isValid() ? week : null;
    },
    
    isValid: function ()
    {
        return this.dayAt(0) <= this.lastDay;
    }
}




Calendar.create = function (containerId, fieldId)
{
    var calendar = new Calendar();
    calendar.registerContainerById(containerId);
    calendar.registerDateFieldById(fieldId);
    calendar.selectFromField();
}

function Calendar () 
{
}

Calendar.prototype = 
{
    container: null,
    dateField: null,
    month: null,
    selectedDate: null,
    
    selectDay: function (day)
    {
        var date = this.month.toDate(day);
        this.select(date);
    },
    
    selectFromField: function ()
    {
        var date = new Date(this.dateField.value);
        this.select(date);
    },
    
    select: function (date)
    {
        this.selectedDate = date;
        this.month = CalendarMonth.from(date);
        this.render();
        this.updateDateField(date);
    },
    
    updateDateField: function (date)
    {
        if (this.dateField)
        {
            this.dateField.value = this.formattedDate(date);
        }
    },
    
    formattedDate: function (date)
    {
        var month = date.getMonth() + 1;
        var year = date.getFullYear();
        var day = date.getDate() ;
        return month + "/" + day + "/" + year;
    },
    
    previousMonth: function ()
    {
        this.selectMonth(this.month.previous());
    },
    
    nextMonth: function ()
    {
        this.selectMonth(this.month.next());
    },
    
    selectMonth: function (month)
    {
        this.month = month;
        this.render();
      },
    
    render: function ()
    {
        if (this.container == null) return;
        
        var renderer = new CalendarRenderer();
        renderer.prepare(this);
        
        var table = renderer.buildTable();
        
        var container = this.container;
        
        while (container.hasChildNodes()) 
        {
            container.removeChild(container.firstChild);
        }
        
        container.appendChild(table);
    },
    
    firstWeek: function ()
    {
        return this.month.firstWeek();
    },
    
    title: function ()
    {
        return this.month.toString();
    },
    
    selectedDay: function ()
    {
        var date = this.selectedDate;
        return this.month.contains(date) ? date.getDate() : -1;
    },
    
    registerContainerById: function(id)
    {
        var node = document.getElementById(id);
        this.registerContainer(node);
    },
    
    registerDateFieldById: function (id)
    {
        var node = document.getElementById(id);
        this.registerDateField(node);
    },
    
    registerContainer: function(node)
    {
        this.container = node;
    },
    
    registerDateField: function (node)
    {
        this.dateField = node;
    },
    
    registerDaySelector: function (node, day)
    {
        var calendar = this;
        node.onclick = function ()
        {
            calendar.selectDay(day);
        };
    },
    
    registerPreviousMonthSelector: function (node)
    {
        var calendar = this;
        node.onclick = function ()
        {
            calendar.previousMonth();
        }
    },
    
    registerNextMonthSelector: function (node)
    {
        var calendar = this;
        node.onclick = function ()
        {
            calendar.nextMonth();
        }
    }
}



function Tag (node) 
{
    this.node = node;
}

Tag.create = function (tagName)
{
    var node = document.createElement(tagName);
    var tag = new Tag(node);
    return tag;
}

Tag.prototype = 
{
    node: null,
    
    child: function (tagName)
    {
        var tag = Tag.create(tagName);
        tag.appendTo(this);
        return tag;
    },
    
    appendTo: function (tag)
    {
        tag.appendNode(this.node);
    },
    
    text: function (html)
    {
        this.node.innerHTML = html;
        return this;
    },
    
    colspan: function (count)
    {
        this.node.colSpan = count;
        return this;
    },
    
    style: function (className)
    {
        this.node.className = className;
        return this;
    },
    
    noBorder: function ()
    {
        this.node.border = "0";
        return this;
    },
    
    noCellspacing: function ()
    {
        this.node.cellPadding = "0";
        this.node.cellSpacing = "0";
        return this;
    },
    
    clone: function ()
    {
        return new Tag(this.node.cloneNode(true));
    },

    appendNode: function (node)
    {
        this.node.appendChild(node);
        return this;
    }    
}
