// -*-Mode: Java;-*- 
/*
JourneySat, a library to georeference a travel journal using Google Maps.
Copyright (C) 2005  Paolo Montrasio, paolo AT paolomontrasio.com

This file is part of JourneySat (the Program).

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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

/**
 * @author Paolo Montrasio paolo AT paolomontrasio.com
 */

// Syntax for class definition based on
// http://justatheory.com/computers/programming/javascript/emulating_namespaces.html

// This is a kind of package JourneySat declaration in Java
if (JourneySat == undefined) var JourneySat = {};

/**
 * Makes childClass inherits methodsAndAttributes from parentClass.
 * Can be used to set up multiple inheritance by calling it with the same
 * childClass and difference parentClasses. Naming collisions are avoided
 * by selecting the methods and attributes to be inherited.
 * Be careful to select all the private methods and attributes required
 * by the public ones.
 * @param childClass The class that inherits
 * @param parentClass The class that implements the methods and attributes
 * @param methodsAndAttributes The methods and attributes to be inherited.
 *                       Example: inherits(Ferry, Vehicle, "engine", "tank");
 *                                inherits(Ferry, Boat, "hull", "decks");
 */

function inherits(childClass, parentClass, methodsAndAttributes) {
  for (var i = 2; i < arguments.length; i++) {
    var name = arguments[i];
    childClass.prototype[name] = parentClass.prototype[name];
  }
}

/* *****************************************
 *                                         *
 *         Class JourneySat.Tree           *
 *                                         *
 *******************************************/

/**
 * @class
 * JourneySat.Tree manages a tree data structure of JourneySat.Node objects.
 * @see JourneySat.Node
 * @constructor
 */
JourneySat.Tree = function () {};
  /**
   * The root of the tree
   * @type JourneySat.Node
   * @private
   */
JourneySat.Tree.prototype.root = null;

/**
 * Removes a node from a tree. References to parent and siblings are
 * cleared. References to child nodes are preserved so this is potentially
 * a memory leak. If the node is unlinked and never used the client must
 * traverse the subtree and set to null all the references inside it.
 * @param {JourneytSat.Node} node The node
 */
JourneySat.Tree.unlinkNode = function (node) {
  var parent = node.parent;
  var prevNode = node.prevSibling;
  var nextNode = node.nextSibling;
  if (prevNode != null) {
    prevNode.nextSibling = nextNode;
  }
  if (nextNode != null) {
    nextNode.prevSibling = prevNode;
  }
  if (parent.firstChild == node) {
    parent.firstChild = nextNode;
  }
  if (parent.lastChild == node) {
    parent.lastChild = prevNode;
  }
  
  node.parent = null;
  node.prevSibling = null;
  node.nextSibling = null;
};

/*
 * Swaps two adiacent nodes. No operation is done if the nodes
 * are not adiacent.
 * @param {JourneySat.Node} node1 the first node in the original sequence.
 * @param {JourneySat.Node} node2 the second node
 */
JourneySat.Tree.swapNodes = function(node1, node2) {
  if (node1.nextSibling != node2) {
    return;
  }

  // 4 nodes involved: prevNode, node1, node2, nextNode
  var prevNode = node1.prevSibling;
  var nextNode = node2.nextSibling;

  if (prevNode != null) {
    prevNode.nextSibling = node2;
  }
  node2.prevSibling = prevNode;

  if (nextNode != null) {
    nextNode.prevSibling = node1;
  }
  node1.nextSibling = nextNode;

  node1.prevSibling = node2;
  node2.nextSibling = node1;

  var parent = node1.parent;
  if (parent.firstChild == node1) {
    parent.firstChild = node2;
  }
  if (parent.lastChild == node2) {
    parent.lastChild = node1;
  }
};

/**
 * Inserts newDayNode before dayNode.
 * @param {JourneySat.Node} newNode The node to insert
 * @param {JournaySat.Node} node The node to add before of
 */

JourneySat.Tree.insertNode = function (newNode, node) {

  // Can't insert before null because there is no way to know
  // who's the parent of the new node
  if (node == null) {
    return;
  }

  var parent = node.parent;

  // Fix links to root
  if (parent.firstChild == node) {
    parent.firstChild = newNode;
  }
  newNode.parent = parent;
  
  // Fix links to the previous sibling
  newNode.prevSibling = node.prevSibling;
  if (node.prevSibling != null) {
    node.prevSibling.nextSibling = newNode;
  }
  // Fix links to the next sibling
  newNode.nextSibling = node;
  node.prevSibling = newNode;  
};

/**
 * Adds newDayNode after dayNode.
 * @param {JourneySat.Node} newNode The node to insert
 * @param {JournaySat.Node} node The node to add before of
 */
JourneySat.Tree.appendNode = function (newNode, node) {

  // Can't insert after null because there is no way to know
  // who's the parent of the new node
  if (node == null) {
    return;
  }

  var parent = node.parent;
  newNode.parent = parent;

  // Last node in the sequence
  if (node.nextSibling == null) {
    node.nextSibling = newNode;
    newNode.prevSibling = node;
    parent.lastChild = newNode;
    return;
  }

  // Fix links to root
  if (parent.lastChild == node) {
    parent.lastChild = newNode;
  }

  // Stores reference to the next sibling
  var nextNode = node.nextSibling;
  
  // Fix links to the previous sibling
  newNode.prevSibling = node;
  node.nextSibling = newNode;

  // Fix links to the next sibling
  if (nextNode != null) {
    nextNode.prevSibling = newNode;
  }
  newNode.nextSibling = nextNode;
};

/* *****************************************
 *                                         *
 *         Class JourneySat.Node           *
 *                                         *
 *******************************************/

/**
 * @class
 * This class implements the node of a tree.
 * Every node has a link to its parent, previous sibling, next sibling,
 * first child and last child.
 * @see JourneySat.Tree
 * @constructor
 */
JourneySat.Node = function () {};
/**
 * Reference to the first child of this node
 * @type JourneySat.Node
 */
JourneySat.Node.prototype.firstChild = null;

/**
 * Reference to the last child of this node
 * @type JourneySat.Node
 */
JourneySat.Node.prototype.lastChild = null;

/**
 * Reference to the parent of this node
 * @type JourneySat.Node
 */
JourneySat.Node.prototype.parent = null;

/**
 * Reference to the sibling of this node which is one position
 * closer to parent.firstChild in the siblings list
 * @type JourneySat.Node
 */
JourneySat.Node.prototype.prevSibling = null;

/**
 * Reference to the sibling of this node which is one position
 * closer to parent.lastChild in the siblings list
 */
JourneySat.Node.prototype.nextSibling = null;

/**
 * The value of this node. It is any object or any type that has
 * a toString() method
 */
JourneySat.Node.prototype.value = null;

/**
 * Creates a new node and adds it to the tree as a child of this node.
 * It becomes this.lastChild
 * @param value The value of the new node.
 *              It must implement a toString() method
 * @returns A reference to the new node 
 */
JourneySat.Node.prototype.addChild = function (value) {
  var node = new JourneySat.Node();
  node.value = value;
  node.parent = this;
  var lastChild = this.lastChild;
  node.prevSibling = lastChild;
  if (lastChild != null) {
    lastChild.nextSibling = node;
  }
  this.lastChild = node;
  if (this.firstChild == null) {
    this.firstChild = node;
  }
  return node;
};
  
/**
 * Creates a new node and adds it to the tree as a sibling of this node.
 * It becomes this.nextSibling
 * @param value The value of the new node
 *              It must implement a toString() method
 * @returns A reference to the new node
 */
JourneySat.Node.prototype.insertSibling = function (value) {
  var node = new JourneySat.Node();
  node.value = value;
  node.parent = this.parent;
  var nextSibling = this.nextSibling;
  node.nextSibling = nextSibling;
  node.prevSibling = this;
  this.nextSibling = node;
  if (nextSibling != null) {
    nextSibling.prevSibling = node;
  } else {
    this.parent.lastChild = node;
  }
  return node;
};

/**
 * Removes a node from the tree. All children of this node are removed
 * as well.
 */
JourneySat.Node.prototype.remove = function () {
  // Depth first removal. Removes the firstChild until no children are left.
  //alert("this = " + this.toString());
  while (this.firstChild != null) {
    //alert("this.firstChild = " + this.firstChild.toString());
    this.firstChild.remove();
  }
  
  // Keep connected the siblings of the node where the removal started
  var prevSibling = this.prevSibling;
  var nextSibling = this.nextSibling;
  if (nextSibling != null) {
    nextSibling.prevSibling = prevSibling;
  }
  if (prevSibling != null) {
    // This branch can be executed for the node where the removal started
    prevSibling.nextSibling = nextSibling;
  }
  
  // Fix the links of the parent node
  if (this.parent.firstChild == this) {
    this.parent.firstChild = nextSibling;
  }
  if (this.parent.lastChild == this) {
    this.parent.lastChild = prevSibling;
  }
  
  // Removes all references starting from this node
  this.value = null;
  this.lastChild = null; // Only useful if the node had children
  // this.firstChild is null because of the while loop
  this.nextSibling = null;
  this.prevSibling = null;
  this.parent = null;
};

/**
 * Moves this node one position closer to parent.firstChild in its
 * siblings list.
 */
JourneySat.Node.prototype.swapPrevSibling = function () {
  var prevSibling = this.prevSibling;
  if (prevSibling == null) {
    return;
  }
  var prevPrevSibling = prevSibling.prevSibling;
  var nextSibling = this.nextSibling;
  
  if (this.parent.firstChild == prevSibling) {
    this.parent.firstChild = this;
  }
  if (this.parent.lastChild == this) {
    this.parent.lastChild = prevSibling;
  }
  
  // 4 siblings are involved: prevPrev, prev, this, next
  
  if (prevPrevSibling != null) {
    prevPrevSibling.nextSibling = this;
  }
  this.prevSibling = prevPrevSibling;
  this.nextSibling = prevSibling;
  prevSibling.prevSibling = this;
  prevSibling.nextSibling = nextSibling;
  if (nextSibling != null) {
    nextSibling.prevSibling = prevSibling;
  }
};
  
/**
 * Moves this node one position closer to parent.lastChild in its
 * siblings list.
 */
JourneySat.Node.prototype.swapNextSibling = function () {
  var nextSibling = this.nextSibling;
  if (nextSibling == null) {
    return;
  }
  var nextNextSibling = nextSibling.nextSibling;
  var prevSibling = this.prevSibling;
  
  if (this.parent.lastChild == nextSibling) {
    this.parent.lastChild = this;
  }
  if (this.parent.firstChild == this) {
    this.parent.firstChild = nextSibling;
  }
  
  // 4 siblings are involved: prev, this, next, nextNext
  
  if (prevSibling != null) {
    prevSibling.nextSibling = nextSibling;
  }
  nextSibling.prevSibling = prevSibling;
  nextSibling.nextSibling = this;
  this.prevSibling = nextSibling;
  this.nextSibling = nextNextSibling;
  if (nextNextSibling != null) {
    nextNextSibling.prevSibling = this;
  }
  
};

/**
 * Returns a string representation of this node, in the format
 * this.value = <value>, this.prevSibling.value = <value>,
 * this.nextSibling.value = <value>, this.firstChild.value = <value>,
 * this.lastChild.value = <value>
 * @returns The node as a string
 * @type String
 */
JourneySat.Node.prototype.toString = function () {
  var s = 'this.value = "' + this.nodeValueString() + '"';
  if (this.parent != null) { // It could be the root
    s += ', this.parent.value = "' + this.parent.nodeValueString() + '"';
  } else {
    s += ', this.parent = "null"';
  }
  if (this.prevSibling != null) {
    s += ', this.prevSibling.value = "'
    + this.prevSibling.nodeValueString() + '"';
  } else {
    s += ', this.prevSibling = "null"';
  }
  if (this.nextSibling != null) {
    s += ', this.nextSibling.value = "'
    + this.nextSibling.nodeValueString() + '"';
  } else {
    s += ', this.nextSibling = "null"';
  }
  if (this.firstChild != null) {
    s += ', this.firstChild.value = "'
    + this.firstChild.nodeValueString() + '"';
  } else {
    s += ', this.firstChild = "null"';
  }
  if (this.lastChild != null) {
    s += ', this.lastChild.value = "'
    + this.lastChild.nodeValueString() + '"';
  } else {
    s += ', this.lastChild = "null"';
  }
  
  return s;
};

/**
 * Returns a string representation of the value of a node
 * @returns The value of the node as a string
 * @type String
 * @private
 */
JourneySat.Node.prototype.nodeValueString = function () {
  if (this.value == null) {
    return "null";
  } else {
    return this.value.toString();
  }
};



/* *****************************************
 *                                         *
 *         Class JourneySat.Day            *
 *                                         *
 *******************************************/

/**
 * @class
 * This class represents a day of the travel.
 * @constructor
 */
JourneySat.Day = function () {};


/**
 * The sequence number of the day in the travel
 * @type int
 * @private
 */
JourneySat.Day.prototype.sequence = null;

/**
 * Returns the sequence number of the day
 * @returns The sequence number
 * @type int
 */
JourneySat.Day.prototype.getSequence = function () {
  return this.sequence;
};

/**
 * Sets the sequence number of the day
 * @param {int} sequence The sequence number 
 */
JourneySat.Day.prototype.setSequence = function (sequence) {
  this.sequence = sequence;
};
  
/**
 * The date
 * @type JourneySat.Date
 * @private
 */
JourneySat.Day.prototype.date = {};

/**
 * Returns the date of the day
 * @returns The date
 * @type JourneySat.Date
 */
JourneySat.Day.prototype.getDate = function () {
  return this.date;
};

/**
 * Sets the date of the day
 * @param {JourneySat.Date} date the date
 */
JourneySat.Day.prototype.setDate = function (date) {
  this.date = date;
};

/**
 * Converts the day to a String
 * @returns A string representation of the day in the format
 *          <seq>,<date>. The date is formatted using the
 *          JourneySat.Date.toString method.
 * @type String
 */
JourneySat.Day.prototype.toString = function () {
  var dt = "";
  if (this.date != null) {
    dt = this.date.toString();
  }
  return this.sequence + "," + dt;
};

/**
 * Checks two days for equality
 * @param {JourneySat.Day} day The day to compare this one with
 * @returns True if the objects have the same values, false if not
 * @type boolean
 */
JourneySat.Day.prototype.equals = function (day) {
  if (day == null) { return false; }
  if (this.sequence != day.sequence) { return false; }
  if (this.date == null) {
    return (day.date == null);
  }
  if (!this.date.equals(day.date)) { return false; }
  return true;
}


/* *****************************************
 *                                         *
 *         Class JourneySat.Place          *
 *                                         *
 *******************************************/

/**
 * @class
 * This class represents a place visited in the travel.
 * @constructor
 */
JourneySat.Place = function () {};

/**
 * The sequence number of the day in the travel
 * @private
 */
JourneySat.Place.prototype.sequence = null;

/**
 * Returns the sequence number of the place
 * @returns The sequence number
 * @type int
 */
JourneySat.Place.prototype.getSequence = function () {
  return this.sequence;
};
  
/**
 * Sets the sequence number of the place
 * @param {int} sequence The sequence number 
 */
JourneySat.Place.prototype.setSequence = function (sequence) {
  this.sequence = sequence;
};

/**
 * The name of the place
 * @type String
 * @private
 */
JourneySat.Place.prototype.name = null;

/**
 * Returns the name of the place
 * @returns The name
 * @type String
 */
JourneySat.Place.prototype.getName = function () {
  return this.name;
};

/**
 * Sets the name of the place
 * @param {String} name The name 
 */
JourneySat.Place.prototype.setName = function (name) {
  this.name = name;
};
  
/**
 * The latitude of the place
 * @type double
 * @private
 */
JourneySat.Place.prototype.lat = null;

/**
 * Returns the latitude of the place
 * @returns The latitude
 * @type double
 */
JourneySat.Place.prototype.getLat = function () {
  return this.lat;
};

/**
 * Sets the latitude of the place
 * @param {double} latitude The latitude 
 */
JourneySat.Place.prototype.setLat = function (lat) {
  this.lat = lat;
};

  
/**
 * The longitude of the place
 * @type double
 * @private
 */
JourneySat.Place.prototype.lng = null;

/**
 * Returns the longitude of the place
 * @returns The longitude
 * @type double
 */
JourneySat.Place.prototype.getLng = function () {
  return this.lng;
};

/**
 * Sets the longitude of the place
 * @param {double} longitude The longitude 
 */
JourneySat.Place.prototype.setLng = function (lng) {
  this.lng = lng;
};
  
/**
 * The description of the place.
 * @type String
 * @private
 */
JourneySat.Place.prototype.description = null;

/**
 * Returns the description of the place.
 * @returns The description
 * @type String
 */
JourneySat.Place.prototype.getDescription = function () {
  return this.description;
};

/**
 * Sets the description of the place
 * @param {String} description The description
 */
JourneySat.Place.prototype.setDescription = function (description) {
  this.description = description;
};

/**
 * The zoomLevel of the place
 * @type int
 * @private
 */
JourneySat.Place.prototype.zoomLevel = null;

/**
 * Returns the zoomLevel of the place
 * @returns The zoomLevel
 * @type int
 */
JourneySat.Place.prototype.getZoomLevel = function () {
  return this.zoomLevel;
};

/**
 * Sets the zoomLevel of the place
 * @param {int} zoomLevel The zoomLevel
 */
JourneySat.Place.prototype.setZoomLevel = function (zoomLevel) {
  this.zoomLevel = zoomLevel;
};

/**
 * Sets all the attributes of the place
 * @param {int} sequence The sequence number
 * @param {String} name The name
 * @param {double} lat The latitude
 * @param {double} lng The longitude
 * @param {String} description The description
 * @param {int} zoom The zoom level
 */
JourneySat.Place.prototype.setAll =
  function (sequence, name, lat, lng, description, zoom) {
    this.sequence = sequence;
    this.name = name;
    this.lat = lat;
    this.lng = lng;
    this.description = description;
    this.zoomLevel = zoom;
  };

/**
 * Checks two places for equality. Contents belonging to the places
 * aren't considered in the comparison.
 * @param {JourneySat.Place} place The place to compare this one with
 * @returns True if the objects have the same values, false if not
 * @type boolean
 */
JourneySat.Place.prototype.equals = function (place) {
  if (place == null) { return false; }
  if (this.sequence != place.sequence) { return false; }
  if (this.name != place.name) { return false; }
  if (this.lat != place.lat) { return false; }
  if (this.lng != place.lng) { return false; }
  if (this.description != place.description) { return false; }
  if (this.zoomLevel != place.zoomLevel) { return false; }
  return true;
}

/**
 * Converts a place to a String
 * @returns A string representation of the place in the format
 *          <seq>,<name>,<lat>,<lng>,<description>,<zoomLevel>.
 * @type String
 */

JourneySat.Place.prototype.toString = function () {
  return (this.sequence + "," + this.name + "," + this.lat + "," +
	  this.lng + "," + this.description + "," + this.zoomLevel);
}

/* *****************************************
 *                                         *
 *         Class JourneySat.Content        *
 *                                         *
 *******************************************/

/**
 * @class
 * This class stores the information about a piece of multimedia content
 * (photo, movie, audio). The content will be shown as an icon
 * on the map at a given lat/lng coordinate which should be as close
 * as possible to the coordinates where the content refers to.
 * @constructor
 */
JourneySat.Content = function () {};

/**
 * The type of the content
 * @type String
 * @private
 */
JourneySat.Content.prototype.type = null;

/**
 * Returns the type of the content
 * @returns The type
 * @type String
 */
JourneySat.Content.prototype.getType = function () {
  return this.type;
};

/**
 * Sets the type of the content
 * @param {String} type The type
 */
JourneySat.Content.prototype.setType = function (type) {
    this.type = type;
};

/**
 * The latitude of the content
 * @type double
 * @private
 */
JourneySat.Content.prototype.lat = null;

/**
 * Returns the latitude of the content
 * @returns The latitude
 * @type double
 */
JourneySat.Content.prototype.getLat = function () {
  return this.lat;
};

/**
 * Sets the latitude of the content
 * @param {double} lat The latitude
 */
JourneySat.Content.prototype.setLat = function (lat) {
    this.lat = lat;
};

/**
 * The longitude of the content
 * @type double
 * @private
 */
JourneySat.Content.prototype.lng = null;

/**
 * Returns the longitude of the content
 * @returns The longitude
 * @type double
 */
JourneySat.Content.prototype.getLng = function () {
  return this.lng;
};

/**
 * Sets the longitude of the content
 * @param {double} lng The longitude
 */
JourneySat.Content.prototype.setLng = function (lng) {
  this.lng = lng;
};

/**
 * The URL of the content
 * @type String
 * @private
 */
JourneySat.Content.prototype.href = null;

/**
 * Returns the URL of the content
 * @returns The href
 * @type String
 */
JourneySat.Content.prototype.getHref = function () {
  return this.href;
};

/**
 * Sets the URL of the content
 * @param {String} href The URL
 */
JourneySat.Content.prototype.setHref = function (href) {
  this.href = href;
};

/**
 * The description of the content. DEPRECATED, use 
 * JourneySat.Content.prototype.cbody instead
 * @type String
 * @private
 */
JourneySat.Content.prototype.descr = null;
  
/**
 * Returns the description of the content. DEPRECATED, use getCbody instead.
 * @returns The description
 * @type String
 */
JourneySat.Content.prototype.getDescr = function () {
  return this.cbody;
};

/**
 * Sets the description of the content. DEPRECATED, use setCbody instead.
 * @param {String} descr The description
 */
JourneySat.Content.prototype.setDescr = function (descr) {
  this.cbody = descr;
};

/**
 * The cbody of the content.
 * @type String
 * @private
 */
JourneySat.Content.prototype.cbody = null;
  
/**
 * Returns the cbody of the content.
 * @returns The cbody
 * @type String
 */
JourneySat.Content.prototype.getCbody = function () {
  return this.cbody;
};

/**
 * Sets the cbody of the content.
 * @param {String} cbody The cbody
 */
JourneySat.Content.prototype.setCbody = function (cbody) {
  this.cbody = cbody;
};

/**
 * The zoom level to use when viewing the content
 * @type int
 * @private
 */
JourneySat.Content.prototype.zoom = null;
  
/**
 * Returns the zoom level for the content
 * @returns The zoom level
 * @type int
 */
JourneySat.Content.prototype.getZoomLevel = function () {
  return this.zoom;
};

/**
 * Sets the zoom level for the content
 * @param {int} zoom The zoom level
 */
JourneySat.Content.prototype.setZoomLevel = function (zoom) {
  this.zoom = zoom;
};

/**
 * Sets all the attributes of the content
 * @param {String} type The type
 * @param {double} lat The latitude
 * @param {double} lng The longitude
 * @param {String} href The URL
 * @param {String} cbody The cbody (the description in releases < 1.1.0)
 * @param {int} zoom The zoom level
 */
JourneySat.Content.prototype.setAll = function (type, lat, lng, href, cbody,
						zoom) {
  this.type = type;
  this.lat = lat;
  this.lng = lng;
  this.href = href;
  this.cbody = cbody;
  this.zoom = zoom;
};

/**
 * Checks two contents for equality
 * @param {JourneySat.Content} content The content to compare this one with
 * @returns True if the objects have the same values, false if not
 * @type boolean
 */
JourneySat.Content.prototype.equals = function (content) {
  if (content == null) { return false; }
  if (!(   (this.type == content.type)
	|| (this.type == null && content.type == "photo")
	|| (this.type == "photo" && content.type == null))) { return false; }


  if (this.lat != content.lat) { return false; }
  if (this.lng != content.lng) { return false; }
  if (this.href != content.href) { return false; }
  if (this.cbody != content.cbody) {
    return false;
  }
  if (this.zoom != content.zoom) { return false; }
  return true;
};


/**
 * Converts a content to a String
 * @returns A string representation of the content in the format
 *          <type>,<lat>,<lng>,<href>,<cbody>,<zoom>. Commas present in
 *          any of the attributes of the object are unquoted in the
 *          returned string.
 * @type String
 */

JourneySat.Content.prototype.toString = function () {
  return this.type + "," + this.lat + "," + this.lng + "," + this.href + ","
  + this.cbody + "," + this.zoom;
};


/* *****************************************
 *                                         *
 *         Class JourneySat.Loader         *
 *                                         *
 *******************************************/

/**
 * @class
 * The class Loader parses the XML that contains the information about
 * the travel and builds the data struction for the application.
 * @constructor
 */
JourneySat.Loader = function () {};

/**
 * The status of the loading process
 * @type int
 * @private
 */
JourneySat.Loader.prototype.loading = 0;
  
/**
 * Returns the status of the loading process
 * @returns 0 = nothing loaded, 1 = loading, 2 = loaded
 * @type int
 */
JourneySat.Loader.prototype.loadingStatus = function () {
  return this.loading;
};
  
/**
 * Processes the XML file and creates the data
 * structure for the application.
 * Usually triggered by the onreadystatechange event on the request
 * issued by Travel.load().
 * @param {JourneySat.Travel} travel The travel object
 * @type function
 */
JourneySat.Loader.prototype.parse = function (travel) {

  return function () {
    var request = travel.getRequest();
    if (travel.isAsynchronous()) {
      if (request.readyState != 4) {
	return;
      }
    }
    this.loading = 1;
    
    var xmlDoc = request.responseXML;
    var root = xmlDoc.documentElement;
    
    xmlParser = new JourneySat.XMLTravelParser();
    
    // Parses the HEAD part of the XML file
    
    var head = xmlParser.getNode("head", root);
    if (head == null) {
      travel.log("Missing HEAD tag in the XML file");
      this.loading = 0;
      return;
    }
    
    var name = xmlParser.getName(head);
    if (name == null) {
      travel.log("Missing NAME tag into HEAD in the XML file");
      this.loading = 0;
      return null;
    }
    travel.setTravelTitle(name);
    
    var descr = xmlParser.getDescription(head);
    if (descr == null) {
      travel.log("Missing DESCRIPTION tag into HEAD in the XML file");
      this.loading = 0;
      return null;
    }
    travel.setTravelDescription(descr);
    
    // Reads the date of the beginning and end of travel
    var startDate = xmlParser.getNode("start", head);
    if (startDate != null) {
      var date = xmlParser.getNode("date", startDate);
      if (date != null) {
	var startDate = new JourneySat.Date();
	startDate.setDate(date.attributes.getNamedItem("year").value,
			  date.attributes.getNamedItem("month").value,
			  date.attributes.getNamedItem("day").value);
	travel.setStartDate(startDate);
      }
    }
    var endDate = xmlParser.getNode("end", head);
    if (endDate != null) {
      var date = xmlParser.getNode("date", endDate);
      if (date != null) {
	var endDate = new JourneySat.Date();
	endDate.setDate(date.attributes.getNamedItem("year").value,
			date.attributes.getNamedItem("month").value,
			date.attributes.getNamedItem("day").value);
	travel.setEndDate(endDate);
      }
    }
    // No error if the dates are missing

    var zoom = xmlParser.getZoom(head);
      if (zoom != null) {
	travel.setZoomLevel(parseInt(zoom));
      }
    // No error if the zoom is missing
    
    
    // Parses the BODY part of the XML file
    // The BODY is a sequence of DAYs which in turn are
    // a sequence of PLACEs
    
    var body = xmlParser.getNode("body", root);
    if (body == undefined) {
      travel.log("Missing BODY tag in the XML file");
      this.loading = 0;
      return;
    }
    
    // Creates the tree that stores days, places and contents
    // declared in the XML file
    var root = new JourneySat.Node();
    root.value = "root"; // useful only for debugging
    travel.tree = new JourneySat.Tree();
    travel.tree.root = root;
    
    // Loops throught days and places and fills in the array
    for (i = 0; i < body.childNodes.length; i++) {
      var lev1Node = body.childNodes[i];
      
      // Ignores anything that's not a <day>
      if (lev1Node.nodeName == "day") {
	// Reads the day from the XML
	var daySeq = lev1Node.attributes.getNamedItem("seq").value;
	
	// Creates a tree node for this day
	var day = new JourneySat.Day();
	day.setSequence(daySeq);
	// The date of the day is at the second level of the XML structure
	// Nevertheless set an empty date because it is an optional element
	day.setDate(new JourneySat.Date());
	var dayNode = root.addChild(day);
	travel.numberOfDays++;

	// Processes the second level of the XML structure: places and date
	for (j = 0; j < lev1Node.childNodes.length; j++) {
	  var lev2Node = lev1Node.childNodes[j];

	  // Processes the <place> tag
	  if (lev2Node.nodeName == "place") {
	    // Reads the place attributes from the XML
	    var placeSeq = lev2Node.attributes.getNamedItem("seq").value;
	    var placeName = xmlParser.getName(lev2Node);
	    var placeDescription = xmlParser.getDescription(lev2Node);
	    var coords = xmlParser.getNode("coords", lev2Node);
	    var lat = coords.attributes.getNamedItem("lat").value;
	    var lng = coords.attributes.getNamedItem("long").value;

	    // Creates a tree node for this place
	    var place = new JourneySat.Place();
	    place.setSequence(placeSeq);
	    place.setName(placeName);
	    place.setDescription(placeDescription);
	    place.setLat(lat);
	    place.setLng(lng);
	    place.setZoomLevel(-1);
	    // Overwrites the zoom level if it is defined in the XML
	    // or leave it at -1, which means that it is undefined.
	    var zoom = xmlParser.getZoom(lev2Node);
	    if (zoom != null) {
	      place.setZoomLevel(parseInt(zoom));
	    }
	    
	    // Creates a tree node for this place
	    var placeNode = dayNode.addChild(place);
	    travel.numberOfPlaces++;

	    // Processes the third level of the XML structure: <content> tags
	    for (z = 0; z < lev2Node.childNodes.length; z++) {
	      var lev3Node = lev2Node.childNodes[z];
	      if (lev3Node.nodeName == "content") {
		// Reads a content from XML
		var type = xmlParser.getType(lev3Node);
		var coords = xmlParser.getNode("coords", lev3Node);
		var lat = null;
		var lng = null;
		if (coords != null) {
		  lat = coords.attributes.getNamedItem("lat").value;
		  lng = coords.attributes.getNamedItem("long").value;
		}
		var cBody = xmlParser.getCbody(lev3Node);
		if (cBody == null) {
		  var descr = xmlParser.getDescription(lev3Node); // DEPRECATED
		  cBody = descr;
		}
		var href = xmlParser.getHref(lev3Node);
		var zoom = xmlParser.getZoom(lev3Node);
		if (zoom == null) {
		  zoom == place.getZoomLevel();
		}
		if (zoom == -1) {
		  zoom == travel.getZoomLevel();
		}

		// Creates a tree node for this content
		var content = new JourneySat.Content();
		content.setAll(type, lat, lng, href, cBody, zoom);
		placeNode.addChild(content);
		travel.numberOfContents++;
	      }
	    }
	  }

	  // Processes the <date> tag
	  if (lev2Node.nodeName == "date") {
	    var date = new JourneySat.Date();
	    yyyy = lev2Node.attributes.getNamedItem("year").value;
	    mm = lev2Node.attributes.getNamedItem("month").value;
	    dd = lev2Node.attributes.getNamedItem("day").value;
	    date.setDate(yyyy, mm, dd);
	    // Sets the date into the day object already added to the tree
	    day.setDate(date);
	  }
	} // for lev1Node.childNodes
      } // if lev1Node == day
    } // for body.childNodes

    // Sets currentDay and currentPlace
    travel.resequenceDays(); // Ensures that the days sequence is consistent
    travel.resequencePlaces();
    travel.firstPlace();
    travel.currentDay = travel.tree.root.firstChild; // Overrides the day

    // Confirms the end of the XML processing
    this.loading = 2;
    travel.onload();
    
  }
};


/* *****************************************
 *                                         *
 *         Class JourneySat.Travel         *
 *                                         *
 *******************************************/

/**
 * @class
 * This class manages the information about a travel and the status
 * of the navigation in the journal.
 * A travel is sequence of days (JourneySat.Day);
 * each day contains a sequence of places (JourneySat.Place) and
 * each place holds a sequence of contents (JourneySat.Content).
 * The status of the travel is represented by a current day, a current
 * place and a current content. It can be accessed using the getCurrentDay,
 * getCurrentDayIndex, getCurrentPlace, getCurrentPlaceIndex, getCurrentContent
 * and getCurrentContentIndex methods.
 * The contents in a place can also be accessed with getContentArray.
 * 
 * @constructor
 */
JourneySat.Travel = function () {};

/**
 * The synchronization mode for the XMLHttpRequest object
 * @type boolean
 * @private
 */
JourneySat.Travel.prototype.asynchronous = true;

/**
 * Sets the synchronization mode for the XMLHttpRequest that loads the XML
 * from the remote server. The default mode is asynchronous communication.
 * @param {boolean} mode
 *             true for asynchronous communication, false for synchronous.
 */
JourneySat.Travel.prototype.asynchronousMode = function (mode) {
  this.asynchronous = mode;
};

/**
 * Returnes the synchronization mode 
 * @returns True for asynchronous communication, false for synchronous.
 * @type boolean
 */
JourneySat.Travel.prototype.isAsynchronous = function () {
  return this.asynchronous;
};

  
/**
 * The title of the travel
 * @type String
 * @private
 */
JourneySat.Travel.prototype.travelTitle = null;
	     
/**
 * Returns the travel title
 * @returns The travel title
 * @type String
 */
JourneySat.Travel.prototype.getTravelTitle = function () {
  return this.travelTitle;
};

/**
 * Sets the travel title
 * @param {String} title The travel title
 */
JourneySat.Travel.prototype.setTravelTitle = function (title) {
  this.travelTitle = title;
};

/**
 * The description of the travel
 * @type String
 * @private
 */
JourneySat.Travel.prototype.travelDescription = null;

/**
 * Returns the description of the travel
 * @returns The description of the travel
 * @type String
 */
JourneySat.Travel.prototype.getTravelDescription = function () {
  return this.travelDescription;
};

/**
 * Sets the description of the travel
 * @param {String} descr The description of the travel
 */
JourneySat.Travel.prototype.setTravelDescription = function (descr) {
  this.travelDescription = descr;
};
  
/**
 * The start date of the travel
 * @type JourneySat.Date
 * @private
 */
JourneySat.Travel.prototype.startDate = null;

/**
 * Returns the start date of the travel as a JourneySat.Date object
 * @returns The start date
 * @type JourneySat.Date
 */
JourneySat.Travel.prototype.getStartDate = function () {
  return this.startDate;
};

/**
 * Sets the start date of the travel
 * @param {JourneySat.Date} date The start date, of type JourneySat.Date
 */
JourneySat.Travel.prototype.setStartDate = function (date) {
  this.startDate = date;
};

/**
 * The end date of the travel
 * @type JourneySat.Date
 * @private
 */
JourneySat.Travel.prototype.endDate = null;

/**
 * Returns the end date of the travel as a JourneySat.Date object
 * @returns The end date
 * @type JourneySat.Date
 */
JourneySat.Travel.prototype.getEndDate = function () {
  return this.endDate;
};

/**
 * Sets the end date of the travel
 * @param {JourneySat.Date} date The end date, of type JourneySat.Date
 */
JourneySat.Travel.prototype.setEndDate = function (date) {
  this.endDate = date;
};

/**
 * The tree used to store the days, places and contents
 * @type JourneySat.Tree
 * @private
 */
JourneySat.Travel.prototype.tree = null;

/**
 * The number of days in the travel
 * @type int
 * @private
 */
JourneySat.Travel.prototype.numberOfDays = null;

/**
 * Returns the number of days in the travel
 * @returns The number of days
 * @type int
 */
JourneySat.Travel.prototype.getNumberOfDays = function () {
  return this.numberOfDays;
};

/**
 * Sets the number of days
 * @param {int} days The number of days
 */
JourneySat.Travel.prototype.setNumberOfDays = function (days) {
  this.days = days;
};

/**
 * The number of places in the travel
 * @type int
 * @private
 */
JourneySat.Travel.prototype.numberOfPlaces = null;

/**
 * Returns the number of places in the travel
 * @returns The number of places
 * @type int
 */
JourneySat.Travel.prototype.getNumberOfPlaces = function () {
  return this.numberOfPlaces;
};

/**
 * Sets the number of places
 * @param {int} places The number of places
 */
JourneySat.Travel.prototype.setNumberOfPlaces = function (places) {
  this.numberOfPlaces = places;
};

/**
 * The number of days in the travel
 * @type int
 * @private
 */
JourneySat.Travel.prototype.numberOfContents = null;

/**
 * Returns the number of contents in the travel
 * @returns The number of contents
 * @type int
 */
JourneySat.Travel.prototype.getNumberOfContents = function () {
  return this.numberOfContents;
};

/**
 * Sets the number of contents
 * @param {int} contents The number of contents
 */
JourneySat.Travel.prototype.setNumberOfContents = function (contents) {
  this.contents = contents;
};

/**
 * A reference to the node containing the current place.
 * The place is currentPlace.value
 * Note that getCurrentPlace returns a reference to the JourneySat.Place
 * object and not to the node.
 * @type JourneySat.Node
 * @private
 */
JourneySat.Travel.prototype.currentPlace = null;

/**
 * Returns the current place.
 * @returns The current place
 * @type JourneySat.Place
 */
JourneySat.Travel.prototype.getCurrentPlace = function () {
  if (this.currentPlace != null) {
    return this.currentPlace.value;
  } else {
    return null;
  }
};

/**
 * The position of the current place in the sequence of travel places.
 * @type int
 * @private
 */
JourneySat.Travel.prototype.currentPlaceIndex = -1;

/**
 * Returns the position of the current place in the sequence of travel days.
 * @returns The current place position, base 0
 * @type int
 */
JourneySat.Travel.prototype.getCurrentPlaceIndex = function () {
  return this.currentPlaceIndex;
};

/**
 * A reference to the node containing the current day.
 * The day is currentPlace.value
 * Note that getCurrentDay() returns a reference to the JourneySat.Day
 * object and not to the node.
 * @type JourneySat.Node
 * @private
 */
JourneySat.Travel.prototype.currentDay = null;

/**
 * Returns the current day.
 * @returns The current day
 * @type JourneySat.Day
 */
JourneySat.Travel.prototype.getCurrentDay = function () {
  if (this.currentDay != null) {
    return this.currentDay.value;
  } else {
    return null;
  }
};

/**
 * The position of the current day in the sequence of travel days.
 * @type int
 * @private
 */
JourneySat.Travel.prototype.currentDayIndex = -1;

/**
 * Returns the position of the current day in the sequence of travel days.
 * This is not
 * necessarily the day sequence number because there might be gaps in
 * those numbers.
 * @returns The current day position, base 0
 * @type int
 */
JourneySat.Travel.prototype.getCurrentDayIndex = function () {
  return this.currentDayIndex;
};

/**
 * The default zoom level for the travel.
 * This is not map.getZoomLevel which is the one
 * set by the user of the web application. zoomLevel is the default
 * level desired by the creator of the XML file
 * @type int
 * @private
 */
JourneySat.Travel.prototype.zoomLevel = 4;

/**
 * Returns the default zoom level for the travel.
 * @returns The zoom level
 * @type int
 */
JourneySat.Travel.prototype.getZoomLevel = function () {
  return this.zoomLevel;
};
  
/**
 * Sets the default zoom level for the travel.
 * @param {int} zoomLevel The zoom level 
 */
JourneySat.Travel.prototype.setZoomLevel = function (zoomLevel) {
  this.zoomLevel = zoomLevel;
};
  
/**
 * The request object used to get the XML from the server
 * @type XMLHttpRequest
 * @private
 */
JourneySat.Travel.prototype.request = null;
  
/**
 * Returns the request object used to retrieve the XML from the server
 * @param the request
 * @type XMLHttpRequest
 */
JourneySat.Travel.prototype.getRequest = function () {
  return this.request;
};
  
/*
 * The loader for the XML data file 
 * @type JourneySat.Loader
 * @private
 */
JourneySat.Travel.prototype.loader = null;

/**
 * The URL of the XML file containing the information about the travel
 * @type String
 * @private 
 */
JourneySat.Travel.prototype.dataFile = null;

/**
 * Specifies the URL of the XML data file. Browsers usually force it
 * the be a URL on the web server hosting the web page that contains
 * the map.
 * @param {String} dataFile the URL of the XML data file
 */
JourneySat.Travel.prototype.setDataFile = function (dataFile) {
  this.dataFile = dataFile;
};

/**
 * Loads the XML file and creates the internal data structure that
 * represents the travel.
 * The current day is set to the first day of the travel and
 * the current place is set to the first place of that day.
 */
JourneySat.Travel.prototype.load = function () {
    
  if (this.dataFile == null) {
    return;
  }
  
  // Prepares async request
  this.request = GXmlHttp.create();
  this.request.open("GET", this.dataFile, this.asynchronous);
  
  // Creates the parser for the XML file
  this.loader = new JourneySat.Loader();
    
  if (this.asynchronous) {
    // Sets the trigger
    this.request.onreadystatechange = this.loader.parse(this);
  }
  
  // Sends the request
  this.request.send(null);
  
  if (!this.asynchronous) {
    // Be careful: Loader.parse returns a function!
    var func = this.loader.parse(this);
    func();
  }
};
  
/**
 * Logs a message. The current implementation is an alert dialog.
 * Recommended only for debugging purposes.
 * @param {String} msg the message to log
 */
JourneySat.Travel.prototype.log = function (msg) {
  alert(msg);
};
  
/** 
 * Returns the GPoint object corresponding to the coordinates
 * of the current place
 * @returns The current point
 * @type GPoint
 */
JourneySat.Travel.prototype.getCurrentPoint = function () {
  var place = this.currentPlace.value;
  var lng = place.getLng();
  var lat = place.getLat();
  return new GPoint(lng, lat);
};

/**
 * Moves to the first day of the travel and updates. If the day has no places
 * the current place will be set to null and its index to -1, otherwise
 * they refer to the first place of the day.
 * The current content is set to the first content of the place, or to null
 * if place is null.
 */  
JourneySat.Travel.prototype.firstDay = function () {
  this.currentDay = this.tree.root.firstChild;
  if (this.currentDay != null) {
    this.currentDayIndex = 0;
  } else {
    this.currentDayIndex = -1;
  }
  this.currentPlace = this.currentDay.firstChild;
  this.currentPlaceIndex = this.getIndexOfPlace(this.currentPlace);
  if (this.currentPlace == null) {
    this.currentContent = null;
    this.currentContentIndex = -1;
  } else {
    this.currentContent = this.currentPlace.firstChild;
    this.currentContentIndex = this.getIndexOfContent(this.currentContent);
  }
};

/**
 * Moves to the previous day of the travel. If the day has no places
 * the current place will be set to null and its index to -1, otherwise
 * they refer to the last place of the day.
 * The current content is set to the first content of the place, or to null
 * if place is null.
 * If the day is the first day of the travel, nothing is done.
 */  
JourneySat.Travel.prototype.prevDay = function () {
  if (this.currentDay == null) {
    return;
  }

  var prevDay = this.currentDay.prevSibling;
  if (prevDay == null) {
    return;
  }

  this.currentDay = prevDay;
  this.currentDayIndex--;

  var formerPlace = this.currentPlace;
  this.currentPlace = this.currentDay.lastChild;
  this.currentPlaceIndex = this.getIndexOfPlace(this.currentPlace);
  if (this.currentPlace == null) {
    this.currentContent = null;
    this.currentContentIndex = -1;
  } else {
    this.currentContent = this.currentPlace.firstChild;
    this.currentContentIndex = this.getIndexOfContent(this.currentContent);
  }

};

/**
 * Moves to the next day of the travel. If the day has no places
 * the current place will be set to null and its index to -1, otherwise
 * they refer to the first place of the day.
 * The current content is set to the first content of the place, or to null
 * if place is null.
 * If the day is the last day of the travel, nothing is done.
 */
JourneySat.Travel.prototype.nextDay = function () {
  if (this.currentDay == null) {
    return;
  }

  var nextDay = this.currentDay.nextSibling;
  if (nextDay == null) {
    return;
  }

  this.currentDay = nextDay;
  this.currentDayIndex++;

  var formerPlace = this.currentPlace;
  this.currentPlace = this.currentDay.firstChild;
  this.currentPlaceIndex = this.getIndexOfPlace(this.currentPlace);
  if (this.currentPlace == null) {
    this.currentContent = null;
    this.currentContentIndex = -1;
  } else {
    this.currentContent = this.currentPlace.firstChild;
    this.currentContentIndex = this.getIndexOfContent(this.currentContent);
  }
};

/**
 * Moves to the last day of the travel. If the day has no places
 * the current place will be set to null and its index to -1, otherwise
 * they refer to the last place of the day.
 * The current content is set to the first content of the place, or to null
 * if place is null.
 */  
JourneySat.Travel.prototype.lastDay = function () {
  this.currentDay = this.tree.root.lastChild;
  if (this.currentDay != null) {
    this.currentDayIndex = this.numberOfDays - 1;
  } else {
    this.currentDayIndex = -1;
  }
  this.currentPlace = this.currentDay.lastChild;
  if (this.currentPlace != null) {
    this.currentPlaceIndex = this.numberOfPlaces - 1;
    this.currentContent = this.currentPlace.firstChild;
    this.currentContentIndex = this.getIndexOfContent(this.currentContent);
  } else {
    this.currentPlaceIndex = -1;
    this.currentContent = null;
    this.currentContentIndex = -1;
  }

};

/**
 * Moves to the n-th day in the travel. Does nothing if n is <= 0 or
 * greater than the number of days in the travel.
 * If the day has no places
 * the current place will be set to null and its index to -1, otherwise
 * they refer to the last place of the day.
 * The current content is set to the first content of the place, or to null
 * if place is null.
 * @param {int} n The number of the day, the first day of the travel
 * is numbered as 1.
 */
JourneySat.Travel.prototype.goToDay = function (n) {
  if (n <= 0 || n > this.numberOfDays) {
    return;
  }

  // Scans days
  n--; // to base 0
  var dayCount = 0;
  for (var day = this.tree.root.firstChild;
       day != null; day = day.nextSibling) {
    if (dayCount == n) {
      this.currentPlace = day.firstChild;
      this.currentPlaceIndex = this.getIndexOfPlace(this.currentPlace);
      this.currentDay = day;
      this.currentDayIndex = n;
      if (this.currentPlace == null) {
	this.currentContent = null;
	this.currentContentIndex = -1;
      } else {
	this.currentContent = this.currentPlace.firstChild;
	this.currentContentIndex = this.getIndexOfContent(this.currentContent);
      }
      return;
    }
    dayCount++;
  }

};

/**
 * Moves to the first place of the travel. If there are no places in this
 * travel the current place is set to null.
 * The current content is set to the first content of the place, or to null
 * if place is null.
 */
JourneySat.Travel.prototype.firstPlace = function () {

  var firstDay = this.tree.root.firstChild;
  if (firstDay == null) {
    this.currentDay = null;
    this.currentDayIndex = -1;
    this.currentPlace = null;
    this.currentPlaceIndex = -1;
    this.currentContent = null
    this.currentContentIndex = -1;
    return;
  }

  // Doesn't assume that every day has a place
  var idx = -1;
  for (var day = firstDay; day != null; day = day.nextSibling) {
    idx++;
    if (day.firstChild != null) {
      this.currentDay = day;
      this.currentDayIndex = idx;
      this.currentPlace = day.firstChild;
      this.currentPlaceIndex = 0;
      this.currentContent = this.currentPlace.firstChild;
      this.currentContentIndex = this.getIndexOfContent(this.currentContent);
      return;
    }
  }
};
  
/**
 * Moves the current place to the next place in the travel and
 * update the current day if needed
 */
JourneySat.Travel.prototype.nextPlace = function () {
  /*
   * There are 3 cases
   *
   * 1) currentPlace != null
   *    This is the normal one: goes to currentPlace.nextSibling or to
   *    day.fistChild where day is the last day with places inside
   *    after the current one
   *
   * 2) currentPlace == null && currentDay == null
   *    Empty travel, just return
   *
   * 3) currentPlace == null && currentDay != null
   *    Starts from currentDay and moves forward until it finds a place.
   *    If it finds no places, currentPlace keeps being null, otherwise
   *    counts how many places there are to the beginning of the travel
   *    to set currentPlaceIndex.
   * 
   */

  // Case 1
  if (this.currentPlace != null) {
    var nextPlace = this.currentPlace.nextSibling;
    if (nextPlace != null) {
      // This is a next place inside this same day
      this.currentPlace = nextPlace;
      this.currentPlaceIndex++;
      this.currentContent = this.currentPlace.firstChild;
      this.currentContentIndex = this.getIndexOfContent(this.currentContent);
    } else {
      // Move to the next days
      var dCount = this.currentDayIndex;
      for (var day = this.currentDay.nextSibling;
	   day != null; day = day.nextSibling) {
	nextPlace = day.firstChild;
	dCount++;
	if (nextPlace != null) {
	  this.currentPlace = nextPlace;
	  this.currentDay = nextPlace.parent;
	  this.currentDayIndex = dCount;
	  this.currentPlaceIndex++;
	  this.currentContent = this.currentPlace.firstChild;
	  this.currentContentIndex =
	    this.getIndexOfContent(this.currentContent);
	  break;
	}
      }
    }
    // Returns with no changes to the status if scanning days forward
    // doesn't find a place.
    return;
  }

  // Case 2
  if (this.currentPlace == null && this.currentDay == null) {
    return;
  }

  // Case 3
  if (this.currentDay.firstChild != null) {
    alert("Internal error. Please report to the maintainer of the JourneySat project.");
    alert("The error is: the day contains places but it shouldn't");
    alert("The day is: " + this.currentDay);
    return;
  }

  var dCount = this.currentDayIndex;
  var nextPlace;
  for (var day = this.currentDay.nextSibling;
       day != null; day = day.nextSibling) {
    nextPlace = day.firstChild;
    dCount++;
    if (nextPlace != null) {
      this.currentPlace = nextPlace;
      this.currentPlaceIndex = this.getIndexOfPlace(nextPlace);
      this.currentDay = day;
      this.currentDayIndex = dCount;
      this.currentContent = this.currentPlace.firstChild;
      this.currentContentIndex = this.getIndexOfContent(this.currentContent);
      return;
    }
  }

  // If the program gets here there is no next place.
  // Leaves everything unchanged.
};
  
/**
 * Moves the current place to the previous place in the travel and also
 * update the current day if needed.
 */
JourneySat.Travel.prototype.prevPlace = function () {

  /*
   * There are 3 cases
   *
   * 1) currentPlace != null
   *    This is the normal one: goes to currentPlace.prevSibling or to
   *    day.lastChild where day is the first day with places inside
   *    before the current one
   *
   * 2) currentPlace == null && currentDay == null
   *    Empty travel, just return
   *
   * 3) currentPlace == null && currentDay != null
   *    Starts from currentDay and moves backward until it finds a place.
   *    If it finds no places, currentPlace keeps being null, otherwise
   *    counts how many places there are to the beginning of the travel
   *    to set currentPlaceIndex.
   * 
   */

  // Case 1
  if (this.currentPlace != null) {
    var prevPlace = this.currentPlace.prevSibling;
    if (prevPlace != null) {
      // This is a prev place inside this same day
      this.currentPlace = prevPlace;
      this.currentPlaceIndex--;
      this.currentContent = this.currentPlace.firstChild;
      this.currentContentIndex = this.getIndexOfContent(this.currentContent);
    } else {
      // Move to previous days
      var dCount = this.currentDayIndex;
      for (var day = this.currentDay.prevSibling;
	   day != null; day = day.prevSibling) {
	prevPlace = day.lastChild;
	dCount--;
	if (prevPlace != null) {
	  this.currentPlace = prevPlace;
	  this.currentDay = prevPlace.parent;
	  this.currentDayIndex = dCount;
	  this.currentPlaceIndex--;
	  this.currentContent = this.currentPlace.firstChild;
	  this.currentContentIndex =
	    this.getIndexOfContent(this.currentContent);
	  break;
	}
      }
    }
    // Returns with no changes to the status if scanning days backward
    // doesn't find a place.
    return;
  }

  // Case 2
  if (this.currentPlace == null && this.currentDay == null) {
    return;
  }

  // Case 3
  if (this.currentDay.firstChild != null) {
    alert("Internal error. Please report to the maintainer of the JourneySat project.");
    alert("The error is: the day contains places but it shouldn't");
    alert("The day is: " + this.currentDay);
    return;
  }

  var dCount = this.currentDayIndex;
  var prevPlace;
  for (var day = this.currentDay.prevSibling;
       day != null; day = day.prevSibling) {
    prevPlace = day.lastChild;
    dCount--;
    if (prevPlace != null) {
      this.currentPlace = prevPlace;
      this.currentPlaceIndex = this.getIndexOfPlace(prevPlace);
      this.currentDay = day;
      this.currentDayIndex = dCount;
      this.currentContent = this.currentPlace.firstChild;
      this.currentContentIndex = this.getIndexOfContent(this.currentContent);
      return;
    }
  }

  // If the program gets here there is no prev place.
  // Leaves everything unchanged.
};
  
/**
 * Moves to the last place and to the last day of the travel.
 * The current content is set to the first content of the place.
 */
JourneySat.Travel.prototype.lastPlace = function () {

  var lastDay = this.tree.root.lastChild;
  if (lastDay == null) {
    this.currentDay = null;
    this.currentDayIndex = -1;
    this.currentPlace = null;
    this.currentPlaceIndex = -1;
    this.currentContent = null;
    this.currentContentIndex = -1;

    return;
  }

  // Doesn't assume that every day has a place
  var idx = this.numberOfDays;
  for (var day = lastDay; day != null; day = day.prevSibling) {
    idx--;
    if (day.lastChild != null) {
      this.currentDay = day;
      this.currentDayIndex = idx;
      this.currentPlace = day.lastChild;
      this.currentPlaceIndex = this.numberOfPlaces - 1;
      this.currentContent = this.currentPlace.firstChild;
      this.currentContentIndex = this.getIndexOfContent(this.currentContent);
      return;
    }
  }
};
  
/**
 * Moves to the n-th place in the travel. Does nothing if n is <= 0 or
 * greater than the number of places in the travel.
 * @param {int} n The number of the place, the first place of the travel
 * is numbered as 1.
 */
JourneySat.Travel.prototype.goToPlace = function (n) {
  if (n <= 0 || n > this.numberOfPlaces) {
    return;
  }

  n--; // to base 0
  var placeCount = 0;
  var dayCount = 0;
  // Scans days
  for (var day = this.tree.root.firstChild;
       day != null; day = day.nextSibling) {
    // Scans places inside days
    for (var place = day.firstChild;
	 place != null; place = place.nextSibling) {
      if (placeCount == n) {
	this.currentPlace = place;
	this.currentPlaceIndex = placeCount;
	this.currentDay = place.parent;
	this.currentDayIndex = dayCount;
	this.currentContent = this.currentPlace.firstChild;
	this.currentContentIndex = this.getIndexOfContent(this.currentContent);
	return;
      }
      placeCount++;
    }
    dayCount++;
  }
};

/**
 * Returns the base-0 index of a place.
 * @params {JourneySat.Node} place The tree node that contains the place
 * @returns The index of the place
 * @type int
 * @private
 */
JourneySat.Travel.prototype.getIndexOfPlace = function (place) {
  // Counts places backward to find the index of the 
  // current place
  var pCount = -1;
  var p = this.currentPlace;
  if (p == null) {
    return -1;
  }
  var d = p.parent;
  while (1) {
    for (; p != null; p = p.prevSibling) {
      pCount++;
    }
    d = d.prevSibling;
    if (d == null) {
      break;
    } else {
      p = d.lastChild;
    }
  }
  return pCount;
}


/**
 * Handler function, called when Loader completes loading
 * the XML file. Usually defined by the client of Travel.
 */
JourneySat.Travel.prototype.onload = function () {};

/**
 * Returns a XML representation of the places array.
 * @returns The XML string containing the travel data.
 * @type String
 */
JourneySat.Travel.prototype.toString = function () {

  // Users can save empty or incompletely defined travels
  // so all attributes need default values
  var startDate = this.getStartDate();
  var sdY = startDate.getYear();
  var sdM = startDate.getMonth();
  var sdD = startDate.getDay();

  var endDate = this.getEndDate();
  var edY = this.endDate.getYear();
  var edM = this.endDate.getMonth();
  var edD = this.endDate.getDay();

  var travelName = this.getTravelTitle();
  if (travelName == null) {
    travelName = "";
  }
  
  var travelDescr = this.getTravelDescription();
  if (travelDescr == null) {
    travelDescr = "";
  }
  var zoom = this.getZoomLevel();
  if (zoom == null) {
    zoom = "5";
  }
  
  var xml = '<?xml version="1.0" ?>\n';
  xml += '<!DOCTYPE travel SYSTEM "http://journeysat.sourceforge.net/travel.dtd">\r\n';
  xml += '<travel>\r\n';
  xml += '  <head>\r\n';
  
  xml += '    <name><![CDATA[' + travelName + ']]></name>\r\n';
  xml += '    <start>\r\n';
  xml += '      <date year="' + sdY + '" month="' + sdM
  + '" day="' + sdD + '"/>\r\n';
  xml += '    </start>\r\n';
  xml += '    <end>\r\n';
  xml += '      <date year="' + edY + '" month="' + edM
  + '" day="' + edD + '"/>\r\n';
  xml += '    </end>\r\n';
  xml += '    <description><![CDATA[' + travelDescr + ']]></description>\r\n';
  xml += '    <zoom>' + zoom + '</zoom>\r\n';
  xml += '  </head>\n\r\n';
  xml += '  <body>\r\n';
  
  // Scans days
  for (var dayNode = this.tree.root.firstChild;
       dayNode != null; dayNode = dayNode.nextSibling) {
    // Writes the markup for the day
    var day = dayNode.value;
    xml += '    <day seq="' + day.getSequence() + '">\r\n';
    var date = day.getDate();
    if (date != null) {
      if (date.isValid()) {
	xml += '      <date year="' + date.getYear() + '" month="'
	  + date.getMonth() + '" day="' + date.getDay() + '"/>\r\n';
      }
    }
    // Scans places inside days
    for (var placeNode = dayNode.firstChild;
	 placeNode != null; placeNode = placeNode.nextSibling) {
      var place = placeNode.value;
      var placeSeq = place.getSequence();
      var placeName = place.getName();
      if (placeName == null) { placeName = ""; }
      var placeDescr = place.getDescription();
      if (placeDescr == null) { placeDescr = ""; }
      var placeLat = place.getLat();
      if (placeLat == null) { placeLat = "0.0"; }
      var placeLng = place.getLng();
      if (placeLng == null) { placeLng = "0.0"; }
      var placeZoom = place.getZoomLevel();
      // Zoom -1 means that the user chose not to define it
      if (placeZoom == -1) {
	placeZoom = null;
      }
      
      xml += '      <place seq="' + placeSeq + '">\r\n';
      xml += '        <name><![CDATA[' + placeName + ']]></name>\r\n';
      xml += '        <coords long="' + placeLng + '" lat="' + placeLat
             + '"/>\r\n';
      xml += '        <description><![CDATA[' + placeDescr
	+ ']]></description>\r\n';
      if (placeZoom != null) {
	xml += '        <zoom>' + placeZoom + '</zoom>\r\n';
      }
      
      // Scans contents inside places
      for (var contentNode = placeNode.firstChild;
	   contentNode != null; contentNode = contentNode.nextSibling) {
	var content = contentNode.value;
	var cLat = content.getLat(); if (cLat == null) { cLat = "0.0"; }
	var cLng = content.getLng(); if (cLng == null) { cLng = "0.0"; }
	var cHref = content.getHref(); if (cHref == null) { cHref = ""; }
	var cBody = content.getCbody(); if (cBody == null) { cBody = ""; }
	var cType = content.getType();
	var cZoom = content.getZoomLevel(); if (cZoom == null) { cZoom = -1; }
	
	xml += '        <content>\n';
	if (cType != null) {
	  xml += '          <type>' + cType + '</type>\r\n';
	}
	xml += '          <cbody><![CDATA[' + cBody + ']]></cbody>\r\n';
	if (cLat != null && cLng != null) {
	  xml += '          <coords long="' + cLng+ '" lat="' + cLat + '"/>\r\n';
	}
	xml += '          <href>' + cHref + '</href>\r\n';
	if (cZoom != -1) {
	  xml += '          <zoom>' + cZoom + '</zoom>\r\n';
	}
	xml += '        </content>\r\n';
      }
      xml += '      </place>\r\n';
    }
    xml += '    </day>\r\n\r\n';
  }
  
  xml += '  </body>\r\n';
  xml += '</travel>\r\n';
  
  return xml;
};

/**
 * Updates the sequence number of the days
 * @private
 */
JourneySat.Travel.prototype.resequenceDays = function () {
  var dayCount = 0;
  for (var day = this.tree.root.firstChild;
       day != null; day = day.nextSibling) {
    day.value.setSequence(dayCount);
    dayCount++;
  };
};

/**
 * Updates the sequence number of the places
 * @private
 */
JourneySat.Travel.prototype.resequencePlaces = function () {
  for (var day = this.tree.root.firstChild;
       day != null; day = day.nextSibling) {
    var placeCount = 1; // The first place of a day has seq="1"
    for (var place = day.firstChild; place != null;
	 place = place.nextSibling) {
      place.value.setSequence(placeCount);
      placeCount++;
    }
  };
};

/**
 * Returns the contents of the current place as an array.
 * @returns The contents of the current place
 * @type JourneySat.Content[]
 * @see #getCurrentContent
 */
JourneySat.Travel.prototype.getContentArray = function () {
  var contents = [];
  var p = this.currentPlace;
  for (var c = p.firstChild; c != null; c = c.nextSibling) {
    contents[contents.length] = c.value;
  }
  return contents;
};

/**
 * Returns the date range of the travel as a string in the format
 * yyyy/mm/dd - yyyy/mm/dd
 * @returns The date range of the travel
 * @type String
 */
JourneySat.Travel.prototype.getTravelDates = function () {
  if (this.startDate == null) { return ""; }
  if (this.endDate == null) { return ""; }
  if (this.startDate.isValid() && this.endDate.isValid()) {
    return this.startDate.toString() + " - " + this.endDate.toString();
  } else {
    return "";
  }
};


/**
 * Adds a new day to the travel. If a day has a date it is inserted at
 * the appropriate place in the days list, to keep the days sorted by date.
 * If the day doesn't have a date it is inserted before the current day.
 * If the day has a date and there is already a day with the same date in
 * the travel, the day isn't added.
 * After the inserted current day is set to the new day, the days are
 * resequenced and the index to the current day is set.
 * The current place is set to null and the index of the current place
 * is set to -1.
 * The current content is set to null.
 * @param {JourneySat.Day} day The day
 * @returns True if the day has been added to the travel, false if not
 * @type boolean
 */
JourneySat.Travel.prototype.insertDay = function (newDay) {

  // Creates the node for the new day
  var newDayNode = new JourneySat.Node();
  newDayNode.value = newDay;

  // Case 1: there are no days in the travel
  if (this.currentDay == null) {
    this.tree.root.firstChild = newDayNode;
    this.tree.root.lastChild = newDayNode;
    newDayNode.parent = this.tree.root;
    this.numberOfDays = 1;
    this.currentDay = newDayNode;
    this.currentDayIndex = 0;
    this.currentPlace = null;
    this.currentPlaceIndex = -1;
    this.currentContent = null;
    this.currentContentIndex = -1;
    return true;
  }

  var newDate = newDay.getDate();

  if (!newDate.isValid()) {
    // Case 2: date is unvalid
    // Inserts the day before currentDay
    JourneySat.Tree.insertNode(newDayNode, this.currentDay);
    // Updates the travel status
    this.numberOfDays++;
    this.currentPlace = null;
    this.currentPlaceIndex = -1;
    this.currentDay = newDayNode;
    this.resequenceDays();
    this.currentDayIndex = parseInt(newDayNode.value.getSequence());
    this.currentContent = null;
    this.currentContentIndex = -1;
    return true;
  }

  // Case 3: scans days until there is a gap to fit this day in
  for (var dayNode = this.tree.root.firstChild; dayNode != null;
       dayNode = dayNode.nextSibling) {
    var day = dayNode.value;
    var date = day.getDate();
    if (newDate.equals(date)) {
      return false;
    }
    if (newDate.lessThan(date)) {
      JourneySat.Tree.insertNode(newDayNode, dayNode);
      // Updates the travel status
      this.numberOfDays++;
      this.currentPlace = null;
      this.currentPlaceIndex = -1;
      this.currentDay = newDayNode;
      this.resequenceDays();
      this.currentDayIndex = parseInt(newDayNode.value.getSequence());
      this.currentContent = null;
      this.currentContentIndex = -1;
      return true;
    }
  }

  // Case 4: the date is greater than any other date.
  // Appends the day at the end of the travel.
  var lastChild = this.tree.root.lastChild;
  newDayNode.prevSibling = lastChild;
  lastChild.nextSibling = newDayNode;
  newDayNode.parent = this.tree.root;
  this.tree.root.lastChild = newDayNode;
  this.currentDay = newDayNode;
  this.currentDayIndex = this.numberOfDays; // Not incremented yet
  this.currentPlace = null;
  this.currentPlaceIndex = -1;
  this.numberOfDays++;
  this.resequenceDays();
  this.currentContent = null;
  this.currentContentIndex = -1;
  return true;
};

/**
 * Changes the date of the current day.
 * If !newDate.isValid the day the sequence number doesn't change,
 * otherwise the day is moved to keep the day sequence sorted by date.
 * If there is already a day with a date equal to the day's new date no
 * changes are made.
 * @param {JourneySat.Node} dayNode A reference to the tree node
 *                                  that contains the day
 * @param {JourneySat.Date} newDate The new date for the day
 * @returns True for success, false if there is already a day with date
 *          equal to newDate or the travel doesn't contain any day
 * @type boolean
 */
JourneySat.Travel.prototype.changeDay = function (newDate) {
  
  var dayNode = this.currentDay;
  if (dayNode == null) {
    return false;
  }

  // Checks for null or unvalid date
  if (newDate == null) {
    dayNode.value.setDate(null);
    return true;
  }
  if (!newDate.isValid()) {
    dayNode.value.setDate(newDate);
    return true;
  }
  
  // Scans days until either it finds a day with the same date or
  // one with date greater than newDate
  for (var node = this.tree.root.firstChild; node != null;
       node = node.nextSibling) {
    var day = node.value;
    var date = day.getDate();
    if (newDate.equals(date)) {
      return false;
    }
    if (newDate.lessThan(date)) {
      // Sets the date
      dayNode.value.setDate(newDate);

      // Unlinks dayNode from its position
      JourneySat.Tree.unlinkNode(dayNode);

      // Inserts it before node
      JourneySat.Tree.insertNode(dayNode, node);
      this.resequenceDays();
      this.currentDayIndex = parseInt(dayNode.value.getSequence());
      return true;
    }
  }

  // The date is greater than any other date in the travel
  // Appends the day to the end of the travel
  JourneySat.Tree.unlinkNode(dayNode);
  var lastDay = this.tree.root.lastChild;

  if (lastDay == null) { // Happens if dayNode is the only day in the travel
    this.tree.root.firstChild = dayNode;
  }
  this.tree.root.lastChild = dayNode;

  dayNode.prevSibling = lastDay;
  if (lastDay != null) {
    lastDay.nextSibling = dayNode;
  }

  this.resequenceDays();
  this.currentDayIndex = parseInt(dayNode.value.getSequence());  
  return false;
};

/**
 * Removes the current day from the travel sequence.
 * The day after the removed one becomes the current day. If 
 * the day that was removed was the last in the travel, the day before it
 * becomes the current day.
 * The current place is the first place in the current day.
 * The current content is set to the first content of the current place
 * or to null is place is null.
 */
JourneySat.Travel.prototype.deleteDay = function () {

  var dayNode = this.currentDay;
  if (dayNode == null) {
    return;
  }

  // Finds the new current day
  var newCurrentDay = dayNode.nextSibling;
  if (newCurrentDay == null) {
    newCurrentDay = dayNode.prevSibling;
  }

  // Removes current day and keeps the tree correctly linked
  JourneySat.Tree.unlinkNode(dayNode);

  // Starts the recursive removal of places and contents
  // to prevent a memory leak and to set the number of places and of
  // contents
  var p = dayNode.firstChild;
  while (p != null) {
    this.currentPlace = p;
    p = p.nextSibling;
    this.deletePlace();
  }

  
  // Sets the status
  if (newCurrentDay == null) {
    // There are no days left in the travel
    this.currentDay = null;
    this.currentDayIndex = -1;
    this.currentPlace = null;
    this.currentPlaceIndex = -1;
    this.currentContent = null;
    this.currentContentIndex = -1;
  } else {
    // Sets status
    this.currentDay = newCurrentDay;
    this.resequenceDays();
    this.currentDayIndex = parseInt(this.currentDay.value.getSequence());
    this.currentPlace = newCurrentDay.firstChild;
    this.currentPlaceIndex = this.getIndexOfPlace(this.currentPlace);
    this.currentContent = this.currentPlace.firstChild;
    this.currentContentIndex = this.getIndexOfContent(this.currentContent);

  }

  // Clears the references originating from the deleted node
  dayNode.parent = null;
  dayNode.nextSibling = null;
  dayNode.prevSibling = null;
  dayNode.firstChild = null;
  dayNode.lastChild = null;
  
  this.numberOfDays--;
};

/**
 * Swaps the current day with the one that precedes it in the travel sequence.
 * Dates must keep an ascending order so at least one of the two days must
 * have an empty date.
 * Applying this method to the first day of the travel doesn't change
 * the travel sequence.
 * The current day and place don't change. The index of the current day is
 * decreased by one, the index of the current place changes as necessary
 * @return true if the swap succeeded, false if not. It returns true also when
 *         the current day is the first day of the travel or the travel
 *         doesn't contain any day.
 * @type boolean
 */
JourneySat.Travel.prototype.moveUpDay = function () {
  var thisDay = this.currentDay;
  if (thisDay == null) {
    return true; // No days in the travel
  }
  var prevDay = thisDay.prevSibling;
  if (prevDay == null) {
    return true; // First day in the travel
  }
  
  var thisDate = thisDay.value.getDate();
  var prevDate = prevDay.value.getDate();
  // The are four cases
  //             prevDate
  // thisDate  null   y/m/d
  // null      OK     OK
  // y/m/d     OK     NO    prevDate < thisDate because of the sorting order
  if (thisDate.isValid() && prevDate.isValid()) {
    return false;
  }
  
  // Checks passed, swap days
  JourneySat.Tree.swapNodes(prevDay, thisDay);
  var tmp = thisDay.value.getSequence();
  thisDay.value.setSequence(prevDay.value.getSequence());
  prevDay.value.setSequence(tmp);
  this.currentDayIndex--;
  this.currentPlaceIndex = this.getIndexOfPlace(this.currentPlace);

  return true;
};

/**
 * Swaps the current day with the one that follows it in the travel sequence.
 * Dates must keep an ascending order so at least one of the two days must
 * have an empty date.
 * Applying this method to the last day of the travel doesn't change
 * the travel sequence.
 * The Current day and place don't change. The index of the current day is
 * increased by one, the index of the current place changes as necessary
 * @return true if the swap succeeded, false if not
 * @type boolean
 */
JourneySat.Travel.prototype.moveDownDay = function () {
  var thisDay = this.currentDay;
  if (thisDay == null) {
    return true; // No days in the travel
  }
  var nextDay = thisDay.nextSibling;
  if (nextDay == null) {
    return true; // Last day in the travel
  }
  
  var thisDate = thisDay.value.getDate();
  var nextDate = nextDay.value.getDate();
  // The are four cases
  //             nextDate
  // thisDate  null   y/m/d
  // null      OK     OK
  // y/m/d     OK     NO  thisDate < nextDate because of the sorting order
  if (thisDate.isValid() && nextDate.isValid()) {
    return false;
  }
  
  // Checks passed, swap days
  JourneySat.Tree.swapNodes(thisDay, nextDay);
  var tmp = thisDay.value.getSequence();
  thisDay.value.setSequence(nextDay.value.getSequence());
  nextDay.value.setSequence(tmp);
  this.currentDayIndex++;
  this.currentPlaceIndex = this.getIndexOfPlace(this.currentPlace);

  return true;
};

/**
 * Adds a new place to the travel. The place belongs to the current day and
 * is inserted after the current place. The new place becomes the current
 * one.If there are no days in the travel it returns without doing anything.
 * @returns True if the place has been added to the travel, false if not.
 * @type boolean
 */

JourneySat.Travel.prototype.insertPlace = function () {

  var dayNode = this.currentDay;
  if (dayNode == null) {
    return false;
  }

  // Creates the place and its node in the tree
  var place = new JourneySat.Place();
  var node = new JourneySat.Node();
  node.value = place;


  // First place in the day
  if (this.currentPlace == null) {
    dayNode.firstChild = node;
    dayNode.lastChild = node;
    node.parent = dayNode;
  } else {
    JourneySat.Tree.appendNode(node, this.currentPlace);

  }

  this.currentPlace = node;
  this.resequencePlaces();
  this.currentPlaceIndex = this.getIndexOfPlace(node);
  this.numberOfPlaces++;
  this.currentContent = null;
  this.currentContentIndex = -1;

  return true;

};

// Probably changePlace is useless because the Place methods can
// be used to alter the current place without any side effect.
// This is different from altering the date of a day because that
// operation changes the day sequence.

//JourneySat.Travel.prototype.changePlace = function () {};

/**
 * Removes the current place from the travel.
 * The place after the removed one becomes the current place. If 
 * the place that was removed was the last one in the day, the place before it
 * becomes the current place.
 */
JourneySat.Travel.prototype.deletePlace = function () {
  
  var placeNode = this.currentPlace;
  if (placeNode == null) {
    return;
  }

  // Finds the new current place
  var newCurrentPlace = placeNode.nextSibling;
  if (newCurrentPlace == null) {
    newCurrentPlace = placeNode.prevSibling;
  }

  // Removes current place and keeps the tree correctly linked
  JourneySat.Tree.unlinkNode(placeNode);
  // Starts the recursive removal of contents to prevent a memory leak
  var c = placeNode.firstChild;
  while (c != null) {
    this.currentContent = c;
    c = c.nextSibling;
    this.deleteContent();
  }

  // Sets the status
  this.currentPlace = newCurrentPlace;
  if (newCurrentPlace == null) {
    // There are no days left in the travel
    this.currentPlaceIndex = -1;
    this.currentContent = null;
    this.currentContentIndex = -1;
  } else {
    // Sets status
    this.resequencePlaces();
    this.currentPlaceIndex = this.getIndexOfPlace(this.currentPlace);
    this.currentContent = this.currentPlace.firstChild;
    this.currentContentIndex = this.getIndexOfContent(this.currentContent);
  }

  // Clears the references originating from the deleted node
  placeNode.parent = null;
  placeNode.nextSibling = null;
  placeNode.prevSibling = null;
  placeNode.firstChild = null;
  placeNode.lastChild = null;
  
  this.numberOfPlaces--;

};

/**
 * Swaps the current place with the one that precedes it in a day.
 * The index of the current place is decreased by one.
 * Applying this method to the first place in a day doesn't do anything.
 */
JourneySat.Travel.prototype.moveUpPlace = function () {

  var placeNode = this.currentPlace;
  if (placeNode == null) {
    return;
  }

  var prevNode = placeNode.prevSibling;
  if (prevNode == null) {
    return;
  }

  JourneySat.Tree.swapNodes(prevNode, placeNode);

  // Swaps sequence numbers
  var tmp = prevNode.value.getSequence();
  prevNode.value.setSequence(placeNode.value.getSequence());
  placeNode.value.setSequence(tmp);

  this.currentPlaceIndex--;

};

/**
 * Swaps the current place with the one that follows it in a day.
 * The index of the current place is increased by one.
 * Applying this method to the last place in a day doesn't do anything.
 */
JourneySat.Travel.prototype.moveDownPlace = function () {

  var placeNode = this.currentPlace;
  if (placeNode == null) {
    return;
  }

  var nextNode = placeNode.nextSibling;
  if (nextNode == null) {
    return;
  }

  JourneySat.Tree.swapNodes(placeNode, nextNode);

  // Swaps sequence numbers
  var tmp = nextNode.value.getSequence();
  nextNode.value.setSequence(placeNode.value.getSequence());
  placeNode.value.setSequence(tmp);

  this.currentPlaceIndex++;

};


/**
 * A reference to the node containing the current content.
 * The content is currentContent.value
 * Note that getCurrentContent returns a reference to the JourneySat.Content
 * object and not to the node.
 * @type JourneySat.Node
 * @private
 */
JourneySat.Travel.prototype.currentContent = null;

/**
 * Returns the current content.
 * @returns The current content
 * @type JourneySat.Content
 */
JourneySat.Travel.prototype.getCurrentContent = function () {
  var c = this.currentContent;
  if (c == null) {
    return null;
  } else {
    return c.value;
  }
};


/**
 * The position of the current content in the sequence of the contents
 * inside the current place.
 * @type int
 * @private
 */
JourneySat.Travel.prototype.currentContentIndex = -1;

/**
 * Returns the position of the current content in the sequence of the
 * contents inside the current place.
 * @see #getContentArray
 * @returns The current content position, base 0
 * @type int
 */
JourneySat.Travel.prototype.getCurrentContentIndex = function () {
  return this.currentContentIndex;
};

/**
 * Returns the base-0 index of a content inside the sequence of 
 * contents of the current place.
 * @params {JourneySat.Node} content The tree node that holds the content
 * @returns The index of the content or -1 if the content or the current
 *          place are null.
 * @type int
 * @private
 */
JourneySat.Travel.prototype.getIndexOfContent = function (node) {

  if (this.currentPlace == null) {
    return -1;
  }

  // Counts how many contents there are from node
  // to the first content in the place
  var idx = -1;
  for (var n = node; n != null; n = n.prevSibling) {
    idx++;
  }

  return idx;
}

/*
 * Adds a new content to the travel. The content belongs to the
 * current place and is
 * is inserted after the current content. The new content becomes the current
 * one. If the current place is null it returns without doing anything.
 * @returns True if the content has been added to the travel, false if not.
 * @type boolean
 */
JourneySat.Travel.prototype.insertContent = function () {

  var place = this.currentPlace;
  if (place == null) {
    return false;
  }

  var node = new JourneySat.Node();
  var content = new JourneySat.Content();
  node.value = content;
  node.parent = place;

  if (this.currentContent != null) {
    var current = this.currentContent;
    var nextNode = current.nextSibling;
    current.nextSibling = node;
    node.prevSibling = current;
    if (nextNode != null) {
      nextNode.prevSibling = node;
    }
    node.nextSibling = nextNode;
    if (place.lastChild == current) {
      place.lastChild = node;
    }
  } else {
    place.firstChild = node;
    place.lastChild = node;
  }
  
  this.currentContent = node;
  this.currentContentIndex = this.getIndexOfContent(node);
  this.numberOfContents++;
    
  return true;
};

/**
 * Removes the current content from the travel.
 * The content after the removed one becomes the current one. If 
 * the place that was removed was the last one in the day, the place before it
 * becomes the current place.
 */
JourneySat.Travel.prototype.deleteContent = function () {

  var contentNode = this.currentContent;
  if (contentNode == null) {
    return;
  }

  var newCurrentContent = contentNode.nextSibling;
  if (newCurrentContent == null) {
    newCurrentContent = contentNode.prevSibling;
  }

  JourneySat.Tree.unlinkNode(contentNode);

  this.currentContent = newCurrentContent;
  this.currentContentIndex = this.getIndexOfContent(this.currentContent);
  this.numberOfContents--;
  
};

// Useless
//JourneySat.Travel.prototype.changeContent = function () {};

/**
 * Moves the current content to the one that precedes it in the same place.
 */
JourneySat.Travel.prototype.prevContent = function () {

  var content = this.currentContent;
  if (content != null) {
    var prevContent = content.prevSibling;
    if (prevContent != null) {
      this.currentContent = prevContent;
      this.currentContentIndex--;
    }
  }

};

/**
 * Moves the current content to the one that follows it in the same place.
 */
JourneySat.Travel.prototype.nextContent = function () {

  var content = this.currentContent;
  if (content != null) {
    var nextContent = content.nextSibling;
    if (nextContent != null) {
      this.currentContent = nextContent;
      this.currentContentIndex++;
    }
  }

};

/**
 * Swaps the current content with the one that precedes it in a place.
 * The index of the current content is decreased by one.
 * Applying this method to the first content in a place doesn't do anything.
 */
JourneySat.Travel.prototype.moveUpContent = function () {

  var contentNode = this.currentContent;
  if (contentNode == null) {
    return;
  }

  var prevNode = contentNode.prevSibling;
  if (prevNode == null) {
    return;
  }

  JourneySat.Tree.swapNodes(prevNode, contentNode);
  this.currentContentIndex--;

};

/**
 * Swaps the current content with the one that follows it in a place.
 * The index of the current content is increased by one.
 * Applying this method to the last content in a place doesn't do anything.
 */
JourneySat.Travel.prototype.moveDownContent = function () {

  var contentNode = this.currentContent;
  if (contentNode == null) {
    return;
  }

  var nextNode = contentNode.nextSibling;
  if (nextNode == null) {
    return;
  }

  JourneySat.Tree.swapNodes(contentNode, nextNode);
  this.currentContentIndex++;

};
  
/**
 * Prepares a formatted representation of a date in the format
 * yyyy<separator>mm<separator>dd.
 * The values are left-padded with 0 characters and
 * the separation character can be any string (even empty or longer
 * than one character)
 * If year and month and day are null or empty strings an empty string
 * is returned regardless of the value of the separator.
 * @param {int} sY the year
 * @param {int} sM the month
 * @param {int} sD the day
 * @param {String} sep the separator
 * @returns A date in the format yyyy<separator>mm<separator>dd.
 * @type String
 */
JourneySat.Travel.getFormattedDate = function (sY, sM, sD, sep) {

  if (sY == null) { sY = ""; }
  if (sM == null) { sM = ""; }
  if (sD == null) { sD = ""; }
  if (String(sY) == "" && String(sM) == "" && String(sD) == "") {
    return "";
  }


  // Converting to String and checking the length
  // takes care of any problem deriving from JS's loose handling of types
  // i.e. sM is the string "09", which is < 10 and yelds "009"
  // which is not OK
  sYLen = String(sY).length;
  if (sY < 100 && sYLen < 3) { sY = "0" + sY; }   // this must be the 1st test 
  if (sY < 1000 && sYLen < 4) { sY = "0" + sY; }
  if (sM < 10 && String(sM).length < 2) { sM = "0" + sM; }
  if (sD < 10 && String(sD).length < 2) { sD = "0" + sD; }
  return String(sY) + sep + String(sM) + sep + String(sD);
}


/* *****************************************
 *                                         *
 *         Class JourneySat.Date           *
 *                                         *
 *******************************************/

/**
 * @class
 * This class represents dates
 * @constructor
 */

JourneySat.Date = function () {};


/*
 * The year
 * @type String
 * @private
 */
JourneySat.Date.prototype.yyyy = null;

/*
 * The month
 * @type String
 * @private
 */
JourneySat.Date.prototype.mm = null;

/*
 * The day
 * @type String
 * @private
 */
JourneySat.Date.prototype.dd = null;
  
/**
 * Converts a string into a Date object.
 * The only valid format for the date is yyyy/mm/dd with 1 <= mm <= 12
 * and 1 <= dd <= 31. No checks are made on the consistency of 
 * month and day.
 * Invalid strings result into invalid dates (i.e. date.isValid() == false)
 * @param {String} dateStr The date
 */
JourneySat.Date.prototype.parseDate = function (dateStr) {
  if (dateStr == null)         { this.setDate("", "", ""); return; }
  
  var dt = this.regexp.exec(dateStr);
  
  if (dt == null)              { this.setDate("", "", ""); return; }
  
  if (dt.length != 4)          { this.setDate("", "", ""); return; }
  
  this.yyyy = dt[1];
  if (dt[2] < 1 || dt[2] > 12) { this.setDate("", "", ""); return; }
  this.mm = dt[2];
  if (dt[3] < 1 || dt[3] > 31) { this.setDate("", "", ""); return; }
  this.dd = dt[3];
};
  
/**
 * The regular expression used to parse the date.
 * It's an attribute because it is compiled only once at program start up.
 * @type String
 * @private
 */
JourneySat.Date.prototype.regexp = /([0-9]{1,4})\/([0-9]{1,2})\/([0-9]{1,2})/;
  
/**
 * Sets a date. No checks are made on the arguments
 * @param {int} yyyy the year
 * @param {int} mm the month
 * @param {int} dd the day
 */
JourneySat.Date.prototype.setDate = function (yyyy, mm, dd) {
  this.yyyy = yyyy;
  this.mm = mm;
  this.dd = dd;
};

/**
 * Checks if the date is valid. A date is unvalid if any of its components
 * is null or the empty string. Any other values for the year, month and day
 * will pass the check.
 * @returns True if valid, false if not
 * @type boolean
 */
JourneySat.Date.prototype.isValid = function () {
  return !(this.yyyy == null || this.yyyy == ""
	   || this.mm == null || this.mm == ""
	   || this.dd == null || this.dd == "");
};

/**
 * Checks if the date is initialized (no private attribute == null)
 * @returns True if null, false if not
 * @type boolean
 */
JourneySat.Date.prototype.isNull = function () {
  return (this.yyyy == null || this.mm == null || this.dd == null);
};

/**
 * Returns the year of the date
 * @returns The year 
 * @type int
 */
JourneySat.Date.prototype.getYear = function () {
  return this.yyyy;
};

/**
 * Returns the month of the date
 * @returns The month 
 * @type int
 */
JourneySat.Date.prototype.getMonth = function () {
  return this.mm;
};

/**
 * Returns the day of the date
 * @returns The day
 * @type int
 */
JourneySat.Date.prototype.getDay = function () {
  return this.dd;
};

/**
 * Returns a string representation of the date. The format is yyyy/mm/dd
 * @returns The date as a string
 * @type String
 */
JourneySat.Date.prototype.toString = function () {
  return this.format("/");
};

/**
 * Returns a string representation of the date.
 * The format is yyyy<sep>mm<sep>dd
 * @param {String} sep The separator
 * @returns The date as a string
 * @type String
 */
JourneySat.Date.prototype.format = function (sep) {
  if (this.yyyy == null || this.yyyy == ""
      || this.mm == null || this.mm == ""
      || this.dd == null || this.dd == "") {
    return "";
  }
  
  // Converting to String and checking the length
  // takes care of any problem deriving from JS's loose handling of types
  // i.e. sM is the string "09", which is < 10 and yelds "009"
  // which is not OK
  var y = this.yyyy;
  var m = this.mm;
  var d = this.dd;
  
  yLen = String(y).length;
  
  if (y < 100 && yLen < 3) { y = "0" + y; }     // This must be the 1st test
  if (y < 1000 && yLen < 4) { y = "0" + y; }
  if (m < 10 && String(m).length < 2) { m = "0" + m; }
  if (d < 10 && String(d).length < 2) { d = "0" + d; }
  
  return String(y) + sep + String(m) + sep + String(d);
};   

/**
 * Compares two dates and return true if they are the same
 * @param {JourneySat.Date} date The other date
 * @returns True if the are the same, false if not
 * @type boolean
 */
JourneySat.Date.prototype.equals = function (date) {
  if (date == null) { return false; }
  return (this.yyyy == date.yyyy
	  && this.mm == date.mm
	  && this.dd == date.dd);
};

/**
 * Compares to a date and return true if it is less than that one.
 * A null or empty date is always less than any other non-null and non-empty
 * date.
 * @param {JourneySat.Date} date The other date
 * @returns True if this date is less than the other date, false if not
 * @type boolean
 */
JourneySat.Date.prototype.lessThan = function (date) {
  if (date == null) { return false; }
  if (this.yyyy < date.yyyy) { return true; }
  if (this.mm < date.mm) { return true; }
  if (this.dd < date.dd) { return true; }
  return false;
};

/**
 * Compares to a date and return true if it is greater than that one
 * A null or empty date is always less than any other non-null and non-empty
 * date.
 * @param {JourneySat.Date} date The other date
 * @returns True if this date is greater than the other date, false if not
 * @type boolean
 */
JourneySat.Date.prototype.greaterThan = function (date) {
  if (date == null) { return true; }
  if (this.yyyy > date.yyyy) return true;
  if (this.mm > date.mm) return true;
  if (this.dd > date.dd) return true;
  return false;
};
