// $Id: transit.js 909 2007-03-05 19:48:59Z das-svn $

var transNS = "http://www.gophernet.org/schemata/transit.xsd";
var addrNS = "http://www.gophernet.org/schemata/address.xsd";

var googleKmlPrefix = "http://maps.google.com/mapsdt";
var kml = "http://www.gophernet.org/maps/data/marta.kml";

var map;

// this needs to be global for showStation, called through the form in HTML
var stationarr;

// IE workarounds, since IE doesn't have the node object
var elementType;
var textType;

if (typeof(Node) != "undefined") {
   elementType = Node.ELEMENT_NODE;
   textType = Node.TEXT_NODE;
} else {
   elementType = 1;
   textType = 3;
}

// IE doesn't understand getElementsByTagNameNS
function getElements(element, namespace, tagname)
{
   // I had planned to use selectNodes in IE, but the xpath implementation is
   // super-retarded, lacking local-name and namespace-uri functions.  Going
   // with slow and correct.
   function walk(element)
   {
      for (var i=0; i<element.childNodes.length; i++) {
         var child = element.childNodes.item(i);
         // MSXML is also missing localName, has baseName instead
         var localName;
         if (child.localName) {
            localName = child.localName;
         } else {
            localName = child.baseName;
         }

         if ((localName == tagname) &&
               (child.namespaceURI == namespace)) {
            list.push(child);
         }
      }
   }

   var list = new Array();
   list.item = function(i) { return this[i]; };
   walk(element);
   return list;
}

// returns the value of the text children of a node
function getText(node)
{
   var text = "";

   for (var i=0; i<node.childNodes.length; i++) {
      var child = node.childNodes[i];
      if (child.nodeType == textType) {
         text += child.nodeValue;
      }
   }

   return text;
}

function Point(lat, lng)
{
   this.lat = lat;
   this.lng = lng;

   return this;
}

// Creates a station object and adds it to the stations array
function Station(stationelm)
{
   if (!stationelm) return null;

   var stationAddr = getElements(stationelm, addrNS, "address")[0];
   var stationPoint = getElements(stationelm, transNS, "point")[0];
   
   this.id = stationelm.getAttribute("id");
   this.point = new Point(stationPoint.getAttribute("lat"),
                           stationPoint.getAttribute("lng"));
   this.name = getText(getElements(stationelm, transNS, "name")[0]);
   this.county = getText(getElements(stationelm, transNS, "county")[0]);
   this.type = getText(getElements(stationelm, transNS, "type")[0]);

   var link = getElements(stationelm, transNS, "link");
   if (link.length) this.link = getText(link[0]);

   var addr1 = getElements(stationAddr, addrNS, "address1");
   var addr2 = getElements(stationAddr, addrNS, "address2");
   var city = getElements(stationAddr, addrNS, "city");
   var state = getElements(stationAddr, addrNS, "state");
   var zip = getElements(stationAddr, addrNS, "postalCode");

   if (addr1.length) this.addr1 = getText(addr1[0]);
   if (addr2.length) this.addr2 = getText(addr2[0]);
   if (city.length) this.city = getText(city[0]);
   if (state.length) this.state = getText(state[0]);
   if (zip.length) this.zip = getText(zip[0]);

   return this;
}

function ParkAndRide(parkelm)
{
   if (!parkelm) return null;

   var parkPoint = getElements(parkelm, transNS, "point")[0];

   this.id = parkelm.getAttribute("id");
   this.point = new Point(parkPoint.getAttribute("lat"),
                  parkPoint.getAttribute("lng"));
   this.name = getText(getElements(parkelm, transNS, "name")[0]);
   this.location = getText(getElements(parkelm, transNS, "location")[0]);
   this.county = getText(getElements(parkelm, transNS, "county")[0]);

   var link = getElements(parkelm, transNS, "link");
   if (link.length) this.link = getText(link[0]);

   return this;
}

function BusStop(stopelm)
{
   if (!stopelm) return null;

   var stopPoint = getElements(stopelm, transNS, "point")[0];

   this.id = stopelm.getAttribute("id");
   this.point = new Point(stopPoint.getAttribute("lat"),
                  stopPoint.getAttribute("lng"));
   this.name = getText(getElements(stopelm, transNS, "name")[0]);

   // for display routes served by the stop
   this.routes = new Array();

   return this;
}

function RailLine(lineelm)
{
   if (!lineelm) return null;

   this.id = lineelm.getAttribute("id");
   this.name = lineelm.getAttribute("name");
   this.link = lineelm.getAttribute("link");

   return this;
}

function BusRoute(routeelm)
{
   if (!routeelm) return null;

   this.id = routeelm.getAttribute("id");
   this.name = routeelm.getAttribute("name");
   this.link = routeelm.getAttribute("link");

   return this;
}

function initMARTAIcons()
{
   var icons = new Array();
   
   var normal = new GIcon();
   var shortterm = new GIcon();
   var longterm = new GIcon();
   var parkride = new GIcon();
   var busstop = new GIcon();

   normal.image = "images/blank.png";
   normal.iconSize = new GSize(11, 11);
   normal.iconAnchor = new GPoint(5, 5);
   normal.infoWindowAnchor = new GPoint(7, 3);

   shortterm.image = "images/24hr.png";
   shortterm.iconSize = new GSize(16, 16);
   shortterm.iconAnchor = new GPoint(7, 7);
   shortterm.infoWindowAnchor = new GPoint(11, 4);

   longterm.image = "images/longterm.png";
   longterm.iconSize = new GSize(17, 17);
   longterm.iconAnchor = new GPoint(7, 7);
   longterm.infoWindowAnchor = new GPoint(11, 4);

   parkride.image = "images/parkride.png";
   parkride.iconSize = new GSize(24, 20);
   parkride.iconAnchor = new GPoint(12, 10);
   parkride.infoWindowAnchor = new GPoint(18, 7);

   busstop.image = "images/smalldot.png";
   busstop.iconSize = new GSize(6, 6);
   busstop.iconAnchor = new GPoint(3, 3);
   busstop.infoWindowAnchor = new GPoint(4, 2);

   icons["normal"] = normal;
   icons["shortterm"] = shortterm;
   icons["longterm"] = longterm;
   icons["parkride"] = parkride;
   icons["busstop"] = busstop;
   return icons;
}

function initCCTIcons()
{
   var icons = initMARTAIcons();

   // override the park and ride icon
   var parkride = new GIcon();
   parkride.image = "images/cct_parkride.png";
   parkride.iconSize = new GSize(24, 24);
   parkride.iconAnchor = new GPoint(12, 12);
   parkride.infoWindowAnchor = new GPoint(18, 4);

   icons["parkride"] = parkride;
   return icons;
}

function initGRTAIcons()
{
   var icons = initMARTAIcons();

   // override the park and ride icon
   var parkride = new GIcon();
   parkride.image = "images/generic_parkride.png";
   parkride.iconSize = new GSize(24, 24);
   parkride.iconAnchor = new GPoint(12, 12);
   parkride.infoWindowAnchor = new GPoint(18, 4);

   icons["parkride"] = parkride;
   return icons;
}


function addr2Html(addr1, addr2, city, state, zip)
{
   var output = "";

   if (addr1) output += addr1 + '<br />';
   if (addr2) output += addr2 + '<br />';

   // if city and state exist, display <city>, <state> [<zip>]
   // if only one is available, display whichever is available and zip on
   // next line.
   if (city && state) {
      output += city + ", " + state;
      if (zip) output += " " + zip;
      output += '<br />';
   } else {
      if (city) output += city + '<br />';
      if (state) output += state + '<br />';
      if (zip) output += zip + '<br />';
   }

   return output;
}

function loadPoints(points, map, stationarr, parkarr, stoparr, icons)
{
   var stationGroup = getElements(points, transNS, "stations");
   var parkGroup = getElements(points, transNS, "parkandridelots");
   var stopGroup = getElements(points, transNS, "busstops");

   if (stationGroup.length) {
      var stations = getElements(stationGroup[0], transNS, "station");
      for (var i=0; i<stations.length; i++) {
         var station = new Station(stations[i]);
         stationarr[station.id] = station;
         displayStation(station, map, icons);
      }
   }

   if (parkGroup.length) {
      var parkrides = getElements(parkGroup[0], transNS, "parkandride");
      for (var i=0; i<parkrides.length; i++) {
         var parkride = new ParkAndRide(parkrides[i]);
         parkarr[parkride.id] = parkride;
         displayParkRide(parkride, map, icons["parkride"]);
      }
   }

   if (stopGroup.length) {
      var stops = getElements(stopGroup[0], transNS, "stop");
      for (var i=0; i<stops.length; i++) {
         var stop = new BusStop(stops[i]);
         stoparr[stop.id] = stop;
         displayBusStop(stop, map, icons["busstop"]);
      }
   }
}

function displayStation(station, map, icons)
{
   var stationDisplay = "";
   var stationName = station.id + " - " + station.name;
   var stationIcon;

   if (station.link) {
      stationDisplay += '<a href="' + station.link + '">' + stationName + '</a>';
   } else {
      stationDisplay += stationName;
   }

   stationDisplay += '<br />';
   stationDisplay += addr2Html(station.addr1, station.addr2, station.city,
                        station.state, station.zip);
   
   stationDisplay += station.county + " County";

   // pick the icon
   switch (station.type) {
      case "shortterm":
         stationIcon = icons["shortterm"];
         break;
      case "longterm":
         stationIcon = icons["longterm"];
         break;
      default:
         stationIcon = icons["normal"];
   }

   station.marker = addMarker(map, new GLatLng(station.point.lat, station.point.lng),
         stationIcon, stationDisplay);
   station.info = stationDisplay;
}

function displayParkRide(park, map, icon)
{
   var parkDisplay = "";

   if (park.link) {
      parkDisplay += '<a href="' + park.link + '">' + park.name + '</a>';
   } else {
      parkDisplay += park.name;
   }

   parkDisplay += '<br />';
   parkDisplay += park.location + '<br />';
   parkDisplay += park.county + ' County';

   park.marker = addMarker(map, new GLatLng(park.point.lat, park.point.lng),
      icon, parkDisplay);
   park.info = parkDisplay;
}

function displayBusStop(stop, map, icon)
{
   var stopDisplay = stop.name + '<br />'
   var routes = stop.routes;

   // Don't display a bus stop if no routes intersect it to prevent the map
   // looking like it has a case of measles
   if (routes.length == 0) return;

   stopDisplay += "Routes served: ";
   for (var i in stop.routes) {
      var route = stop.routes[i];
      if (route.link) {
         stopDisplay += '<a href="' + route.link + '">' + route.name + '</a>';
      } else {
         stopDisplay += route.name;
      }
      stopDisplay += '<br />';
   }

   stop.marker = addMarker(map, new GLatLng(stop.point.lat, stop.point.lng),
         icon, stopDisplay);
   stop.info = stopDisplay;
}

// returns a function that calls openInfoWindowHtml for the marker
function openMarker(marker, info)
{
   return function() { marker.openInfoWindowHtml(info); }
}

function addMarker(map, point, icon, info)
{
   var marker = new GMarker(point, icon);
   GEvent.addListener(marker, "click", openMarker(marker, info));
   map.addOverlay(marker);
   return marker;
}

// The paths themselves are handled in KML.  Rail routes can be ignored,
// and bus routes only need to be parsed to determine which routes are
// serviced by a particular stop
function loadRoutes(routes, map, stationarr, parkarr, stoparr, icons)
{
   var railGroup = getElements(routes, transNS, "raillines");
   var busGroup = getElements(routes, transNS, "busroutes");

   var railPaths = new Array();
   var busPaths = new Array();

   // this array is indexed by route id.  the content is a BusRoute object
   // containing data for the route
   var routeGroups = new Array();

   if (railGroup.length) {
      var form = document.getElementById("stationForm");
      for (var i=0; i<railGroup[0].childNodes.length; i++) {
         var lineelm = railGroup[0].childNodes[i];
         var line;
         if (lineelm.nodeType != elementType) continue;
         line = new RailLine(lineelm);
         if (lineelm.tagName == "path") {
            railPaths[line.id] = lineelm;
         } else if (lineelm.tagName == "line") {
            var linesel = getForm(line, form);
            makeLine(lineelm, railPaths, stationarr, null, null,
                  function (station) {
                     var option = new Option(station.name, station.id);
                     linesel.options[linesel.options.length] = option;
                  }, null, null);
         }
      }
   }

   if (busGroup.length) {
      var paths = getElements(busGroup[0], transNS, "path");
      var groups = getElements(busGroup[0], transNS, "routegroup");
      var routeelems = getElements(busGroup[0], transNS, "route");

      for (var i=0; i<paths.length; i++) {
         var path = paths[i];
         busPaths[path.getAttribute("id")] = path;
      }

      for (var i=0; i<groups.length; i++) {
         var group = groups[i];
         var grouproutes = getElements(group, transNS, "route");
         var groupobj = new BusRoute(group);

         for (var j=0; j<grouproutes.length; j++) {
            var grouproute = grouproutes[j];
            routeGroups[grouproute.getAttribute("routeid")] = groupobj;
         }
      }

      for (var i=0; i<routeelems.length; i++) {
         var route = routeelems[i];
         var routeid = route.getAttribute("id");
         var routeobj;

         if (routeGroups[routeid]) {
            routeobj = routeGroups[routeid];
         } else {
            routeobj = new BusRoute(route);
         }

         var routepath = makeLine(route, busPaths, stationarr, parkarr,
               stoparr, null, null,
               function (stop) {
                  // the length property is not updated when
                  // adding the routes through assignment instead of
                  // calls to push().
                  stop.routes.length++;
                  stop.routes[routeobj.id] = routeobj;
                  map.removeOverlay(stop.marker);
                  displayBusStop(stop, map, icons["busstop"]);
               });
      }
   }
}

function getForm(line, form)
{
   var lineName;
   switch (line.id) {
      case "Nline":
         lineName = "northStations";
         break;
      case "NEline":
         lineName = "northeastStations";
         break;
      case "Sline":
         lineName = "southStations";
         break;
      case "Eline":
         lineName = "eastStations";
         break;
      case "Wline":
         lineName = "westStations";
         break;
      case "PCline":
         lineName = "proctorcreekStations";
         break;
   }

   return form[lineName];
}

// This function no longer needs to build a path, so all it really does
// now is process things through the callbacks
function makeLine(line, paths, stationarr, parkarr, stoparr, stationcall,
      parkcall, stopcall)
{
   for (var i=0; i<line.childNodes.length; i++) {
      var pointelm = line.childNodes[i];
      var point;
      if (pointelm.nodeType != elementType) continue;

      switch (pointelm.tagName) {
         case "point":
            break;
         case "stationpoint":
            if (stationcall) {
               stationcall(stationarr[pointelm.getAttribute("stationid")]);
            }
            break;
         case "parkandridepoint":
            if (parkcall) {
               parkcall(parkarr[pointelm.getAttribute("parkandrideid")]);
            }
            break;
         case "stoppoint":
            if (stopcall) {
               stopcall(stoparr[pointelm.getAttribute("stopid")]);
            }
            break;
         case "pathref":
            var path = paths[pointelm.getAttribute("pathid")];
            makeLine(path, paths, stationarr, parkarr, stoparr, stationcall,
               parkcall, stopcall);
            break;
      }
   }
}

function load()
{
   if (GBrowserIsCompatible()) {
      var mapelm = document.getElementById("map");
      var tilelayer = new GTileLayer(new GCopyrightCollection(), 0, 17);
      map = new GMap2(mapelm);

      // set the tile layer to fetch data from the KML file
      tilelayer.getTileUrl = function(a, b) {
         b = this.maxResolution() - b;
         return googleKmlPrefix + "?id=" + kml + "&x=" + a.x +
            "&y=" + a.y + "&zoom=" + b;
      };
      tilelayer.isPng = function() { return true; };

      // add a resize event handler:
      if (window.attachEvent) {
         window.attachEvent("onresize", map.checkResize);
      } else {
         window.addEventListener("resize", map.checkResize, false);
      }

      // add the map controls
      map.addControl(new GLargeMapControl());
      map.addControl(new GMapTypeControl());
      map.addControl(new GScaleControl());
      map.addControl(new GOverviewMapControl());

      // center roughly at the I-20/I-75/85 interchange
      map.setCenter(new GLatLng(33.8, -84.355), 11);

      // arrays of data passed around by event functions
      stationarr = new Array();
      var parkarr = new Array();
      var stoparr = new Array();

      function loadTransit(icons) {
         return function(data, responseCode)
         {
            var transitXml = GXml.parse(data);

            var points = getElements(transitXml.documentElement, transNS, "transitPoints");
            var routes = getElements(transitXml.documentElement, transNS, "transitRoutes");
            // route data doesn't affect display

            if (points.length) {
               loadPoints(points[0], map, stationarr, parkarr, stoparr, icons);
            }

            if (routes.length) {
               loadRoutes(routes[0], map, stationarr, parkarr, stoparr, icons);
            }
         };
      }

      // load MARTA
      GDownloadUrl("data/marta.xml", loadTransit(initMARTAIcons()));

      // load CCT
      GDownloadUrl("data/cct.xml", loadTransit(initCCTIcons()));

      // load GRTA X-press
      GDownloadUrl("data/grta.xml", loadTransit(initGRTAIcons()));

      // load the line overlays from the KML data
      map.addOverlay(new GTileLayerOverlay(tilelayer));
   }
}

function unload() {
   if (window.detachEvent) {
      window.detachEvent("onresize", map.checkResize);
   } else {
      window.removeEventListener("resize", map.checkResize, false);
   }

   GUnload();
}

function showStation(lineName) {
   var form = document.getElementById("stationForm");
   var sel = form[lineName + "Stations"];
   var station = stationarr[sel.options[sel.selectedIndex].value];
   openMarker(station.marker, station.info)();
   sel.selectedIndex = 0;
}

