// ====================================================================
// Copyright (c) 2005 and onwards, Josh Glover <jmglov@jmglov.net>
//
// LICENCE:
//
//   This file is distributed under the terms of the BSD-2 License.
//   See the COPYING file, which should have been distributed
//   with this file, for details. If you did not receive the
//   COPYING file, see:
//
//   http://www.jmglov.net/opensource/licenses/bsd.txt
//
// MarkerCatalogue.js
//
// DESCRIPTION:
//
//   A Gaijin's Guide to Central Yokohama, MarkerCatalogue class
//
// MODIFICATIONS:
//
//   Josh Glover <jmglov@jmglov.net> (2005/11/01): Initial revision
// ====================================================================


// Object: MarkerCatalogue
//
// Stores markers in categories.


// Constructor: MarkerCatalogue()
//
// Parameters:
//
//   gmap - [optional] GMap object associated with this catalogue

function MarkerCatalogue( gmap ) {

  this.categories = new Array();
  this.info       = null;
  this.gmap       = gmap;
  this.size       = 0;

  this.addCategory       = addCategory;
  this.addNewCategory    = addNewCategory;
  this.addMarker         = addMarker;
  this.addNewMarker      = addNewMarker;
  this.delCategory       = delCategory;
  this.delMarker         = delMarker;
  this.getCategories     = getCategories;
  this.getCategory       = getCategory;
  this.getCategoryHTML   = getCategoryHTML;
  this.getGMap           = getGMap;
  this.getMarker         = getMarker;
  this.getMarkerAtIndex  = getMarkerAtIndex;
  this.getMarkers        = getMarkers;
  this.getNumCategories  = getNumCategories;
  this.getNumMarkers     = getNumMarkers;
  this.getOpenInfoWindow = getOpenInfoWindow;
  this.getVisibility     = getVisibility;
  this.populateFromXML   = populateFromXML;
  this.setGMap           = setGMap;
  this.setOpenInfoWindow = setOpenInfoWindow;
  this.setVisibility     = setVisibility;
  this.renameCategory    = renameCategory;
  this.updateCategory    = updateCategory;
  this.updateMarker      = updateMarker;

} // MarkerCatalogue()


// Method: addCategory()
//
// Adds a category to the catalogue. <addCategory()> *will not* overwrite an
// existing category; if this is what you want, use <updateCategory()> instead.
//
// Parameters:
//
//   cat  - Category object to add
//   name - [default: name of cat param] name of the category
//
// Returns:
//
//   The newly added MarkerCategory object on success, null on failure

function addCategory( cat, name ) {

  if (cat == null) { return null }

  if (name == null) { name = cat.getName() }
  
  // No overwriting existing categories!
  if (this.categories[name] != null) { return null }

  // Set the marker's category and add it
  cat.setName( name );
  this.categories[name] = cat;
  this.size++;

  return cat;

} // addCategory()


// Method: addNewCategory()
//
// Creates a new MarkerCategory object and adds it to this category. Like
// <addCategory()>, <addNewCategory()> *will not* overwrite an existing
// category.
// 
// Parameters:
//
//   Same as for <MarkerCategory::MarkerCategory()>
//
// Returns:
//
//   Newly added MarkerCategory object on success; null on failure

function addNewCategory( name, vis ) {

  var cat = new MarkerCategory( this, name, vis, this.getGMap() );

  return this.addCategory( cat );

} // addNewCategory()


// Method: addMarker()
//
// Adds a new marker to this catalogue. <addMarker()> will add new categories
// if they do not exist unless the auto_cat parameter is set to false.
// 
// Parameters:
//
//   marker   - Marker object
//   cat      - [optional; default: category of marker param] name of the category
//   name     - [optional; default: name of marker param] name of the marker
//   auto_cat - [optional; default: true] automatically create and add categories
//              that do not exist in the catalogue?
//   vis      - [optional; default: see <MarkerCategory::MarkerCategory()] if this
//              category needs to be created, should it be visible?
//
// Returns:
//
//   Newly added Marker object on success; null on failure

function addMarker( marker, cat, name, auto_cat, vis ) {

  if (marker == null) { return null }

  if (cat      == null) { cat      = marker.getCategory() }
  if (name     == null) { name     = marker.getName()     }
  if (auto_cat == null) { auto_cat = true                 }

  // Try to add the new category if auto_cat is enabled (this does no harm to
  // existing categories)
  var c;
  if (auto_cat == true) { c = this.addNewCategory( cat, vis ) }

  // Unless we just added the category (and thus already have a valid
  // MarkerCategory object), we will need to get it so that we can add the marker
  if (c == null) { c = this.getCategory( cat ) }

  // If we still do not have a category, return failure
  if (c == null) { return null }

  // If control reaches here, we have a MarkerCategory object, so add the marker
  // to it
  return c.addMarker( marker, name );
  
} // addMarker()


// Method: addNewMarker()
//
// Creates a new Marker object, then adds it to this catalogue. <addNewMarker()>
// will add new categories if they do not exist unless the auto_cat parameter is
// set to false.
// 
// Parameters:
//
//   cat      - name of the category
//   name     - name of the marker
//   title    - see <Marker::Marker()>
//   vis    -   [optional; default: see <MarkerCategory::MarkerCategory()] if this
//              category needs to be created, should it be visible?
//   gmarker  - see <Marker::Marker()>
//   auto_cat - [optional; default: true] automatically create and add categories
//              that do not exist in the catalogue?
//   info     - see <Marker::Marker()>
//   date     - see <Marker::Marker()>
//
// Returns:
//
//   Newly added Marker object on success; null on failure

function addNewMarker( cat, name, title, vis, gmarker, auto_cat, info, date ) {

  var marker = new Marker( this, cat, name, title, (vis == null ? true : vis),
                           gmarker, this.getGMap(), info, date );

  return this.addMarker( marker, cat, name, auto_cat, vis );

} // addNewMarker()


// Method: delCategory()
//
// Deletes a category from this catalogue.
//
// Parameters:
//
//   name - name of the category
//
// Returns:
//
//   MarkerCategory object that was just deleted from this catalogue on success;
//   null on failure

function delCategory( name ) {

  var cat = this.getCategory( name );
  if (cat == null) { return null }

  this.categories[name] = null;
  this.size--;

  return cat;

} // delCategory()


// Method: delMarker()
//
// Deletes a marker from this catalogue.
//
// Parameters:
//
//   cat  - category of the marker
//   name - name of the marker
//
// Returns:
//
//   Marker object that was just deleted from this catalogue on success;
//   null on failure

function delMarker( cat, name ) {

  var cat = this.getCategory( cat );
  if (cat == null) { return null }
  
  return cat.delMarker( name );

} // delMarker()


// Method: getCategories()
//
// Gets all categories.
//
// Returns:
//
//   Associative array of MarkerCategory objects on success; null on failure

function getCategories() {

  return this.categories;

} // getCategories()


// Method: getCategory()
//
// Retrieves a category by name.
//
// Parameters:
//
//   name - name of the category to fetch
//
// Returns:
//
//   A MarkerCategory object on success, null on failure

function getCategory( name ) {

  // Validate params
  if (name == null) { return null }

  // Fetch the MarkerCategory object if we have one
  if (this.categories != null) { return this.categories[name] }

  // If control reaches here, we have failed
  return null;

} // getCategory()


// Method: getCategoryHTML()
//
// Retrieves the HTML representation of category names.
//
// Parameters:
//
//   columns - number of columns to use
//
// Returns:
//
//   String of HTML on success, null on failure

function getCategoryHTML( columns ) {

  // Validate params
  if (columns == null) { return null }

  var cat_html = '<table>';
  var cats     = this.getCategories();
  var i        = 0;
  for (c in cats) {

    var name    = cats[c].getName();
    var pname   = '';
    var pname_a = name.split( '-' );
    for (var j = 0; j < pname_a.length; j++) {

      if (j > 0) { pname += ' ' }
      pname += pname_a[j].charAt( 0 ).toUpperCase() + pname_a[j].substring( 1 );
          
    } // for (fixing up name for printing)
        
    var colour  = cats[c].getColour();
    if (i % columns == 0) { cat_html += "<tr>" }
    cat_html += "<td align=\"center\">" +
                "<a href=\"JavaScript:toggleCat( '" + name + "' );\" " +
                "style=\"color: " + colour + "\">" +
                "<img src=\"icons/m_" + name + ".png\" /><br />" +
                pname + "</a></td>";
    if (i % columns == columns - 1) { cat_html += "</tr>" }

    i++;

  } // for (traversing categories)

  cat_html += "</table>";

  return cat_html;

} // getCategoryHTML()


// Method: getGMap()
//
// Gets the GMap object associated with this catalogue.
//
// Returns:
//
//   GMap object on success, null on failure

function getGMap() {

  return this.gmap;

} // getGMap()


// Method: getMarker()
//
// Gets a marker from this catalogue.
// 
// Parameters:
//
//   cat  - category
//   name - marker name
//
// Returns:
//
//   Marker object on success; null on failure

function getMarker( cat, name ) {

  c = this.getCategory( cat );
  if (c == null) { return null }
  
  // If control reaches here, we have a MarkerCategory object, so get the marker
  // from it
  return c.getMarker( name );

} // getMarker()


// Method: getMarkerAtIndex()
//
// Gets a marker from this catalogue at the specified index in the specified
// category.
// 
// Parameters:
//
//   cat   - category
//   index - index
//
// Returns:
//
//   Marker object on success; null on failure

function getMarkerAtIndex( cat, index ) {

  c = this.getCategory( cat );
  if (c == null) { return null }
  
  // If control reaches here, we have a MarkerCategory object, so get the marker
  // from it
  return c.getMarkerAtIndex( index );

} // getMarkerAtIndex()


// Method: getMarkers()
//
// Gets all markers in this category.
//
// Parameters:
//
//   cat - category name
// 
// Returns:
//
//   Associative array of Marker objects on success; null on failure

function getMarkers( cat ) {

  c = this.getCategory( cat );
  if (c == null) { return null }
  
  // If control reaches here, we have a MarkerCategory object, so get the markers
  // from it
  return c.getMarkers();

} // getMarkers()


// Method: getNumCategories()
//
// Returns the number of categories being stored in the catalogue.
//
// Returns:
//
//   Number of categories on success, 0 on failure

function getNumCategories() {

  return this.size;

} // getNumCategories()


// Method: getNumMarkers()
//
// Returns the number of markers being stored in the catalogue.
//
// Returns:
//
//   Number of markers on success, 0 on failure

function getNumMarkers() {

  if (this.categories == null) { return 0 }

  var num = 0;

  for (c in this.categories) { num += this.categories[c].getNumMarkers() }

  return num;

} // getNumCategories()


// Method: getOpenInfoWindow()
//
// Returns the name of the category that the info window belongs to.
//
// Returns:
//
//   Name of category on success, null on failure

function getOpenInfoWindow() {

  return this.info;

} // getOpenInfoWindow()


// Method: getVisibility()
//
// Gets the visibility of this category.
//
// Parameters:
//
//   cat - category name
// 
// Returns:
//
//   The visibility of this category on success, null on failure

function getVisibility( cat ) {

  var c = this.getCategory( cat );
  if (c == null) { return null }

  return c.getVisibility();

} // getVisibility()


// Method: populateFromXML()
//
// Populates a catalogue from an XML request's responseXML property (e.g. one
// obtained by using a GXmlHttp object).
//
// Parameters:
//
//   xml - XML request's responseXML property
//
// Returns:
//
//   The catalogue that was populated on success, null on failure

function populateFromXML( xml ) {

  if (xml == null) { return null }

  var xml_catalogue =
    xml.getElementsByTagName( "catalogue" );
  if (xml_catalogue == null || xml_catalogue.length != 1) { return null }
  
  var xml_categories =
    xml_catalogue[0].getElementsByTagName( "categories" );
  if (xml_categories == null || xml_categories.length != 1) { return null }

  var xml_category =
    xml_categories[0].getElementsByTagName( "category" );

  // Having no categories is strange, but not an error; if this is the case, just
  // return this, as our work is done
  if (xml_category == null) { return this }

  for (var i = 0; i < xml_category.length; i++) {

    // We have to set the name of the new category to the empty string since the
    // constructor requires a name parameter. This hack will go away when I figure
    // out static class methods.
    var cat = new MarkerCategory( this, '', null, this.getGMap() );
    if (cat.populateFromXML( xml_category[i] ) == null) { return null }

    if (this.addCategory( cat ) == null) { return null }

  } // for (populating categories)

  return this;

} // populateFromXML()


// Method: renameCategory()
//
// Renames a category that is already stored in this catalogue.
//
// Paramters:
//
//   cur_name - current name of the category
//   new_name - new name of the category
//
// Returns:
//
//   The new MarkerCategory object on success; null on failure

function renameCategory( cur_name, new_name ) {

  if (cur_name == null || new_name == null ||
      cur_name == '' || new_name == '') { return null }

  var c = this.getCategory( cur_name );
  if (c == null) { return null }

  if (c.setName( new_name ) == null) { return null }
  this.categories[cur_name] = null;
  this.categories[new_name] = category;

  return c;

} // renameCategory()


// Method: setGMap()
//
// Sets the GMap object associated with this catalogue.
//
// Parameters:
//
//   gmap - GMap object
//
// Returns:
//
//   New GMap object on success, null on failure

function setGMap( gmap ) {

  this.gmap = gmap;
  return this.gmap;

} // setGMap()


// Method: setOpenInfoWindow()
//
// Sets the name of the category that the info window belongs to.
//
// Parameters:
//
//   info - category to which open info window belongs
//
// Returns:
//
//   Name of category on success, null on failure

function setOpenInfoWindow( info ) {

  var c = this.getCategory( info );
  if (c == null) { return null }

  this.info = info;

  return this.info;

} // setOpenInfoWindow()


// Method: setVisibility()
//
// Sets the visibility of this category.
//
// Parameters:
//
//   cat - category name
//   vis - visibility
// 
// Returns:
//
//   The new visibility of this category on success, null on failure

function setVisibility( cat, vis ) {

  var c = this.getCategory( cat );
  if (c == null) { return null }

  return c.setVisibility( vis );

} // setVisibility()


// Method: updateCategory()
//
// Updates a category that is already stored in this catalogue. <updateCategory()>
// *will not* create a new category if it does not already exist; use
// <addCategory()> instead.
//
// Paramters:
//
//   name    - name of the category
//   cat     - new MarkerCategory object to store in the catalogue
//
// Returns:
//
//   The new MarkerCategory object on success; null on failure

function updateCategory( name, cat ) {

  var c = this.getCategory( name );
  if (c == null) { return null }

  cat.setName( name );
  this.categories[name] = cat;
  
  return cat;

} // updateCategory()


// Method: updateMarker()
//
// Updates a marker that is already stored in this catalogue. <updateMarker()>
// *will not* create a new marker if it does not already exist; use <addMarker()>
// instead.
//
// Paramters:
//
//   cat     - category of the marker
//   name    - name of the marker
//   title   - [optional] title
//   vis     - [optional] visibility
//   gmarker - [optional] GMarker object
//   gmap    - [optional] GMap object
//   info    - [optional] info
//   date    - [optional] date
//
// Returns:
//
//   The new Marker object on success; null on failure

function updateMarker( cat, name, title, vis, gmarker, gmap, info, date ) {

  var c = this.getCategory( cat );
  if (c == null) { return null }

  return c.updateMarker( name, title, vis, gmarker, gmap, info, date );

} // updateMarker()
