  /*
	Month Calendar - A Monthly Calendar with Week Numbers
	Copyright © 2005-2008 Harry Whitfield

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License as
	published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public
	License along with this program; if not, write to the Free
	Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
	MA  02110-1301  USA
	
	Month Calendar - version 2.4
	12 March, 2008
	Copyright © 2005-2008 Harry Whitfield
	mailto:g6auc@arrl.net
*/

		// globals

		var listSep = "::";
		var itemSep = "<<>>";

		var invalidMonth = "20051304";		// invalid date
		var cachedMonth  = invalidMonth;
		var cachedMonthList = "";


/*
		eFlag = false;	// make true for more debug information

		function eprint(theString) { if (eFlag) { print(theString); } }

		function escapePath(s) { return s.replace(/([\W])/g,"\\$1"); }
		
		function XescapePath(s)	// for Unix/runCommand paths
		{
			var del = String.fromCharCode(127);
			var ok  = "+,-./0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_abcdefghijklmnopqrstuvwxyz{}";
			var t = "";
			for (var i = 0; i < s.length; i += 1)
			{
				if (ok.indexOf(s[i]) >= 0) { t += s[i]; }
				else if ((" " <= s[i]) && (s[i] < del)) { t += "\\" + s[i]; }
				else { t += s[i]; }
			}
			return t;
		}
*/

		function xtn(s)
		{
    		var ext;
    		var idx = s.lastIndexOf(".");
    		if (idx >= 0)
    		{
        		ext = s.substring(idx);
        		if (ext.length > 7) { return  ""; }
        		if (ext.length > 1) { return ext; }
    		}
   		 	return "";
		}

		function makeISODate1(theDate)
		{
			var year  = theDate.getFullYear();
			var month = theDate.getMonth(); // 0..11
			var date  = theDate.getDate();
		
			var sYear  = String(year);
			var sMonth = String(month - 0 + 1); // "1".."12"
			var sDate  = String(date);
			
			while (sYear.length < 4) { sYear  = "0" + sYear;  }
			if (sMonth.length  == 1) { sMonth = "0" + sMonth; }
			if (sDate.length   == 1) { sDate  = "0" + sDate;  }
			return sYear + sMonth + sDate;
		}
		
		function unfold(data) { return data.replace(/\n(\x20|\t)/g, ""); }

		function getDates(data) // data should be the contents of an ics file
		{		
			var anyText 	= '(.*)';					// rest of the line
			var anyInteger 	= '(\\d*)';					// sequence of digits
			var anISODate   = '(\\d{8})';				// sequence of exactly 8 digits
			var anISOTime 	= '(\\d{8})T(\\d{6}Z?)';	// e.g. 20060118T235959Z
			var noValue     = '';
			
			//function x(s) { return s.replace(/([\W])/g,"\\$1"); }
			
			function stripBackSlash(data) { return data.replace(/\\/g, ""); }

			function getEvent(data, name, params, value) { return data.match(name + params + '([^\\:]*?)\\:' + value + '\\n'); }
			
			function getAlarms(data) { return data.match(/BEGIN\:VALARM\n[.\s\S]*?END\:VALARM\n/g); }

			function formatTime(time)
			{
				var found = String(time).match(/(\d\d)(\d\d)(\d\d)(Z?)/);
				if (found === null ) { return ""; }
				var result = ' ' + found[1] + ":" + found[2];
				switch (preferences.iCalDatePref.value)
				{
					case "none":		   return "";
					case "hh:mm[ GMT]":	   break;
					case "hh:mm:ss[ GMT]": result += ":" + found[3]; break;
				}
				if (found[4] == 'Z') { result += ' GMT'; }
				return result;
			}

			function getType1Event(data)
			{
				var o = {};
			  	var summary = getEvent(data, 'SUMMARY', noValue, anyText);
				if (summary !==  null) { o.summary = stripBackSlash(summary[2]); } else { o = null; return null; }
				var dtStart = getEvent(data, 'DTSTART', ';VALUE=DATE', anISODate);
		 		if (dtStart !==  null) { o.dtStart = dtStart[2]; } else { o = null; return null; }
		 		var dtEnd   = getEvent(data, 'DTEND', ';VALUE=DATE', anISODate);
		 		if (dtEnd	!==  null) { if (dtStart[2] < dtEnd[2]) { o.dtEnd = dtEnd[2]; } else { o.dtEnd = ""; } } else { o.dtEnd = ""; }
		 		var rRule = getEvent(data, 'RRULE', noValue, anyText);
		 		if (rRule !== null) { o.rRule = rRule[2]; } else { o.rRule = ""; }
		 		o.alarms = getAlarms(data);
		 		return o;
			}

			function getType2Event(data)
			{
				var ft;
				var o = {};
			  	var summary = getEvent(data, 'SUMMARY', noValue, anyText);
				if (summary !==  null) { o.summary = stripBackSlash(summary[2]); } else { o = null; return null; }
				var dtStart = getEvent(data, 'DTSTART', noValue, anISOTime);
		 		if (dtStart !==  null)
		 		{
		 			o.dtStart = dtStart[2]; ft = formatTime(dtStart[3]); if (ft.indexOf('00:00:00') < 0) { o.summary += ft; }
		 		} else { o = null; return null; }
		 		var dtEnd   = getEvent(data, 'DTEND', noValue, anISOTime);
		 		if (dtEnd	!==  null) { if (dtStart[2] < dtEnd[2]) { o.dtEnd = dtEnd[2]; } else { o.dtEnd = ""; } } else { o.dtEnd = ""; }
		 		var rRule = getEvent(data, 'RRULE', noValue, anyText);
		 		if (rRule !== null) { o.rRule = rRule[2]; } else { o.rRule = ""; }
		 		o.alarms = getAlarms(data);
		 		return o;
			}

			data = unfold(data);	// unfold folded lines
			
			if (data.indexOf("BEGIN:VCALENDAR") < 0) { return null; }	// not an ics file
			
			var found = data.match(/BEGIN\:VEVENT\n[.\s\S]*?END\:VEVENT\n/g);

			//var pattern = x('BEGIN:VEVENT') + '[.\\s\\S]*?' + x('END:VEVENT');
			//var lookFor = new RegExp(pattern, "g");
			//var found = data.match(lookFor);
			
			if (found  === null) { return null; }
			
			var item = [];
			var n = 0, o;
			for (var i = 0; i < found.length; i += 1)
			{
		 		o = getType1Event(found[i]);
		 		if (o !== null) { item[n] = o; n += 1; }
		 		else
		 		{
		 			o = getType2Event(found[i]);
		 			if (o !== null) { item[n] = o; n += 1; }
		 		}
			}
			return item;
		}

		function Xscan(path, isoDate)	// look for isoDate in the ics file.
		{
			var result, i;
			
			if (!filesystem.itemExists(path))
			{
				beep(); alert("Calendar file " + path + " is missing.");
				return "";
			}
			var data  = filesystem.readFile(path).replace(/\r\n?/g, '\n');
			var dates = getDates(data);	   // process the ics data
			if (dates !==  null)
			{
				result = "";
				for (i = 0; i < dates.length; i += 1) // scan the dates
		 		{
		  			if ( (dates[i].dtStart !== "") && (dates[i].summary !== "") )
		  			{
		  				if (dates[i].dtEnd !== "")
		  				{
		  					if ( (dates[i].dtStart <= isoDate) && (isoDate < dates[i].dtEnd) )
		  					{ 
		  						//eprint(dates[i].dtStart + ',' + dates[i].dtEnd +',' + dates[i].summary);
		  						if (result === "") { result += dates[i].summary; } else { result += listSep + dates[i].summary; }
			  				}
			  			}
			  			else
			  			{
			  				if (dates[i].dtStart == isoDate)
			  				{
		  						//eprint(dates[i].dtStart + ',' + dates[i].dtEnd +',' + dates[i].summary);
		  						if (result === "") { result += dates[i].summary; } else { result += listSep + dates[i].summary; }
			  				}
			  			}
		  			}
		 		}
		 		return result;
			}
			else
			{
				beep(); alert("Calendar file " + path + " contains no dates.");
				return "";
			}
		}
		
		function XgetEvents(isoDate)
		{
			var path;
			var fileList = preferences.calFileList.value;
			if (fileList === "") { return ""; }
			var files = fileList.split(listSep);
			var list = "";
			for (var i = 0; i < files.length; i += 1)
			{
				path = memoFolderPath + '/iCalFiles/' + files[i];
				if (list === "") { list += scan(path, isoDate); } else { list += listSep + scan(path, isoDate); }
			}
			return list;
		}
		
		function theISOYear(isoDate)  { return isoDate.substring(0, isoDate.length - 4); }	// YYYY
		
		function theISOMonth(isoDate) { return isoDate.substring(0, isoDate.length - 2); }	// YYYYMM

		function theISOMonthDay(isoDate) { return isoDate.substring(isoDate.length - 4); }	// MMDD

		function applyRule(date, isoMonth)	// return modified date in isoMonth, or null
		{
			//RRULE:FREQ=YEARLY;INTERVAL=1
			//RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=12
			//RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20081212;BYMONTH=12
			//RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=3MO;BYMONTH=2
			//RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=4TH;BYMONTH=11
			
			function rFrequency(rule)
			{
				var found = rule.match(/FREQ\=(.*?)\;/);
				if (found !== null) { return found[1]; } else { return ""; }
			}
			
			function rInterval(rule)
			{
				var found = rule.match(/INTERVAL\=(\d*)(\D|$)/);
				if (found !== null) { return found[1]; } else { return ""; }
			}
			
			function rByMonth(rule)
			{
				var found = rule.match(/BYMONTH\=(\d{1,2})(\D|$)/);
				if (found !== null) { return found[1]; } else { return ""; }
			}
			
			function rByDay(rule)
			{
				var found = rule.match(/BYDAY\=(\-?\d{1,2}(SU|MO|TU|WE|TH|FR|SA))(\;|$)/);
				if (found !== null) { return found[1]; } else { return ""; }
			}
			
			function rUntilDate(rule)
			{
				var found = rule.match(/UNTIL\=(\d{8})(\D|$)/);
				if (found !== null) { return found[1]; } else { return ""; }
			}
			
			var dtStart = date.dtStart;
			var dtEnd   = date.dtEnd;
			var summary = date.summary;
			var rRule   = date.rRule;
			
			//eprint('input: ' + dtStart + ',' + dtEnd +',' + summary + ',' + rRule);

			if (rRule === "") { return null; }	// no rule - we should not be here
			if (dtStart.substring(4,6) != isoMonth.substring(4,6)) { return null; } // month does not match
		
			var frequency = rFrequency(rRule);
			var interval  = rInterval(rRule);
			var byMonth   = rByMonth(rRule);
			var byDay     = rByDay(rRule);
			var untilDate = rUntilDate(rRule);
			var dtStartMonth, untilMonth, o;
		
			if ((frequency == 'YEARLY') && (interval == "1") && (byDay === ""))
			{
				dtStartMonth = theISOMonth(dtStart);
				untilMonth   = '999912';
				if (untilDate !== "") {  untilMonth = theISOMonth(untilDate); } else { untilDate = '99991231'; }
		
				if ((dtStartMonth < isoMonth) && (isoMonth <= untilMonth))
				{
					dtStart = theISOYear(isoMonth + '00') + theISOMonthDay(dtStart);
					if (dtStart > untilDate) { return null; }
					if (dtEnd !== "") { dtEnd = theISOYear(isoMonth + '00') + theISOMonthDay(dtEnd); }

					o = {};
					o.dtStart = dtStart;
					o.dtEnd   = dtEnd;
					o.summary = summary;
					o.rRule   = rRule;
					//eprint('output: ' + o.dtStart + ',' + o.dtEnd +',' +o.summary + ',' + o.rRule);
					return o;
				}
				return null;
			}
			else { return null; }
		}
		
		function scanMonth(path, isoDate)	// look for dates in the current month in the ics file.
		{
			if (!filesystem.itemExists(path))
			{
				beep(); alert("Calendar file " + path + " is missing.");
				return "";
			}
			var data  = filesystem.readFile(path).replace(/\r\n?/g, '\n');	// read the .ics file
			var dates = getDates(data);	   									// get events from the ics data

			var isoMonth, result, i, item;
			
			if (dates !==  null)
			{
				isoMonth = theISOMonth(isoDate);
				
				result = "";
				for (i = 0; i < dates.length; i += 1) // scan the dates
		 		{
		  			if ( (dates[i].dtStart !== "") && (dates[i].summary !== "") )
		  			{
		  				if ( (theISOMonth(dates[i].dtStart) == isoMonth) || (isoMonth == theISOMonth(dates[i].dtEnd)) )
		  				{ 
							item = dates[i].dtStart + itemSep + dates[i].dtEnd + itemSep + dates[i].summary;
							//eprint(dates[i].dtStart + ',' + dates[i].dtEnd +',' + dates[i].summary + ',' + dates[i].rRule);
							if (result === "") { result += item; } else { result += listSep + item; }
			  			}
			  			else if (dates[i].rRule !== "")
			  			{
			  				date = applyRule(dates[i], isoMonth);
			  				if (date !== null)
			  				{
			  					item = date.dtStart + itemSep + date.dtEnd + itemSep + date.summary;
			  					//eprint(date.dtStart + ',' + date.dtEnd +',' + date.summary + ',' + date.rRule);
			  					if (result === "") { result += item; } else { result += listSep + item; }
			  				}
			  			}
		  			}
		 		}
		 		return result;
			}
			else
			{
				beep(); alert("Calendar file " + path + " contains no dates.");
				return "";
			}
		}
		
		function getMonthEvents(isoDate)
		{
			eprint('getMonthEvents(' + isoDate + ')');

			var fileList = preferences.calFileList.value;
			
			eprint('fileList=' + fileList);
			
			if (fileList === "") { return ""; }
			var files = fileList.split(listSep);
			var list = "", path;
			for (var i = 0; i < files.length; i += 1)
			{
				path = memoFolderPath + '/iCalFiles/' + files[i];
				if (list === "") { list += scanMonth(path, isoDate); } else { list += listSep + scanMonth(path, isoDate); }
			}
			return list;
		}

		function clearCachedEvents()
		{
			cachedMonth  = invalidMonth;
			cachedMonthList = "";
		}

		function getCachedEvents(isoDate)
		{
			//eprint('getCachedEvents(' + isoDate + ')');
			var isoMonth = theISOMonth(isoDate);
			
			if ( isoMonth != theISOMonth(cachedMonth) )
			{
				cachedMonthList = getMonthEvents(isoDate);
				cachedMonth = isoDate;
			}
			
			var dates, result, i, item, dtStart, dtEnd, summary;
			if (cachedMonthList !==  "")
			{
				dates = cachedMonthList.split(listSep);
				result = "";
				
				for (i = 0; i < dates.length; i += 1) // scan the dates
		 		{
		  			item = dates[i].split(itemSep);
		  			dtStart = item[0];
		  			dtEnd   = item[1];
		  			summary = item[2];

		  			if (dtEnd !== "")
		  			{
		  				if ( (dtStart <= isoDate) && (isoDate < dtEnd) )
		  				{ 
		  					//eprint(dtStart + ',' + dtEnd +',' + summary);
		  					if (result === "") { result += summary; } else { result += listSep + summary; }
			  			}
			  		}
			  		else
			  		{
			  			if (dtStart == isoDate)
			  			{
		  					//eprint(dtStart + ',' + dtEnd +',' + summary);
		  					if (result === "") { result += summary; } else { result += listSep + summary; }
		  				}
			  		}
		 		}
		 		return result;
			}
			return "";
		}

		function makeCalDir(accessMode)
		{
			var path  = memoFolderPath + '/iCalFiles';
			if (filesystem.itemExists(path) && filesystem.isDirectory(path)) { return; }
			eprint('makeCalDir:mkdir=' + filesystem.createDirectory(path));
			eprint('makeCalDir:chmod=' + filesystem.changeMode(path, accessMode));
		}

		function importCalFile()
		{
			var idx, filename, path;
			var importFilePath = chooseFile(".ics");
			if (importFilePath !== null)
			{
				importFilePath = importFilePath.replace(/\\/g,'/');
				idx = importFilePath.lastIndexOf('/');
				filename = importFilePath.substring(idx+1);
				path  = memoFolderPath + '/iCalFiles/' + filename;
				filesystem.writeFile( path, filesystem.readFile(importFilePath));
				filesystem.changeMode(path, accessMode);
				return filename;
			}
			return "";
		}

		function iCalFiles()
		{
			var file, idx;
			var path  = memoFolderPath + '/iCalFiles';
			var files = filesystem.getDirectoryContents(path, false);

			var result = "";
			for (var i = 0; i < files.length; i += 1)
			{
				file = files[i];
				idx  = file.lastIndexOf('/');
				if (idx >= 0) { file = file.substring(idx+1); }
				
				if ( xtn(file) == ".ics")
				{
					if (result === "") { result += file; } else { result += listSep + file; }
				}
			}
			return result;
		}
		
		function inFileList(file, oldFileList)
		{
			if (oldFileList.indexOf(file) >= 0) { return 'Enable'; }
			return 'Disable';
		}

		function stemOf(iCalFile)
		{
			var idx = iCalFile.indexOf('.ics');
			return iCalFile.substring(0,idx);
		}

		function iCalForm(oldFileList)
		{
			var iCalList = iCalFiles();
			
			if (iCalList === "")
			{
				beep();
				alert("There are no iCal files.");
				return "";
			}
			
			var iCalArray = iCalList.split(listSep);
			iCalArray = iCalArray.sort();
			var formfields = [];
		
			for (var i = 0; i < iCalArray.length; i += 1)
			{
				formfields[i] = new FormField();
				formfields[i].title = stemOf(iCalArray[i]) + ':';
				formfields[i].type = 'popup';
				formfields[i].option = [];
				formfields[i].option[0] = 'Enable';
				formfields[i].option[1] = 'Disable';
				formfields[i].option[2] = '(-';
				formfields[i].option[3] = 'Delete';
				formfields[i].defaultValue = inFileList(iCalArray[i], oldFileList);
				formfields[i].description = "";
			}
		
			var formResults = form(formfields, 'Calendar Management', 'OK');
			var result = "";
			var n = 0, file, path;
			
			if (formResults !== null)
			{
				for (i = 0; i < iCalArray.length ; i += 1)
				{
					file = iCalArray[i];
					if (formResults[i] == 'Enable')
					{
						if (result === "") { result += file; } else { result += listSep + file; }
					}
					else if (formResults[i] == 'Delete' )
					{
						path = memoFolderPath + '/iCalFiles/' + file;
						filesystem.remove(path);
					}
				}
			}
			else { result = oldFileList; }
		
			for (i = 0; i < iCalArray.length; i += 1)
			{
				delete formfields[i];
				formfields[i] = null;
			}
			formfields = null;
			return result;
		}
		
		function selectFiles()
		{
			var oldCalFileList  = preferences.calFileList.value;
			var newCalFileList  = iCalForm(oldCalFileList);
			if ( newCalFileList != oldCalFileList)
			{
				preferences.calFileList.value = newCalFileList;
				savePreferences();
				clearCachedEvents();
				updateCalendar();
			}
		}

		function importFile()
		{
			var file = importCalFile();
			if (file !== "") { selectFiles(); }
		}

		function importNetworkCalFile()
		{
			var filename, outputFile, result;
			var url = preferences.networkCalFile.value;
			if (url !== "")
			{
				filename = 'Network-Calendar.ics';
				outputFile  = memoFolderPath + '/iCalFiles/' + filename;
				result = getFile(url, outputFile, "");
				if (result === "") { return filename; }
				beep();
				alert('Could not import Network iCal file from ' + url);
				return "";
			}
			beep();
			alert('The Network iCal File Preference is blank.');
			return "";
		}
			
		function importNetworkFile()
		{
			var file = importNetworkCalFile();
			if (file !== "") { selectFiles(); }
		}
