/*!

* FullCalendar v3.5.1 Google Calendar Plugin
* Docs & License: https://fullcalendar.io/
* (c) 2017 Adam Shaw
*/

(function(factory) {

if (typeof define === 'function' && define.amd) {
        define([ 'jquery' ], factory);
}
else if (typeof exports === 'object') { // Node/CommonJS
        module.exports = factory(require('jquery'));
}
else {
        factory(jQuery);
}

})(function($) {

var FC = $.fullCalendar; var Promise = FC.Promise; var EventSource = FC.EventSource; var JsonFeedEventSource = FC.JsonFeedEventSource; var EventSourceParser = FC.EventSourceParser; var applyAll = FC.applyAll;

;;

var GcalEventSource = EventSource.extend({

// TODO: eventually remove "googleCalendar" prefix (API-breaking)
googleCalendarApiKey: null,
googleCalendarId: null,
googleCalendarError: null, // optional function
ajaxSettings: null,

fetch: function(start, end, timezone) {
        var _this = this;
        var url = this.buildUrl();
        var requestParams = this.buildRequestParams(start, end, timezone);
        var ajaxSettings = this.ajaxSettings;
        var onSuccess = ajaxSettings.success;

        if (!requestParams) { // could have failed
                return Promise.reject();
        }

        return Promise.construct(function(onResolve, onReject) {
                $.ajax($.extend(
                        {}, // destination
                        JsonFeedEventSource.AJAX_DEFAULTS,
                        ajaxSettings,
                        {
                                url: url,
                                data: requestParams,
                                success: function(responseData) {
                                        var rawEventDefs;
                                        var successRes;

                                        if (responseData.error) {
                                                _this.reportError('Google Calendar API: ' + responseData.error.message, responseData.error.errors);
                                                onReject();
                                        }
                                        else if (responseData.items) {
                                                rawEventDefs = _this.gcalItemsToRawEventDefs(
                                                        responseData.items,
                                                        requestParams.timeZone
                                                );

                                                successRes = applyAll(
                                                        onSuccess,
                                                        this, // forward `this`
                                                        // call the success handler(s) and allow it to return a new events array
                                                        [ rawEventDefs ].concat(Array.prototype.slice.call(arguments, 1))
                                                );

                                                if ($.isArray(successRes)) {
                                                        rawEventDefs = successRes;
                                                }

                                                onResolve(_this.parseEventDefs(rawEventDefs));
                                        }
                                }
                        }
                ));
        });
},

gcalItemsToRawEventDefs: function(items, gcalTimezone) {
        var _this = this;

        return items.map(function(item) {
                return _this.gcalItemToRawEventDef(item, gcalTimezone);
        });
},

gcalItemToRawEventDef: function(item, gcalTimezone) {
        var url = item.htmlLink || null;

        // make the URLs for each event show times in the correct timezone
        if (url && gcalTimezone) {
                url = injectQsComponent(url, 'ctz=' + gcalTimezone);
        }

        return {
                id: item.id,
                title: item.summary,
                start: item.start.dateTime || item.start.date, // try timed. will fall back to all-day
                end: item.end.dateTime || item.end.date, // same
                url: url,
                location: item.location,
                description: item.description
        };
},

buildUrl: function() {
        return GcalEventSource.API_BASE + '/' +
                encodeURIComponent(this.googleCalendarId) +
                '/events?callback=?'; // jsonp
},

buildRequestParams: function(start, end, timezone) {
        var apiKey = this.googleCalendarApiKey || this.calendar.opt('googleCalendarApiKey');
        var params;

        if (!apiKey) {
                this.reportError("Specify a googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/");
                return null;
        }

        // The API expects an ISO8601 datetime with a time and timezone part.
        // Since the calendar's timezone offset isn't always known, request the date in UTC and pad it by a day on each
        // side, guaranteeing we will receive all events in the desired range, albeit a superset.
        // .utc() will set a zone and give it a 00:00:00 time.
        if (!start.hasZone()) {
                start = start.clone().utc().add(-1, 'day');
        }
        if (!end.hasZone()) {
                end = end.clone().utc().add(1, 'day');
        }

        params = $.extend(
                this.ajaxSettings.data || {},
                {
                        key: apiKey,
                        timeMin: start.format(),
                        timeMax: end.format(),
                        singleEvents: true,
                        maxResults: 9999
                }
        );

        if (timezone && timezone !== 'local') {
                // when sending timezone names to Google, only accepts underscores, not spaces
                params.timeZone = timezone.replace(' ', '_');
        }

        return params;
},

reportError: function(message, apiErrorObjs) {
        var calendar = this.calendar;
        var calendarOnError = calendar.opt('googleCalendarError');
        var errorObjs = apiErrorObjs || [ { message: message } ]; // to be passed into error handlers

        if (this.googleCalendarError) {
                this.googleCalendarError.apply(calendar, errorObjs);
        }

        if (calendarOnError) {
                calendarOnError.apply(calendar, errorObjs);
        }

        // print error to debug console
        FC.warn.apply(null, [ message ].concat(apiErrorObjs || []));
},

getPrimitive: function() {
        return this.googleCalendarId;
},

applyManualRawProps: function(rawProps) {
        var superSuccess = EventSource.prototype.applyManualRawProps.apply(this, arguments);
        var googleCalendarId = rawProps.googleCalendarId;

        if (googleCalendarId == null && rawProps.url) {
                googleCalendarId = parseGoogleCalendarId(rawProps.url);
        }

        if (googleCalendarId != null) {
                this.googleCalendarId = googleCalendarId;

                return superSuccess;
        }

        return false;
},

applyOtherRawProps: function(rawProps) {
        this.ajaxSettings = rawProps;
}

});

GcalEventSource.API_BASE = 'www.googleapis.com/calendar/v3/calendars';

GcalEventSource.allowRawProps({

// manually process...
url: false,
googleCalendarId: false,

// automatically transfer...
googleCalendarApiKey: true,
googleCalendarError: true

});

GcalEventSource.parse = function(rawInput, calendar) {

var rawProps;

if (typeof rawInput === 'object') { // long form. might fail in applyManualRawProps
        rawProps = rawInput;
}
else if (typeof rawInput === 'string') { // short form
        rawProps = { url: rawInput }; // url will be parsed with parseGoogleCalendarId
}

if (rawProps) {
        return EventSource.parse.call(this, rawProps, calendar);
}

return false;

};

function parseGoogleCalendarId(url) {

var match;

// detect if the ID was specified as a single string.
// will match calendars like "asdf1234@calendar.google.com" in addition to person email calendars.
if (/^[^\/]+@([^\/\.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) {
        return url;
}
// try to scrape it out of a V1 or V3 API feed URL
else if (
        (match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^\/]*)/.exec(url)) ||
        (match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^\/]*)/.exec(url))
) {
        return decodeURIComponent(match[1]);
}

}

// Injects a string like “arg=value” into the querystring of a URL function injectQsComponent(url, component) {

// inject it after the querystring but before the fragment
return url.replace(/(\?.*?)?(#|$)/, function(whole, qs, hash) {
        return (qs ? qs + '&' : '?') + component + hash;
});

}

// expose

EventSourceParser.registerClass(GcalEventSource);

FC.GcalEventSource = GcalEventSource;

;;

});