(function($){
    Array.prototype.in_array = function(v) {
        for(var i in this) {
            if(this[i] == v) {
                return true;
            }
        }
        return false;
    }

    // default uses date time
    var date = new Date();
    date.setHours(0);
    date.setMinutes(0);
    date.setSeconds(0);
    date.setMilliseconds(0);

    var globalToday = new Date();
    globalToday.setHours(0, 0, 0, 0);

    // constructor
    $.workcalendar = function(el, options) {
        var d = date;

        this.elm = el;
        this.options = $.extend({
            label: {"sun": "Sun", "mon": "Mon", "tue": "Tue", "wed": "Wed", "thu": "Thu", "fri": "Fri", "sat": "Sat"},
            holidays: ["Sun", "Sat"],
            ext_holiday: [],
            onToday: function() {},
            onPrev: function() {},
            onNext: function() {}
        }, options || {});

        // now date
        this.now_year  = d.getFullYear();
        this.now_month = d.getMonth();
        this.now_date  = d.getDate();
        this.now_day   = d.getDay();

        // selected date
        this.sel_year  = this.now_year;
        this.sel_month = this.now_month;
        this.sel_date  = this.now_date;
        this.sel_day   = this.now_day;


		// clone extra holidays
		var _clone = [];
		for(var i = 0; i < this.options.ext_holiday.length; i ++) {
			_clone[i] = this.options.ext_holiday[i];
		}

		this.first_ext_holiday = new Date(_clone.shift());
		this.first_ext_holiday.setHours(0);
		this.first_ext_holiday.setMinutes(0);
		this.first_ext_holiday.setSeconds(0);
		this.first_ext_holiday.setMilliseconds(0);
		if(_clone.length == 0) {
			this.last_ext_holiday = new Date(this.first_ext_holiday);
		}
		else {
			this.last_ext_holiday = new Date(_clone.pop());
		}
		this.last_ext_holiday.setHours(0);
		this.last_ext_holiday.setMinutes(0);
		this.last_ext_holiday.setSeconds(0);
		this.last_ext_holiday.setMilliseconds(0);

        this.init();
    };

    $.workcalendar.fn = $.workcalendar.prototype = {};
    $.workcalendar.fn.extend = $.extend;
    $.workcalendar.fn.extend({
        init: function() {
            var t = this,
                $el = $(this.elm),
                thead = "";

            this._mapHolidays();

            thead += '<table border="0" cellpadding="0" cellspacing="0" class="workcalendar">';
                thead += '<thead><tr class="buttons">';
                    thead += '<th class="prev"><span class="calendar_prev"></span></th>'; // prev
                    thead += '<th class="current" colspan="5"><span class="go_to_today"><span class="current_year">'+ date.getFullYear() +'</span>'
                             + '/'
                             + '<span class="current_month">' + (date.getMonth() + 1) +'</span></span></th>'; // current year/month
                    thead += '<th class="next"><span class="calendar_next"></span></th>'; // next
                thead += "</tr>";
                thead += '<tr class="weeks">' + this._getWeeklyHeader() + "</tr></thead>";
            thead += "<tbody></tbody></table>";
            $el.append(thead);

            // append events
            this.prev_btn = $(".calendar_prev", this.elm).bind("click", function(ev){
                t.prevMonth(ev);
            });
            this.next_btn = $(".calendar_next", this.elm).bind("click", function(ev){
                t.nextMonth(ev);
            });
            this.today_btn = $(".go_to_today", this.elm).bind("click", function(ev){
                t.toDay(ev);
            });

            // jQuery Objects
            this.current_year  = $(".current_year", this.elm);
            this.current_month = $(".current_month", this.elm);

            this.toDay();
        },
        /**
         * create an Calendar
         *
         * @return Void
         */
        create: function() {
            var t = this, $el = $("tbody", this.elm), calendar = "";

            $el.html(calendar);

            // padding to today
            var first_day = this._getFirstDay();
            for(var i = 0; i < first_day; i ++) {
                calendar += "<td>&nbsp;</td>";
            }

            // days
            var days = this._getLastDay();

            // leap year
            if(this._isLeapYear() == this.sel_month == 2) {
                ++ days;
            }

            var day = this._getDateInstance();
            for(i = 1; i <= days; i ++) {
                day.setDate(i);
                if(day.getDay() == 0) {calendar += "<tr>";}
                calendar += '<td class="';

                // is today
                if(day.getTime() == globalToday.getTime()) calendar += " today";
                // holiday
                if(this._isHoliday(day.getDay(i))) calendar += " holiday";
                // extra holiday
                if(this._isExtraHoliday(day)) calendar += " ext_holiday";

                calendar += '">' + i +"</td>";

                if(day.getDay() == 6) {calendar += "</tr>";}
            }

            day.setDate(days);
            var pad = day.getDay();
            while(pad < 6) {
                calendar += "<td>&nbsp;</td>";
                ++pad;
            }

            this.render(calendar);
        },
        /**
         * render calendar
         *
         * @param  Calendar    String
         * @return Void
         */
        render: function(calendar) {
            var $el = $("tbody", this.elm);
            if(calendar.substr(0, 4) != "<tr>") {
                calendar = "<tr>" + calendar;
            }
            if(calendar.substr(calendar.length, -5) != "</tr>") {
                calendar += "</tr>";
            }

            $el.html(calendar);
        },
        /**
         * set selected year to now year.
         *
         * @access public
         * @return Void
         */
        toDay: function() {
/*            this.sel_year  = this.now_year;
            this.sel_month = this.now_month;
            this.sel_date  = this.now_date;
            this.sel_day   = this.now_day;*/
            var d = new Date();
            date.setYear(d.getFullYear());
            date.setMonth(d.getMonth());
            date.setDate(d.getDate());
            //date.setDay(d.getDay());

            // on today callback
            if($.isFunction(this.options.onToday))
                this.options.onToday.apply(this.elm, arguments);

            this.current_year.text(date.getFullYear());
            this.current_month.text(date.getMonth() + 1);

            this.create();
        },
        /**
         * next calendar
         *
         * @access public
         * @return Void
         */
        nextMonth: function(ev) {
            date.setMonth((date.getMonth() + 1), 1);
            if(date.getMonth() == 0) {
                //date.setMonth(0);
                //date.setYear(date.getFullYear());
                this.current_year.text(date.getFullYear());
            }
/*            ++this.sel_month;
            if(this.sel_month > 11) {
                this.sel_month = 0;
                ++this.sel_year;
                this.current_year.text(this.sel_year);
            }*/

            // next callback
            if($.isFunction(this.options.onNext))
                this.options.onNext.apply(this.elm, arguments);

			var now = this._getDateInstance().getTime();
			if(now >= this.last_ext_holiday.getTime()) {
				--this.sel_month;
				return false;
			}

            this.current_month.text(date.getMonth() + 1);
            this.create();
        },
        /**
         * previous calendar
         *
         * @access public
         * @return Void
         */
        prevMonth: function(ev) {
            // previous callback
            if($.isFunction(this.options.onPrev))
                this.options.onPrev.apply(this.elm, arguments);

			var now = this._getDateInstance().getTime();
			if(this.first_ext_holiday.getTime() > now) {
				return false;
			}

            date.setMonth(date.getMonth() - 1);
            if(date.getMonth() == 11) {
                //date.setMonth(11);
                //date.setYear(date.getFullYear() - 1);
                this.current_year.text(date.getFullYear());
            }
/*
            --this.sel_month;
            if(this.sel_month < 0) {
                this.sel_month = 11;
                --this.sel_year;
                this.current_year.text(this.sel_year);
            }*/

            this.current_month.text(date.getMonth() + 1);
            this.create();
        },
        /**
         * mapping to holiday data
         *
         * @return Void
         */
        _mapHolidays: function() {
            var reg = this.options.holidays,
                ext = this.options.ext_holiday;

            // regular holiday
            for(var i = 0; i < reg.length; i ++) {
                switch(reg[i].toLowerCase()) {
                    case "sun":reg[i] = 0;break;
                    case "mon":reg[i] = 1;break;
                    case "tue":reg[i] = 2;break;
                    case "wed":reg[i] = 3;break;
                    case "thu":reg[i] = 4;break;
                    case "fri":reg[i] = 5;break;
                    case "sat":reg[i] = 6;break;
                }
            }
            this.options.holidays = reg;

            // do parse extra holidays
            for(var j = 0; j < ext.length; j ++ ) {
                var tmp = new Date(ext[j]);
                tmp.setHours(0);
                tmp.setMilliseconds(0);
                tmp.setMinutes(0);
                tmp.setSeconds(0);
                ext[j] = tmp;
                delete tmp;
            }
            this.options.ext_holiday = ext;
        },
        /**
         * get a current year/month start day
         *
         * @return Int
         */
        _getFirstDay: function() {
            var d = new Date();
            d.setYear(date.getFullYear());
            d.setMonth(date.getMonth(), 1);
            d.setDate(1);
            d.setHours(0);
            d.setMinutes(0);
            d.setSeconds(0);
            d.setMilliseconds(0);
            return d.getDay();
        },
        /**
         * get last day for selected year.
         *
         * @access private
         * @return Int
         */
        _getLastDay: function() {
            var today = new Date(
                date.getFullYear(),
                date.getMonth(),
                date.getDate()
            );
            today.setHours(0, 0, 0, 0);
            today.setMonth((today.getMonth() + 1), 1);
            today.setDate(1);
            today.setTime(today.getTime() - 1);

            /*
            var today = new Date(
                this.sel_year,
                this.sel_month,
                this.sel_date
            );
            today.setMonth(this.sel_month);
            today.setDate(1);
            today.setTime(today.getTime() - 1);
            */
            return today.getDate();
        },
        /**
         * create new Date instance
         * 
         * @param  d     Int
         * @return Date
         */
        _getDateInstance: function(d) {
            var day = new Date();
            day.setYear(date.getFullYear());
            //day.setMonth(date.getMonth(), 1);
            day.setHours(0);
            day.setMinutes(0);
            day.setSeconds(0);
            day.setMilliseconds(0);

            if(d) day.setMonth(date.getMonth(), d);
            else day.setMonth(date.getMonth(), date.getDate());

            return day;
        },
        /**
         * is in a holiday ?
         *
         * @param  d
         * @return Bool
         */
        _isHoliday: function(d) {
            return this.options.holidays.in_array(d);
        },
        /**
         * a day of extra holiday ?
         *
         * @param  t    Int    timestap
         * @return Bool
         */
        _isExtraHoliday: function(t) {
            var o = this.options.ext_holiday;
            for(var i = 0; i <  o.length; i ++) {
                if(o[i].getTime() == t.getTime() && o[i].getDay() == t.getDay()) {
                    return true;
                }
            }
            return false;
        },
        /**
         * is selected year is leap year ?
         *
         * @return Bool
         */
        _isLeapYear: function() {
            return ((((this.sel_year % 4)% 100) % 400) === 0);
        },
        /**
         * get weekly header tags
         *
         * @return String
         */
        _getWeeklyHeader: function() {
            var header = "",
                opt = this.options.label;
            for(var i in opt) {
                header += "<th>"+ opt[i] +"</th>";
            }
            return header;
        }
    });


    $.fn.workcalendar = function(options) {
        return this.each(function(){
            new $.workcalendar(this, options);
        });
    }
})(jQuery);