text: /* global Module */
  
  /* Magic Mirror
   * Module: Calendar
   *
   * By Michael Teeuw http://michaelteeuw.nl
   * MIT Licensed.
   */
  
  Module.register("calendar",{
  
  	// Define module defaults
  	defaults: {
  		maximumEntries: 10, // Total Maximum Entries
  		maximumNumberOfDays: 365,
  		displaySymbol: true,
  		defaultSymbol: "calendar", // Fontawesome Symbol see http://fontawesome.io/cheatsheet/
  		displayRepeatingCountTitle: false,
  		defaultRepeatingCountTitle: '',
  		maxTitleLength: 30,
  		fetchInterval: 5 * 60 * 1000, // Update every 5 minutes.
  		animationSpeed: 2000,
  		fade: true,
  		urgency: 7,
  		timeFormat: "relative",
  		fadePoint: 0.25, // Start on 1/4th of the list.
  		calendars: [
  			{
  				symbol: "calendar",
  				url: "http://www.calendarlabs.com/templates/ical/US-Holidays.ics",
  			},
  			{	
  				symbol: "calendar",
  				url: "https://calendar.google.com/calendar/ical/matellio184%40gmail.com/public/basic.ics",
  			}
  		],
  		titleReplace: {
  			"De verjaardag van ": "",
  			"'s birthday": ""
  		},
  	},
  
  	// Define required scripts.
  	getStyles: function() {
  		return ["calendar.css", "font-awesome.css"];
  	},
  
  	// Define required scripts.
  	getScripts: function() {
  		return ["moment.js"];
  	},
  
  	// Define required translations.
  	getTranslations: function() {
  		// The translations for the defaut modules are defined in the core translation files.
  		// Therefor we can just return false. Otherwise we should have returned a dictionairy.
  		// If you're trying to build your own module including translations, check out the documentation.
  		return false;
  	},
  
  	// Override start method.
  	start: function() {
  		Log.log("Starting module: " + this.name);
  
  		// Set locale.
  		moment.locale(config.language);
  
  		for (var c in this.config.calendars) {
  			var calendar = this.config.calendars[c];
  			calendar.url = calendar.url.replace("webcal://", "http://");
  			this.addCalendar(calendar.url, calendar.user, calendar.pass);
  		}
  
  		this.calendarData = {};
  		this.loaded = false;
  	},
  
  	// Override socket notification handler.
  	socketNotificationReceived: function(notification, payload) {
  		if (notification === "CALENDAR_EVENTS") {
  			if (this.hasCalendarURL(payload.url)) {
  				this.calendarData[payload.url] = payload.events;
  				this.loaded = true;
  			}
  		} else if (notification === "FETCH_ERROR") {
  			Log.error("Calendar Error. Could not fetch calendar: " + payload.url);
  		} else if (notification === "INCORRECT_URL") {
  			Log.error("Calendar Error. Incorrect url: " + payload.url);
  		} else {
  			Log.log("Calendar received an unknown socket notification: " + notification);
  		}
  
  		this.updateDom(this.config.animationSpeed);
  	},
  
  	// Override dom generator.
  	getDom: function() {
  
  		var events = this.createEventList();
  		var wrapper = document.createElement("table");
  		wrapper.className = "small";
  
  		if (events.length === 0) {
  			wrapper.innerHTML = (this.loaded) ? this.translate("EMPTY") : this.translate("LOADING");
  			wrapper.className = "small dimmed";
  			return wrapper;
  		}
  
  		for (var e in events) {
  			var event = events[e];
  
  			var eventWrapper = document.createElement("tr");
  			eventWrapper.className = "normal";
  
  			if (this.config.displaySymbol) {
  				var symbolWrapper =  document.createElement("td");
  				symbolWrapper.className = "symbol";
  				var symbol =  document.createElement("span");
  				symbol.className = "fa fa-" + this.symbolForUrl(event.url);
  				symbolWrapper.appendChild(symbol);
  				eventWrapper.appendChild(symbolWrapper);
  			}
  
  			var titleWrapper = document.createElement("td"),
  				repeatingCountTitle = '';
  
  
  			if (this.config.displayRepeatingCountTitle) {
  
  				repeatingCountTitle = this.countTitleForUrl(event.url);
  
  				if(repeatingCountTitle !== '') {
  					var thisYear = new Date().getFullYear(),
  						yearDiff = thisYear - event.firstYear;
  
  					repeatingCountTitle = ', '+ yearDiff + '. ' + repeatingCountTitle;
  				}
  			}
  
  			titleWrapper.innerHTML = this.titleTransform(event.title) + repeatingCountTitle;
  			titleWrapper.className = "title bright";
  			eventWrapper.appendChild(titleWrapper);
  
  			var timeWrapper =  document.createElement("td");
  			//console.log(event.today);
  			var now = new Date();
  			// Define second, minute, hour, and day variables
  			var one_second = 1000; // 1,000 milliseconds
  			var one_minute = one_second * 60;
  			var one_hour = one_minute * 60;
  			var one_day = one_hour * 24;
  			if (event.fullDayEvent) {
  				if (event.today) {
  					timeWrapper.innerHTML = this.translate("TODAY");
  				} else if (event.startDate - now < one_day && event.startDate - now > 0) {
  					timeWrapper.innerHTML = this.translate("TOMORROW");
  				} else if (event.startDate - now < 2*one_day && event.startDate - now > 0) {
  				/*Provide ability to show "the day after tomorrow" instead of "in a day" 
  				 *if "DAYAFTERTOMORROW" is configured in a language's translation .json file, 
  				 *,which can be found in MagicMirror/translations/
  				 */
  					if (this.translate('DAYAFTERTOMORROW') !== 'DAYAFTERTOMORROW') {
      						timeWrapper.innerHTML = this.translate("DAYAFTERTOMORROW");
  					} else {
      						timeWrapper.innerHTML = moment(event.startDate, "x").fromNow();
  					}
  				} else {
  					/* Check to see if the user displays absolute or relative dates with their events
  					 * Also check to see if an event is happening within an 'urgency' time frameElement
  					 * For example, if the user set an .urgency of 7 days, those events that fall within that
  					 * time frame will be displayed with 'in xxx' time format or moment.fromNow()
  					 *
  					 * Note: this needs to be put in its own function, as the whole thing repeats again verbatim
  					 */
  					if (this.config.timeFormat === "absolute") {
  						if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * one_day))) {
  							// This event falls within the config.urgency period that the user has set
  							timeWrapper.innerHTML = moment(event.startDate, "x").fromNow();
  						} else {
  							timeWrapper.innerHTML = moment(event.startDate, "x").format("MMM Do");
  						}
  					} else {
  						timeWrapper.innerHTML =  moment(event.startDate, "x").fromNow();
  					}
  				}
  			} else {
  				if (event.startDate >= new Date()) {
  					if (event.startDate - now < 2 * one_day) {
  						// This event is within the next 48 hours (2 days)
  						if (event.startDate - now < 6 * one_hour) {
  							// If event is within 6 hour, display 'in xxx' time format or moment.fromNow()
  							timeWrapper.innerHTML = moment(event.startDate, "x").fromNow();
  						} else {
  							// Otherwise just say 'Today/Tomorrow at such-n-such time'
  							timeWrapper.innerHTML = moment(event.startDate, "x").calendar();
  						}
  					} else {
  						/* Check to see if the user displays absolute or relative dates with their events
  						 * Also check to see if an event is happening within an 'urgency' time frameElement
  						 * For example, if the user set an .urgency of 7 days, those events that fall within that
  						 * time frame will be displayed with 'in xxx' time format or moment.fromNow()
  						 *
  						 * Note: this needs to be put in its own function, as the whole thing repeats again verbatim
  						 */
  						if (this.config.timeFormat === "absolute") {
  							if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * one_day))) {
  								// This event falls within the config.urgency period that the user has set
  								timeWrapper.innerHTML = moment(event.startDate, "x").fromNow();
  							} else {
  								timeWrapper.innerHTML = moment(event.startDate, "x").format("MMM Do");
  							}
  						} else {
  							timeWrapper.innerHTML = moment(event.startDate, "x").fromNow();
  						}
  					}
  				} else {
  					timeWrapper.innerHTML =  this.translate("RUNNING") + ' ' + moment(event.endDate,"x").fromNow(true);
  				}
  			}
  			//timeWrapper.innerHTML += ' - '+ moment(event.startDate,'x').format('lll');
  			//console.log(event);
  			timeWrapper.className = "time light";
  			eventWrapper.appendChild(timeWrapper);
  
  			wrapper.appendChild(eventWrapper);
  
  			// Create fade effect.
  			if (this.config.fade && this.config.fadePoint < 1) {
  				if (this.config.fadePoint < 0) {
  					this.config.fadePoint = 0;
  				}
  				var startingPoint = events.length * this.config.fadePoint;
  				var steps = events.length - startingPoint;
  				if (e >= startingPoint) {
  					var currentStep = e - startingPoint;
  					eventWrapper.style.opacity = 1 - (1 / steps * currentStep);
  				}
  			}
  		}
  
  		return wrapper;
  	},
  
  	/* hasCalendarURL(url)
  	 * Check if this config contains the calendar url.
  	 *
  	 * argument url sting - Url to look for.
  	 *
  	 * return bool - Has calendar url
  	 */
  	hasCalendarURL: function(url) {
  		for (var c in this.config.calendars) {
  			var calendar = this.config.calendars[c];
  			if (calendar.url === url) {
  				return true;
  			}
  		}
  
  		return false;
  	},
  
  	/* createEventList()
  	 * Creates the sorted list of all events.
  	 *
  	 * return array - Array with events.
  	 */
  	createEventList: function() {
  		var events = [];
  		var today = moment().startOf("day");
  		for (var c in this.calendarData) {
  			var calendar = this.calendarData[c];
  			for (var e in calendar) {
  				var event = calendar[e];
  				event.url = c;
  				event.today = event.startDate >= today && event.startDate < (today + 24 * 60 * 60 * 1000);
  				events.push(event);
  			}
  		}
  
  		events.sort(function(a, b) {
  			return a.startDate - b.startDate;
  		});
  
  		return events.slice(0, this.config.maximumEntries);
  	},
  
  	/* createEventList(url)
  	 * Requests node helper to add calendar url.
  	 *
  	 * argument url sting - Url to add.
  	 */
  	addCalendar: function(url, user, pass) {
  		this.sendSocketNotification("ADD_CALENDAR", {
  			url: url,
  			maximumEntries: this.config.maximumEntries,
  			maximumNumberOfDays: this.config.maximumNumberOfDays,
  			fetchInterval: this.config.fetchInterval,
  			user: user,
  			pass: pass
  		});
  	},
  
  	/* symbolForUrl(url)
  	 * Retrieves the symbol for a specific url.
  	 *
  	 * argument url sting - Url to look for.
  	 *
  	 * return string - The Symbol
  	 */
  	symbolForUrl: function(url) {
  		for (var c in this.config.calendars) {
  			var calendar = this.config.calendars[c];
  			if (calendar.url === url && typeof calendar.symbol === "string")  {
  				return calendar.symbol;
  			}
  		}
  
  		return this.config.defaultSymbol;
  	},
  	/* countTitleForUrl(url)
  	 * Retrieves the name for a specific url.
  	 *
  	 * argument url sting - Url to look for.
  	 *
  	 * return string - The Symbol
  	 */
  	countTitleForUrl: function(url) {
  		for (var c in this.config.calendars) {
  			var calendar = this.config.calendars[c];
  			if (calendar.url === url && typeof calendar.repeatingCountTitle === "string")  {
  				return calendar.repeatingCountTitle;
  			}
  		}
  
  		return this.config.defaultRepeatingCountTitle;
  	},
  
  	/* shorten(string, maxLength)
  	 * Shortens a sting if it's longer than maxLenthg.
  	 * Adds an ellipsis to the end.
  	 *
  	 * argument string string - The string to shorten.
  	 * argument maxLength number - The max lenth of the string.
  	 *
  	 * return string - The shortened string.
  	 */
  	shorten: function(string, maxLength) {
  		if (string.length > maxLength) {
  			return string.slice(0,maxLength) + "&hellip;";
  		}
  
  		return string;
  	},
  
  	/* titleTransform(title)
  	 * Transforms the title of an event for usage.
  	 * Replaces parts of the text as defined in config.titleReplace.
  	 * Shortens title based on config.maxTitleLength
  	 *
  	 * argument title string - The title to transform.
  	 *
  	 * return string - The transformed title.
  	 */
  	titleTransform: function(title) {
  		for (var needle in this.config.titleReplace) {
  			var replacement = this.config.titleReplace[needle];
  			title = title.replace(needle, replacement);
  		}
  
  		title = this.shorten(title, this.config.maxTitleLength);
  		return title;
  	}
  });
  