//
/* 
 * GAIA web services
 *
 * Depends on:
 * gaia.js
 * gaia_date.js
 * Sarissa library?
 *
 * Provides:
 *
 * gaiaWS.webService(): download JSON file and return object
 *
 
*/

// Have all function and variables stored inside one object to prevent
// clashes with other modules
if (!window.gaiaWS)
  window.gaiaWS = new Object;


gaiaWS.extractDbidsFromArray = function (a) {
  var r = new Array;
  if (a)
    for (var i = 0; i < a.length; ++i) 
      r.push(parseInt(a[i]['@dbid']));
  return r;
};

gaiaWS.extractStringsFromJsonArray = function (a) {
  var r = new Array;
  if (a)
    for (var i = 0; i < a.length; ++i) 
      r.push(a[i]['$']);  
  return r;
};
  
// Constructor
gaiaWS.Node = function (obj) {
  this.dbid = this.url = this.timeout = this.description = 
  this.online = this.originalOnlineStatus = null;

  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.Node) {
      for (var name in this)
	this[name] = obj[name];
      this.location = new gaiaWS.Location(this.location);
    }
    else {
      this.dbid = parseInt(obj['@dbid']);
      this.url = obj.url['$'];
      this.timeout = ((parseInt(obj.timeout['$'])*1e3) 
		      || gaiaWS.Node.defaultTimeout);
      this.description = obj.description['$'];
      this.online = (true == obj.online['$']);
      this.originalOnlineStatus = this.online;
      this.location = new gaiaWS.Location(obj.location);
    }
  }
};

gaiaWS.Node.defaultTimeout = 25e3;

gaiaWS.Node.prototype.protocol = 'http';

gaiaWS.Node.prototype.getUrl = function( ) {
  return this.url;
};

gaiaWS.Node.prototype.getDbid = function ( ) {
  return this.dbid;
};

gaiaWS.Node.prototype.getTimeout = function ( ) {
  return this.timeout;
};

gaiaWS.Node.prototype.isOnline = function ( ) {
  return this.online;
};
  
gaiaWS.Node.prototype.toString = function( ) {
  return 'Node #' + this.dbid + ', URL=' + this.getUrl()
  + ' timeout=' + this.timeout + 'ms '
  + (this.online ? ' (online)' : ' (offline)');
};

gaiaWS.Node.prototype.setOnline = function( ) {
  this.online = true;
  return this;
};

gaiaWS.Node.prototype.setOffline = function ( ) {
  this.online = false;
  return this;
};

gaiaWS.Node.prototype.resetOnlineStatus = function ( ) {
  this.online = this.originalOnlineStatus;
  return this;
};

gaiaWS.Node.prototype.getLocation = function ( ) {
  return this.location;
};

gaiaWS.Node.prototype.getDescription = function ( ) {
  return this.description;
};

gaiaWS.Project = function (obj) {
  this.dbid = this.abbreviation = this.name = this.stationDbids 
  = this.instituteDbids = this.principal_investigatorDbids 
  = this.contactDbids = this.urls = null;
  
  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.Project)
      for (var name in this)
	this[name] = obj[name];
    else {
      this.dbid = parseInt(obj['@dbid']);
      this.abbreviation = obj.abbreviation['$'];
      this.name = obj.name['$'];
      this.stationDbids = gaiaWS.extractDbidsFromArray(obj.stations.station);
      this.instituteDbids 
	= gaiaWS.extractDbidsFromArray(obj.institutes.institute);
      this.principal_investigatorDbids 
	= gaiaWS.extractDbidsFromArray(obj.principal_investigators.contact);
      this.contactDbids = gaiaWS.extractDbidsFromArray(obj.contacts.contact);
      this.urls = gaiaWS.extractStringsFromJsonArray(obj.url);
    }
  }
};

gaiaWS.Project.prototype.getDbid = function ( ) {
  return this.dbid;
};

gaiaWS.Project.prototype.getName = function ( ) {
  return this.name;
};

gaiaWS.Project.prototype.getAbbreviation = function ( ) {
  return this.abbreviation;
};

gaiaWS.Project.prototype.getUrls = function( ) {
  return this.urls;
};

gaiaWS.Project.prototype.getStations = function ( ) {
  return gaiaWS.getStationByDbid(this.stationDbids);
};


gaiaWS.Project.prototype.getPIs = function ( ) {
  var a = new Array;
  var i;
  for (i = 0; i < this.principal_investigatorDbids.length; ++i) {
    var c = gaiaWS.getContactByDbid(this.principal_investigatorDbids[i]);
    if (c)
      a.push(c);
  }
  return a;
};



gaiaWS.Project.prototype.getContacts = function ( ) {
  var a = new Array;
  var i;
  for (i = 0; i < this.contactDbids.length; ++i) {
    var c = gaiaWS.getContactByDbid(this.contactDbids[i]);
    if (c)
      a.push(c);
  }
  return a;
};


gaiaWS.Project.prototype.getInstitutes = function ( ) {
  var a = new Array;
  var i;
  for (i = 0; i < this.instituteDbids.length; ++i) {
    var c = gaiaWS.getInstituteByDbid(this.instituteDbids[i]);
    if (c)
      a.push(c);
  }
  return a;
};

gaiaWS.Project.prototype.getCreditsTable = function ( doc ) {
  var table = doc.createElement('table');
  var tr;
  var td;
  var i;
  // PI(s)
  var PIs = this.getPIs();
  tr = doc.createElement('tr');
  td = doc.createElement('td');
  td.appendChild(doc.createTextNode(PIs.length >= 2 ? 
				    'Project PIs: ' :
				    'Project PI: '));
  tr.appendChild(td);
  
  td = doc.createElement('td');
  if (PIs.length) {
    td.appendChild(PIs[0].getNameElement(doc));
    for (i = 1; i < PIs.length; ++i) {
      td.appendChild(doc.createTextNode(', '));
      td.appendChild(PIs[i].getNameElement(doc));
    }
  }
  else
    td.appendChild(doc.createTextNode('none'));
  tr.appendChild(td);
  table.appendChild(tr);
  
  // contact(s)
  var contacts = this.getContacts();
  if (contacts.length) {
    tr = doc.createElement('tr');
    td = doc.createElement('td');
    td.appendChild(doc.createTextNode(contacts.length >= 2 ? 
				      'Project contacts: ' :
				      'Project contact: '));
    tr.appendChild(td);
    
    td = doc.createElement('td');
    
    td.appendChild(contacts[0].getNameElement(doc));
    for (i = 1; i < contacts.length; ++i) {
      td.appendChild(doc.createTextNode(', '));
      td.appendChild(contacts[i].getNameElement(doc));
    }
    tr.appendChild(td);
    table.appendChild(tr);
  }

  // institute(s)
  var institutes = this.getInstitutes();
  if (institutes.length) {
    tr = doc.createElement('tr');
    td = doc.createElement('td');
    td.appendChild(doc.createTextNode(institutes.length >= 2 ? 
				      'Project institutes: ' :
				      'Project institute: '));
    tr.appendChild(td);
    
    td = doc.createElement('td');
    
    td.appendChild(institutes[0].getNameElement(doc));
    for (i = 1; i < institutes.length; ++i) {
      td.appendChild(doc.createTextNode(', '));
      td.appendChild(institutes[i].getNameElement(doc));
    }
    tr.appendChild(td);
    table.appendChild(tr);
  }

  // data request
  tr = doc.createElement('tr');
  td = doc.createElement('td');
  td.appendChild(doc.createTextNode('Data requests: '));
  tr.appendChild(td);
  td = doc.createElement('td');
  var count = 0;
  var dru = gaiaViewer.state.selectedChannel.getDataRequestUrls(doc);
  if (dru.length) {
    td.appendChild(dru[0]);
    for (i = 1; i < dru.length; ++i) {
      // insert a non-breaking space
      td.appendChild(doc.createTextNode("\u00a0"));
      td.appendChild(dru[i]);
    }
  }
  else
    td.appendChild(doc.createTextNode('Data request URL(s) not known'));

  tr.appendChild(td);
  table.appendChild(tr);

  return table;
};

gaiaWS.Project.prototype.toString = function () {
  return "Project " + this.getAbbreviation();
};


gaiaWS.Station = function (obj) {
  this.dbid = this.projectDbid = this.channelDbids = this.abbreviation
  = this.staticLoc;
  
  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.Station) {
      for (var name in this)
	this[name] = obj[name];
      if (this.staticLoc)
	this.location = new gaiaWS.Location(obj.location);
    }
    else {
      this.dbid = parseInt(obj['@dbid']);
      this.projectDbid = parseInt(obj.project['@dbid']);
      this.channelDbids = gaiaWS.extractDbidsFromArray(obj.channels.channel);
      this.abbreviation = obj.abbreviation['$'];
      
      this.staticLoc = (true == obj['@static']);
      if (this.staticLoc)
	this.location = new gaiaWS.Location(obj.location);
    }
  }
};

gaiaWS.Station.prototype.getDbid = function ( ) {
  return this.dbid;
};

gaiaWS.Station.prototype.getAbbreviation = function ( ) {
  return this.abbreviation;
};

gaiaWS.Station.prototype.getLocation = function ( ) {
  return this.location;
};

gaiaWS.Station.prototype.getLatitude = function ( ) {
  if (this.staticLoc)
    return this.location.getLatitude();
  else
    return NaN;
};

gaiaWS.Station.prototype.getLongitude = function ( ) {
  if (this.staticLoc)
    return this.location.getLongitude();
  else
    return NaN;
};

gaiaWS.Station.prototype.getLatLon = function ( ) {
  if (this.staticLoc)
    return [ this.location.getLatitude(), this.location.getLongitude()];
  else
    return [ NaN, NaN ];
};

gaiaWS.Station.prototype.getProjectDbid = function ( ) {
  return this.projectDbid;
};

gaiaWS.Station.prototype.getProject = function ( ) {
  return gaiaWS.getProjectByDbid(this.projectDbid);
};

gaiaWS.Station.prototype.toString = function ( ) {
  return 'Station ' + this.getProject().getAbbreviation() 
  + ' / ' + this.abbreviation;
};


gaiaWS.Station.prototype.isStaticLocation = function ( ) {
  return this.staticLoc;
};


gaiaWS.Station.prototype.getChannels = function ( ) {
  return gaiaWS.getChannelByDbid(this.channelDbids);
};


gaiaWS.Location = function (obj) {
  this.latitude = this.longitude = this.placename 
  = this.state = this.countryDbid = null;
  
  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.Location)
      for (var name in this)
	this[name] = obj[name];
    else {
      this.latitude = parseFloat(obj.latitude['$']);
      this.longitude = parseFloat(obj.longitude['$']);
      if (obj.placename)
	this.placename = obj.placename['$'];
      if (obj.state)
	this.state = obj.state['$'];
      this.countryDbid = parseInt(obj.country['@dbid']);
    }
  }
};

gaiaWS.Location.prototype.getLatitude = function ( ) {
  return this.latitude;
};

gaiaWS.Location.prototype.getLongitude = function ( ) {
  return this.longitude;
};

gaiaWS.Location.prototype.getLatLon = function ( ) {
  return [ this.latitude, this.longitude ];
};

gaiaWS.Location.prototype.getPlacename = function ( ) {
  return this.placename;
};

gaiaWS.Location.prototype.getState = function ( ) {
  return this.state;
};

gaiaWS.Location.prototype.getCountry = function ( ) {
  return gaiaWS.getCountryByDbid(this.countryDbid);
};

gaiaWS.Location.prototype.toString = function ( ) {
  var a = new Array;
  if (this.placename)
    a.push(this.placename);
  if (this.state)
    a.push(this.state);
  a.push(this.getCountry);
  return a.join(', ') + '. ' 
  + gaiaWS.formatLatLon(this.latitude, this.longitude);
};


gaiaWS.formatLatLon = function($lat, $lon) {
  var latDir;
  var lonDir;
  if (lat == 0)
    latDir = '';
  else
    if (lat == 90)
      return "90&deg;N";
    else
      if (lat == -90)
	return "90&deg;S";
      else
	if (lat > 0)
	  latDir = ' N';
	else
	  if (lat < 0) 
	    latDir = ' S';
	  else
	    latDir = ' ?'; // some kind of logic error!
  
  if (lon > 180)
    lon -= 360;
  if (lon < -180)
    lon += 360;

  if (lon == 0 || lon == 180)
    lonDir = '';
  else
    if (lon > 0 && lon < 180)
      lonDir = ' E';
    else
      if (lon < 0 && lon > -180)
	lonDir = ' W';
      else
	lonDir = ' ?'; // some kind of logic error!
  return (lat.toFixed(4) + '&deg;' + latDir + ', '
	  + lon.toFixed(4) + '&deg;' + lonDir);
};


gaiaWS.Channel = function (obj) {
  this.dbid = this.projectDbid = this.stationDbid = this.channelTypeDbid 
  = this.urls = this.attribution = this.paletteDbid 
  = this.startTime = this.endTime = this.cadence = this.transferDelay 
  = null;
  
  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.Channel) {
      for (var name in this)
	this[name] = obj[name];
      
      this.dataLocations = new Object;
      if (obj.dataLocations.keogram)
	this.dataLocations.keogram 
	  = new gaiaWS.DataLocations(obj.dataLocations.keogram);
      
      this.zippedThumbnails = obj.zippedThumbnails;
      if (obj.dataLocations.thumbnail)
	this.dataLocations.thumbnail 
	  = new gaiaWS.DataLocations(obj.dataLocations.thumbnail);
      
      if (obj.dataLocations.summaryData)
	this.dataLocations.summaryData 
	  = gaiaWS.DataLocations(obj.dataLocations.summaryData);

      this.dataRequest = gaia.clone(obj.dataRequest);
    
    }
    else {
      this.dbid = parseInt(obj['@dbid']);
      this.projectDbid = parseInt(obj.project['@dbid']);
      this.stationDbid = parseInt(obj.station['@dbid']);
      this.channelTypeDbid = parseInt(obj.channel_type['@dbid']);
      this.urls = gaiaWS.extractStringsFromJsonArray(obj.url);
      this.attribution = obj.attribution['$'];
      this.paletteDbid = parseInt(obj.palette['@dbid']);
      this.startTime = obj.start_time['$'];
      this.endTime = obj.end_time['$'];

      // Convert the interval from seconds to ms
      this.cadence = parseInt(obj.cadence['$']) * 1e3;
      this.transferDelay = parseInt(obj.transfer_delay['$']) * 1e3;

      this.dataLocations = new Object;
      if (obj.keogram)
	this.dataLocations.keogram 
	  = new gaiaWS.DataLocations(obj.keogram.data_location);
      
      this.zippedThumbnails = true;
      if (obj.thumbnail) {
	this.zippedThumbnails = obj.thumbnail['@zipped'];
	this.dataLocations.thumbnail 
	  = new gaiaWS.DataLocations(obj.thumbnail.data_location);
      }
      
      if (obj.summary_data)
	this.dataLocations.summaryData 
	  = new gaiaWS.DataLocations(obj.summary_data.data_location);
      
      this.dataRequest = new Array;

      if (obj.data_request) {
	for (var i = 0; i < obj.data_request.length; ++i) {
	  this.dataRequest[i] = new Object;
	  this.dataRequest[i].description = obj.data_request[i].description['$'];
	  this.dataRequest[i].url = obj.data_request[i].url['$'];
	  this.dataRequest[i].method = obj.data_request[i].method['$'];
	  this.dataRequest[i].queryParameters = new Array;
	  if (obj.data_request[i].query_param) 
	    for (var j = 0; j < obj.data_request[i].query_param.length; ++j) {
	      this.dataRequest[i].queryParameters[j] = new Object;
	      this.dataRequest[i].queryParameters[j].name = 
		obj.data_request[i].query_param[j]['@name'];
	      this.dataRequest[i].queryParameters[j].value = 
		obj.data_request[i].query_param[j]['$'];
	      this.dataRequest[i].queryParameters[j].time = 
		obj.data_request[i].query_param[j]['@time'];
	    }
	}
      }
      
    }
  }
      
};

gaiaWS.Channel.prototype.getUrls = function () {
  return this.urls
};

gaiaWS.Channel.prototype.getDbid = function () {
  return this.dbid;
};

gaiaWS.Channel.prototype.getProject = function () {
  return gaiaWS.getProjectByDbid(this.projectDbid);
};

gaiaWS.Channel.prototype.getProjectDbid = function () {
  return this.projectDbid;
};

gaiaWS.Channel.prototype.getStation = function () {
  return gaiaWS.getStationByDbid(this.stationDbid);
};

gaiaWS.Channel.prototype.getStationDbid = function () {
  return this.stationDbid;
};

gaiaWS.Channel.prototype.getChannelType = function () {
  return gaiaWS.getChannelTypeByDbid(this.channelTypeDbid);
};

gaiaWS.Channel.prototype.getPalette = function () {
  return gaiaWS.getPaletteByDbid(this.paletteDbid);
};

gaiaWS.Channel.prototype.getAttribution = function () {
  return this.attribution;
};

gaiaWS.Channel.prototype.getStartTime = function () {
  return this.startTime;
};

gaiaWS.Channel.prototype.getEndTime = function () {
  return this.endTime;
};

gaiaWS.Channel.prototype.wasOperating = function (t) {
  return (t.valueOf() >= this.startTime.valueOf() 
	  && t.valueOf() < this.endTime.valueOf());
};

gaiaWS.Channel.prototype.getKeogramDataLocations = function () {
  if (this.dataLocations.keogram)
    return this.dataLocations.keogram;
  else
    return new gaiaWS.DataLocations;
};

gaiaWS.Channel.prototype.getThumbnailDataLocations = function () {
  if (this.dataLocations.thumbnail)
    return this.dataLocations.thumbnail;
  else
    return new gaiaWS.DataLocations;
};

gaiaWS.Channel.prototype.getSummaryDataLocations = function () {
  if (this.dataLocations.summaryData)
    return this.dataLocations.summaryData;
  else
    return new Array;
};

gaiaWS.Channel.prototype.hasKeograms = function () {
  return (this.dataLocations.keogram ? true : false);
};

gaiaWS.Channel.prototype.hasThumbnails = function () {
  return (this.dataLocations.thumbnail ? true : false);
};

gaiaWS.Channel.prototype.hasSummaryData = function () {
  return (this.dataLocations.summaryData ? true : false);
};

gaiaWS.Channel.prototype.getLabel = function () {
  return this.getProject().getAbbreviation() + ' / '
  + this.getStation().getAbbreviation() + ' / ' 
  + this.getChannelType().getName();
};

gaiaWS.Channel.prototype.toString = function () {
  return "Channel " + this.getLabel();
};


gaiaWS.Channel.prototype.getKeogramUrl = function(orientation, date) {
  var orientStr = orientation.getAbbreviation();
  var orNS = orientStr.substr(0,1);
  var orUD = orientStr.substr(1,1);
  var station = this.getStation();

  if (orNS == 'e')
    if (station.getLatitude() >= 0)
      orNS = 's';
    else
      orNS = 'n';
    
  if (orNS == 'p')
    if (station.getLatitude() >= 0)
      orNS = 'n';
    else
      orNS = 's';
  
  var r;
  var proj_abbrev = this.getProject().getAbbreviation().toLowerCase();
  var stat_abbrev = station.getAbbreviation().toLowerCase();
  var chan_name = this.getChannelType().getRefName().toLowerCase();
  var palette = gaiaViewer.state.palette.getRefName();
  if (!(this.getChannelType().getIsGreyscale()))
    palette = 'none';

  if (gaiaWS.inOnlineMode())
    r = ('/palette/' + palette + '/keogram/' 
	 + orNS + '/'
	 + proj_abbrev + '/' + stat_abbrev + '/' + chan_name
	 + '/%Y-%m-%dZ');
  else
    r = (gaiaWS.getImageRoot() + '/' + orNS + orUD + '/' + 
	 palette + '/'
	 + proj_abbrev + '/' + stat_abbrev + '/' + chan_name + '/%Y/%m/'
	 + proj_abbrev + '_' + stat_abbrev + '_' + chan_name + '_%Y%m%d.png');

  if (date !== null && date !== void 0)
    r = date.UTCstrftime(r);
  return r;
};


gaiaWS.Channel.prototype.getSummaryDataUrl = function(date) {
  var station = this.getStation();
  
  var r;
  var proj_abbrev = this.getProject().getAbbreviation().toLowerCase();
  var stat_abbrev = station.getAbbreviation().toLowerCase();
  var chan_name = this.getChannelType().getRefName().toLowerCase();
  if (gaiaWS.inOnlineMode())
    r = ('/lineplot/' + proj_abbrev + '/' + stat_abbrev + '/' + chan_name
	 + '/%Y-%m-%dZ');
  else
    r = (gaiaWS.getImageRoot()
	 + '/lineplot/' + proj_abbrev + '/' + stat_abbrev + '/' + chan_name
	 + '/%Y/%Y%m%d.png');
  
  if (date !== null && date !== void 0)
    r = date.UTCstrftime(r);
  return r;
};


gaiaWS.Channel.prototype.getThumbnailUrl = function(orientation, date) {
  var orientStr = orientation.getAbbreviation();
  var orNS = orientStr.substr(0,1);
  var orUD = orientStr.substr(1,1);
  var station = this.getStation();

  if (orNS == 'e')
    if (station.getLatitude() >= 0)
      orNS = 's';
    else
      orNS = 'n';
    
  if (orNS == 'p')
    if (station.getLatitude() >= 0)
      orNS = 'n';
    else
      orNS = 's';
  
  var r;
  var proj_abbrev = this.getProject().getAbbreviation().toLowerCase();
  var stat_abbrev = station.getAbbreviation().toLowerCase();
  var chan_name = this.getChannelType().getRefName().toLowerCase();
  var palette = gaiaViewer.state.palette.getRefName();
  if (!(this.getChannelType().getIsGreyscale()))
    palette = 'none';
  
  if (gaiaWS.inOnlineMode())
    r = ('/palette/' + palette + '/thumbnail/' 
	 + orNS + orUD + '/'
	 + this.getProject().getAbbreviation().toLowerCase() + '/'
	 + station.getAbbreviation().toLowerCase() + '/'
	 + this.getChannelType().getRefName().toLowerCase() 
	 + '/%Y-%m-%dT%H:%M:%SZ');
  else
    r = (gaiaWS.getImageRoot() + '/' + orNS + orUD + '/' + 
	 palette + '/'
	 + proj_abbrev + '/' + stat_abbrev + '/' + chan_name + '/%Y/%m/'
	 + proj_abbrev + '_' + stat_abbrev + '_' + chan_name 
	 + '_%Y%m%d_%H%M.png');
  
  if (date !== null && date !== void 0)
    r = date.UTCstrftime(r);
  return r;
};


gaiaWS.Channel.prototype.getDataRequestUrls = function (doc) {
  r = new Array;
  for (var i = 0; i < this.dataRequest.length; ++i) {
    var desc = this.dataRequest[i].description;
    var url = this.dataRequest[i].url;
    var method = this.dataRequest[i].method.toUpperCase();
    var queryParameters = this.dataRequest[i].queryParameters;
    if (url.match(/^http/) 
	&& (method == 'POST' || queryParameters.length)) {
      // Have to use generate a form
      var f = doc.createElement('form');
      f.action = url;
      f.method = method;
      f.target = '_blank';
      f.name = 'data-request-' + i; // Need a unique name to refer to

      // Now add hidden elements
      for (var j = 0; j < queryParameters.length; ++j) {
	var h = doc.createElement('input');
	h.type = 'hidden';
	h.name = queryParameters[j].name;
	h.value = queryParameters[j].value;
	h.time = queryParameters[j].time;
	//h.setAttribute('time', queryParameters[j].time);
	f.appendChild(h);
      }
    
      // Now add a plain button. Don't use a input (type=submit) since
      // that would send its name/value information to the form.
      var b = doc.createElement('button');
      b.formName = f.name;
      b.onclick = function() { gaiaWS.sendDataRequestForm(this); };

      // Add a description for the button
      b.appendChild(doc.createTextNode(desc));

      // Don't put the button inside the form else it gets used as
      // submit button, even though it isn't. Create a span element to
      // hold both the form and the button.

      var s = doc.createElement('span');
      s.appendChild(f);
      s.appendChild(b);
      r.push(s);
    }
    else {
      // No need to use a form, just use a plain button
      var a = doc.createElement('button');
      
      a.url = url; // cannot use just 'url' else all closures get the same url
      a.onclick = function() { gaiaWS.openDataRequestWindow(this.url); };
      a.appendChild(doc.createTextNode(desc));
      r.push(a);
    }
  }
  return r;
};


gaiaWS.openDataRequestWindow = function (url) {
  return window.open(gaiaViewer.state.time.UTCstrftime(url), '_blank');
};


gaiaWS.sendDataRequestForm = function (b) {
  var f = document.forms[b.formName];
  var t = gaiaViewer.state.time; // selected time
  var st = gaiaDate.getStartOfDay(t); // start of day
  var et = new Date(gaiaViewer.state.time.valueOf() + 86400e3); // end of day
  
  // expand any strftime format specifiers in the action
  f.action = t.UTCstrftime(f.action);

  // Convert the strftime format strings into actual values for the
  // current time
  for (var i = 0; i < f.elements.length; ++i) {
    if (f.elements[i].tagName == 'INPUT') {
      // save current value
      f.elements[i].oldValue = f.elements[i].value;

      var timeval = f.elements[i].time;
      switch (timeval) {
      case 'time':
	f.elements[i].value = t.UTCstrftime(f.elements[i].value);
	break;
      
      case 'end_time':
	f.elements[i].value = et.UTCstrftime(f.elements[i].value);
	break;

      default:
	// All input element should have been created as a result of
	// query parameters and hence should have a known value for time
	console.log('unknown time value: ' + timeval);
	break;
      };
    }
  }
  
  // Submit the form
  f.submit();

  // Put old values back
  for (var i = 0; i < f.elements.length; ++i)
    if (f.elements[i].tagName == 'INPUT')
      f.elements[i].value = f.elements[i].oldValue;
  
  return true;
};


gaiaWS.ChannelOrder = function (obj) {
  this.dbid = this.refName = this.description = null;
  
  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.ChannelOrder)
      for (var name in this)
	this[name] = obj[name];
    else {
      this.dbid = parseInt(obj['@dbid']);
      this.refName = obj['@ref_name'];
      this.description = obj.description['$'];
    }
  }
};
    
gaiaWS.ChannelOrder.prototype.getDbid = function ( ) {
  return this.dbid;
};

gaiaWS.ChannelOrder.prototype.getRefName = function ( ) {
  return this.refName;
};

gaiaWS.ChannelOrder.prototype.getDescription = function ( ) {
  return this.description;
};


gaiaWS.ChannelType = function (obj) {
  this.dbid = this.refName = this.name = this.isGreyscale = null;
  
  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.ChannelType)
      for (var name in this)
	this[name] = obj[name];
    else {
      this.dbid = parseInt(obj['@dbid']);
      this.refName = obj['@ref_name'];
      this.name = obj.name['$'];
      this.isGreyscale = (true == obj.is_greyscale['$']);
    }
  }
};

gaiaWS.ChannelType.prototype.getDbid = function ( ) {
  return this.dbid;
};

gaiaWS.ChannelType.prototype.getRefName = function ( ) {
  return this.refName;
};

gaiaWS.ChannelType.prototype.getName = function ( ) {
  return this.name;
};

gaiaWS.ChannelType.prototype.getIsGreyscale = function ( ) {
  return this.isGreyscale;
};


gaiaWS.DataLocations = function (obj) {
  this.urls = new Array;
  this.nodeDbids = new Array;
  if (!obj)
    return;
  
  if (typeof obj == "object" && obj.constructor == gaiaWS.DataLocations) { 
    this.urls = obj.urls.concat();
    this.nodeDbids = obj.nodeDbids.concat();
  }
  else
    if (obj.splice)
      // An array
      for (var i = 0; i < obj.length; ++i) {
	this.nodeDbids.push(obj[i]['@node_dbid']);
	this.urls.push(obj[i]['$']);
      }
  
};

gaiaWS.DataLocations.prototype.getUrls = function () {
  return this.urls.concat(); // return a copy of the array
};


gaiaWS.DataLocations.prototype.getNodes = function () {
  if (!gaiaWS.inOnlineMode())
    return void 0;
  var r = new Array;
  for (var i = 0; i < this.nodeDbids.length; ++i)
    r.push(gaiaWS.getNodeByDbid(this.nodeDbids[i]));
  return r;
};


gaiaWS.Orientation = function (obj) {
  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.Orientation) {
      this.dbid = obj.dbid;
      this.abbreviation = obj.abbreviation;
      this.description = obj.description;
    }
    else {
      this.dbid = parseInt(obj['@dbid']);
      this.abbreviation = obj.abbreviation['$'];
      this.description = obj.description['$'];
    }
  }
};

gaiaWS.Orientation.prototype.getDbid = function ( ) {
  return this.dbid;
};

gaiaWS.Orientation.prototype.getAbbreviation = function ( ) {
  return this.abbreviation;
};

gaiaWS.Orientation.prototype.getDescription = function ( ) {
  return this.description;
};


gaiaWS.Palette = function (obj) {
  this.dbid = this.refName = this.name = this.filename = this.url 
  = this.description = null
  
  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.Palette)
      for (var name in this)
	this[name] = obj[name];
    else {
      this.dbid = parseInt(obj['@dbid']);
      this.refName = obj['@ref_name'];
      this.name = obj.name['$'];
      this.filename = obj.filename['$'];
      this.url = obj.url['$'];
      this.description = obj.description['$'];
    }
  }
};

gaiaWS.Palette.prototype.getDbid = function ( ) {
  return this.dbid;
};

gaiaWS.Palette.prototype.getRefName = function ( ) {
  return this.refName;
};

gaiaWS.Palette.prototype.getName = function ( ) {
  return this.name;
};

gaiaWS.Palette.prototype.getFilename = function ( ) {
  return this.filename;
};

gaiaWS.Palette.prototype.getUrl = function ( ) {
  return this.url;
};

gaiaWS.Palette.prototype.getDescription = function ( ) {
  return this.description;
};


gaiaWS.Country = function (obj) {
  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.Country) {
      this.dbid = obj.dbid;
      this.name = obj.name;
      this.continentDbid = obj.continentDbid;
    }
    else {
      this.dbid = parseInt(obj['@dbid']);
      this.name = obj.name['$'];
      this.continentDbid = parseInt(obj.continent['@dbid']);
    }
  }
};

gaiaWS.Country.prototype.getDbid = function ( ) {
  return this.dbid;
};

gaiaWS.Country.prototype.getName = function ( ) {
  return this.name;
};

gaiaWS.Country.prototype.getContinentDbid = function ( ) {
  return this.continentDbid;
};

gaiaWS.Country.prototype.getContinent = function ( ) {
  return gaiaWS.getContinentByDbid(this.continentDbid);
};


gaiaWS.Continent = function (obj) {
  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.Continent) {
      this.dbid = obj.dbid;
      this.name = obj.name;
    }
    else {
      this.dbid = parseInt(obj['@dbid']);
      this.name = obj['$'];
    }
  }
};

gaiaWS.Continent.prototype.getDbid = function ( ) {
  return this.dbid;
};

gaiaWS.Continent.prototype.getName = function ( ) {
  return this.name;
};


gaiaWS.Contact = function (obj) {
  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.Contact) {
      this.dbid = obj.dbid;
      this.firstname = obj.firstname;
      this.surname = obj.surname;
      this.email = obj.email;
    }
    else {
      this.dbid = parseInt(obj['@dbid']);
      this.firstname = obj.firstname['$'];
      this.surname = obj.surname['$'];
      this.email = gaiaWS.getEmail(obj.email);
    }
  }
};

gaiaWS.Contact.prototype.getDbid = function ( ) {
  return this.dbid;
};

gaiaWS.Contact.prototype.getName = function ( ) {
  var r = '';
  if (this.firstname)
    r += this.firstname;

  if (this.surname) {
    if (r)
      r += ' ';
    r += this.surname;
  }
  return r;
};

gaiaWS.Contact.prototype.getEmail = function ( ) {
  return this.email;
};

// Get a HTML element of the formatted name
gaiaWS.Contact.prototype.getNameElement = function ( doc ) {
  var a = doc.createTextNode(this.getName());
  if (this.email) {
    var r = doc.createElement('a');
    r.href = 'mailto:' + this.email;
    r.appendChild(a);
    return r;
  }
  else
    return a;
};


gaiaWS.Institute = function (obj) {
  if (typeof obj == "object") {
    if (obj.constructor == gaiaWS.Contact) {
      this.dbid = obj.dbid;
      this.countryDbid = obj.countryDbid;
      this.name = obj.name;
      this.url = obj.url;
      this.address = obj.address;
    }
    else {
      this.dbid = parseInt(obj['@dbid']);
      this.countryDbid = obj.country['@dbid'];
      this.name = obj.name['$'];
      this.url = obj.url['$'];
      if (obj.address)
	this.address = obj.address['$'];
      else
	this.address = undefined;
    }
  }
};

gaiaWS.Institute.prototype.getDbid = function ( ) {
  return this.dbid;
};


gaiaWS.Institute.prototype.getCountry = function ( ) {
  return gaiaWS.getCountryByDbid(this.countryDbid);
};

gaiaWS.Institute.prototype.getName = function ( ) {
  return this.name;
};

gaiaWS.Institute.prototype.getAddress = function ( ) {
  return this.address;
};

gaiaWS.Institute.prototype.getUrl = function ( ) {
  return this.url;
};

gaiaWS.Institute.prototype.getNameElement = function ( doc ) {
  var a = doc.createTextNode(this.getName() + ', ' 
			     + this.getCountry().getName());
  if (this.url) {
    var r = doc.createElement('a');
    r.href = this.url;
    r.appendChild(a);
    // if (this.address)
    //   r.title = this.address;
    return r;
  }
  else
    return a;
};


// Data availability object
// date is required, chan is optional (get details for all channels)
// or a Channel object or array of Channel objects.
gaiaWS.DataAvailability = function(date, chan) {
  if (typeof date == "object" && date.constructor == gaiaWS.DataAvailability) {
    this.date = date.date;
    this.availability = gaia.clone(data.availability);
    return;
  }

  this.date = gaia.clone(date);
  this.availability = new Object;

  if (gaiaWS.inOnlineMode()) {
    // only attempt to fetch data availability when online
    var url = date.UTCstrftime(gaiaWS.webServiceBaseUrl 
			       + '/data_availability/%Y/%m/%d');
    if (chan !== null && chan !== void 0) {
      if (chan.constructor.splice) {
	// chan is an array
	var dbids = new Array;
	for (var i = 0; i < chan.length; ++i)
	  dbids.push(chan.getDbid());
	url += '/' + dbids.join(',');
      }
      else
	url += '/' + chan.getDbid();
    }
    else
      // use all channels
      chan = gaiaWS.data.channels;
  
    var da = gaiaWS.webService(url);
    if (da && da.data_availability_list && 
	da.data_availability_list.data_availability) {
      // Process everything in the list
      for (var i = 0; i < da.data_availability_list.data_availability.length;
	   ++i) {
	var d = da.data_availability_list.data_availability[i];
	var chanDbid = d.channel['@dbid'];
	var ni = parseInt(d.num_images['$']);
	var c = gaiaWS.getChannelByDbid(chanDbid);
      
	this.availability[chanDbid] = {
	  "keograms": (c.hasKeograms() && ni > 0 ? true : false),
	  "thumbnails": (c.hasThumbnails() && ni > 0 ? true : false),
	  "summaryData": (c.hasSummaryData() && ni > 0 ? true : false),
	  // Indicate that the availability exists
	  "availability": true
	};
      }
    }
  }
  else 
    if (chan === null || chan === void 0)
      // assume all channels
      chan = gaiaWS.data.channels;

  var timeNow = new Date;

  // Check details are present for all of the requested channels
  for (var i = 0; i < chan.length; ++i) {
    var chanDbid = chan[i].getDbid();
    if (!this.availability[chanDbid]) {
      // No availability details so make an educated guess
      this.availability[chanDbid] = new Object;
      if (chan[i].wasOperating(date) && 
	  (date.valueOf() <= timeNow.valueOf() - chan[i].transferDelay)) {
	// Was operating and date is old enough that data should have
	// been transferred, assume we have data.
	this.availability[chanDbid] = {
	  "keograms": chan[i].hasKeograms(),
	  "thumbnails": chan[i].hasThumbnails(),
	  "summaryData": chan[i].hasSummaryData(),
	  "availability": false
	};
      }
      else {
	// Not operating or data too recent to have been transferred.
	this.availability[chanDbid] = {
	  "keograms": false,
	  "thumbnails": false,
	  "summaryData": false,
	  "availability": false
	};
      }
    }
  }
};

gaiaWS.DataAvailability.prototype.hasData = function (chan, dataType) {
  if (chan.splice) {
    // chan is an array
    var r = new Array;
    for (var i = 0; i < chan.length; ++i) {
      var dbid = chan[i].getDbid();
      if (this.availability[dbid])
	r[i] = this.availability[dbid][dataType];
      else
	r[i] = false; // channel details not present
    }
  }
  else {
    var dbid = chan.getDbid();
    if (this.availability[dbid])
      return this.availability[dbid][dataType];
    else
      return false; // channel details not present
  }
};


gaiaWS.DataAvailability.prototype.hasKeograms = function (chan) {
  return this.hasData(chan, 'keograms');
};


gaiaWS.DataAvailability.prototype.hasThumbnails = function (chan) {
  return this.hasData(chan, 'thumbnails');
};


gaiaWS.DataAvailability.prototype.hasSummaryData = function (chan) {
  return this.hasData(chan, 'summaryData');
};

gaiaWS.DataAvailability.prototype.hasAvailability = function (chan) {
  return this.hasData(chan, 'availability');
};


gaiaWS.webService = function (url) {
  var xmlhttp =  new XMLHttpRequest();
  xmlhttp.open('GET', url, false); // synchronous
  xmlhttp.send(null);

  var r;
  try {
    // console.log('Fetching ' + url + ' ...');
    eval('r = ' + xmlhttp.responseText + ';');
    // console.log('... done');
    return r;
  }
  catch(exception) {
    console.log('Error with parsing JSON: ' + exception + ' url was ' + url);
    alert(exception + ' url was ' + url);
    throw(exception);
  }
  return false;
};

if (document.location.protocol == "file:")
  // Use an alternative way of 'downloading' data
  gaiaWS.webService = function (url) {
    var head = document.getElementsByTagName('head')[0];
    s = document.createElement('script');
    s.type = "text/javascript";
    s.src = url;
    console.log('URL:' + url)
    delete window.json;
    // insert script into the doucment to have it executed
    head.appendChild(s); 
    //     if (head.firstChild)
    //       head.insertBefore(s, head.firstChild);
    //     else
    //       head.appendChild(s);

    var r = window.json;
    console.log(r);
    // head.removeChild(s);
    return r;
  };

gaiaWS.loadJavaScript = function (url) {
  var xmlhttp =  new XMLHttpRequest();
  xmlhttp.open('GET', url, false); // synchronous
  xmlhttp.send(null);
    
  var r;
  try {
    console.log('Loading ' + url + ' ...');
    eval('r = ' + xmlhttp.responseText + ';');
    console.log('...done');
    return r;
  }
  catch(exception) {
    alert(exception + '. URL was ' + url);
    throw(exception);
  }
};


gaiaWS.getEmail = function (obj, link) {
  var r = obj['$'];
  if (obj['@cipher'] == 'rot-13')
    r = gaia.rot13(r);
  
  if (link)
    r = '<a href="mailto:' + r + '">' + r + '</a>';

  return r;
};
  
gaiaWS.inOnlineMode = function () {
  return (document.location.protocol != "file:");
};
  
// The location where the main GAIA files are stored. Note that the
// viewer is in a subdirectory 'viewer'. This should only be needed in
// offline mode.
gaiaWS.documentRoot = null;
gaiaWS.getDocumentRoot = function () {
  if (gaiaWS.documentRoot)
    return gaiaWS.documentRoot;

  // Try an educated guess; remove last two parts from the path
  var a = window.document.location.pathname.split('/');
  a.pop();
  a.pop();
  return window.document.location.protocol + '//' +  a.join('/');
};


// The location where the GAIA image files are stored. This should
// only be needed in offline mode.
gaiaWS.imageRoot = null;
gaiaWS.getImageRoot = function () {
  if (gaiaWS.inOnlineMode())
    return gaiaWS.config.image_base_url;

  if (gaiaWS.imageRoot)
    return gaiaWS.imageRoot;

  // Try an educated guess; remove last two parts from the path
  var r = '/gaia_offline_data';
  if (window.navigator && (window.navigator.platform == 'Win32'
			   || window.navigator.platform == 'Win64'))
    r = 'c:' + r;
  return window.document.location.protocol + '//' +  r;
};




// Need to know where the web services live before we can ask for
// the web services for that kind of information! Fortunately PHP
// has set his variable
gaiaWS.jsonData = new Object;
gaiaWS.webServiceBaseUrl = '/rest_json'; // set by PHP
 // ;

gaiaWS.init = function () {
  // ### DEBUG Nasty hack for offline mode
  var matches = document.location.toString().match("^(file://.*)[/\]viewer[/\]index.html$");
  
  var wsUrlSuffix = '';
  if (matches && matches.length >= 2) {
    gaiaWS.webServiceBaseUrl = matches[1] + "/rest_json";
    wsUrlSuffix = '.js';
  }

  if (window.json_data) {
    gaiaWS.jsonData = new Object;
    for (var name in window.json_data)
      gaiaWS.jsonData[name] = window.json_data[name][name];
  }
  else {
    // Fetch the list of services
    var tmp = gaiaWS.webService(gaiaWS.webServiceBaseUrl + '/index' 
				+ wsUrlSuffix);
    gaiaWS.jsonData.services = tmp.services;
  
    // Now fetch all details
    for (var name in gaiaWS.jsonData.services) {
      if (gaiaWS.jsonData.services[name]['@href']) {
	gaiaWS.tmp = gaiaWS.webService(gaiaWS.jsonData.services[name]['@href']
				       + wsUrlSuffix);
	gaiaWS.jsonData[name] = gaiaWS.tmp[name];
      }
    }
  }
  // Now store all data arranged by dbid
  gaiaWS.dataByDbid = new Object;
  for (var rootName in gaiaWS.jsonData) {
    if (rootName == 'services' || rootName == 'data_availability')
      continue;
  
    gaiaWS.dataByDbid[rootName] = new Object;
    for (var childName in gaiaWS.jsonData[rootName]) {

      for (var i = 0; i < gaiaWS.jsonData[rootName][childName].length; ++i) {
	var dbid = gaiaWS.jsonData[rootName][childName][i]['@dbid'];
	if (dbid != '') {
	  gaiaWS.dataByDbid[rootName][dbid] =
	    gaiaWS.jsonData[rootName][childName][i];
	}
      }
    }
      
    // Convert configuration data to more useful form
    gaiaWS.config = { };
    for (var i = 0; i < gaiaWS.jsonData.configuration.config.length; ++i)
      gaiaWS.config[gaiaWS.jsonData.configuration.config[i].name['$']]
	= gaiaWS.jsonData.configuration.config[i].value['$'];
      
  }


  // Now convert start/end dates from strings to Date, and find the
  // earliest and latest dates
  gaiaWS.earliestDate = null;
  gaiaWS.latestDate = null;
  for (var i = 0; i < gaiaWS.jsonData.channels.channel.length; ++i) {
    var c = gaiaWS.jsonData.channels.channel[i];
    c.start_time['$'] = gaiaDate.ISOStringToUTCDate(c.start_time['$']);
    if (gaiaWS.earliestDate === null
	|| c.start_time['$'] < gaiaWS.earliestDate)
      gaiaWS.earliestDate = new Date(c.start_time['$']);
      
    c.end_time['$'] = gaiaDate.ISOStringToUTCDate(c.end_time['$']);
    if (gaiaWS.latestDate === null
	|| c.end_time['$'] > gaiaWS.latestDate)
      gaiaWS.latestDate = new Date(c.end_time['$']);
  }
  
  var classNameToRootName = {
    "Country": "countries",
    "Continent": "continents",
    "Contact" : "contacts",
    "Channel": "channels",
    "ChannelOrder": "channel_orders",
    "ChannelType": "channel_types",
    "Institute" : "institutes",
    "Node": "nodes",
    "Orientation": "orientations",
    "Palette": "palettes",
    "Project": "projects",
    "Station": "stations"
  };

  var classNameToDataName = {
    "Country": "countries",
    "Continent": "continents",
    "Channel": "channels",
    "Contact" : "contacts",
    "ChannelType": "channelTypes",
    "ChannelOrder": "channelOrders",
    "Institute" : "institutes",
    "Node": "nodes",
    "Orientation": "orientations",
    "Palette": "palettes",
    "Project": "projects",
    "Station": "stations"
  };
  
  
  gaiaWS.data = new Object;
  for (var className in classNameToRootName) {
    var rootName = classNameToRootName[className];
    if (rootName == 'services' || rootName == 'data_availability')
      continue;
  
    var dataName = classNameToDataName[className];
    
    gaiaWS.dataByDbid[dataName] = new Object;
    gaiaWS.data[dataName] = new Array;

    for (var childName in gaiaWS.jsonData[rootName]) {
      for (var i = 0; i < gaiaWS.jsonData[rootName][childName].length; ++i) {
	var obj = new gaiaWS[className](gaiaWS.jsonData[rootName][childName][i]);
	gaiaWS.data[dataName][i] = obj;

	// var dbid = obj.getDbid();
	// if (dbid != '') {
	gaiaWS.dataByDbid[dataName][obj.getDbid()] = obj;
	// }
      }
    }

  }
  
  // Prevent multiple initialisiation
  gaiaWS.init = function () { return false; };
  return true;
};

// gaiaWS.init();
// less useful form of config data
delete gaiaWS.jsonData.configuration;
delete gaiaWS.jsonData.nodes;
delete gaiaWS.jsonData;

gaiaWS.getContactByDbid = function (dbid) {
  return gaiaWS.dataByDbid.contacts[dbid];
};

gaiaWS.getContinentByDbid = function (dbid) {
  return gaiaWS.dataByDbid.continents[dbid];
};

gaiaWS.getCountryByDbid = function (dbid) {
  return gaiaWS.dataByDbid.countries[dbid];
};

gaiaWS.getInstituteByDbid = function (dbid) {
  return gaiaWS.dataByDbid.institutes[dbid];
};

gaiaWS.getOrientationByDbid = function (dbid) {
  return gaiaWS.dataByDbid.orientations[dbid];
};

gaiaWS.getProjectByDbid = function(dbid) {
  return gaiaWS.dataByDbid.projects[dbid];
};

gaiaWS.getStationByDbid = function (dbid) {
  if (dbid.splice) {
    var r = new Array;
    for (var i = 0; i < dbid.length; ++i)
      r.push(gaiaWS.dataByDbid.stations[dbid[i]]);
    return r;
  }
  return gaiaWS.dataByDbid.stations[dbid];
};

gaiaWS.getChannelByDbid = function (dbid) {
  if (dbid.splice) {
    var r = new Array;
    for (var i = 0; i < dbid.length; ++i)
      r.push(gaiaWS.dataByDbid.channels[dbid[i]]);
    return r;
  }
  return gaiaWS.dataByDbid.channels[dbid];
};

gaiaWS.getChannelsByProjectDbid = function (dbid) {
  var r = new Array;
  for (var i = 0; i < gaiaWS.data.channels.length; ++i)
    if (gaiaWS.data.channels[i].getProject().getDbid() == dbid)
      r.push(gaiaWS.data.channels[i]);
  
  return r;
};

gaiaWS.getChannelOrderByDbid = function (dbid) {
  return gaiaWS.dataByDbid.channelOrders[dbid];
};

gaiaWS.getChannelTypeByDbid = function (dbid) {
  return gaiaWS.dataByDbid.channelTypes[dbid];
};

gaiaWS.getNodeByDbid = function (dbid) {
  return gaiaWS.dataByDbid.nodes[dbid];
};

gaiaWS.getPaletteByDbid = function (dbid) {
  return gaiaWS.dataByDbid.palettes[dbid];
};


// For an array of objects (Channels, Nodes etc) get their database
// IDs. Also works where arr is an object and the object values are
// Channels, Nodes etc.
gaiaWS.getDbids = function (arr) {
  var r = new Array;
  for (var name in arr)
    r.push(arr[name].getDbid());
  return r;
}


gaiaWS.displayNodeStatus = function (elem) {
  var r = '';
  for (var i = 0; i < gaiaWS.data.nodes.length; ++i) 
    r += gaiaWS.data.nodes[i].getUrl() + ': ' 
      + (gaiaWS.data.nodes[i].isOnline() ? 'Online' : 'Offline') 
      + "\n";
  
  if (elem == null) 
    alert(r);
  else {
    // When inserting into an element use <br/> tags in addition to newlines
    r = r.replace(/\n/g,'<br/>\n'); 
    elem.innerHTML = r;
  }
  return true;
};


gaiaWS.resetNodeStatus = function () {
  for (var i = 0; i < gaiaWS.data.nodes.length; ++i) 
    gaiaWS.data.nodes[i].resetOnlineStatus();
};

// Return the project DBID for the array of channels. If the unique
// set of channels is all known data channels then the "all
// projects" value (-1) is returned. If the array is empty, contains
// channels from multiple projects, or is an incomplete list of
// channels from one project the "custom selection" value (-2) is
// returned.
gaiaWS.getProjectDbidFromChannels = function (channels) {
  if (channels.length == 0)
    return -2;
    
  var chanDbids = new Array;
  var projDbids = new Array;
  for (var i = 0; i < channels.length; ++i) {
    chanDbids.push(channels[i].getDbid());
    projDbids.push(channels[i].getProjectDbid());
  }
    
  projDbids = gaia.unique(projDbids);
  chanDbids = gaia.unique(chanDbids);
  if (projDbids.length != 1)
    // multiple projects, is it all the data channels?
    if (chanDbids.length == gaiaWS.data.channels.length)
      return -1; // All channels from all projects
    else
      return -2; // multiple projects = custom selection
    
  if (chanDbids.length 
      == gaiaWS.getChannelsByProjectDbid(projDbids[0]).length)
    // all channels from one project
    return projDbids[0];

  return -2;
};



// Returns a new array.
gaiaWS.sortChannels = function (channels, channelOrder) {
  // var c2 = gaia.clone(channels);
  var c2 = channels.concat();
  var getSortDataFunc;
  switch (channelOrder.getRefName()) {
  case "n_to_s":
    getSortDataFunc = function (a) {
      return [ -a.getStation().getLatitude(), 
	       a.getChannelType().getRefName()]};
    break;

  case "s_to_n":
    getSortDataFunc = function (a) {
      return [ a.getStation().getLatitude(), 
	       a.getChannelType().getRefName()]};
    break;
    
  case "e_to_w":
    getSortDataFunc = function (a) {
      return [ -a.getStation().getLongitude(), 
	       a.getChannelType().getRefName()]};
    break;

  case "w_to_e":
    getSortDataFunc = function (a) {
      return [ a.getStation().getLongitude(), 
	       a.getChannelType().getRefName()]};
    break;

  case "pole_to_eq":
    getSortDataFunc = function (a) {
      return [ -Math.abs(a.getStation().getLatitude()), 
	       a.getChannelType().getRefName()]};
    break;

  case "project":
    getSortDataFunc = function (a) {
      return [a.getProject().getAbbreviation(),
	      a.getStation().getAbbreviation(),
	      a.getChannelType().getRefName()];
    };
    break;

  case "channel_type":
    getSortDataFunc = function (a) {
      return [a.getChannelType().getRefName(),
	      a.getStation().getLatitude()];
    };
    break;
    
  default:
    console.warn('Unknown sort type: ' + channelOrder.getRefName());
    return c2;
  }
    
  c2.sort(function (a, b) {
	    var aa = getSortDataFunc(a);
	    var bb = getSortDataFunc(b);
	    var tmp;
	    for (var i = 0; i < aa.length; ++i) {
	      if (aa[i] < bb [i])
		return -1;
	      if (aa[i] > bb[i])
		return +1;
	      if (typeof aa[i] == "number" && typeof bb[i] == "number") {
		// check if either is NaN. Place NaNs last.
		if (tmp = (isNaN(aa[i]) - isNaN(bb[i])))
		  return tmp;
	      }
	      // same, try next array elements
	    }
	    return 0; // still identical
	  });

  getSortDataFunc = null;
  return c2;
};



