/****************************************************************************
*                   Copyright (c) 2006 Mercator GeoSystems Ltd
*
*                           Mercator GeoSystems Ltd
*                           10 St Quentin View
*                           Sheffield
*                           South Yorkshire
*                           S17 4PS
*                           ENGLAND
*
*                           Tel    +44 (0) 114 235 3409
*                           Fax    +44 (0) 114 235 3409
*                           E-Mail info@mercatorgeosystems.co.uk
*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/

/* Revision history

$Id$

*/

var interval_id;

// Initiliase general, application strings here (such as those for alert boxes)
function initStrings()
{
  strings['Map']       = 'Map';
  strings['Postcodes'] = 'Postcodes';
  strings['Places']    = 'Places';
  strings['Creating drive times... Please wait...'] = 'Planning route... Please wait...';
  strings['Sorry, no more map locations allowed'] = 'Sorry, no more map locations allowed';
  strings["Checking progress"] = "Checking progress";
  strings["Getting drive time..."] = "Getting drive time...";
  strings["Create 'return to base' leg"] = "Create 'return to base' leg";
  strings["Optimise on distance instead of time"] = "Optimise on distance instead of time";
  strings["Require tachograph breaks"] = "Require tachograph breaks";
  strings["Scheduled break"] = "Scheduled break";
  strings["Allow individual wait times"] = "Specify wait times / first drop";
};

// The spinner is the "please wait" DIV in the centre of the screen that is displayed
// when anything time consuming happens
// just "hides" the spinner by making it "transparent"
function hideSpinner()
{
  waitMsg.hide();
};

// Shows the "loading image. Please wait" image
function showSpinner()
{
  waitMsg.show();
  waitMsg.bringToFront();
};

function loadFramework()
{
  // create the relevant UI components
  // position elms
  var offset = 50;
  var option_h = 300;
  var w = domapi.bodyWidth() / 3;
  var h = domapi.bodyHeight() - offset;
  var e1 = domapi.Elm({id:"userDiv"});
  if (e1) {
    e1.style.position = "absolute";
    e1.setX(0);
    e1.setY(offset);
    e1.setW(w);
  }
  var e2 = domapi.Elm({id:"locationsDiv"});
  if (e2) {
    e2.style.position = "absolute";
    e2.setX(w + 5);
    e2.setY(offset);
    e2.setW((w * 2) - 20);
    e2.setH(h);
  }
  var e3= domapi.Elm({id:"mapDiv"});
  if (e3) {
    e3.style.position = "relative";
    e3.setX(0);
    e3.setY(0);
    e3.setW((w * 2) - 35);
    e3.setH(h - 20);
  }
  var e4= domapi.Elm({id:"optionDiv"});
  if (e4) {
    e4.style.position = "absolute";
    e4.setX(0);
    e4.setY(e1.getH()+20);
    e4.setW(w);
  }
  // within the map panel of the first splitpane we have a pagecontrol
  p = domapi.getElm("locationsDiv");
  pagecontrol = domapi.Pagecontrol({parent:p, h:h});  // same height as map
  // which has a map, postcode & locations page
  pgMap = pagecontrol.addPage({text:strings['Map'], type:"DIV"});
  pagecontrol.assignElement({id:"mapDiv",index:0});
  pgPostcode = pagecontrol.addPage({text:strings['Postcodes'], type:"DIV"});
  pagecontrol.assignElement({id:"postcodesDiv",index:1});
  pgPlaces = pagecontrol.addPage({text:strings['Places'], type:"DIV"});
  pagecontrol.assignElement({id:"placesDiv",index:2});
  pagecontrol.setIndex(start_page);
  // parent div
  var p = domapi.Elm({id:"optionDiv"});
  // create the buttons
  var btnWidth = 80;
  var btnHeight = 50;
  var padding = 8;
  var y_offset = 30;
  btnGo = domapi.Button({parent:p, x:padding, y:y_offset, h:btnHeight, w:btnWidth, text:"Go"});
  btnGo.onclick = getDriveTime;
  btnReset = domapi.Button({parent:p, x:btnWidth + 25, y:y_offset, h:btnHeight, w:btnWidth, text:"Reset"});
  btnReset.onclick = resetApp;
  btnOptions = domapi.Button({parent:p, x:(btnWidth * 2) + 50, y:y_offset, h:btnHeight, w:btnWidth, text:"Options"});
  btnOptions.onclick = showOptions;
};

function showOptions()
{
  // create the options window
  var window_width  = 280;
  var window_height = 270;
  var window_height_small = 190;
  var padding = 5;
  var y_offset = 30;
  var label_height = 20;
  var rowPos = 1;
  var yPos = y_offset;
  optionWnd = domapi.Window({
    text:'Options',
    x:150,y:40,
    w:window_width,h:window_height_small,
    visibleButtons:wbClose});
  optionWnd.setModal(false);
  optionWnd.allowResize(false);  
  // disable the option button
  btnOptions.setEnabled(false);
  // re-enable when closed
  optionWnd.onclose = function() {
    // re-enable button
    btnOptions.setEnabled(true);
    // save values
    returnToBase = rtb.checked;
    waitTime = wt.value;
    startTime = st.value;
  };
  // return to base
  var rtb = domapi.Imagecheckbox({parent:optionWnd, x:4, y:y_offset, text:strings["Create 'return to base' leg"], h:label_height, w:168});
  rtb.setState((returnToBase ? "checked" : "cleared"));
  rtb.setAlign("right");
  rtb.setBgColor("white");
  rtb.onchange = function() {
    returnToBase = (this.checkedState == "checked");
  };
  // time at each way point
  yPos   = y_offset+(label_height * rowPos)+(padding*rowPos)+rowPos;
  var lblWidth = 190;
  var label = domapi.Label({parent:optionWnd, x:padding+6, y:yPos, w:lblWidth, text:"Minutes to wait at each location : "});
  var wt = domapi.Spinedit({parent:optionWnd, x:lblWidth+6, y:yPos, w:70});
  wt.setValue(waitTime);
  wt.onchange = function() {
    waitTime = this.value;
  };
  // Individual waiting times?
  // time at each way point
  rowPos++;
  yPos   = y_offset+(label_height * rowPos)+(padding*rowPos)+rowPos;
  var cbIndivWait = domapi.Imagecheckbox({parent:optionWnd, x:8, y:yPos, text:strings["Allow individual wait times"], h:label_height, w:228});
  cbIndivWait.setState((indivWaitTimes ? "checked" : "cleared"));
  cbIndivWait.setAlign("left");
  cbIndivWait.setBgColor("white");
  cbIndivWait.onchange = function() {
    indivWaitTimes = (this.checkedState == "checked");
  };  
  
  // start time picker & img
  rowPos++;
  yPos   = y_offset+(label_height * rowPos)+(padding*rowPos)+rowPos;
  lblWidth = 110;
  var label = domapi.Label({parent:optionWnd, x:padding+6, y:yPos, w:lblWidth, text:"Route start time : "});
  var st = domapi.Elm({parent:optionWnd, id:"timepicker1", type:'input', x:lblWidth+6, y:yPos, w:60});
  st.value = startTime;
  st.onchange = function() {
    startTime = this.value;
  };
  var tpimg = domapi.Elm({parent:optionWnd, id:"timepickerImg", type:'img', border:0, x:lblWidth+74, y:yPos});
  tpimg.style.position = "absolute";
  tpimg.src = 'icons/timepicker/timepicker.gif';
  tpimg.onclick=function() {
    selectTime(this,st);
  };
  // Distance optimiser instead of time
  rowPos++;
  yPos   = y_offset+(label_height * rowPos)+(padding*rowPos)+rowPos;
  var distOpt = domapi.Imagecheckbox({parent:optionWnd, x:8, y:yPos, text:strings["Optimise on distance instead of time"], h:label_height, w:228});
  distOpt.setState((optimiseOnDistance ? "checked" : "cleared"));
  distOpt.setAlign("left");
  distOpt.setBgColor("white");
  distOpt.onchange = function() {
    optimiseOnDistance = (this.checkedState == "checked");
  };  
  // Break start time & duration
  // Enable / disable break checkbox
  rowPos++;
  yPos   = y_offset+(label_height * rowPos)+(padding*rowPos)+rowPos;
  var schedBreak = domapi.Imagecheckbox({parent:optionWnd, x:8, y:yPos, text:strings["Scheduled break"], h:label_height, w:228});
  schedBreak.setState((scheduledBreak ? "checked" : "cleared"));
  schedBreak.setAlign("left");
  schedBreak.setBgColor("white");
  schedBreak.onchange = function() {
    scheduledBreak = (this.checkedState == "checked");
    // hide show other controls
    if (scheduledBreak == true) {
      // show break controls
      breakStartlabel.show();
      inputBreakStartTime.show();
      breakTpImg.show();
      breakDurationLabel.show();
      cmbBreakDuration.show();
      cbBreakToStop.show();
      // expand window
      optionWnd.setH(window_height);
    } else {
      // hide break controls
      breakStartlabel.hide();
      inputBreakStartTime.hide();
      breakTpImg.hide();
      breakDurationLabel.hide();
      cmbBreakDuration.hide();
      cbBreakToStop.hide();
      // shrink window
      optionWnd.setH(window_height_small);
    }
  };  
  // Break elements
  {
    var indent = 40;
    // Break start time
    rowPos++;
    yPos   = y_offset+(label_height * rowPos)+(padding*rowPos)+rowPos;
     breakStartlabel = domapi.Label({parent:optionWnd, x:indent, y:yPos, w:lblWidth, text:"Break start time : "});
    inputBreakStartTime  = domapi.Elm({parent:optionWnd, id:"timepicker1", type:'input', x:40+lblWidth, y:yPos, w:60});
    inputBreakStartTime.value = breakStartTime;
    inputBreakStartTime.onchange = function() {
      breakStartTime = this.value;
    };
    breakTpImg = domapi.Elm({parent:optionWnd, id:"timepickerImg", type:'img', border:0, x:indent+lblWidth+70, y:yPos});
    breakTpImg.style.position = "absolute";
    breakTpImg.src = 'icons/timepicker/timepicker.gif';
    breakTpImg.onclick=function() {
      selectTime(this,inputBreakStartTime);
    };  
    // Break duration
    rowPos++;
    yPos   = y_offset+(label_height * rowPos)+(padding*rowPos)+rowPos;
    breakDurationLabel = domapi.Label({parent:optionWnd, x:indent, y:yPos, text:"Break Duration : "});
    cmbBreakDuration   = domapi.Combobox({x:140, y:yPos, h:50, w:100, doAllowEdit:false});
    domapi.transferElm(cmbBreakDuration, optionWnd);
    cmbBreakDuration.setBgColor("white");
    cmbBreakDuration.addItem("15 minutes", "15");
    cmbBreakDuration.addItem("30 minutes", "30");
    cmbBreakDuration.addItem("45 minutes", "45");
    cmbBreakDuration.addItem("1 hour",     "60");
    cmbBreakDuration.selectItem(1);  
    cmbBreakDuration.onchange = function(v) {
      breakDuration = v;
      //alert(v);
    };
    // Snap break to stop
    rowPos++;
    yPos   = y_offset+(label_height * rowPos)+(padding*rowPos)+rowPos;
    cbBreakToStop = domapi.Imagecheckbox({parent:optionWnd, x:indent, y:yPos, text:"Break at nearest stop", h:label_height, w:228});    schedBreak.setState((scheduledBreak ? "checked" : "cleared"));
    cbBreakToStop.setState((breakAtStop ? "checked" : "cleared"));
    cbBreakToStop.setAlign("left");
    cbBreakToStop.setBgColor("white");
    cbBreakToStop.onchange = function() {
      breakAtStop = (this.checkedState == "checked");
    }
  } 
};

// general application initialisation
function init() {  
  // RPC defaults
  domapi.rpc.ontimeout    = mgTimeoutHandler;
  domapi.rpc.charset      = "UTF-8";
  domapi.rpc.manageCursor = false;
  domapi.rpc.doDebug = false;
  // lang
  lang = 'en_UK';
  // define image size
  screenW = domapi.bodyWidth();
  screenH = domapi.bodyHeight();
  // create layout
  loadFramework();
  // create "wait" object 
  var waitW = 230;
  var waitH = 60;
  // Container
  waitMsg = domapi.Elm({position:"absolute", 
                               w:waitW, 
                               h:waitH, 
                               x:(screenW/2)-(waitW/2), 
                               y:(screenH/2)-(waitH/2)});
  waitMsg.setBgColor("lightgrey");   
  waitMsg.style.border="1px solid black";   
  // Label
  waitLbl = domapi.Label({parent:waitMsg,
                        position:"relative", 
                               w:waitW, 
                               h:waitH, 
                               x:5, 
                               y:5});
  waitLbl.setBgColor("lightgrey");   
  waitLbl.setTextAlign("center");
  waitLbl.setText(strings['Creating drive times... Please wait...']);
  // create progress bar
  npProgress = domapi.Progressbar({parent:waitMsg, x:5, y:30, w:waitW-10, max:100});
  // hide it
  hideSpinner();
  // create map
  map = new mgMap(config_url);  
};

// loads DomAPI
function loadDOMAPI()
{
  // load DOMAPI functions
  domapi.loadUnit("pagecontrol");
  domapi.loadUnit("rpccore");
  domapi.loadUnit("coreutil");
  domapi.loadUnit("progressbar");  
  domapi.loadUnit("window");  
  domapi.loadUnit("imagecheckbox");  
  domapi.loadUnit("button");
  domapi.loadUnit("spinedit");
  domapi.loadUnit("label");
  domapi.loadUnit("combobox");
  domapi.loadUnit("listgrid");
};

function mgTimeoutHandler(packet)
{
  var buttons = [mbOK];
  domapi.messageDlg({title:"Mercator GeoSystems", message:"Sorry, a problem was encountered and we could not calculate this route", kind:mtInformation, buttons:buttons});  
};

// a map class
function mgMap(config_url)
{
  // save config url
  this.config_url = config_url;
  // create array of points
  this.m_points_x = [];
  this.m_points_y = [];
  // create the initial google map
  this.gmap = new GMap2(document.getElementById("mapDiv"), {draggableCursor : 'pointer'});
  //gmap.disableDragging();
  //gmap.disableInfoWindow();
  this.gmap.addControl(new GLargeMapControl());
  this.gmap.addControl(new GMapTypeControl());
  this.gmap.setCenter(new GLatLng(map_y, map_x), map_zoom);  
  // register click handler
  GEvent.addListener(this.gmap, "click", mapClickHandler);
  // create base icon
  baseIcon = new GIcon();
  baseIcon.shadow = "http://www.google.com/mapfiles/shadow50.png";
  baseIcon.iconSize = new GSize(20, 34);
  baseIcon.shadowSize = new GSize(37, 34);
  baseIcon.iconAnchor = new GPoint(9, 34);
  baseIcon.infoWindowAnchor = new GPoint(9, 2);
  baseIcon.infoShadowAnchor = new GPoint(18, 25);
};

// handle google map clicks
function mapClickHandler (marker, point) {
  // temp. disable clicking on existing points
  if (marker) {
    // map.gmap.removeOverlay(marker);
  } else {
    // not exceeded limit (
    if (map.m_points_x.length >= max_points) {
      alert(strings['Sorry, no more map locations allowed']);
    } else {
      //alert(point.x + "," + point.y);
      // save point
      map.m_points_x.push(point.x);
      map.m_points_y.push(point.y);
      // Create an icon for this point using our icon class
      var icon = new GIcon(baseIcon);
      // if this is the first location and no postcodes have been selected
      var pcForm = document.getElementById('pcForm');
      //if ((map.m_points_x.length == 1) && (pcForm.postcode_area.value.length == 0)) {
      //  // then use a special marker for the 'base'
      //  icon.image = "icons/icon_base.png";
      //} else {
      //  icon.image = "icons/icon.png";        
      //}
      icon.image = "icons/icon" + map.m_points_x.length + ".png";
      // add marker
      map.gmap.addOverlay(new GMarker(point, icon));
    }
  }
};

// resets all controls to their initial state
function resetApp()
{
  // reset forms
  document.getElementById('pcForm').reset();
  document.getElementById('placeForm').reset();
  // removes google map markers
  map.gmap.clearOverlays();
  // clear points array
  map.m_points_x = [];
  map.m_points_y = [];
};


// change the progress bar to make it look like something is happening
function updateProgress()
{
  // get current value
  var curr_val = npProgress.progress;
  // if value is 100, and we're going 'up'
  if ((curr_val == 100) && (progressInc == 1))
  {
    // start going down 
    progressInc = -1;
  } 
  // if value is 0, and we're going 'down'
  if ((curr_val == 0) && (progressInc == -1))
  {
    // start going up
    progressInc = 1;
  } 
  // add increment to it
  npProgress.setProgress(curr_val + progressInc);
};


// allow the user to select individual waiting times
function showWaitTimeDlg()
{
  var window_width  = 320;
  var window_height = 270;
  var x_padding = 4;
  var y_padding = 8;
  var y_offset = 24;
  waitTimesWnd = domapi.Window({
    text:'Waiting Times',
    x:150,y:40,
    w:window_width,h:window_height,
    visibleButtons:wbClose});
  waitTimesWnd.setModal(false);
  waitTimesWnd.allowResize(false);    
  // Build list grid
  lgWaitTimes = domapi.Listgrid({parent:waitTimesWnd,x:x_padding,y:y_offset,w:window_width - (x_padding * 2),h:200, doVirtualMode:false, doAllowEdit:true});  
  lgWaitTimes.loadFromJsonUrl("waitTimeCols.txt");
  // Add rows
  lgWaitTimes.clear();
  lgWaitTimes.beginUpdate();
  // build a list of locations
  // Add postcodes
  var pcForm = document.getElementById('pcForm');
  // which split string?
  var split_string = "\n";
  if(domapi.browser == btIExplore) {
    split_string = "\r\n";
  } 
  // get text area contents and split on CR/LF
  var postcodes = pcForm.postcode_area.value.split(split_string);
  // add to list one by one       
  // skip first one as this is base
  for (var e=1;e<postcodes.length;e++) {
    // Has the string any length?  i.e it's not the trailing line after a CR
    if (postcodes[e].length > 0) {
      // test the postcode
      if (postit(postcodes[e])) {
        lgWaitTimes.addRow(postcodes[e]+","+waitTime+","+"pc"+(e+1));  // add one as index is one based below
      } else {
        // get out of here!
        return;
      }
    }
  }
  // Add map locations
  for (var m=0;m<map.m_points_x.length;m++) {
    // skip first one as this is base
    if (m==0 && (lgWaitTimes.data.getRowcount() == 0)) {
      // skip 
    } else {
      // add map location
      var locName = "Map ("+(m+1)+")";
      lgWaitTimes.addRow(locName+","+waitTime+","+"map"+m);
    }
  }
  // Add places
  var placeForm = document.getElementById('placeForm');
  for (e=0;e<placeForm.elements.length;e++) {
    var placeName = "";
    // if a select list
    if (placeForm.elements[e].id.substr(0, 5) == 'place') {
      var box = document.getElementById(placeForm.elements[e].id);
      placeName = box.options[box.selectedIndex].innerHTML;    
    } else {
      placeName = placeForm.elements[e].value;    
    }
    // OK?
    if (placeName.length > 0 && placeName != 'Not Set *') {
      lgWaitTimes.addRow(placeName+","+waitTime+","+"place"+(e+1));  // one based index below
    }
  }
  // end adding items
  lgWaitTimes.endUpdate();
  lgWaitTimes.refresh();
  // Create 'go' buttom
  var btnWidth = 80;
  var btnHeight = 50;
  var btnDtGo = domapi.Button({parent:waitTimesWnd, x:(window_width / 2) - (btnWidth / 2), y:window_height - y_padding - (btnHeight/2), h:btnHeight, w:btnWidth, text:"Go"});
  btnDtGo.onclick = function() { 
    // close window and submit request
    waitTimesWnd.close(); 
    submitRequest(); 
  };
  // Set window height
  //alert(lgWaitTimes.getH() + y_offset + (y_padding * 2));
  //waitTimesWnd.setH(lgWaitTimes.getH() + y_offset + (y_padding * 2));
};

// Submit actual request
function submitRequest()
{
  // build DomAPI RPC packet
  var myPacket = new domapi.RPCPacket({url:"mgDeliveryServer.php",statusText:strings["Getting drive time..."]});
  // add basic parms
  myPacket.data.add("config_url", map.config_url);
  myPacket.data.add("lang",       lang);    
  // Add map locations
  for (var m=0;m<map.m_points_x.length;m++) {
    myPacket.data.add("map_x[]", map.m_points_x[m]);
    myPacket.data.add("map_y[]", map.m_points_y[m]);
  }
  // Add postcodes
  var pcForm = document.getElementById('pcForm');
  // which split string?
  var split_string = "\n";
  if(domapi.browser == btIExplore) {
    split_string = "\r\n";
  } 
  // get text area contents and split on CR/LF
  var postcodes = pcForm.postcode_area.value.split(split_string);
  // add to packet one by one       
  for (e=0;e<postcodes.length;e++) {
    // Has the string any length?  i.e it's not the trailing line after a CR
    if (postcodes[e].length > 0) {
      // test the postcode
      if (postit(postcodes[e])) {
        // ok, carry on building packet
        myPacket.data.add("pc"+(e+1), postcodes[e]);    
      }
      else 
      {
        // Get out of here!
        return;
      }
    }
  }
  // Add places
  var placeForm = document.getElementById('placeForm');
  for (e=0;e<placeForm.elements.length;e++) {
    // if a select list
    if (placeForm.elements[e].id.substr(0, 5) == 'place') {
      var box = document.getElementById(placeForm.elements[e].id);
      myPacket.data.add(placeForm.elements[e].id, box.options[box.selectedIndex].value);    
    } else {
      myPacket.data.add(placeForm.elements[e].id, placeForm.elements[e].value);    
    }
  }
  // submit the options
  // return to base
  myPacket.data.add("rtb", (returnToBase ? "yes" : "no"));
  // wait time
  myPacket.data.add("wait_time", waitTime);
  // start time
  myPacket.data.add("start_time", startTime);
  // Optimise on distance?
  myPacket.data.add("use_distance", (optimiseOnDistance ? "yes" : "no"));
  // Break details
  myPacket.data.add("scheduled_break",  (scheduledBreak ? "yes" : "no"));
  myPacket.data.add("break_start_time", breakStartTime);
  myPacket.data.add("break_duration",   breakDuration)    
  myPacket.data.add("break_at_stop",    (breakAtStop ? "yes" : "no"));
  // individual waiting times
  myPacket.data.add("indiv_wait_times", (indivWaitTimes ? "yes" : "no"));
  if (indivWaitTimes) {
  	// Got a first drop already?
  	var got_first_drop = false;
    // add values
    for (var r=0;r<lgWaitTimes.data.getRowcount();r++) {
      // get wait time & key
      myPacket.data.add("wait_" + lgWaitTimes.data.getValue(r,2), lgWaitTimes.data.getValue(r,1));
      // Fixed drop position?
      var firstdrop = lgWaitTimes.data.getValue(r,3);
      if (firstdrop == true) {
      	if (got_first_drop == true) {
       	  // Mesasge
       	  var buttons = [mbOK];
       	  domapi.messageDlg({title:"Mercator GeoSystems", message:"Only one location can be the 'First Drop'", kind:mtWarning, buttons:buttons});  
       	  // Get out!
       	  return;
      	} else {
      	  // add to payload
      	  myPacket.data.add("first_drop", lgWaitTimes.data.getValue(r,2));
      	  // save flag
      	  got_first_drop = true;
      	}
      }
    }
  }
  // add timestamp as a unique identifier (tag)
  plan_tag = "" + Math.round(new Date().getTime() * Math.random());
  myPacket.data.add("tag", plan_tag);
  // show spinner
  showSpinner();
  // send packet
  domapi.rpc.sendPacket({packet:myPacket, timeout:600, onreceive:resultHandler});      ' 10 minute timeout'
  // setup progress bar
  npProgress.setProgress(0);
  // disable go & reset
  btnGo.setEnabled(false);
  btnReset.setEnabled(false);
  // start progress bar moving 
  interval_id = setInterval(updateProgress, 25);  
};

// Get the drive time for this journey
function getDriveTime()
{
  // Must have at least two locations
  if ((pagecontrol.tabIndex == 0) && (map.m_points_x.length < 2)) {
    var buttons = [mbOK];
    domapi.messageDlg({title:"Mercator GeoSystems", message:"You must choose at least two locations", kind:mtWarning, buttons:buttons});
  } else {
    // Allowing individual waiting times>?
    if (indivWaitTimes) {
      showWaitTimeDlg();
    } else {
      submitRequest(); 
    }
  }    
};

// when a location has been found this is called
function resultHandler(packet)
{
  // stop updating progress
  clearInterval(interval_id);
  // hide progress stuff
  hideSpinner();
  // reset progress bar (for next run)
  npProgress.setValue(0);
  // enable go & reset
  btnGo.setEnabled(true);
  btnReset.setEnabled(true);
  // did anything go wrong?
  var error_message = packet.data.findValueByName("error_message");
  if (error_message.length > 0) {
    alert(error_message);
  } else {      
    // get results
    var map_url    = packet.data.findValueByName("map_url");
    var route_time = packet.data.findValueByName("route_time");
    var total_time = packet.data.findValueByName("total_time");
    var drive_time = packet.data.findValueByName("drive_time");
    var time_units = packet.data.findValueByName("time_units");
    var drive_dist = packet.data.findValueByName("drive_dist");
    var dist_units = packet.data.findValueByName("dist_units");
    // display appropriate message
    var buttons = [mbOK];
    var retMsg = (returnToBase ? "round trip " : "");
    //var msg = "Total " + retMsg +"journey time is " + drive_time + " " + time_units + " (" + drive_dist + " " + dist_units + ")<br><br>";
    var msg = "Total " + retMsg +"journey time is " + total_time + " (" + drive_dist + " " + dist_units + ")<br><br>";
    // get pdf url
    var pdf_url = packet.data.findValueByName("pdf_url");
    msg = msg + 'Click <a href="' + pdf_url + '" target="_blank">here</a> for a printable PDF itinerary.';
    msg = msg + '<br/>';
    // get CSV url
    var csv_url = packet.data.findValueByName("csv_url");
    msg = msg + 'Click <a href="' + csv_url + '" target="_blank">here</a> for a CSV itinerary.';
    msg = msg + '<br/>';
    // map URL
    msg = msg + 'Click <a href="' + map_url + '" target="_blank">here</a> for a map of the route.';
    domapi.messageDlg({title:"Mercator GeoSystems", message:msg, kind:mtInformation, buttons:buttons});
  }
};