jQuery.fn.abtCal = function(options) {

    var defaults = {
        today: new Date(),
        weekdayFormat: "Short", // 'Short', 'Long', [Custom Object]
        monthFormat: "Long", 	// 'Short', 'Long', [Custom Object]
        firstDayOfWeek: "Sun", 	// Short format
        previousText: "Previous",
        todayText: "Goto Today",
        nextText: "Next",
        controlsFormat: 'Bottom', // 'Bottom', 'Top', 'None'
        popupLocation: "LeftTop", // 'LeftTop', 'LeftCenter', 'LeftBottom', 'TopLeft', 'TopCenter', 'TopRight' ...
        popupWidth: 225,
        googleFeed: "", 		// Google Cal XML Feed
        scriptJson: []
    };
    var settings = jQuery.extend({}, defaults, options);

    var calendarElement;
    var mainElement;
    var popupElement;

    var currentMonth;
    var currentDay;
    var currentYear;
    var weekOffset = 0;
    var firstDayOfMonth;
    var lastDayOfMonth;
    var currentEventList = [];

    var LongMonths = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
    var ShortMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    var LongDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    var ShortDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];


    return this.each(function() {

        Init(this);

        // Load First Calendar
        calendarElement.append(BuildCalendar().show());

        SetListeners()
    });

    function Init(element) {
        currentMonth = settings.today.getMonth();
        currentDay = settings.today.getDate();
        currentYear = settings.today.getFullYear();
        SetFirstAndLast();

        if (Object.isArray(settings.monthFormat)) {
            LongMonths = settings.monthFormat;
            settings.monthFormat = "Long";
        }
        if (Object.isArray(settings.weekdayFormat)) {
            LongDays = settings.weekdayFormat;
            settings.weekdayFormat = "Long";
        }


        for (var i = 0; i < 7; i++) {
            if (LongDays[i].toLowerCase() == settings.firstDayOfWeek.toLowerCase()
				|| ShortDays[i].toLowerCase() == settings.firstDayOfWeek.toLowerCase()) {
                weekOffset = i;
            }
        }

        mainElement = jQuery(element);
        mainElement.append('<div class="abtCal-calendars"></div>');
        calendarElement = jQuery('.abtCal-calendars', mainElement);
        mainElement.append(String.Format('<div class="abtCal-popup"><div class="abtCal-popupContent"></div><div class="abtCal-tip {0}"></div><div class="abtCal-close">[close]</div></div>', settings.popupLocation.toLowerCase()));
        popupElement = jQuery('.abtCal-popup', mainElement).hide().css({ 'position': 'absolute' }).width(settings.popupWidth);

        jQuery('.abtCal-close', popupElement).click(function() {
            popupElement.fadeOut();
        });

        var controlString = String.Format('<div class="abtCal-controls"><ul class="abtCal-buttons"><li class="abtCal-button prev" title="Previous Month"><span>{0}</span></li><li class="abtCal-button goto-today" title="Current Month"><span>{1}</span></li><li class="abtCal-button next" title="Next Month"><span>{2}</span></li></ul></div>', settings.previousText, settings.todayText, settings.nextText);
        if (settings.controlsFormat.toLowerCase() == 'bottom') {
            mainElement.append(controlString);
        }
        if (settings.controlsFormat.toLowerCase() == 'top') {
            mainElement.prepend(controlString);
        }
        settings.googleFeed = settings.googleFeed.replace(/\/basic$/, '/full');
    }

    function SetFirstAndLast() {
        firstDayOfMonth = new Date(currentYear, currentMonth, 1, 0, 0, 0, 0);
        lastDayOfMonth = new Date((((currentMonth + 1) % 12 === 0) ? currentYear + 1 : currentYear), (currentMonth + 1) % 12, 1, 23, 59, 59, 0);
        lastDayOfMonth.setDate(lastDayOfMonth.getDate() - 1);
    }

    function BuildCalendar() {
        if (jQuery('.abtCal-' + ShortMonths[currentMonth] + (currentYear % 100), calendarElement).length > 0) { return; }

        var cal = jQuery('<div class="abtCal-monthView abtCal-' + ShortMonths[currentMonth] + (currentYear % 100) + '"></div>');
        cal.hide();

        var monthName = ShortMonths[currentMonth];
        if (settings.monthFormat.toLowerCase() == 'long') { monthName = LongMonths[currentMonth]; }
        cal.append(String.Format('<h3 class="abtCal-title">{0} {1}</h3>', monthName, currentYear));
        cal.append('<table class="abtCal-table"><thead></thead><tbody></tbody></table>');
        GenerateTableHeader(jQuery('thead', cal));
        GenerateDatesTable(jQuery('tbody', cal));

        LoadEvents();
        return cal;
    }

    function GenerateTableHeader(thead) {
        thead.append('<tr class="weekdays"></tr>');
        var row = jQuery('tr', thead);

        var weekdays = ShortDays;
        if (settings.weekdayFormat.toLowerCase() == 'long') { weekdays = LongDays; }

        for (var i = 0; i < 7; i++) {
            var dayOfWeek = (i + weekOffset) % 7;
            var dayName = weekdays[dayOfWeek];
            var className = ShortDays[dayOfWeek].toLowerCase();
            if (dayOfWeek == 0 || dayOfWeek == 6) {
                className = className + ' weekend';
            }

            row.append(String.Format('<th class="{0}" scope="col">{1}</th>', className, dayName));
        }
    }

    function GenerateDatesTable(tbody) {
        var monthStart = new Date(firstDayOfMonth);
        var startWeek = new Date(monthStart.setDate(monthStart.getDate() - monthStart.getDay() + weekOffset));
        var endWeek = new Date(monthStart.setDate(startWeek.getDate() + 6));

        if (startWeek.getDate() != 1 && startWeek.getMonth() == currentMonth) {
            // Make sure to get the first week that has current month
            startWeek.setDate(startWeek.getDate() - 7);
            endWeek.setDate(endWeek.getDate() - 7);
        }

        while (startWeek.getMonth() == currentMonth || endWeek.getMonth() == currentMonth) {
            tbody.append(GenerateDateRow(startWeek));
            startWeek.setDate(endWeek.getDate() + 1);
            endWeek.setDate(endWeek.getDate() + 7);

        }
    }

    function GenerateDateRow(startDate) {
        var weekElement = jQuery('<tr class="week"></tr>');
        for (var i = 0; i < 7; i++) {
            var activeDay = startDate.getDate();
            var activeMonth = startDate.getMonth();
            var activeDayOfWeek = startDate.getDay();
            var activeString = startDate.toDateString();

            var dayElement = jQuery(String.Format('<td><div class="abtCal-day"><p class="date">{0}</p><div class="abtCal-content" style="display: none;"></div></div></td>', activeDay));

            if (activeMonth == currentMonth)
                dayElement.addClass('current-month');
            else
                dayElement.addClass('inactive-month');

            dayElement.addClass(ShortDays[activeDayOfWeek].toLowerCase());
            if (activeDayOfWeek == 0 || activeDayOfWeek == 6)
                dayElement.addClass('weekend');

            if (activeString == settings.today.toDateString())
                dayElement.addClass('today');

            dayElement.addClass('day' + activeDay);

            weekElement.append(dayElement);
            startDate.setDate(startDate.getDate() + 1)
        }

        return weekElement;
    }


    function LoadEvents() {
        jQuery.each(settings.scriptJson, function(i, entry) {
            var startDate = new Date(entry.start);
            if (startDate == 'Invalid Date') { startDate = new Date(); }
            var endDate = new Date(entry.end);
            if (endDate == 'Invalid Date') { endDate = startDate; }


            if (startDate.getMonth() == currentMonth || endDate.getMonth() == currentMonth) {
                currentEventList.push({
                    //	id: entry.id,
                    title: entry.title,
                    url: entry.url,
                    start: startDate,
                    end: endDate,
                    location: entry.location,
                    description: entry.description
                });
            }
        });

        if (settings.googleFeed.length > 0) {
            var params = {
                'start-min': Date.RFC3339(firstDayOfMonth),
                'start-max': Date.RFC3339(lastDayOfMonth),
                'singleevents': true,
                'max-results': 9999
            };
            jQuery.getJSON(settings.googleFeed + "?alt=json-in-script&callback=?", params, function(data) { EventsDataCallback(data); });
        }
        else {
            SetEventsData();
        }
    }

    function EventsDataCallback(data) {
        // Merge Google Calendar with onpage JSON
        if (data.feed.entry == undefined) return;
        //var limit_count = 40;
        jQuery.each(data.feed.entry, function(i, entry) {
            //if (limit_count > 0) {

            var start = new Date(Date.toRFC3339(entry['gd$when'][0]['startTime']));
            var end = new Date(Date.toRFC3339(entry['gd$when'][0]['endTime']));
            //			alert(start + '-' + end);
            var url = '';
            jQuery.each(entry.link, function() {
                if (this.type == 'text/html') {
                    url = this.href + '&ctz=America/New_York'; ;
                }
            });
            if (entry['gd$when'][0]['startTime'].indexOf('T') == -1) { end.setDate(end.getDate() - 1); }

            currentEventList.push({
                //	id: entry['gCal$uid']['value'],
                title: entry['title']['$t'],
                url: url,
                start: start,
                end: end,
                location: entry['gd$where'][0]['valueString'],
                description: entry['content']['$t']
            });
            //limit_count--;
            //}
        });
        SetEventsData();
    }

    function SetEventsData() {
        var currentCalendar = jQuery('.abtCal-' + ShortMonths[currentMonth] + (currentYear % 100), calendarElement);
        var calendarDays = jQuery('.current-month', currentCalendar);
        while (currentEventList.length > 0) {
            var event = currentEventList.pop();

            var eventContent = jQuery('<div class="abtCal-event"></div>');

            // Add Title
            if (event.title != undefined && event.url != undefined) {
                eventContent.append(String.Format('<p class="abtCal-eventTitle"><strong><a href="{1}" target="_blank">{0}</a></strong></p>', event.title, event.url));
            }
            else if (event.title != undefined) {
                eventContent.append(String.Format('<p class="abtCal-eventTitle"><strong>{0}</strong></p>', event.title));
            }

            // Add Date
            if (event.start.getDate() == event.end.getDate()) {
                eventContent.append(String.Format('<p class="abtCal-eventTime"><em>{0}</em></p>', event.start.toLocaleDateString()));
            }
            else {
                eventContent.append(String.Format('<p class="abtCal-eventTime"><em>{0}</em><span> - </span><em>{1}</em></p>', event.start.toLocaleDateString(), event.end.toLocaleDateString()));
            }


            // Add Location
            if (event.location != undefined) {
                eventContent.append(String.Format('<p class="abtCal-eventLocation"><em>{0}</em></p>', event.location));
            }

            // Add Description
            if (event.description != undefined) {
                eventContent.append(String.Format('<p class="abtCal-eventDesc">{0}</p>', event.description));
            }


            // Add Linky
            if (event.url != undefined) {
                eventContent.append(String.Format('<p class="abtCal-eventLink"><a href="{0}" target="_blank">Read More</a></p>', event.url));
            }

            //			var currentMonthEndDate = event.end.getDate();
            //			var currentMonthStartDate = event.start.getDate();
            //			if (currentMonth != event.end.getMonth()) {
            //				currentMonthEndDate = 31;
            //			}
            //			if (currentMonth != event.start.getMonth()) {
            //				currentMonthStartDate = 1;
            //			}
            //			for (var i = currentMonthStartDate; i <= currentMonthEndDate; i++)
            for (var i = event.start.getDate(); i <= event.end.getDate(); i++) {
                var targetDay = calendarDays.filter('.day' + i).addClass('eventful');
                targetDay.find('.abtCal-content').append(eventContent.clone());
            }
        }

        jQuery('.eventful', currentCalendar).click(function() {

            var clickedElm = jQuery(this);
            var otherEvents = jQuery('.eventful', currentCalendar).not(clickedElm).removeClass('open');


            jQuery('.abtCal-popupContent', mainElement).html(clickedElm.find('.abtCal-content').html());
            var clickedElmOffset = clickedElm.position();
            switch (settings.popupLocation.toLowerCase()) {
                case 'lefttop': popupElement
									.css({ 'top': (clickedElmOffset.top - popupElement.height() + clickedElm.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left - popupElement.width()) + 'px' });
                    break;
                case 'leftcenter': popupElement
									.css({ 'top': (clickedElmOffset.top - Math.round(popupElement.height() / 2) + Math.round(clickedElm.height() / 2)) + 'px' })
									.css({ 'left': (clickedElmOffset.left - popupElement.width()) + 'px' });
                    break;
                case 'leftbottom': popupElement
									.css({ 'top': (clickedElmOffset.top) + 'px' })
									.css({ 'left': (clickedElmOffset.left - popupElement.width()) + 'px' });
                    break;
                case 'topleft': popupElement
									.css({ 'top': (clickedElmOffset.top - popupElement.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left - popupElement.width() + clickedElm.width()) + 'px' });
                    break;
                case 'topcenter': popupElement
									.css({ 'top': (clickedElmOffset.top - popupElement.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left - Math.round(popupElement.width() / 2) + Math.round(clickedElm.width() / 2)) + 'px' });
                    break;
                case 'topright': popupElement
									.css({ 'top': (clickedElmOffset.top - popupElement.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left) + 'px' });
                    break;
                case 'righttop': popupElement
									.css({ 'top': (clickedElmOffset.top - popupElement.height() + clickedElm.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left + clickedElm.width()) + 'px' });
                    break;
                case 'rightcenter': popupElement
									.css({ 'top': (clickedElmOffset.top - Math.round(popupElement.height() / 2) + Math.round(clickedElm.height() / 2)) + 'px' })
									.css({ 'left': (clickedElmOffset.left + clickedElm.width()) + 'px' });
                    break;
                case 'rightbottom': popupElement
									.css({ 'top': (clickedElmOffset.top) + 'px' })
									.css({ 'left': (clickedElmOffset.left + clickedElm.width()) + 'px' });
                    break;
                case 'bottomleft': popupElement
									.css({ 'top': (clickedElmOffset.top + clickedElm.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left - popupElement.width() + clickedElm.width()) + 'px' });
                    break;
                case 'bottomcenter': popupElement
									.css({ 'top': (clickedElmOffset.top + clickedElm.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left - Math.round(popupElement.width() / 2) + Math.round(clickedElm.width() / 2)) + 'px' });
                    break;
                case 'bottomright': popupElement
									.css({ 'top': (clickedElmOffset.top + clickedElm.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left) + 'px' });
            }

            if (clickedElm.hasClass('open')) {
                clickedElm.removeClass('open');
                popupElement.fadeOut();
            }
            else {
                clickedElm.addClass('open');
                popupElement.hide();
                popupElement.fadeIn();
            }
        });
    }

    function SetListeners() {
        jQuery('.abtCal-controls .prev', mainElement).click(function() {

            popupElement.hide();
            currentMonth = (currentMonth + 11) % 12;
            if (currentMonth == 11) currentYear--;
            SetFirstAndLast();

            calendarElement.prepend(BuildCalendar());
            var newCalendar = jQuery('.abtCal-' + ShortMonths[currentMonth] + (currentYear % 100), calendarElement);

            jQuery('.abtCal-monthView', calendarElement).hide();
            newCalendar.fadeIn(400);

        });
        jQuery('.abtCal-controls .goto-today', mainElement).click(function() {

            popupElement.hide();
            currentMonth = settings.today.getMonth();
            currentYear = settings.today.getFullYear();
            SetFirstAndLast();

            calendarElement.append(BuildCalendar());
            var newCalendar = jQuery('.abtCal-' + ShortMonths[currentMonth] + (currentYear % 100), calendarElement);

            jQuery('.abtCal-monthView', calendarElement).hide();
            newCalendar.fadeIn(400);
        });
        jQuery('.abtCal-controls .next', mainElement).click(function() {

            popupElement.hide();
            currentMonth = (currentMonth + 13) % 12;
            if (currentMonth == 0) currentYear++;
            SetFirstAndLast();

            calendarElement.append(BuildCalendar());
            var newCalendar = jQuery('.abtCal-' + ShortMonths[currentMonth] + (currentYear % 100), calendarElement);

            jQuery('.abtCal-monthView', calendarElement).hide();
            newCalendar.fadeIn(400);

        });
    }
};

Object.isArray = function(obj) {
    if (obj.constructor.toString().indexOf("Array") == -1)
        return false;
    else
        return true;
}

Date.RFC3339 = function(date) {
    var stamp = new Date(date);
    return String.Format('{0}-{1}-{2}T{3}:{4}:{5}Z',
							stamp.getUTCFullYear(),
							String.Right('00' + (stamp.getUTCMonth() + 1), 2),
							String.Right('00' + stamp.getUTCDate(), 2),
							String.Right('00' + (stamp.getUTCHours() + 1), 2),
							String.Right('00' + (stamp.getUTCMinutes() + 1), 2),
							String.Right('00' + (stamp.getUTCSeconds() + 1), 2)
						);
};

Date.toRFC3339 = function(date) {
    var year = date.substring(0, 4);
    var month = parseInt(date.substring(5, 7), 10) - 1;
    var day = date.substring(8, 10);
    var hours = date.substring(11, 13);
    var minutes = date.substring(14, 16);
    var seconds = date.substring(17, 19);
    return new Date(year, month, day, hours, minutes, seconds, 0);
};

String.Format = function(text) {
    if (arguments.length <= 1) { return text; }
    var tokenCount = arguments.length - 2;
    for (var token = 0; token <= tokenCount; token++)
    { text = text.replace(new RegExp('\\{' + token + '\\}', 'gi'), arguments[token + 1]); }
    return text;
};

String.Right = function(text, number) {
    if (number <= 0)
        return "";
    else if (number > String(text).length)
        return text;
    else {
        var iLen = String(text).length;
        return String(text).substring(iLen, iLen - number);
    }
};


