/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is 
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or 
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// FOR TESTING XXX

function StartCodeTime ( ) {
	CodeTimeInitial = new Date();
	}

function EndCodeTime ( str ) {
	var CodeTimeFinal = new Date();
	var diff = CodeTimeFinal.getTime() - CodeTimeInitial.getTime();
	dump("Timing " + str + " took " + diff + " milliseconds.\n");
	}

// ************************************************************
// Initialize stuff
// ************************************************************

haveRead = false;
currentTime = new Date(); // a new Date object with the current time
monthShowing = new Date();
nowShowing = new Date();

month_names_internal = new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");


// the namespace of the RDF literals
rdf_ns="http://when.com/1999/07/03-appts-rdf#";

// find the location of the appointments data - relative URLs don't work (now, anyway)

// the filename within the directory of the XUL document
var RDFRelFileName = 'bug12012.rdf';

dump("Getting window.location.href...\n");
// to get the directory, start with the filename of the XUL document
var baseURL = window.location.href;
dump("...done.\n");

// XXX Needs three slashes because of file:// URL parsing bug (see Bug 9236)
//if ((baseURL.indexOf("file:/") != -1) && (baseURL.indexOf("file:///") == -1)) {
	//baseURL = "file:///" + baseURL.substring(6);
//}
if (baseURL.indexOf("file:") == 0) {
	baseURL = baseURL.substring(5);
	while (baseURL.charAt(0) == '/') {
		baseURL = baseURL.substring(1);
	}
	baseURL = "file:///" + baseURL;
}

// and take off whatever is after the last "/"
baseURL = baseURL.substring(0, baseURL.lastIndexOf("/") + 1);

// combine these to get the filename for the RDF
RDFFileName = baseURL + RDFRelFileName;
appts_resourcename = RDFFileName + "#WhenComAppointmentsRoot";

// the rdf service
RDFService = Components.classes['@mozilla.org/rdf/rdf-service;1'].getService();
RDFService = RDFService.QueryInterface(Components.interfaces.nsIRDFService);

// ************************************************************
// Event handler for Edit code
// ************************************************************

function ascendToTR (node) {
	var rv = node;
	while ( rv.nodeName.toLowerCase() != "tr" ) {
		rv = rv.parentNode;
	}
	return rv;
}

function handleRClick ( event ) {
	// XXX event.type and event.currentNode and event.button are broken
	var currentNode = ascendToTR(event.target);
	EditApptById(currentNode.getAttribute("id"));
}

// ************************************************************
// Functions for event handlers (e.g., onclick)
// ************************************************************

function gotoURL( url ) {
	window._content.location = url;
	}

function getAddDiv() {
	return document.documentElement.firstChild.nextSibling.nextSibling.firstChild.firstChild;
	}

function getScheduleDiv() {
	return document.documentElement.firstChild.nextSibling.nextSibling.firstChild.firstChild.nextSibling;
	}

function getPickDiv() {
	return document.documentElement.firstChild.nextSibling.nextSibling.firstChild.firstChild.nextSibling.nextSibling;
	}

function buttonDisabled ( number ) {
	// 0 for AddEvent
	// 1 for Schedule
	// 2 for Go To Date

	var tabTR = document.getElementById("tabsTR");
	tabTR.childNodes.item(0).className = "tab";
	tabTR.childNodes.item(1).className = "tab";
	tabTR.childNodes.item(2).className = "tab";
	tabTR.childNodes.item(number).className = "tab disable";

	// XXX BUG These two lines cause failure to display
	// setting is on an XULElement
	//tabTR.parentNode.parentNode.style.border="medium solid black";
	//tabTR.parentNode.parentNode.style.border="none";

	// XXX BUG These two lines cover up most of a shifting bug...
	// Setting is on a table
	tabTR.parentNode.style.border="medium solid black";
	tabTR.parentNode.style.border="none";
	
}

function divActive( number ) {
	// 0 for AddEvent
	// 1 for Schedule
	// 2 for Go To Date

	buttonDisabled(number)

	// for speed:
	var AddDiv = getAddDiv();
	var ScheduleDiv = getScheduleDiv();
	var PickDiv = getPickDiv()

	if ( number == 0 ) {
		ScheduleDiv.style.display = "none";
		PickDiv.style.display = "none";
		AddDiv.style.display = "block";
	} else if ( number == 1 ) {
		AddDiv.style.display = "none";
		PickDiv.style.display = "none";
		ScheduleDiv.style.display = "block";
		ShowSchedule();
	} else if ( number == 2 ) {
		AddDiv.style.display = "none";
		ScheduleDiv.style.display = "none";
		PickDiv.style.display = "block";
		ShowPick();
	}
}

function editButtonsActive( number ) {
	// 0 for addbuttons
	// 1 for editbuttons

	var adddiv = getAddDiv();
	var addbdiv = GetElementWithIdIn("addbuttons", adddiv);
	var edbdiv = GetElementWithIdIn("editbuttons", adddiv);

	if (number == 0) {
		edbdiv.style.display = "none";
		addbdiv.style.display = "block";
	} else if (number == 1) {
		addbdiv.style.display = "none";
		edbdiv.style.display = "block";
	}

}

function nextDay() {
	nowShowing.setDate(nowShowing.getDate() + 1);
	ShowSchedule();
}

function nextWeek() {
	nowShowing.setDate(nowShowing.getDate() + 7);
	ShowSchedule();
}

function prevDay() {
	nowShowing.setDate(nowShowing.getDate() - 1);
	ShowSchedule();
}

function prevWeek() {
	nowShowing.setDate(nowShowing.getDate() - 7);
	ShowSchedule();
}

function today() {
	nowShowing = new Date();
	ShowSchedule();
	}

/* Date Picker */

function nextMonth() {
	monthShowing.setMonth(monthShowing.getMonth() + 1);
	ShowPick();
}

function prevMonth() {
	monthShowing.setMonth(monthShowing.getMonth() - 1);
	ShowPick();
}

function nextYear() {
	monthShowing.setFullYear(monthShowing.getFullYear() + 1);
	ShowPick();
}

function prevYear() {
	monthShowing.setFullYear(monthShowing.getFullYear() - 1);
	ShowPick();
}

function thisMonth() {
	monthShowing = new Date();
	ShowPick();
	}

function pickDate( year, month, date ) {
	// the month is 1..12
	nowShowing = new Date(year, month - 1, date);
	divActive(1); // calls ShowSchedule
}


// ************************************************************
// Utility functions
// ************************************************************

// XXX Should this be needed?

function GetElementWithIdIn ( id, ancestor ) {
// recursive search for an element with a given id
	if ( ancestor.getAttribute("id") == id ) {
		return ancestor;
	} else {  // else shows logic, not really needed because of return
		for (var i = 0; i < ancestor.childNodes.length ; i++ ) {
			// This isn't an official way to use named constants, but
			// Mozilla supports it
			if ( ancestor.childNodes.item(i).nodeType == Node.ELEMENT_NODE ) {
				var rv = GetElementWithIdIn(id, ancestor.childNodes.item(i));
				if (rv != null) {
					return rv; // exits the function here, since we found the target
				}
			}
		}
		return null;
	}
}

// ************************************************************
// The Appointment type
// ************************************************************

function ApptToString () {
	return "[" + String(this.date) + "," + this.title + "]";
}

Appointment.prototype.toString = ApptToString;

function Appointment ( date, title, desc, dur, id ) { // used as a constructor
	this.date = date; // date
	this.title = title; // title
	this.desc = desc; // description
	this.dur = dur; // duration (minutes)
	this.id = id; // id (in the RDF and in the table row)

// A useful debugging function:
// XXX If error in string, get silent error:
	//this.toString = new Function('return "[" + String(this.date) + "," + this.title + "]"');
	}

function compareAppt ( x , y ) { // for passing to Array::sort
	// should return negative if x<y, 0 if x=y, and positive if x>y
	// Date::getTime returns milliseconds since Jan 1, 1970, so
	return x.date.getTime() - y.date.getTime();
}

// ************************************************************
// Code used for showing the schedule
// ************************************************************

function getOutputTBody() {
	// get the TBody element for output
	// XXX a hack, since getElementById doesn't work with mixed XUL/HTML, it
	// seems.

	//return document.documentElement.childNodes.item(2).childNodes.item(1);

	// hack within a hack, to get around bug 8044
	// for thead:
	return getScheduleDiv().firstChild.nextSibling.firstChild.nextSibling;
	// for no thead:
	// return getScheduleDiv().firstChild.nextSibling.firstChild;
}

function getOutputForDate() {
 // 2, 1, 3
	return getScheduleDiv().firstChild.firstChild.firstChild.nextSibling.nextSibling;
}

function timeString( date ) {
	var minutes = date.getMinutes();
	if (minutes < 10 ) {
		minutes = "0" + minutes;
	}
	var hours = date.getHours();
	if (hours < 12) {
		var suffix = "A";
	} else {
		var suffix = "P";
	}
	if (hours == 0) {
		hours = 12;
	} else if (hours > 12) {
		hours -= 12;
	}
	return hours + ":" + minutes + suffix;
}

function dateString( date ) {
// month is 0..11
	// ISO-8601 version:
	// return date.getFullYear() + "-" + ( date.getMonth() + 1) + "-" + date.getDate();

	return month_names_internal[date.getMonth()] +  // both 0-based
		" " + date.getDate() + ", " + date.getFullYear();
}

function getAttr(rdf_datasource,service,attr_name) {
	var attr = rdf_datasource.GetTarget(service,
					 RDFService.GetResource(rdf_ns + attr_name),
					 true);
	if (attr)
		attr = attr.QueryInterface(
					Components.interfaces.nsIRDFLiteral);
	if (attr)
			attr = attr.Value;
	return attr;
}

function ReadAndSort () {
	if ( ! haveRead ) {
		// enumerate all of the flash datasources.
		var enumerator = appts_container.GetElements();

		// put all the appointments into an array
		allAppts = new Array();
	
		try {
			while (enumerator.hasMoreElements()) {
				var service = enumerator.getNext().QueryInterface(
												Components.interfaces.nsIRDFResource);
		
				// get the title text
				var title = getAttr(rdf_datasource, service, 'title');
		
				// get the description text
				var descrip = getAttr(rdf_datasource, service, 'notes');
		
				// get the date, and see if it's for us
				var year = getAttr(rdf_datasource, service, 'year');
				var month = getAttr(rdf_datasource, service, 'month');
				var date = getAttr(rdf_datasource, service, 'date');
				var hour = getAttr(rdf_datasource, service, 'hour');
				var minute = getAttr(rdf_datasource, service, 'minute');

				// get the full resource URL:
				var theid = service.Value;
				// and use only the fragment identifier:
				theid = theid.substring(theid.lastIndexOf("#") + 1);

				var duration = getAttr(rdf_datasource, service, 'duration');
				// month is 0..11
				var apptDate = new Date(year, month - 1, date, hour, minute);

				allAppts[allAppts.length] = new Appointment( apptDate, title, descrip, duration, theid );
		}
		} catch (ex) {
			window.alert("Caught exception [[" + ex + "]]\n");
		}
	haveRead = true;
	}

	var todaysAppts = new Array();

	for ( var i = 0 ; i < allAppts.length ; i++ ) {
		var appt = allAppts[i];
		if ( (appt.date.getFullYear() == nowShowing.getFullYear())
			&& (appt.date.getMonth() == nowShowing.getMonth())
			&& (appt.date.getDate() == nowShowing.getDate()) ) {

		todaysAppts[todaysAppts.length] = appt;
		}
	}

	// sort todaysAppts using the JavaScript builtin Array::sort(), by
	// providing a sort function
	todaysAppts.sort( compareAppt );

	return todaysAppts;
	}

function ShowSchedule() {
	// Get the nodes where we will output
	var outTB = getOutputTBody();
	var outDate = getOutputForDate();

	// Remove all its existing children, since we'll rewrite them
	while (outTB.hasChildNodes() ) {
		outTB.removeChild(outTB.firstChild);
	}
	while (outDate.hasChildNodes() ) {
		outDate.removeChild(outDate.firstChild);
	}

	// Write the date at the top
	outDate.appendChild(document.createTextNode(dateString(nowShowing)));

	// and write the appointments to the table...

	// XXX Hack: get around insertRow() bug.
	var newrow = outTB.insertRow(outTB.rows.length);

	// Get the appointments for today, already sorted, from some kind of input
	// this is an Array of Appointment

	var todaysAppts = ReadAndSort();

	for ( var i = 0 ; i < todaysAppts.length ; i++ ) {
		var appt = todaysAppts[i];

		// add the appointment to the list:

		var newrow = outTB.insertRow(outTB.rows.length);

		// set up event handling
		newrow.setAttribute("id", appt.id);
		// XXX extra param shouldn't be needed
		newrow.addEventListener("click", handleRClick, false, false);

		// XXX Hack: get around insertCell() bug by switching order.
		var titlecell = newrow.insertCell(1);
		var timecell = newrow.insertCell(0);
		timecell.appendChild(document.createTextNode(timeString(appt.date)));
		titlecell.appendChild(document.createTextNode(appt.title));
	}
}

// ************************************************************
// Code used for the Goto date function
// ************************************************************

// monthShowing holds Year and Month for current Calendar

function monthString( date ) {
	// ISO-8601 format
	// return date.getFullYear() + "-" + ( date.getMonth() + 1);
	return month_names_internal[date.getMonth()] +  // both 0-based
		" " + date.getFullYear();
}

function ShowPick() {
	var pickDiv = getPickDiv(); // for speed
// StartCodeTime();
	var outCal = GetElementWithIdIn("calendar", pickDiv);
	var outDate = GetElementWithIdIn("MonthOutput", pickDiv);
// EndCodeTime("two runs of GetElementWithIdIn");

// StartCodeTime();
	// Remove all existing children, since we'll rewrite them
	while (outCal.hasChildNodes() ) {
		outCal.removeChild(outCal.firstChild);
	}
	while (outDate.hasChildNodes() ) {
		outDate.removeChild(outDate.firstChild);
	}
// EndCodeTime("removing existing children");

// StartCodeTime();
	// Write the month at the top
	outDate.appendChild(document.createTextNode(monthString(monthShowing)));
// EndCodeTime("initial stuff");

	// and write the calendar to the table...

// StartCodeTime();
	// XXX Hack: get around insertRow() bug.
	var myrow = outCal.insertRow(0);
// EndCodeTime("initial stuff");

	// make room before the first day, which starts on a strange day of the week
	// note that getDay(), 0=Sunday and 6=Saturday

// StartCodeTime();
	var curDay = new Date(monthShowing); // get the month we want to show
	curDay.setDate(1); // start with the first day of the month
// EndCodeTime("setting curDay");

// StartCodeTime();
	myrow = outCal.insertRow(outCal.rows.length);
	var junkcell = myrow.insertCell(0); // XXX Bug in insertCell
	for (var i = 0 ; i < curDay.getDay() ; i++ ) {
		myrow.insertCell(myrow.cells.length);
		}
// EndCodeTime("writing extra cells");

// StartCodeTime();
	var mycell;
	var targMonth = monthShowing.getMonth(); // for speed - saves 30ms
	// for speed:
	var testNowShowing = (
		( monthShowing.getFullYear() == nowShowing.getFullYear()) &&
		( monthShowing.getMonth() == nowShowing.getMonth()) );
	var testCurrentTime = (
		( monthShowing.getFullYear() == currentTime.getFullYear()) &&
		( monthShowing.getMonth() == currentTime.getMonth()) );

	while ( curDay.getMonth() == targMonth ) {
		if ( ( curDay.getDay() == 0) && (curDay.getDate() != 1) ) {
			// if it's Sunday, and not the 1st of the month
			// then add a new row
			myrow = outCal.insertRow(outCal.rows.length);
			junkcell = myrow.insertCell(0); // XXX Bug in insertCell
		}
		mycell = myrow.insertCell( myrow.cells.length );
		// put the number of the current date in the cell
		mycell.appendChild( document.createTextNode( curDay.getDate() ) );
		// and set the onClick handler
		mycell.setAttribute("onclick", "pickDate(" + curDay.getFullYear()
		                               + "," + (curDay.getMonth() + 1)
									   + "," + curDay.getDate()
									   + ")");
		// and set the class
		var classNm = "caldate";
		if ( testNowShowing && ( curDay.getDate() == nowShowing.getDate())) {
			classNm += " nowshowing";
			}
		if ( testCurrentTime && ( curDay.getDate() == currentTime.getDate())) {
			classNm += " today";
			}
		mycell.setAttribute("class", classNm);
		// and loop...
		curDay.setDate(curDay.getDate() + 1);
	}
// EndCodeTime("writing the days");
}


// ************************************************************
// Code used for Adding to the data
// ************************************************************

function makeAssert ( source, data, name ) {
	var target = RDFService.GetLiteral(data);
	var arc = RDFService.GetResource(rdf_ns + name);
	rdf_datasource.Assert(source, arc, target, true);
}

function SendBackToSource( appt ) {

	// make a resource for the new appointment

	// These four all write, but only first appt works
	// var sourceNode = RDFService.GetResource("http://www.w3.org/1999/02/22-rdf-syntax-ns#Description");
	// var sourceNode = RDFService.GetResource("");
	// var sourceNode = RDFService.GetResource("aoeuidhtns");
	// var sourceNode = RDFService.GetResource("http://when.com/aoeuidhtns#");

	// milliseconds since 1970-01-01 00:00 should be a good enough
	// unique identifier (creation time of appt)
	var sourceNode
		= RDFService.GetResource(RDFFileName +"#" + appt.id);

	// create literals and assert a relationship to the new appointment
	// for each piece of data

	makeAssert( sourceNode, appt.date.getFullYear(), 'year');
	makeAssert( sourceNode, appt.date.getMonth() + 1, 'month'); // month is 0..1 in the date structure
	makeAssert( sourceNode, appt.date.getDate(), 'date');
	makeAssert( sourceNode, appt.date.getHours(), 'hour');
	makeAssert( sourceNode, appt.date.getMinutes(), 'minute');
	makeAssert( sourceNode, appt.title, 'title');
	makeAssert( sourceNode, appt.desc, 'notes');
	makeAssert( sourceNode, appt.dur, 'duration');

	// connect the new appointment to the datasource 

	appts_container.AppendElement( sourceNode );

	// write the datasource back to disk

	try {
		rdf_remote_datasource.Flush(); // must be a RemoteDataSource, after 1999-06-23
	} catch (ex) {
		window.alert("Caught exception [[" + ex + "]] in Flush().\n");
	}

}

function Add( appt ) {

	// Add it to our internal data	-- note that we don't show it!
	allAppts[allAppts.length] = appt;

	// and add it to the external data
	SendBackToSource( appt );

}

function ResetAddFormTo( year, month, date, hrs, min, durhrs, durmin, title, notes) {
	var addDiv = getAddDiv(); // for speed

	var yearselect = GetElementWithIdIn("addyear", addDiv );
	var monthselect = GetElementWithIdIn("addmonth", addDiv );
	var dayselect = GetElementWithIdIn("addday", addDiv );
	var hourselect = GetElementWithIdIn("addtimehrs", addDiv );
	var minselect = GetElementWithIdIn("addtimemin", addDiv );
	var durhourselect = GetElementWithIdIn("adddurhrs", addDiv );
	var durminselect = GetElementWithIdIn("adddurmin", addDiv );

	var titleInput = GetElementWithIdIn("addtitle", addDiv );
	var notesInput = GetElementWithIdIn("addnotes", addDiv );

	if (yearselect != null) {  // "if (yearselect)" works too
		// Subtract the value of the first year in the list to set
		// selectedIndex.  JavaScript converts the string value to a number.
		yearselect.selectedIndex =
			year - yearselect.options.item(0).value;
	} else {
		window.alert("Hit a null.");
	}

	if (monthselect != null) {
		// selectedIndex 0 based, so subtract one
    	monthselect.selectedIndex = month - 1;
	} else {
		window.alert("Hit a null.");
	}

	if (dayselect != null) {
		// selectedIndex 0 based, so subtract one
		dayselect.selectedIndex = date - 1;
	} else {
		window.alert("Hit a null.");
	}

	if (hourselect != null) {
		// both 0 based
		hourselect.selectedIndex = hrs ;
	} else {
		window.alert("Hit a null.");
	}

	if (minselect != null) {
		minselect.selectedIndex = Math.round(min / 5);
	} else {
		window.alert("Hit a null.");
	}

	if (durhourselect != null) {
		durhourselect.selectedIndex = durhrs;
	} else {
		window.alert("Hit a null.");
	}

	if (durminselect != null) {
		durminselect.selectedIndex = Math.round(durmin / 5);
	} else {
		window.alert("Hit a null.");
	}

	if (titleInput != null) {
		if ( title == null) {
			title = "";
		}
		titleInput.value = title;
	} else {
		window.alert("Hit a null.");
	}

	if (notesInput != null) {
		if ( notes == null) {
			notes = "";
		}
		notesInput.value = notes;
	} else {
		window.alert("Hit a null.");
	}

}

function ResetAddForm() {
	editButtonsActive(0);
	ResetAddFormTo( nowShowing.getFullYear(),
	                nowShowing.getMonth() + 1,
                    nowShowing.getDate(),
					nowShowing.getHours() + 1, // upcoming hour
					0, // minutes
					0, // duration hours
					0, // duration minutes
					"", // title
					""); // notes
}

function SubmitAddForm() {
	var addDiv = getAddDiv(); // for speed

	var title = GetElementWithIdIn("addtitle", addDiv );
	var years = GetElementWithIdIn("addyear", addDiv );
	var months = GetElementWithIdIn("addmonth", addDiv );
	var days = GetElementWithIdIn("addday", addDiv );
	var timehrs = GetElementWithIdIn("addtimehrs", addDiv );
	var timemin = GetElementWithIdIn("addtimemin", addDiv );
	var durhrs = GetElementWithIdIn("adddurhrs", addDiv );
	var durmin = GetElementWithIdIn("adddurmin", addDiv );
	var notes = GetElementWithIdIn("addnotes", addDiv );

	if ( ( title == null ) ||
		( years == null ) ||
		( months == null ) ||
		( days == null ) ||
		( timehrs == null ) ||
		( timemin == null ) ||
		( durhrs == null ) ||
		( durmin == null ) ||
		( notes == null ) ) {
		window.alert("Something is null.  Addition failed.\n");
	} else {
		// months go 0..11 
		var apptid = "appt" + (new Date().getTime());
		Add( new Appointment( new Date ( years.value, months.value - 1,
		                                 days.value, timehrs.value,
										 timemin.value ),
		                      title.value,
							  notes.value,
							  (durhrs.value * 60) + (durmin.value * 1),
							  apptid));
		// ResetAddForm();
		divActive(1);
	}
}

// ************************************************************
// Edit code, which uses add form
// ************************************************************

function getApptById( id ) {
	for (var i = 0 ; i < allAppts.length ; i++ ) {
		if ( allAppts[i].id == id ) {
			return allAppts[i];
		}
	}
	return null;
}

function removeApptById ( id ) {
	for (var i = 0 ; i < allAppts.length ; i++ ) {
		if ( allAppts[i].id == id ) {
			// Remove from list
			allAppts[i] = allAppts[allAppts.length - 1];
			allAppts[allAppts.length - 1] = null;
			allAppts.length -= 1;
			return;
		}
	}
}

function EditApptById( theid ) {
	idEditing = theid; // global variable

	var apptEditing = getApptById( idEditing );
	if ( apptEditing == null) {
		window.alert("Null appointment.  Something's wrong.\n");
		return;
		}
	divActive(0); // XXX This should come later, but...
	editButtonsActive(1);
	ResetAddFormTo( apptEditing.date.getFullYear(),
	                apptEditing.date.getMonth() + 1,
					apptEditing.date.getDate(),
					apptEditing.date.getHours(),
					apptEditing.date.getMinutes(),
					Math.floor(apptEditing.dur / 60),
					apptEditing.dur % 60,
					apptEditing.title,
					apptEditing.desc );
}

function getAttrLiteral(rdf_datasource,service,attr_name) {
	var attr = rdf_datasource.GetTarget(service,
					 RDFService.GetResource(rdf_ns + attr_name),
					 true);
	if (attr)
		attr = attr.QueryInterface(
					Components.interfaces.nsIRDFLiteral);
	return attr;
}

function makeUnAssert ( rdf_datasource, resource, name ) {
	var target = getAttrLiteral(rdf_datasource, resource, name);
	var data = getAttr(rdf_datasource, resource, name); // function above
	var arc = RDFService.GetResource(rdf_ns + name);
	rdf_datasource.Unassert(resource, arc, target);
}

function DeleteEditing() {
	// delete from our list:
	removeApptById(idEditing);

	// delete from the data source
	var sourceNode = RDFService.GetResource(RDFFileName +"#" + idEditing);
	appts_container.RemoveElement( sourceNode, true );

	// Need to unassert all of sourceNode's relationships
	makeUnAssert(rdf_datasource, sourceNode, 'year');
	makeUnAssert(rdf_datasource, sourceNode, 'month');
	makeUnAssert(rdf_datasource, sourceNode, 'date');
	makeUnAssert(rdf_datasource, sourceNode, 'hour');
	makeUnAssert(rdf_datasource, sourceNode, 'minute');
	makeUnAssert(rdf_datasource, sourceNode, 'title');
	makeUnAssert(rdf_datasource, sourceNode, 'notes');
	makeUnAssert(rdf_datasource, sourceNode, 'duration');

	try {
		rdf_remote_datasource.Flush(); // must be a RemoteDataSource, after 1999-06-23
	} catch (ex) {
		window.alert("Caught exception [[" + ex + "]] in Flush().\n");
	}

}

function EditFormSubmit() {
	DeleteEditing();
	SubmitAddForm();
	divActive(1);
}

function EditFormDelete() {
	DeleteEditing();
	divActive(1);
}

function EditFormCancel() {
	divActive(1);
}

// ************************************************************
// Startup Code
// ************************************************************

function Init()
{
	// Initialize the Sidebar

	// Install all the datasources named in the Flash Registry into
	// the tree control. Datasources are listed as members of the
	// NC:FlashDataSources sequence, and are loaded in the order that
	// they appear in that sequence.
	try {
		// First try to construct a new one and load it
		// synchronously. nsIRDFService::GetDataSource() loads RDF/XML
		// asynchronously by default.
		rdf_datasource = Components.classes['@mozilla.org/rdf/datasource;1?name=xml-datasource'].createInstance();
		rdf_datasource = rdf_datasource.QueryInterface(Components.interfaces.nsIRDFDataSource);

		rdf_remote_datasource = rdf_datasource.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
		rdf_remote_datasource.Init(RDFFileName); // this will throw if it's already been opened and registered.

		dump("Reading datasource synchronously.\n");
		// read it in synchronously.
		rdf_remote_datasource.Refresh(true);
	}
	catch (ex) {
		// if we get here, then the RDF/XML has been opened and read
		// once. We just need to grab the datasource.
		dump("Datasource already read.  Grabbing.\n");
		rdf_datasource = RDFService.GetDataSource(RDFFileName);
	}

	// Create a 'container' wrapper around the appts_resourcename
	// resource so we can use some utility routines that make access a
	// bit easier.
	// NOT var, so it's global
	appts_container = Components.classes['@mozilla.org/rdf/container;1'].createInstance();
	appts_container = appts_container.QueryInterface(Components.interfaces.nsIRDFContainer);

	appts_resource = RDFService.GetResource(appts_resourcename);
	appts_container.Init(rdf_datasource, appts_resource);
}

function Boot() {

	if (document.getElementById("ApptsWindow")) {
dump("Calling init:\n");
		Init();

		// XXX Bug 9136 - this doesn't work when form is hidden.
		// ResetAddForm();

		// ShowSchedule();
dump("Calling divActive(1):\n");
		divActive(1);
dump("Done initializing.\n");
		// and let events take things from there...
	} else {
		setTimeout("Boot()", 0);
	}
}
