/*
 Monthly 2.2.0 by Kevin Thornbloom is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
 */

(function ($) {
    "use strict";
    $.fn.extend({
        monthly: function (customOptions) {

            // These are overridden by options declared in footer
            var defaults = {
                dataType: "html",
                disablePast: false,
                eventList: true,
                events: "",
                jsonUrl: "",
                linkCalendarToEventUrl: false,
                maxWidth: false,
                mode: "event",
                setWidth: false,
                showTrigger: "",
                startHidden: false,
                stylePast: false,
                target: "",
                useIsoDateFormat: false,
                weekStart: 0, // Sunday
                xmlUrl: ""
            };

            var options = $.extend(defaults, customOptions),
                    uniqueId = $(this).attr("id"),
                    parent = "#" + uniqueId,
                    currentDate = new Date(),
                    currentMonth = currentDate.getMonth() + 1,
                    currentYear = currentDate.getFullYear(),
                    currentDay = currentDate.getDate(),
                    locale = (options.locale || defaultLocale()).toLowerCase(),
                    monthNameFormat = options.monthNameFormat || "short",
                    weekdayNameFormat = options.weekdayNameFormat || "short",
                    monthNames = options.monthNames || defaultMonthNames(),
                    dayNames = options.dayNames || defaultDayNames(),
                    markupBlankDay = '<div class="m-d monthly-day-blank"><div class="monthly-day-number"></div></div>',
                    weekStartsOnMonday = options.weekStart === "Mon" || options.weekStart === 1 || options.weekStart === "1",
                    primaryLanguageCode = locale.substring(0, 2).toLowerCase();

            if (options.maxWidth !== false) {
                $(parent).css("maxWidth", options.maxWidth);
            }
            if (options.setWidth !== false) {
                $(parent).css("width", options.setWidth);
            }

            if (options.startHidden) {
                $(parent).addClass("monthly-pop").css({
                    display: "none",
                    position: "absolute"
                });
                $(document).on("focus", String(options.showTrigger), function (event) {
                    $(parent).show();
                    event.preventDefault();
                });
                $(document).on("click", String(options.showTrigger) + ", .monthly-pop", function (event) {
                    event.stopPropagation();
                    event.preventDefault();
                });
                $(document).on("click", function () {
                    $(parent).hide();
                });
            }

            // Add Day Of Week Titles
            _appendDayNames(weekStartsOnMonday);

            // Add CSS classes for the primary language and the locale. This allows for CSS-driven
            // overrides of the language-specific header buttons. Lowercased because locale codes
            // are case-insensitive but CSS is not.
            $(parent).addClass("monthly-locale-" + primaryLanguageCode + " monthly-locale-" + locale);

            // Add Header & event list markup
            $(parent).prepend('<div class="monthly-header"><div class="monthly-header-title"><a href="#" class="monthly-header-title-date" onclick="return false"></a></div><a href="#" class="monthly-prev"></a><a href="#" class="monthly-next"></a></div>').append('<div class="monthly-event-list"></div>');

            // Set the calendar the first time
            setMonthly(currentMonth, currentYear);

            // How many days are in this month?
            function daysInMonth(month, year) {
                return month === 2 ? (year & 3) || (!(year % 25) && year & 15) ? 28 : 29 : 30 + (month + (month >> 3) & 1);
            }

            // Build the month
            function setMonthly(month, year) {
                $(parent).data("setMonth", month).data("setYear", year);

                // Get number of days
                var index = 0,
                        dayQty = daysInMonth(month, year),
                        // Get day of the week the first day is
                        mZeroed = month - 1,
                        firstDay = new Date(year, mZeroed, 1, 0, 0, 0, 0).getDay(),
                        settingCurrentMonth = month === currentMonth && year === currentYear;

                // Remove old days
                $(parent + " .monthly-day, " + parent + " .monthly-day-blank").remove();
                $(parent + " .monthly-event-list, " + parent + " .monthly-day-wrap").empty();
                // Print out the days
                for (var dayNumber = 1; dayNumber <= dayQty; dayNumber++) {
                    // Check if it's a day in the past
                    var isInPast = options.stylePast && (
                            year < currentYear
                            || (year === currentYear && (
                                    month < currentMonth
                                    || (month === currentMonth && dayNumber < currentDay)
                                    ))),
                            innerMarkup = '<div class="monthly-day-number">' + dayNumber + '</div><div class="monthly-indicator-wrap"></div>';
                    if (options.mode === "event") {
                        var dayOfWeek = new Date(year, mZeroed, dayNumber, 0, 0, 0, 0).getDay();
                        $(parent + " .monthly-day-wrap").append("<div"
                                + attr("class", "m-d monthly-day monthly-day-event" + (isInPast ? " monthly-past-day" : ""))
                                + attr("data-number", dayNumber)
                                + ">" + innerMarkup + "</div>");
                        $(parent + " .monthly-event-list").append("<div"
                                + attr("class", "monthly-list-item")
                                + attr("id", uniqueId + "day" + dayNumber)
                                + attr("data-number", dayNumber)
                                + '><div class="monthly-event-list-date">' + dayNames[dayOfWeek] + "<br>" + dayNumber + "</div></div>");
                    } else {
                        $(parent + " .monthly-day-wrap").append("<a"
                                + attr("href", "#")
                                + attr("class", "m-d monthly-day monthly-day-pick" + (isInPast ? " monthly-past-day" : ""))
                                + attr("data-number", dayNumber)
                                + ">" + innerMarkup + "</a>");
                    }
                }

                if (settingCurrentMonth) {
                    $(parent + ' *[data-number="' + currentDay + '"]').addClass("monthly-today");
                }

                // Reset button
                $(parent + " .monthly-header-title").html(
                        '<a href="#" class="monthly-header-title-date" onclick="return false">' + monthNames[month - 1] + " " + year + "</a>"
                        + (settingCurrentMonth ? "" : '<a href="#" class="monthly-reset"></a>'));

                // Account for empty days at start
                if (weekStartsOnMonday) {
                    if (firstDay === 0) {
                        _prependBlankDays(6);
                    } else if (firstDay !== 1) {
                        _prependBlankDays(firstDay - 1);
                    }
                } else if (firstDay !== 7) {
                    _prependBlankDays(firstDay);
                }

                // Account for empty days at end
                var numdays = $(parent + " .monthly-day").length,
                        numempty = $(parent + " .monthly-day-blank").length,
                        totaldays = numdays + numempty,
                        roundup = Math.ceil(totaldays / 7) * 7,
                        daysdiff = roundup - totaldays;
                if (totaldays % 7 !== 0) {
                    for (index = 0; index < daysdiff; index++) {
                        $(parent + " .monthly-day-wrap").append(markupBlankDay);
                    }
                }

                // Events
                if (options.mode === "event") {
                    addEvents(month, year);
                }
                var divs = $(parent + " .m-d");
                for (index = 0; index < divs.length; index += 7) {
                    divs.slice(index, index + 7).wrapAll('<div class="monthly-week"></div>');
                }
            }

            function addEvent(event, setMonth, setYear) {
                // Year [0]   Month [1]   Day [2]
                var fullStartDate = _getEventDetail(event, "startdate"),
                        fullEndDate = _getEventDetail(event, "enddate"),
                        startArr = fullStartDate.split("-"),
                        startYear = parseInt(startArr[0], 10),
                        startMonth = parseInt(startArr[1], 10),
                        startDay = parseInt(startArr[2], 10),
                        startDayNumber = startDay,
                        endDayNumber = startDay,
                        showEventTitleOnDay = startDay,
                        startsThisMonth = startMonth === setMonth && startYear === setYear,
                        happensThisMonth = startsThisMonth;

                if (fullEndDate) {
                    // If event has an end date, determine if the range overlaps this month
                    var endArr = fullEndDate.split("-"),
                            endYear = parseInt(endArr[0], 10),
                            endMonth = parseInt(endArr[1], 10),
                            endDay = parseInt(endArr[2], 10),
                            startsInPastMonth = startYear < setYear || (startMonth < setMonth && startYear === setYear),
                            endsThisMonth = endMonth === setMonth && endYear === setYear,
                            endsInFutureMonth = endYear > setYear || (endMonth > setMonth && endYear === setYear);
                    if (startsThisMonth || endsThisMonth || (startsInPastMonth && endsInFutureMonth)) {
                        happensThisMonth = true;
                        startDayNumber = startsThisMonth ? startDay : 1;
                        endDayNumber = endsThisMonth ? endDay : daysInMonth(setMonth, setYear);
                        showEventTitleOnDay = startsThisMonth ? startDayNumber : 1;
                    }
                }
                if (!happensThisMonth) {
                    return;
                }

                var startTime = _getEventDetail(event, "starttime"),
                        timeHtml = "",
                        eventURL = _getEventDetail(event, "url"),
                        eventTitle = _getEventDetail(event, "name"),
                        eventClass = _getEventDetail(event, "class"),
                        eventColor = _getEventDetail(event, "color"),
                        eventId = _getEventDetail(event, "id"),
                        customClass = eventClass ? " " + eventClass : "",
                        dayStartTag = "<div",
                        dayEndTags = "</span></div>";

                if (startTime) {
                    var endTime = _getEventDetail(event, "endtime");
                    timeHtml = '<div><div class="monthly-list-time-start">' + formatTime(startTime) + "</div>"
                            + (endTime ? '<div class="monthly-list-time-end">' + formatTime(endTime) + "</div>" : "")
                            + "</div>";
                }

                if (options.linkCalendarToEventUrl && eventURL) {
                    dayStartTag = "<a" + attr("href", eventURL);
                    dayEndTags = "</span></a>";
                }

                var markupDayStart = dayStartTag
                        + attr("data-eventid", eventId)
                        + attr("title", eventTitle)
                        // BG and FG colors must match for left box shadow to create seamless link between dates
                        + (eventColor ? attr("style", "background:" + eventColor + ";color:" + eventColor) : ""),
                        markupListEvent = "<a"
                        + attr("href", eventURL)
                        + attr("class", "listed-event" + customClass)
                        + attr("data-eventid", eventId)
                        + (eventColor ? attr("style", "background:" + eventColor) : "")
                        + attr("title", eventTitle)
                        + ">" + eventTitle + " " + timeHtml + "</a>";
                for (var index = startDayNumber; index <= endDayNumber; index++) {
                    var doShowTitle = index === showEventTitleOnDay;
                    // Add to calendar view
                    $(parent + ' *[data-number="' + index + '"] .monthly-indicator-wrap').append(
                            markupDayStart
                            + attr("class", "monthly-event-indicator" + customClass
                                    // Include a class marking if this event continues from the previous day
                                    + (doShowTitle ? "" : " monthly-event-continued")
                                    )
                            + "><span>" + (doShowTitle ? eventTitle : "") + dayEndTags);
                    // Add to event list
                    $(parent + ' .monthly-list-item[data-number="' + index + '"]')
                            .addClass("item-has-event")
                            .append(markupListEvent);
                }
            }

            function addEvents(month, year) {
                if (options.events) {
                    // Prefer local events if provided
                    addEventsFromString(options.events, month, year);
                } else {
                    var remoteUrl = options.dataType === "xml" ? options.xmlUrl : options.jsonUrl;
                    if (remoteUrl) {
                        // Replace variables for month and year to load from dynamic sources
                        var url = String(remoteUrl).replace("{month}", month).replace("{year}", year);
                        $.get(url, {now: $.now()}, function (data) {
                            addEventsFromString(data, month, year);
                        }, options.dataType).fail(function () {
                            console.error("Monthly.js failed to import " + remoteUrl + ". Please check for the correct path and " + options.dataType + " syntax.");
                        });
                    }
                }
            }

            function addEventsFromString(events, setMonth, setYear) {
                if (options.dataType === "xml") {
                    $(events).find("event").each(function (index, event) {
                        addEvent(event, setMonth, setYear);
                    });
                } else if (options.dataType === "json") {
                    $.each(events.monthly, function (index, event) {
                        addEvent(event, setMonth, setYear);
                    });
                }
            }

            function attr(name, value) {
                var parseValue = String(value);
                var newValue = "";
                for (var index = 0; index < parseValue.length; index++) {
                    switch (parseValue[index]) {
                        case "'":
                            newValue += "&#39;";
                            break;
                        case "\"":
                            newValue += "&quot;";
                            break;
                        case "<":
                            newValue += "&lt;";
                            break;
                        case ">":
                            newValue += "&gt;";
                            break;
                        default:
                            newValue += parseValue[index];
                    }
                }
                return " " + name + "=\"" + newValue + "\"";
            }

            function _appendDayNames(startOnMonday) {
                var offset = startOnMonday ? 1 : 0,
                        dayName = "",
                        dayIndex = 0;
                for (dayIndex = 0; dayIndex < 6; dayIndex++) {
                    dayName += "<div>" + dayNames[dayIndex + offset] + "</div>";
                }
                dayName += "<div>" + dayNames[startOnMonday ? 0 : 6] + "</div>";
                $(parent).append('<div class="monthly-day-title-wrap">' + dayName + '</div><div class="monthly-day-wrap"></div>');
            }

            // Detect the user's preferred language
            function defaultLocale() {
                return navigator.languages && navigator.languages.length ? navigator.languages[0] : navigator.language;
            }

            // Use the user's locale if possible to obtain a list of short month names, falling back on English
            function defaultMonthNames() {
                if (typeof Intl === "undefined") {
                    return ["Jan", "Feb", "Mar", "Apr", "May", "June", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
                }
                var formatter = new Intl.DateTimeFormat(locale, {month: monthNameFormat});
                var names = [];
                for (var monthIndex = 0; monthIndex < 12; monthIndex++) {
                    var sampleDate = new Date(2017, monthIndex, 1, 0, 0, 0);
                    names[monthIndex] = formatter.format(sampleDate);
                }
                return names;
            }

            function formatDate(year, month, day) {
                if (options.useIsoDateFormat) {
                    return new Date(year, month - 1, day, 0, 0, 0).toISOString().substring(0, 10);
                }
                if (typeof Intl === "undefined") {
                    return month + "/" + day + "/" + year;
                }
                return new Intl.DateTimeFormat(locale).format(new Date(year, month - 1, day, 0, 0, 0));
            }

            // Use the user's locale if possible to obtain a list of short weekday names, falling back on English
            function defaultDayNames() {
                if (typeof Intl === "undefined") {
                    return ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
                }
                var formatter = new Intl.DateTimeFormat(locale, {weekday: weekdayNameFormat}),
                        names = [],
                        dayIndex = 0,
                        sampleDate = null;
                for (dayIndex = 0; dayIndex < 7; dayIndex++) {
                    // 2017 starts on a Sunday, so use it to capture the locale's weekday names
                    sampleDate = new Date(2017, 0, dayIndex + 1, 0, 0, 0);
                    names[dayIndex] = formatter.format(sampleDate);
                }
                return names;
            }

            function _prependBlankDays(count) {
                var wrapperEl = $(parent + " .monthly-day-wrap"),
                        index = 0;
                for (index = 0; index < count; index++) {
                    wrapperEl.prepend(markupBlankDay);
                }
            }

            function _getEventDetail(event, nodeName) {
                return options.dataType === "xml" ? $(event).find(nodeName).text() : event[nodeName];
            }

            // Returns a 12-hour format hour/minute with period. Opportunity for future localization.
            function formatTime(value) {
                var timeSplit = value.split(":");
                var hour = parseInt(timeSplit[0], 10);
                var period = "AM";
                if (hour > 12) {
                    hour -= 12;
                    period = "PM";
                } else if (hour === 0) {
                    hour = 12;
                }
                return hour + ":" + String(timeSplit[1]) + " " + period;
            }

            function setNextMonth() {
                var setMonth = $(parent).data("setMonth"),
                        setYear = $(parent).data("setYear"),
                        newMonth = setMonth === 12 ? 1 : setMonth + 1,
                        newYear = setMonth === 12 ? setYear + 1 : setYear;
                setMonthly(newMonth, newYear);
                viewToggleButton();
            }

            function setPreviousMonth() {
                var setMonth = $(parent).data("setMonth"),
                        setYear = $(parent).data("setYear"),
                        newMonth = setMonth === 1 ? 12 : setMonth - 1,
                        newYear = setMonth === 1 ? setYear - 1 : setYear;
                setMonthly(newMonth, newYear);
                viewToggleButton();
            }

            // Function to go back to the month view
            function viewToggleButton() {
                if ($(parent + " .monthly-event-list").is(":visible")) {
                    $(parent + " .monthly-cal").remove();
                    $(parent + " .monthly-header-title").prepend('<a href="#" class="monthly-cal"></a>');
                }
            }

            // Advance months
            $(document.body).on("click", parent + " .monthly-next", function (event) {
                setNextMonth();
                event.preventDefault();
            });

            // Go back in months
            $(document.body).on("click", parent + " .monthly-prev", function (event) {
                setPreviousMonth();
                event.preventDefault();
            });

            // Reset Month
            $(document.body).on("click", parent + " .monthly-reset", function (event) {
                $(this).remove();
                setMonthly(currentMonth, currentYear);
                viewToggleButton();
                event.preventDefault();
                event.stopPropagation();
            });

            // Back to month view
            $(document.body).on("click", parent + " .monthly-cal", function (event) {
                $(this).remove();
                $(parent + " .monthly-event-list").css("transform", "scale(0)");
                setTimeout(function () {
                    $(parent + " .monthly-event-list").hide();
                }, 250);
                event.preventDefault();
            });

            // Click A Day
            $(document.body).on("click touchstart", parent + " .monthly-day", function (event) {
                // If events, show events list
                var whichDay = $(this).data("number");
                if (options.mode === "event" && options.eventList) {
                    var theList = $(parent + " .monthly-event-list"),
                            myElement = document.getElementById(uniqueId + "day" + whichDay),
                            topPos = myElement.offsetTop;
                    theList.show();
                    theList.css("transform");
                    theList.css("transform", "scale(1)");
                    $(parent + ' .monthly-list-item[data-number="' + whichDay + '"]').show();
                    theList.scrollTop(topPos);
                    viewToggleButton();
                    if (!options.linkCalendarToEventUrl) {
                        event.preventDefault();
                    }
                    // If picker, pick date
                } else if (options.mode === "picker") {
                    var setMonth = $(parent).data("setMonth"),
                            setYear = $(parent).data("setYear");
                    // Should days in the past be disabled?
                    if ($(this).hasClass("monthly-past-day") && options.disablePast) {
                        // If so, don't do anything.
                        event.preventDefault();
                    } else {
                        // Otherwise, select the date ...
                        $(String(options.target)).val(formatDate(setYear, setMonth, whichDay));
                        // ... and then hide the calendar if it started that way
                        if (options.startHidden) {
                            $(parent).hide();
                        }
                    }
                    event.preventDefault();
                }
            });

            // Clicking an event within the list
            $(document.body).on("click", parent + " .listed-event", function (event) {
                var href = $(this).attr("href");
                // If there isn't a link, don't go anywhere
                if (!href) {
                    event.preventDefault();
                }
            });

        }
    });
}(jQuery));
