// ----------------------------------------------------------------------------
// Written by: J. C. Parker, 2009 - 2011
// Copyright:  J. C. Parker, 2009 - 2011
// ----------------------------------------------------------------------------

var URL_NEXT_WEB_PAGE = 'http://www.bv.com.au/apps/super-tuesday/data-entered/';

var URL_BASE = 'http://www.bv.com.au/bike-counts/gmaps/trafficmovementsdisplay.php';

var map = null;

var cookieName    = 'gmapsRoadMovementCalculator';
var cookieVersion = 1;

// the cookie variables
// Melbourne's GPO is located at Australian Map Grid (AMG) 320 725 E, 5812 900 N, Zone 55
var mapCenterLat = -37.8135784679527;  // set the default to Melbourne's GPO
var mapCenterLng = 144.963341474327;   // set the default to Melbourne's GPO
var mapZoom      = 11;
var mapTypeText  = "m";
var mapType      = google.maps.MapTypeId.ROADMAP;

var mapInfoWindow    = new google.maps.InfoWindow();
var mapSavedPositionLat = 0;
var mapSavedPositionLng = 0;

var ozState      = 'vic';
var surveyDate   = 'invalid';
var access       = '';
var useAmPm      = false;
var isAm         = true;
var old          = false;

var surveyAcceptingResults = false;

var intersectionID   = '';
var intersectionDataForPost = '';

// holds the count for each movement - the index represents the movement type
var movements = [];

var finalResult = '';
var test = '';

var submitting = false;

// ----------------------------------------------------------------------------

var sitesTypes = [];

sitesTypes[0] = "onRd";
sitesTypes[1] = "offRd";
sitesTypes[2] = "fxCnt";
sitesTypes[3] = "ym";
sitesTypes[4] = "noD";
sitesTypes[5] = "unk";

// ----------------------------------------------------------------------------

// marker icon sizing
var siz = new google.maps.Size(64,64);   // sprite size
var org = new google.maps.Point(0,0);    // sprite offset to origin
var anc = new google.maps.Point(16,16);  // middle of icon once resized
var scl = new google.maps.Size(32,32);   // rescale to half size

// make the icons
var amIcon    = new google.maps.MarkerImage('http://maps.google.com/mapfiles/kml/paddle/blu-blank.png',siz,org,anc,scl);
var amPmIcon  = new google.maps.MarkerImage('http://chart.apis.google.com/chart?cht=mm&chs=32x32&chco=ff80ff,c000c0,000000',siz,org,anc,scl);
var pmIcon    = new google.maps.MarkerImage('http://maps.google.com/mapfiles/kml/paddle/red-blank.png',siz,org,anc,scl);
var fxCntIcon = new google.maps.MarkerImage('http://maps.google.com/mapfiles/kml/paddle/ylw-blank.png',siz,org,anc,scl);
var noDIcon   = new google.maps.MarkerImage('http://maps.google.com/mapfiles/kml/paddle/wht-blank.png',siz,org,anc,scl);
var nullIcon  = new google.maps.MarkerImage('http://maps.google.com/mapfiles/kml/shapes/placemark_circle.png',siz,org,anc,scl);
var unkIcon   = new google.maps.MarkerImage('http://maps.google.com/mapfiles/kml/shapes/caution.png',siz,org,anc,scl);
var infoIcon  = new google.maps.MarkerImage('http://maps.google.com/mapfiles/kml/shapes/info.png',siz,org,anc,scl);

// ----------------------------------------------------------------------------
// Wrap a parameter inside an object, so it can be passed by reference
// ----------------------------------------------------------------------------

function parmObject(newVal)
{
   this.value = newVal;
}

// ----------------------------------------------------------------------------
// Display 500 page error in a new window
// http://www.onlamp.com/pub/a/onlamp/2005/05/19/xmlhttprequest.html
// ----------------------------------------------------------------------------

function handleErrFullPage(strIn)
{
   var errorWin;

   // create new window and display error
   try
   {
      errorWin = window.open('', 'errorWin');
      errorWin.document.body.innerHTML = strIn;
   }
   // if pop-up gets blocked, inform user
   catch(e)
   {
      alert('An error occurred, but the error message cannot be' +
            ' displayed because of your browser\'s pop-up blocker.\n' +
            'Please allow pop-ups from this Web site.');
   }
}

// ----------------------------------------------------------------------------
// Handle the response
// Custom messages can be returned - just make sure they contain 'Debug:' or 'Error:'
// http://www.onlamp.com/pub/a/onlamp/2005/05/19/xmlhttprequest.html
//
// In IE7 responseXML is only available if the server sends back an XML document
// with MIME type text/xml, and not if it sends back, for instance, a KML document.
// ----------------------------------------------------------------------------

function handleServerReponse(httpRequest, url, listener)
{
   switch (httpRequest.readyState)
   {
   case 2:
   case 3:
      // display a progress indicator of some kind
      break;
   case 4:
      var textFromServer = httpRequest.responseText;
      switch (httpRequest.status)
      {
         case 200:
            // got a custom error msg?
            if (textFromServer.indexOf('Error:') > -1 || textFromServer.indexOf('Debug:') > -1)
               alert(textFromServer);
            else // return the raw XML text and the XML DOM object result
            {
               if (window.DOMParser)
               {
                  // not an IE browser
                  listener(textFromServer, httpRequest.responseXML);
               }
               else  // IE wants a mime indicating XML - a KML mime is not sufficient
               {
                  // manually parse the text into xml
                  xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
                  xmlDoc.async = false;
                  xmlDoc.loadXML(textFromServer);

                  listener(textFromServer, xmlDoc);
               }
            }
            break;
         case 404: // page not found error
            alert('Error 404: Page not Found. The requested URL ' + url + ' could not be found.');
            break;
         case 500: // display results in a full window for server-side errors
            handleErrFullPage(textFromServer);
            break;
         default:
            alert('Error: Status is ' + httpRequest.status);
         break;
      }
      break;
   default:
      break;
   }
}

// ----------------------------------------------------------------------------
// Get a http request object
// https://developer.mozilla.org/en/AJAX/Getting_Started
// ----------------------------------------------------------------------------

function getXMLObject(handler, url, listener)
{
   // keep it all local so we can make multiple
   // requests without confusion between them
   var httpRequest = false;

   if (window.XMLHttpRequest)
   {  // IE7, Mozilla, Safari, ...
      httpRequest = new XMLHttpRequest();
   } 
   else if (window.ActiveXObject)
   {  // IE
      try { httpRequest = new ActiveXObject("Msxml2.XMLHTTP"); }
      catch (e1)
      {
         try { httpRequest = new ActiveXObject("Microsoft.XMLHTTP"); }
         catch (e2)
         {}
      }
   }

   if (!httpRequest)
   {
      alert('Cannot create XMLHTTP instance');
      return false;
   }

   // the server's response will be screened by the handler and then passed on to the result function
   httpRequest.onreadystatechange = function() { handler(httpRequest, url, listener); };

   return httpRequest;
}

// ----------------------------------------------------------------------------
// do an asynchronous or synchronous GET or POST
// PLEASE encodeURI parameters before passing in the URL if needed
// ----------------------------------------------------------------------------

function ajaxRequest(method, url, async, listener)
{
   // get a reference to the function that will initially screen the server's response
   var handler = handleServerReponse;

   // get the url and the parameters
   var urlParms = url.split("?");

   // set up the object to suit the browser being used
   // once the server's response has been checked, it's passed on to the listener
   var xmlhttp = new getXMLObject(handler, urlParms[0], listener);

   if (xmlhttp) // then execute method
   {
      // confuse cache
      var date    = new Date();
      var theTime = date.getTime();

      if (method == 'POST')
      {
         // get the parms ready to post
         var postThis = '';
         if (urlParms.length > 1) postThis = urlParms[1];

         // defeat caching
         url = urlParms[0] + "?" + theTime;

         xmlhttp.open('POST',url,async);
         xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
         xmlhttp.send(postThis);
      }
      else if (method == 'GET')
      {
         // defeat caching
         url += (url.match(/\?/) === null ? "?" : "&") + theTime;

         xmlhttp.open('GET',url,async);
         xmlhttp.send(null);
      }
   }

   return xmlhttp;
}

// ----------------------------------------------------------------------------
// Check the survey status:
//  - is there a survey accepting results?
//  - on what date is the survey being held?
// ----------------------------------------------------------------------------

function listenerG0(textFromServer, dummy)
{
   //alert('Response to function p0 was: ' + textFromServer);

   if (textFromServer.length >= 0)
   {
      var pieces = textFromServer.split(',');

      surveyDate = pieces[1];

      //alert('surveyDate: ' + surveyDate);

      // got a survey accepting results?
      if (surveyDate != 'inactive')
         surveyAcceptingResults = true;
   }
}

// ----------------------------------------------------------------------------
// A function to create the marker and set up the info panel
// ----------------------------------------------------------------------------

function createMarker(latLng, icon, name, description)
{
   var markerShape =
   {
      coord: [0, 0, 31, 31],
      type: 'rect'
   };

   var marker = new google.maps.Marker({
      map: map,
      position: latLng,
      icon: icon,
      title: name,
      shape: markerShape});

   google.maps.event.addListener(marker, "click", function()
      {
      // the data for this intersection will be posted with an ajax call
      intersectionID = name;

      // show the id of this intersection on the page
      document.getElementById("intersecID").innerHTML = ': '+ ozState + ' ' + intersectionID;

      var htmlCode = '<p><b>' + name + '</b><br/><br/>' + description + '</p>';

      // see also CSS in HTML file
      var divStyle = '<div class="gmapinfowindow" style="max-Height:500px; overflow-y:auto">';

      htmlCode = divStyle + htmlCode + '</div>';

      // the map may move when the infowindow opens - later
      // on, we'll stick it back, from whence it came
      mapSavedPositionLat = mapCenterLat;
      mapSavedPositionLng = mapCenterLng;

      // set up our one and only info window
      mapInfoWindow.setContent(htmlCode);
      mapInfoWindow.open(map,marker);

      //GLog.write(htmlCode);

      // this will return a two element array, as it does not use the g (global match)
      // parameter. array [0] equals the whole matching string including the tags
      // and array [1] equals what's between the tags
      var matches = [];
      matches = htmlCode.match(/<span id="srvyRslt2" style="display: none">(.*?)<\/span>/);
      //alert ('matches = ' + matches);

      if ((matches === null) || (matches.length !== 2))
         alert ('Tag not found - check php prog');
      else
         access = matches[1];

      if (surveyAcceptingResults)
         // allow the access to be typed in
         document.getElementById("access").disabled = false;

      // the map moves when the infowindow opens - later
      // on we'll stick it back to where it came from
      mapSavedPosition = map.getCenter();

      });

   return marker;
}

// ----------------------------------------------------------------------------
// The AJAX request has returned a result - make use of it.
// ----------------------------------------------------------------------------

function listenerKML(dummy, xmlDoc)
{
   /* How the XmlHttp request object works is a bit confusing until you realize that
   it doesn't execute inline. Once you call the request object, control returns to the
   next line in the code while the "get" happens in the background. The request object
   waits patiently for the server to return the results (the onreadystate = 4) and
   then acts on the received data. */

   if (xmlDoc === null)
   {
      alert ('Error - XML file problem - check php output');
      return;
   }

   if (xmlDoc.documentElement === null)
   {
      alert ('Error - XML document is empty - may be an IE problem - it wants a pure XML mime format');
      return;
   }

   var placemarks = xmlDoc.documentElement.getElementsByTagName("Placemark");

   // process the placemarks
   for (var i = 0; i < placemarks.length; i++)
   {
      // placemarks do not necessarily contain markers
      // markers have a 'Point' tag
      if (placemarks[i].getElementsByTagName("Point").length > 0)
      {
         var point  = placemarks[i].getElementsByTagName("Point")[0];
         var coords = point.getElementsByTagName("coordinates")[0].childNodes[0].nodeValue;
         coords     = coords.split(",");
         var name   = placemarks[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;

         // placemarks don't have to have descriptions
         var description = "";
         if (placemarks[i].getElementsByTagName("description").length > 0)
            description = placemarks[i].getElementsByTagName("description")[0].childNodes[0].nodeValue;

         // placemarks don't have to have a styleUrl
         var styleUrl = "";
         var icon = new google.maps.MarkerImage();

         if (placemarks[i].getElementsByTagName("styleUrl").length > 0)
         {
            styleUrl = placemarks[i].getElementsByTagName("styleUrl")[0].childNodes[0].nodeValue;

            // map the styles to the icons
            switch (styleUrl)
            {
               case '#sty_am':
                  icon = amIcon;
                  break;
               case '#sty_amPm':
                  icon = amPmIcon;
                  break;
               case '#sty_pm':
                  icon = pmIcon;
                  break;
               case '#sty_fxCnt':
                  icon = fxCntIcon;
                  break;
               case '#sty_noD':
                  icon = noDIcon;
                  break;
               case '#sty_null':
                  icon = nullIcon;
                  break;
               case '#sty_unk':
                  icon = unkIcon;
                  break;
               case '#sty_info':
                  icon = infoIcon;
                  break;
               default:
                  alert('Error: unknown style');
                  break;
            }
         }

         // create the marker
         var latLng = new google.maps.LatLng (parseFloat(coords[1]), parseFloat(coords[0]));
         var marker = createMarker(latLng, icon, name, description);
      }
   }  // end for all the points

   //GLog.write('Download completed');
}

// ----------------------------------------------------------------------------
// Produce a date string
// ----------------------------------------------------------------------------

function getSurveyDateString(surveyDate)
{
   if (surveyDate == 'inactive')
      return 'NOT ACTIVE';

   var pieces = [];

   // we use underlines in the CSV files since dashes cause
   // problems with Excel reformating the date layout
   // an example date:  2009_3_17
   pieces = surveyDate.split('_');

   // reassembly the pieces as a date string
   var date = pieces[2] + '-' + pieces[1] + '-' + pieces[0];

   return date;
}

// ----------------------------------------------------------------------------
// When a new intersection is displayed clear the text boxes
// ----------------------------------------------------------------------------

function enableEntry()
{
   //enable buttons
   document.getElementById("checkEntry").disabled = false;

   // enable text entry
   document.getElementById("textToConvert").disabled = false;
}

// ----------------------------------------------------------------------------
// A start up or when intersection window is closed, disable entry
// ----------------------------------------------------------------------------

function disableEntry()
{
   document.getElementById("access").value                = '';
   document.getElementById("access").disabled             = true;
   document.getElementById("amRB").checked                = false;
   document.getElementById("pmRB").checked                = false;
   document.getElementById("amRB").disabled               = true;
   document.getElementById("pmRB").disabled               = true;
   document.getElementById("checkEntry").disabled         = true;
   document.getElementById("rotateResults").disabled      = true;
   document.getElementById("textToConvert").disabled      = true;
   document.getElementById("updateIntersection").disabled = true;

   // clear the result text box
   document.getElementById("textResult").value = '';
}

// ----------------------------------------------------------------------------
// If the results don't align with the intersection rotate the results to suit
// ----------------------------------------------------------------------------

function getNumberOfRoads()
{
   var roadIdEnter         = '';
   var roadIdExit          = '';
   var notDone             = true;
   var numberOfRoads       = 0;
   var maxIntersectionSize = 6;

   // determine how many roads there are at this intersection
   // by looking at the id labels in the html code
   var i = 0;
   do
   {
      // make the HTML ID label for each road
      // in the compass rose HTML table
      roadIdEnter = 'rd' + i + 'Ent';
      roadIdExit  = 'rd' + i + 'Ext';

      // if an element with the generated id exists the road exists
      // does this road exist at this particular intersection?
      if ((document.getElementById(roadIdEnter) !== null)
       && (document.getElementById(roadIdExit)  !== null))
      {
         numberOfRoads++;
      }
      else
         notDone = false;

      i++;
   }
   while (notDone && (i < maxIntersectionSize));

   // is the number of roads within limits?
   if ((numberOfRoads < 2) || (numberOfRoads > maxIntersectionSize))
   {
       alert('Error - The number of roads at this location is invalid.');
   }

   return numberOfRoads;
}

// ----------------------------------------------------------------------------
// Accepts BV old spreadsheet info and remaps it to the new format
// ----------------------------------------------------------------------------

function remapOldData(numberOfPossibleMovements, convertedInput)
{
/*
new system: indices of movement types
2 way    3 way    4 way    5 way
0  12    0  12    0  12    0  12
1  21    1  13    1  13    1  13
         2  21    2  14    2  14
         3  23    3  21    3  15
         4  31    4  23    4  21
         5  32    5  24    5  23
                  6  31    6  24
                  7  32    7  25
                  8  34    8  31
                  9  41    9  32
                 10  42   10  34
                 11  43   11  35
                          12  41
                          13  42
                          14  43
                          15  45
                          16  51
                          17  52
                          18  53
                          19  54
*/

   // make remapper
   // note: a 5 way spreadsheet data can be converted - turn information is lost
   // note: a 6 way spreadsheet was never available in the old system
   var twoWay   = [0,1];                        // length = 2
   var threeWay = [1,0,2,3,5,4];                // length = 6
   var fourWay  = [2,1,0,3,5,4,7,6,8,11,10,9];  // length = 12 (a fiveway would equal 20)

   var reMapTable = [];
   reMapTable [2] = twoWay;
   reMapTable [3] = threeWay;
   reMapTable [4] = fourWay;

   var numberOfRoads       = 0;
   var maxIntersectionSize = 4;    // 5 and 6 way not possible with old BV data
   var inputText           = '';
   var i                   = 0;
   var j                   = 0;
   var k                   = 0;
   var argsFound           = [];
   var numberOfArguments   = 0;
   var offset              = 0;

   // this will hold the result of the remapping of the data
   var newData = [];
   var newIdx  = 0;

   finalResult = 'No movements entered';

   // zero the new data array
   for (i = 0; i < numberOfPossibleMovements; i++)
      newData[i] = 0;

   // how many roads at this intersection
   numberOfRoads = getNumberOfRoads();

   if (numberOfRoads > maxIntersectionSize)
   {
      finalResult = "Intersection size exceeds " + maxIntersectionSize + " - entry not possible";
      return finalResult;
   }

   // get the input text to convert
   inputText = document.getElementById("textToConvert").value;

   // got some input?
   if (inputText.length <= 0)
      return finalResult;

   // split into lines of text
   var lines = inputText.split(/\r?\n|\r/);

   // got some lines?
   if (lines.length <= 0)
      return finalResult;

   // process the line(s)
   for (i = 0; i < lines.length; i++)
   {
      var processLine = true;

      // skip blank lines
      processLine = (lines[i].length !== 0);

      // do we continue or skip this line?
      if (processLine)
      {
         // extract out any groups of numbers that may be present
         argsFound = lines[i].match(/\d+/g);

         // any matches?
         if (argsFound === null)
            numberOfArguments = 0;
         else
            numberOfArguments = argsFound.length; 

         // argsFound must match a 2 or 3 or 4 or 5 five way intersection
         if ((numberOfArguments != 2) && (numberOfArguments != 6) && (numberOfArguments != 12) && (numberOfArguments != 20))
         {
            finalResult = 'Error - this line:\n"' + lines[i] + '"\nContains no numbers or too many numbers.';
            return finalResult;
         }

         // got the correct amount of arguments for this intersection?
         if (numberOfArguments != numberOfPossibleMovements)
         {
            finalResult = 'Error found in this line:\n"' + lines[i] + '"\nResults do not match intersection size.';
            return finalResult;
         }

         // remap the BV data to the new data array
         // old BV data is in (movement type, amount of movements) pairs
         for (i = 0; i < numberOfArguments; i++)
         {
            // input index valid?
            if (reMapTable[numberOfRoads][i] === undefined)
            {
               finalResult = "remapper input index does not exist";
               return finalResult;
            }

            // get an index for the new data from the remapper
            // it maps BV's old movement types to the new system
            newIdx = reMapTable[numberOfRoads][i];

            // output index valid?
            if (newData[newIdx] === undefined)
            {
               finalResult = "remapper output index does not exist";
               return finalResult;
            }

            // get the amount and stick it in the new result
            newData[newIdx] = argsFound[i];
         }
      } // end if processLine
   }

/*
   // ready the movement counts for display
   // loop around n*(n-1) times
   var dataIdx = 0;
   for (i = 0; i < numberOfRoads; i++)
   {
      offset = 0;

      for (j = 0; j < (numberOfRoads-1); j++)
      {
         if (i == j)
            offset = 1;

         // assemble the converted result
         //convertedInput.value = convertedInput.value + (i+1) + (j+1+offset) + ' ' + newData[dataIdx] + '\n';
         test = test + (i+1) + (j+1+offset) + ' ' + newData[dataIdx] + '\n';

         dataIdx ++;
      }
      dataIdx ++;
   }
*/

   // assemble the converted result
   test= '';
   j = 1;
   k = 0;
   for (i = 0; i < numberOfPossibleMovements; i++)
   {
      k++;
      if (j == k) k++;
      if (k > numberOfRoads) { j++; k = 1;}
      //convertedInput.value = convertedInput.value + j + k + ' ' + newData[i] + '\n';
      test = test + j + k + ' ' + newData[i] + '\n';
   }

   //alert ('Test: '+test);
   return finalResult;
}

// ----------------------------------------------------------------------------
// Make the HTML content
// ----------------------------------------------------------------------------

function calculate(movements, errorFound, intersectionData)
{
   var i                   = 0;
   var j                   = 0;
   var k                   = 0;
   var enter               = 0;
   var exit                = 0;
   var roadIdEnter         = '';
   var roadIdExit          = '';
   var totalMovements      = 0;
   var argsFound           = [];
   var numberOfArguments   = 0;
   var incMovementsByCnt   = 1;
   var offset              = 0;
   var numberOfRoads       = 0;

   // how many roads at this intersection
   numberOfRoads = getNumberOfRoads();

   // for all the roads make an array with an enter/exit array
   var roads = [];
   for (i = 0; i < numberOfRoads; i++)
      roads.push([0,0]);  // syntax pushes a reference to a 2 element array

   // For the intersection make an array that describes the movements.
   // For an n way intersection: length of movements array = n*(n-1)
   var numberOfPossibleMovements = numberOfRoads * (numberOfRoads-1);
   for (i = 0; i < numberOfPossibleMovements; i++)
      movements[i] = 0;

   finalResult = 'No movements entered';

   var inputText = '';

   if (!old)
      inputText = document.getElementById("textToConvert").value;
   else
   {
      var convertedInput = new parmObject('');
      finalResult = remapOldData(numberOfPossibleMovements, convertedInput);
      //inputText   = convertedInput;
      inputText   = test;
   }

   // got some input?
   if (inputText.length <= 0)
      return finalResult;

   // split into lines of text
   var lines = inputText.split(/\r?\n|\r/);

   // got some lines?
   if (lines.length <= 0)
      return finalResult;

   // process the movements, one per line
   for (i = 0; i < lines.length; i++)
   {
      var processLine = true;

      // skip blank lines
      processLine = (lines[i].length !== 0);

      // These words or part there of are allowed in a line, as these are found in the
      // spreadsheet column headings. The user may have cut and paste them along with
      // the data. We don't process them but they are allowed.
      if (processLine)
      {
         var found = lines[i].toUpperCase().indexOf("MOVEMENT");
         if (found != -1) processLine = false;

         found = lines[i].toUpperCase().indexOf("TOTAL");
         if (found != -1) processLine = false;
      }

      // do we continue or skip this line?
      if (processLine)
      {
         // extract out any groups of numbers that may be present
         argsFound = lines[i].match(/\d+/g);

         // any matches?
         if (argsFound === null)
            numberOfArguments = 0;
         else
            numberOfArguments = argsFound.length; 

         // make sure there are one or two arguments
         if ((numberOfArguments < 1) || (numberOfArguments > 2))
         {
            finalResult = 'Error found in this line:\n"' + lines[i] + '"\nContains no numbers or too many numbers.';
            return finalResult;
         }

         // make sure the first number is valid - it should be only 2 digits long
         if (argsFound[0].length != 2)
         {
            finalResult = 'Error found in this line:\n"' + lines[i] + '"\nThe first number is invalid.';
            return finalResult;
         }

         // only numbers 1 to numberOfRoads inclusive are allowed
         enter = parseInt(argsFound[0].charAt(0),10);
         if (isNaN(enter) || (enter < 1) || (enter > numberOfRoads))
         {
            finalResult = 'Error found in this line:\n"' + lines[i] + '"\nMovement has an invalid road number.';
            return finalResult;
         }

         // only numbers 1 to numberOfRoads inclusive are allowed
         exit  = parseInt(argsFound[0].charAt(1),10);
         if (isNaN(exit) || (exit < 1) || (exit > numberOfRoads))
         {
            finalResult = 'Error found in this line:\n"' + lines[i] + '"\nMovement has an invalid road number.';
            return finalResult;
         }

         // if there is no second argument, then that's a count of zero
         incMovementsByCnt = 0;

         // first number is valid, now check for a second
         // it tells us how many times to duplicate this movement
         if (numberOfArguments == 2)
         {
            incMovementsByCnt = parseInt(argsFound[1],10);

            // make sure all is going to plan so far
            if (isNaN(incMovementsByCnt))
            {
               finalResult = 'Error found in this line:\n"' + lines[i] + '"\n2nd number is NaN';
               return finalResult;
            }

            // make sures the count is 0 or greater
            if (incMovementsByCnt < 0)
            {
               finalResult = 'Error found in this line:\n"' + lines[i] + '"\nRepeat count is invalid.';
               return finalResult;
            }

            // further sanity check
            if (incMovementsByCnt > 5000)
            {
               finalResult = 'Error found in this line:\n"' + lines[i] + '"\nRepeat count cannot be that high.';
               return finalResult;
            }
         }

         // enter and exit roads should be different - that would be a U turn
         if (enter === exit)
         {
            finalResult = 'Error found in this line:\n"' + lines[i] + '"\nLine represents a U turn. U turns are not allowed.';
            return finalResult;
         }

         // count up the number of movements in each direction
         // roads are numbered 1 to x; not 0 to x
         roads[enter-1][0] += incMovementsByCnt;   // 1st digit is an enter at this road number
         roads[exit-1] [1] += incMovementsByCnt;   // 2nd digit is an exit  at this road number

         totalMovements += incMovementsByCnt;

         // arrays start at 0 not one
         // also enter cannot equal enter that creates a hole to skip
         offset = 0;
         if (exit < enter)
            offset = exit-1;
         else
            offset = exit-2;

         // increment the counters for each possible movement at this intersection
         movements[(enter-1)*(numberOfRoads-1) + offset] += incMovementsByCnt;
      }
   }

   // map the movements to the roads - the first
   // road is the most northerly going clockwise
   for (i = 0; i < numberOfRoads; i++)
   {
      // make the HTML ID label for each road
      // in the compass rose HTML table
      roadIdEnter = 'rd' + i + 'Ent';
      roadIdExit  = 'rd' + i + 'Ext';

      // the road exists - assign the movement counts to the road
      document.getElementById(roadIdEnter).innerHTML = roads[i][0];
      document.getElementById(roadIdExit).innerHTML  = roads[i][1];
   }

   finalResult = 'Entry checked OK\nTotal movements = ' + totalMovements + '\n\nCount of each movement:\n';

   // indicate how the data is formatted
   // In this case it's a sequential list of
   // counts for all the possible movements
   if (isAm)
      intersectionData.value = 'A';
   else // pm
      intersectionData.value = 'P';

   // assemble the converted result
   j = 1;
   k = 0;
   for (i = 0; i < numberOfPossibleMovements; i++)
   {
      k++;
      if (j == k) k++;
      if (k > numberOfRoads) { j++; k = 1;}

      finalResult = finalResult + j + k + ' = ' + movements[i] + '\n';

      // this will be sent back to the db
      intersectionData.value = intersectionData.value + ' ' + movements[i];
   }

/*
   // ready the movement counts for display
   // loop around n*(n-1) times
   for (i = 0; i < numberOfRoads; i++)
   {
      offset = 0;

      for (j = 0; j < (numberOfRoads-1); j++)
      {
         if (i == j)
            offset = 1;

         var movementCnt = movements[i*(numberOfRoads-1)+j];

         finalResult = finalResult + (i+1) + (j+1+offset) + ' = ' + movementCnt + '\n';

         // this will be sent back to the db
         intersectionData.value = intersectionData.value + ' ' + movementCnt;
      }
   }
*/

   finalResult = finalResult + '\nEnter, Exit:\n';

   // ready the road counts for display
   for (i = 0; i < numberOfRoads; i++)
   {
      finalResult = finalResult + 'Road ' + (i+1) + ': ' + roads[i][0] + ' ' + roads[i][1] + '\n';
   }

   finalResult = finalResult + '\n';

   // show what the db will get
   finalResult = finalResult + intersectionData.value;

   // if we got this far, then everything must be OK
   errorFound.value = false;

   return finalResult;
}

// ----------------------------------------------------------------------------
// The text has been entered and the create page button clicked
// ----------------------------------------------------------------------------

function checkEntryClicked()
{
   // assume it all goes badly till proven otherwise
   var errorOccured     = new parmObject(true);
   var intersectionData = new parmObject('');

   // make the results
   finalResult = calculate(movements, errorOccured, intersectionData);

   document.getElementById("textResult").value = finalResult;

   // only allow file updating if all went well
   if (errorOccured.value)
      document.getElementById("updateIntersection").disabled = true;
   else // all OK
   {
      // the data for this intersection will be posted with an ajax call
      intersectionDataForPost = intersectionData.value;

      // allow results to be rotated
      document.getElementById("rotateResults").disabled = false;

      // enable the submit button
      document.getElementById("updateIntersection").disabled = false;
   }

   // don't let the button do anything else
   return false;
}

// ----------------------------------------------------------------------------
// If the results don't align with the intersection rotate the results to suit
// ----------------------------------------------------------------------------

function rotateResultsClicked()
{
   var numberOfRoads = 0;
   var tmpArray      = [];
   var inputText     = '';

   // how many roads at this intersection
   numberOfRoads = getNumberOfRoads();

   // For the intersection make an array that describes the movements.
   // For an n way intersection: length of movements array = n*(n-1)
   var numberOfPossibleMovements = numberOfRoads * (numberOfRoads-1);
   for (var i = 0; i < numberOfPossibleMovements; i++)
   {
      tmpArray [i] = 0;
   }

/* jk and jOutkOut count like so (4 way):
12 23
13 24
14 21
21 32
23 34
24 31
31 42
32 43
34 41
41 12
42 13
43 14
*/

   var outIdx = 0;
   var j      = 1;
   var k      = 0;
   var jOut   = 0;
   var kOut   = 0;
   for (i = 0; i < numberOfPossibleMovements; i++)
   {
      k++;
      if (j == k) k++;
      if (k > numberOfRoads) { j++; k = 1;}

      // rotate to next road clockwise
      // just add one and wrap if needed
      if (j < numberOfRoads)
         jOut = j+1;
      else
         jOut = 1;

      if (k < numberOfRoads)
         kOut = k+1;
      else
         kOut = 1;

      // update the output index
      outIdx = (jOut-1)*(numberOfRoads-1);
      if (kOut <= jOut)
         outIdx = outIdx+kOut-1;
      else
         outIdx = outIdx+kOut-2;

      // count test code
      //inputText = inputText + i + ' ' + j + k + ' ' + jOut + kOut + ' ' + outIdx + '\n';

      // copy the data to the new position
      tmpArray[outIdx] = movements[i];
   }

   // update the original array
   movements = tmpArray;

   // assemble the converted result
   j = 1;
   k = 0;
   for (i = 0; i < numberOfPossibleMovements; i++)
   {
      k++;
      if (j == k) k++;
      if (k > numberOfRoads) { j++; k = 1;}

      inputText = inputText + j + k + ' ' + movements[i] + '\n';
   }

   // update the input text
   document.getElementById("textToConvert").value = inputText;

   // calculate a new result based on the new input text
   checkEntryClicked();

   // don't let the button do anything else
   return false;
}

// ----------------------------------------------------------------------------
// Populate the map with the KML data
// ----------------------------------------------------------------------------

function getMarkers()
{
   var uri = URL_BASE + '?fnc=k1' + '&st=' + ozState + '&dt=' + surveyDate;

   uri = encodeURI(uri);
   //GLog.write(uri);

   // set the call back function - this waits for the
   // request to complete as a background thread
   var xmlhttp = ajaxRequest('GET', uri, true, listenerKML);

   // At this point nothing happens until the AJAX result is returned.
   // That event results in the listenerKML function being called
}

// ----------------------------------------------------------------------------
// Check the access
// ----------------------------------------------------------------------------

function keyPressed()
{
   // ignore the keypresses if the entry box is not enabled
   if (document.getElementById("access").disabled)
      return;

   var regNumbers = access.split(' ');

   // must have two strings after the split
   if (regNumbers.length != 2)
   {
      alert ('Not enough reg numbers');
      return;
   }

   var amInt = parseInt(regNumbers[0],10);
   var pmInt = parseInt(regNumbers[1],10);

   var access1 = document.getElementById("access").value
   var accessInt = parseInt(access1,10);

   if (isNaN(amInt) || isNaN(pmInt) || isNaN(accessInt)) return;
   if (accessInt <= 0) return;
   if (!((amInt === accessInt) || (pmInt === accessInt)))  return;

   // make sure further key presses are ignored
   document.getElementById("access").disabled = true;

   if (useAmPm)
   {
      // enableEntry is called once the radio buttons are clicked on
      document.getElementById("amRB").disabled = false;
      document.getElementById("pmRB").disabled = false;

      alert ('Code OK - please select AM or PM then enter your data!');
   }
   else
   {
      // no radio buttons in use, so data entry can start straight away
      enableEntry();

      alert ('Code OK - please enter your data!');
   }
}

// ----------------------------------------------------------------------------
// The user clicked on one of the am/pm radio buttons
// ----------------------------------------------------------------------------

function amPmClicked()
{
   // probably don't need to do this - but just in case
   if ((document.getElementById("amRB").disabled) || (document.getElementById("pmRB").disabled))
      return false;

   // if this button is not checked it must be pm
   isAm = document.getElementById("amRB").checked;

   document.getElementById("amRB").disabled = true;
   document.getElementById("pmRB").disabled = true;

   enableEntry();

   // the buttons are not to perform any further actions
   return false;
}

// ----------------------------------------------------------------------------
// Set the map parameters in the cookie
// ----------------------------------------------------------------------------

function setCookieParameters(years)
{
   // the cookie holds the map layout and the home location
   var cookieValue = cookieVersion +','
                   + mapCenterLat +','+ mapCenterLng +','
                   + mapZoom +','
                   + mapTypeText +','
                   + ozState;
   // the survey date is hard coded

   // set the expiry time
   var date = new Date();
   date.setTime(date.getTime()+(years*365*24*60*60*1000));

   // set the cookie
   document.cookie = cookieName+'='+escape(cookieValue)
                   + '; expires='+date.toGMTString()
                   + '; path=/';

   // if the year is -1 the cookie is destroyed
   return (years != -1);
}

// ----------------------------------------------------------------------------
// Get the map set up parameters from the cookie
// ----------------------------------------------------------------------------

function getCookieParms()
{
   // We deliberately don't remember the Council and show all sites flag.
   // This avoids accidentally changing the view from what would normally
   // be the default ie the active sites in a given state and survey date.

   // set the defaults in case there is no cookie
   // Melbourne's GPO is located at Australian Map Grid (AMG) 320 725 E, 5812 900 N, Zone 55
   mapCenterLat = -37.8135784679527;  // set the default to Melbourne's GPO
   mapCenterLng = 144.963341474327;   // set the default to Melbourne's GPO
   mapZoom      = 2;
   mapTypeText  = "m";
   mapType      = google.maps.MapTypeId.ROADMAP;
   ozState      = 'vic';
   // the survey date is hard coded

   // can the map be centered by estimating the user's location from their IP address?
   if (google.loader !== undefined)
   {
      // set the default to client's estimated location
      mapCenterLat = google.loader.ClientLocation.latitude;
      mapCenterLng = google.loader.ClientLocation.longitude;

      mapZoom = 11;
   }

   // got a cookie? - if not RETURN
   var results = document.cookie.match (cookieName + '=(.*?)(;|$)');
   if (!results)
      return false;

// ----------------------------------------------------------------------------

   // a cookie was found
   var cookiePrms = unescape(results[1]).split(',');

   // is the cookie up to date?
   if (cookiePrms[0] != cookieVersion)
      return false;

   // a cookie was found - load the data from it
   mapCenterLat = parseFloat(cookiePrms[1]);
   mapCenterLng = parseFloat(cookiePrms[2]);

   mapZoom = parseInt(cookiePrms[3],10);

   mapTypeText = cookiePrms[4];

   if      (mapTypeText == "p") { mapType = google.maps.MapTypeId.TERRAIN;   }
   else if (mapTypeText == "k") { mapType = google.maps.MapTypeId.SATELLITE; }
   else if (mapTypeText == "h") { mapType = google.maps.MapTypeId.HYBRID;    }
   else                         { mapType = google.maps.MapTypeId.ROADMAP; mapTypeText = "m"; }

   ozState = cookiePrms[5];

   return true;
}

// ----------------------------------------------------------------------------
// Extract the parms from the URL
// ----------------------------------------------------------------------------

function parseParms()
{
   // get the URL and attempt to split it
   var urlParms = window.location.href.toLowerCase();
   urlParms = urlParms.split("?");

   // did the URL contain any parameters?
   if (urlParms.length > 1)
   {
      // get the parm(s) and attempt to split them
      var parms = urlParms[1].split("&");

      // for all the key value pairs
      for (var i=0; i<parms.length; i++)
      {
         // get the key value pairs and attempt to split them up
         var keyValue = parms[i].split("=");

         // the parms must come in key value pairs else the url is malformed
         if (keyValue.length != 2)
            break;

         // is this the map center lat/lng parameter?
         if (keyValue[0] == 'll')
         {
            // break up the value into lat and lng
            var latLng = keyValue[1].split(",");

            // the lat/lng must come as a pair else the url is malformed
            if (latLng.length != 2)
               break;

            mapCenterLat = parseFloat(latLng[0]);
            mapCenterLng = parseFloat(latLng[1]);
         }

         // is this the zoom parameter?
         if (keyValue[0] == 'z')
            mapZoom = parseInt(keyValue[1],10);

         // is this the map type parameter?
         if (keyValue[0] == 't')
         {
            mapTypeText = keyValue[1];
            if      (mapTypeText == "p") { mapType = google.maps.MapTypeId.TERRAIN;   }
            else if (mapTypeText == "k") { mapType = google.maps.MapTypeId.SATELLITE; }
            else if (mapTypeText == "h") { mapType = google.maps.MapTypeId.HYBRID;    }
            else                         { mapType = google.maps.MapTypeId.ROADMAP; mapTypeText = "m"; }
         }

         // is this the Australian state parameter?
         if (keyValue[0] == 'st')
            ozState = keyValue[1];

         // is this the old parameter?
         // just the presence of this tells the parser to accept old BV style spreadsheet info
         if (keyValue[0] == 'old')
            old = true;
      }
   }
}

// ----------------------------------------------------------------------------
// Show the "Results accepted" message to the user
// ----------------------------------------------------------------------------

function listenerP1(textFromServer, dummy)
{
   alert(textFromServer);

   // the submission process is over
   submitting = false;

   // user OK's the alert box and we are done
   window.location.href = URL_NEXT_WEB_PAGE;
}

// ----------------------------------------------------------------------------
// The "Update intersection" button was clicked
// ----------------------------------------------------------------------------

function submitClicked()
{
   // if the user gets a bit clicky, stop the form from
   // being resubmitted until we get a server response
   if (submitting)
      return false;

   // submit the form and then lock it out from further resubmissions
   submitting = true;

   var access1 = document.getElementById("access").value

   var postMsg = '?fnc=p1' + '&st=' + ozState + '&intersectionID=' + intersectionID + '&intersectionData=' + intersectionDataForPost + '&ri=' + access1;

   ajaxRequest('POST', 'trafficmovementsdisplay.php' + postMsg, true, listenerP1);

   // go back to having nothing selected or enabled
   map.closeInfoWindow();

   // the button is not to perform any further actions
   return false;
}

// ----------------------------------------------------------------------------
// Don't name this 'onload' as it's a reserved word in Firefox
// ----------------------------------------------------------------------------

function load()
{
   document.getElementById("updateIntersection").onclick = submitClicked;

   // data entry not allowed until an intersection is selected
   disableEntry();

   // the result text area is always disabled
   document.getElementById("textResult").disabled = true;

   // get the cookie if there is one
   getCookieParms();

   // parameters in the URL will override all
   parseParms();

   // save manually entered URL parms
   setCookieParameters(3);

   // check the survey status - make this a synchronous call
   var xmlhttp = ajaxRequest('GET', 'trafficmovementsdisplay.php?fnc=g0&st=' + ozState, false, listenerG0);

   if (xmlhttp)
      handleServerReponse(xmlhttp, 'trafficmovementsdisplay.php', listenerG0);

   // do we have a survey with am and pm results?
   // If not am is assumed
   useAmPm = (ozState === 'nsw');

   // Show/hide am pm radio buttons and associated labels
   if (useAmPm)
      document.getElementById("amPmButtons").style.visibility = "visible";
   else
      document.getElementById("amPmButtons").style.visibility = "hidden";

   document.getElementById("date").innerHTML = getSurveyDateString(surveyDate);

   var mapOptions =
   {
      zoom: mapZoom,
      center: new google.maps.LatLng (mapCenterLat, mapCenterLng),
      mapTypeId: mapType
   };

   map = new google.maps.Map(document.getElementById("mapDiv"), mapOptions);

   google.maps.event.addListener(map, "center_changed", function()
   {
      // the map center changed - save it
      var latLngStr = map.getCenter().toUrlValue();
      var latLng    = latLngStr.split(",");
      mapCenterLat  = parseFloat(latLng[0]);
      mapCenterLng  = parseFloat(latLng[1]);

      setCookieParameters(3);
   });

   google.maps.event.addListener(map, "zoom_changed", function()
   {
      // the zoom level changed - save it
      mapZoom = map.getZoom();
      setCookieParameters(3);
   });

   google.maps.event.addListener(map, "maptypeid_changed", function()
   {
      // the map type changed - save it
      mapType = map.getMapTypeId();

      if      (mapType == google.maps.MapTypeId.TERRAIN)   { mapTypeText = "p"; }
      else if (mapType == google.maps.MapTypeId.SATELLITE) { mapTypeText = "k"; }
      else if (mapType == google.maps.MapTypeId.HYBRID)    { mapTypeText = "h"; }
      else    {mapType =  google.maps.MapTypeId.ROADMAP;     mapTypeText = "m"; }  // set to default

      setCookieParameters(3);
   });

   // close the info window when the map is clicked on
   google.maps.event.addListener(map, 'click', function()
   {
      // if this funtion exists the window is open (an undocumented feature)
      if (mapInfoWindow.view)
      { 
         mapInfoWindow.close();
         google.maps.event.trigger(mapInfoWindow,'closeclick');
      }
   });

   // when the info window is closed put the map back from whence it came
   google.maps.event.addListener(mapInfoWindow, 'closeclick', function()
   {
      // data entry not allowed until an intersection is selected
      disableEntry();

      // the map may move when the infowindow opens -
      // we'll now stick it back, from whence it came
      var mapSavedPosition = new google.maps.LatLng(mapSavedPositionLat, mapSavedPositionLng);
      map.setCenter(mapSavedPosition);

      // at this point the 'center_changed' event is fired
   });

   // if there is no current survey don't show any markers
   if (surveyAcceptingResults)
      getMarkers();
   else
      alert ('Results are not being accepted at this time.');
}

// execute this
window.onload = load;

// ----------------------------------------------------------------------------


