/**The objects that the walklist will be dealing with
  *OOP ideas from http://www.webreference.com/js/column79/3.html
  */

document.write('<script type="text/javascript" src="basic_functions.js"></script>');

document.write('<script type="text/javascript" src="Base.js"></script>');
document.write('<script type="text/javascript" src="map_extensions.js"></script>');
document.write('<script type="text/javascript" src="iFrame_draggable.js"></script>');

/**
 * Controller Object: Handles the map, form, and walklist
 */

function ControllerClass() {

    this.mapControl= null;
    this.walklist = null;
    this.controlForm= null;
    this.autoSorter = null;
    this.region=null; 

    this.setMode = setMode;
    this.inRegionMode = inRegionMode;
    this.inClickMode = inClickMode;
    this.inBrowseMode = inBrowseMode;
    this.updateSelButtons = updateSelButtons;
    this.getMap=getMap;
    this.getMapControl=getMapControl;
    this.getRegion = getRegion;
    this.getSorter = getSorter;
    this.getControlForm = getControlForm;
    this.getWalklist = getWalklist;
    this.initializeRegion = initializeRegion;
    this.startNewRegion = startNewRegion;
    this.deleteRegion = deleteRegion;
    this.initialize = initialize;

    this.initialize();

  function initialize() {

    var dims=getWindowDimensions();

    /* Room for scroll bars */
    this.totalWidth=dims[0]-16;
    this.totalHeight=dims[1]-16;
    
    var tblMapData=document.getElementById("entireMapCell");
    var width=Math.floor(this.totalWidth * .65) - 20;
    /** TODO: more exact measure **/
    var height=Math.floor(this.totalHeight * .9);
    var gs=new GSize(width,height);

    this.mapControl= new MapControllerClass(this,new GMap2(document.getElementById("mainMap"),{size: gs, draggableCursor: 'crosshair'}));
    this.walklist = new WalklistClass(this);
    this.controlForm= new ControlFormClass(this,document.getElementById("controllingForm"));
    this.autoSorter = new AutoAddressSorter(this);
    this.region=null;
    this.mapMode;/* begin as undefined */

    /*initailize to map mode*/
    this.setMode(DEFAULT_MAP_MODE);
    this.controlForm.initialize();

  }
     
  function getMap() {
    return this.getMapControl().map;
  }
  
  function getMapControl() {
    return this.mapControl;
  }

 function getRegion() {
    return this.region;
 }
 
 function getControlForm() {
    return this.controlForm;
 }

 function getWalklist() {
    return this.walklist;
 }

 function getSorter() {
    return this.autoSorter;
 }
 
 function updateSelButtons(source) {
    this.controlForm.updateSelButtons(source);
 }
 
 
 function inRegionMode() {
    return (this.mapMode=="region");
 }

 function inClickMode() {
    return (this.mapMode=="click");
 }

 function inBrowseMode() {
    return (this.mapMode=="browse");
 }
 
 function initializeRegion() {
    this.region = new RegionClass(this,this.getMap());
    return this.region;
 }
 
 /**
  * Start a new region, and save the old region
  */
 function startNewRegion(point) {
    this.region.hideMarkers();
    this.initializeRegion();
    this.region.pushMarker(point);
    return this.region;
 } 
 
 function deleteRegion() {
    if (this.region) {
        this.region.hideMarkers();
        this.region=null;
    }
 }
 
  function setMode(mapMode) {
    if ((typeof this.mapMode == "undefined")  || this.mapMode != mapMode) {
        if (mapMode == "browse") {
            if (this.region!=null) this.deleteRegion();
            this.walklist.deselectAll();
        } else if (mapMode == "region") {
             if (this.region==null) this.region=this.initializeRegion();
        } else if (mapMode == "click") {
            if (this.region!=null) this.deleteRegion();
        } else {
            alert("Illegal map mode: " + mapMode);
        }
        this.mapMode = mapMode;
        this.controlForm.setMode(mapMode);
        this.controlForm.updateSelButtons();
        /** When GMaps adds functionality to change draggableCursor property dynamically, the code goes here **/
    }
  }
    
}

/*************************************************/


function RegionClass(inp_parentController,inp_map) {
    this.map = inp_map;
    this.parentController=inp_parentController;
    this.markers = new Array();
    this.polyLines = new Array();
    this.numOfMarkers=0;
    this.connected=false;
    
    this.hideMarkers=hideMarkers;
    this.pushMarker=pushMarker;
    this.popMarker=popMarker;
    this.completeRegion=completeRegion;
    this.getStartingMarker = getStartingMarker;
    this.numVertices = numVertices;
    this.isConnected = isConnected;
    this.getPoints = getPoints;
     
    function getStartingMarker() {
        if (this.numOfMarkers>0) {
            return this.markers[0];
        }
        else return false;
    }

    /* Return the number of vertices */
    function numVertices() {
        if (this.isConnected()) {
            return this.numOfMarkers - 1;
        } else return this.numOfMarkers;
    }
    
    /* Return an array of points */
    function getPoints() {
        var m = this.numOfMarkers;
        if (this.isConnected()) m--;
        pts = new Array();
        for (var i=0;i<m;i++) {
            pts[i]=this.markers[i].getPoint();
        }
        return pts;
    }
    

    /* Whether the region is connected */
    function isConnected() {
        return this.connected;
    }
      
    function pushMarker(point) {
        if (this.isConnected()) {
            this.parentController.startNewRegion(point);
            this.parentController.updateSelButtons("region");
        } else {
            var ico;
            /** pick icon **/
            if (this.numOfMarkers==0) {
                ico=newStartingIcon();
            } else {
                ico=newMiddleIcon();
            }
            
            /** in all cases, add the marker to array **/
            var marker=new GMarker(point,ico);
            this.markers[this.numOfMarkers]=marker;
            this.numOfMarkers++;

            /**If not the final marker, then we need to place the point **/
            if (this.numOfMarkers==1 || point!==this.getStartingMarker().getPoint()) {
                this.map.addOverlay(marker);
            }
            
            /**TODO: switch color of segments on connect **/
            
            /** We may need to overlay a line and delete marker**/
            if (this.numOfMarkers>1) {
                /**Overlay line**/
                var pts = new Array(2);
                pts[0]=this.markers[this.numOfMarkers-1].getPoint();
                pts[1]=this.markers[this.numOfMarkers-2].getPoint();
                var pl = new GPolyline(pts,  "#FF0000",  5,  .5);
                this.polyLines[this.numOfMarkers-2]=pl;
                this.map.addOverlay(pl);
                /**Remove marker from map**/
                if (this.numOfMarkers>2) {
                    this.map.removeOverlay(this.markers[this.numOfMarkers-2]);
                }
            }
            conditionalPan(this.map,marker.getPoint());
            /** Update buttons and properties **/
            this.parentController.updateSelButtons("region");
        }
    }

    function completeRegion() {
        if (this.numOfMarkers>0) {
            var firstPoint=this.markers[0].getPoint();
            this.pushMarker(firstPoint);
            this.connected=true;
            this.parentController.getWalklist().selectRegion(this);
            this.parentController.updateSelButtons("region");
        }
    }

    function popMarker(marker) {
        if (this.isConnected()) {
            this.parentController.getWalklist().undo();
        }
        if (this.numOfMarkers>0) {
            this.map.removeOverlay(this.markers[this.numOfMarkers-1]);
            if (this.numOfMarkers>1) {
                this.map.removeOverlay(this.polyLines[this.numOfMarkers-2]);
                conditionalPan(this.map,this.markers[this.numOfMarkers-2].getPoint());
                if (this.numOfMarkers>2) {
                    this.map.addOverlay(this.markers[this.numOfMarkers-2]);
                }
            }
            this.numOfMarkers--;
        }
        this.connected=false;
        this.parentController.updateSelButtons("region");
    }
    
    function hideMarkers() {
        if (this.numOfMarkers>0) {
            for(var i=0; i<this.numOfMarkers; i++) {
                this.map.removeOverlay(this.markers[i]);
                if (this.polyLines[i]) this.map.removeOverlay(this.polyLines[i]);
            }
            //this.numOfMarkers=0;
            this.connected=false;
            this.parentController.updateSelButtons("region");
        }
    }

}

/*************************************************/

function ControlFormClass(inp_parentController,inp_form) {
  this.parentController=inp_parentController;
  this.form = inp_form;
  this.addressContainer=null;
  this.iframeController=null;
  this.undoWhat="order";
  this.asgDisplay=DEFAULT_DISPLAY_ASSIGNED;
  this.unaDisplay=DEFAULT_DISPLAY_UNASSIGNED;
  
  this.setMode = setMode;
  this.updateSelButtons = updateSelButtons;
  this.updateRouteInfoProperties = updateRouteInfoProperties;
  this.setStatusText = setStatusText;
  this.btnUndoMarkerClick = btnUndoMarkerClick;
  this.setRouteColor = setRouteColor;
  this.initialize = initialize;
  this.setNumRoutes = setNumRoutes;
  this.addHiddenValue = addHiddenValue;
  this.btnAssignRouteClick=btnAssignRouteClick;
  this.btnUndoClick = btnUndoClick;
  this.btnSortAddressesClick = btnSortAddressesClick;
  this.btnExportClick = btnExportClick;
  this.sortRouteAddresses = sortRouteAddresses;
  this.sortSelectedAddresses = sortSelectedAddresses;
  this.selRouteInfoChange = selRouteInfoChange;
  this.updatePrintHeader = updatePrintHeader;
  this.selAssignRouteChange = selAssignRouteChange;
  this.selSelRouteChange = selSelRouteChange;
  this.getShowAssigned = getShowAssigned;
  this.getShowUnassigned = getShowUnassigned;
  this.getActiveRoute = getActiveRoute;
  this.setAsgDisplayMode = setAsgDisplayMode;
  this.setUnaDisplayMode = setUnaDisplayMode;
  this.moveSelectedPoints = moveSelectedPoints;
  this.infoPointsZoomClick = infoPointsZoomClick;
  this.toggleOptions = toggleOptions;
  this.expandOptions = expandOptions;
  this.shrinkOptions = shrinkOptions;
 
  function initialize() {
    this.form.txtNumRoutes.value=DEFAULT_NUM_ROUTES;
    this.setNumRoutes();
    this.selRouteInfoChange();
    
    /** Initialize colors **/
    var selRC=this.form.selRouteColor;
    selRC.length=0;
    for(var i=0; i<COLORS_ASSIGN.length; i++) {
        selRC.options[selRC.length]= new Option(COLORS_ASSIGN[i],COLORS_ASSIGN[i],false,false);
    }
    
    /** Points frame **/
    var pointsFrame = document.getElementById("pointsFrame");
    var tdPointsFrame = document.getElementById("tdPointsFrame");
    //expand height
    var trOptions = document.getElementById("trOptions");
    var allControlsCell = document.getElementById("allControlsCell");
    var heightLeft = (allControlsCell.offsetTop + allControlsCell.offsetHeight) - (trOptions.offsetTop + trOptions.offsetHeight);
    var minHeight = document.getElementById("tdPointsFrame").clientHeight;
    //expand width
    var minWidth = document.getElementById("tdPointsFrame").clientWidth;
    var tblPointsButtons = document.getElementById("tblPointsButtons"); var tdOuterPointsButtons = document.getElementById("tdOuterPointsButtons");
    var pbOffsetLeft = tdOuterPointsButtons.offsetLeft + tblPointsButtons.offsetLeft;
    var widthLeft = (allControlsCell.offsetLeft + allControlsCell.offsetWidth) - (pbOffsetLeft + tblPointsButtons.offsetWidth) - 30;
    //alert(tdOuterPointsButtons.offsetLeft + " " + tblPointsButtons.offsetLeft + " " + tblPointsButtons.offsetWidth);

    pointsFrame.style.height = Math.max(minHeight,heightLeft + tdPointsFrame.offsetHeight) + "px";
    //pointsFrame.style.width  = Math.max(minWidth,widthLeft + tdPointsFrame.offsetWidth)+ "px";
    //tdPointsFrame.style.width=pointsFrame.style.width;

    this.form.selSortAddresses.length=0;
    this.form.selSortAddresses.options[0]=new Option("Entire route","1",true,true);
    this.form.selSortAddresses.options[1]=new Option("Only selected","2",false,false);
    
  }

    function moveSelectedPoints(dest) {
        if (this.iframeController)  this.iframeController.moveSelectedPoints(dest);
    }

  function setAsgDisplayMode(val) {
    var radAsg=this.form.radShowAssigned;
    if (radAsg) {
        for(var i=0; i<radAsg.length; i++) {
            if (radAsg[i].value==val)
                radAsg[i].checked=true;
                this.asgDisplay=val;
        }
        this.parentController.getWalklist().updateDisplayOptions();    
        this.parentController.getWalklist().refreshIcons();
        this.updatePrintHeader();
        this.updateSelButtons();
    }
  }

  function setUnaDisplayMode(val) {
      
    var radUna=this.form.radShowUnassigned;
    if (radUna) {
        for(var i=0; i<radUna.length; i++) {
            if (radUna[i].value==val)
                radUna[i].checked=true;
                this.unaDisplay=val;
        }
        this.parentController.getWalklist().updateDisplayOptions();
        this.parentController.getWalklist().refreshIcons();
        this.updateSelButtons();
    }
  }

  function getActiveRoute() {
    return selectedValue(this.form.selRouteInfo);
  }

  function getShowAssigned() {
    var radAsg=this.form.radShowAssigned;
    if (typeof radAsg=='undefined') return DEFAULT_DISPLAY_ASSIGNED;
    for(var i=0; i<radAsg.length; i++) {
        if (radAsg[i].checked)  return(radAsg[i].value);
    }
    return false;
  }

  function getShowUnassigned() {
    var radUna=this.form.radShowUnassigned;
    if (typeof radUna=='undefined') return DEFAULT_DISPLAY_UNASSIGNED;
    for(var i=0; i<radUna.length; i++) {
        if (radUna[i].checked)  return(radUna[i].value);
    }
    return false;
  }

 function setRouteColor() {

    var selRI=selectedValue(this.form.selRouteInfo);
    var selRC=selectedValue(this.form.selRouteColor);
    if (selRI.length>0 && selRC.length>0)
        this.parentController.getWalklist().setColorToRoute(parseInt(selRI),selRC);
    this.updateSelButtons();
 }
 
 function selAssignRouteChange() {
    var selVal=selectedValue(this.form.selAssignRoute);
    if (selVal.length>0 && selVal!="None") {
        setSelected(this.form.selRouteInfo,selVal);
        this.selRouteInfoChange();
    }
 }
 
 function selSelRouteChange() {
    var selVal=selectedValue(this.form.selSelRoute);
    var valid=true; var infoVal;
    if (selVal.length>0) {
        if (selVal.substring(0,5)=="route") {
            var routeNum=parseInt(selVal.substring(6));
            if (!(isNaN(routeNum))) {
                infoVal=routeNum;
            } else {
                alert("Bad value: " + selVal);
                valid=false;
            }
        } else {
            if(selVal=="unassigned") {
                routeNum="0";
                infoVal="";
            } else if (selVal=="all points") {
                routeNum="-1";
                infoVal="";
            } else if (selVal=="no points") {
                routeNum="-2";
                infoVal="";
            } else {
                alert("Bad value: " + selVal);
                valid=false;
            }
        }
        if (valid) {
            setSelected(this.form.selRouteInfo,infoVal);
            this.parentController.getWalklist().selectRoute(routeNum);
            this.selRouteInfoChange();
        }
    }
     this.updateSelButtons();
 }
  
 function selRouteInfoChange() {
    var routeNum=selectedValue(this.form.selRouteInfo);
    if (routeNum.length==0) {
        document.getElementById("tblRouteInfo").style.visibility="hidden";
    } else {
        routeNum=parseInt(routeNum);
        document.getElementById("tblRouteInfo").style.visibility="visible";
        this.updateRouteInfoProperties();
    }
    this.updatePrintHeader();
    this.parentController.getWalklist().updateDisplayOptions();
    this.parentController.getWalklist().refreshIcons();
    this.updateSelButtons();
    document.getElementById("printHeader").firstChild.value
 }
 
 function updatePrintHeader() {
    var routeNum=selectedValue(this.form.selRouteInfo);
    /** Printing header change **/
    document.getElementById("printHeader").removeChild(document.getElementById("printHeader").firstChild);
    if (routeNum.length>0 && this.parentController.getWalklist().displayAssigned=="Active") {
        document.getElementById("printHeader").appendChild(document.createTextNode("Map of Route " + routeNum));
    } else {
        document.getElementById("printHeader").appendChild(document.createTextNode("General Map"));
    }
 }
 
 function infoPointsZoomClick() {
    var routeNum=selectedValue(this.form.selRouteInfo);
    if (routeNum.length>0) this.parentController.getMapControl().centerMap(parseInt(routeNum));
 }
 
 /** Call whenever the current route changes or an assignment occurs **/
 function updateRouteInfoProperties() {
    var routeNum=selectedValue(this.form.selRouteInfo);
    if (routeNum.length>0) {
        routeNum=parseInt(routeNum);
        rc = this.parentController.getWalklist().getColorFromRoute(routeNum);
        if (typeof rc == 'undefined' || !rc) rc = COLORS_ASSIGN[(routeNum-1) % COLORS_ASSIGN.length];
        setSelected(this.form.selRouteColor,rc,strict=true);
        var addrs=this.parentController.getWalklist().getRouteAddresses(routeNum);
        var numOfPoints=document.getElementById("numOfPoints");
        if (numOfPoints.hasChildNodes()) numOfPoints.removeChild(numOfPoints.childNodes[0]);
        document.getElementById("numOfPoints").appendChild(document.createTextNode(addrs.length));
        this.form.infoPointsZoom.disabled = (addrs.length==0);
        
        var pointsFrame = window.frames["pointsFrame"].document;
        var pointsFrameBody = pointsFrame.body;
        addrs.sort(this.parentController.getWalklist().sortByIconNum);
        this.addressContainer = pointsFrame.getElementById("DragContainerID");
        if (typeof this.addressContainer == "undefined" || this.addressContainer==null) {
            this.iframeController=new IFrameControlClass(this,window.frames["pointsFrame"]);
            this.addressContainer = AddressContainerClass(this.iframeController,this.parentController.getWalklist(),routeNum);
        }
        else this.addressContainer.routeNum=routeNum;
        
        /** Remove existing addresses **/
        while(this.addressContainer.hasChildNodes()) {
            this.addressContainer.removeChild(this.addressContainer.firstChild);
        }
        
        /**Add current addresses **/
        this.addressContainer.style.visibility=(addrs.length==0) ? "hidden" : "visible";
        for (var i = 0; i < addrs.length; i++) {
            var addr = addrs[i];
            var node = AddressNodeClass(this.iframeController,addr);
            addr.setIFrameNode(node);
            this.addressContainer.appendChild(node);
        }
        pointsFrameBody.appendChild(this.addressContainer);        
        
        this.iframeController.createDragContainer(this.addressContainer);

    }
 }
 
 function btnAssignRouteClick() {
    var routeNum=selectedValue(this.form.selAssignRoute);
    var changed=false;
    if (routeNum.length>0 && routeNum=="None") {
        this.parentController.getWalklist().deAssignSelected();
        changed=true;
        this.setStatusText("Points de-assigned.");   
    } else if (routeNum.length>0) {
        if (selectedValue(this.form.selRouteInfo)!=selectedValue(this.form.selAssignRoute)) {
            setSelected(this.form.selRouteInfo,routeNum,true);
            this.updateRouteInfoProperties();
        }
        var col = selectedValue(this.form.selRouteColor);
        routeNum = parseInt(routeNum);
        /* This is triggered when the user doesn't change the default color */
        if (typeof this.parentController.getWalklist().getColorFromRoute(routeNum) =="undefined"&& 
            selectedValue(this.form.selRouteInfo)==selectedValue(this.form.selAssignRoute)) {
            this.parentController.getWalklist().setColorToRoute(routeNum,col);
        }
        this.parentController.getWalklist().assignSelectedTo(routeNum);
        changed=true;
        this.setStatusText("Assign further walklist routes or change the order of this route.");   
    }
    if (changed) {
        this.parentController.getWalklist().deselectAll();
        this.parentController.deleteRegion();
        this.updateRouteInfoProperties();
        this.updateSelButtons("assignRoute");
        this.parentController.getMapControl().centerMap(routeNum);
    }
 }
 
 function btnUndoClick() {
    if (this.undoWhat=="order") {
        this.parentController.getWalklist().undo();
        this.updateRouteInfoProperties();
        this.updateSelButtons();
    }
    else if (this.undoWhat=="marker") {
        this.btnUndoMarkerClick();
    } else {
        alert("Illegal undo: " + this.undoWhat);
    }
 }

 function btnUndoMarkerClick() {
    this.parentController.getRegion().popMarker();
 }

 function btnExportClick() {
     /** Adapted from: http://www.quirksmode.org/js/croswin.html **/
     if (typeof popupWindow == 'undefined')
        var popupWindow=null;
     if (popupWindow!=null && !popupWindow.closed && popupWindow.location) {
        popupWindow.location.href="field_match.php?_export_flag=1";
     } else {
        popupWindow=openPopup(true);
        if (!popupWindow.opener) newwindow.opener = self;
     }
     
     /** Change form's state **/
     var oldAction = this.form.action;
     this.form.target="popUpWindow";
     this.form.action="field_match.php";
     this.form.enctype="";

    /**Write the data **/
     var infoArrays=this.parentController.getWalklist().exportToPHP();
     this.addHiddenValue("_export_flag","1");
     this.addHiddenValue("exportDataRoute",infoArrays[0]);
     this.addHiddenValue("exportDataIcon",infoArrays[1]);
     
     this.form.submit();
     
     if (window.focus) {popupWindow.focus()}
     
     /** Reset Form **/
     this.form.enctype="multipart/form-data";
     this.form.action=oldAction;
     this.form.target="";
 }

 function addHiddenValue(name,value) {
    var fld=document.createElement("input");
    var app=true;
    for(var i=0;i<this.form.childNodes.length; i++) {
        if (this.form.childNodes[i].name==name) {
            fld=this.form.childNodes[i];
            app=false;
        }
    }
    fld.name=name;
    fld.type="hidden";
    fld.value=value;
    if (app) this.form.appendChild(fld);
 }

 function setNumRoutes() {
    
    var numRoutes=this.form.elements["txtNumRoutes"].value;
    if (numRoutes.length>0 && checkLong(numRoutes,1,99)) {
        numRoutes = parseInt(numRoutes); /** convert to integer **/
        var selAR=this.form.elements["selAssignRoute"];
        var selRI=this.form.elements["selRouteInfo"];
        var selARsi = selAR.selectedIndex; var selRIsi = selRI.selectedIndex;
        selAR.length=0; selRI.length=0;
        this.form.selAssignRoute.options[0]=new Option("","",true,false);
        this.form.selAssignRoute.options[1]=new Option("None","None",false,false);
        this.form.selRouteInfo.options[0]=new Option("","",true,false);
        for(var i=1;i<=numRoutes;i++) {
            selAR.options[selAR.length]= new Option(i,i,false,(selARsi == i));
            selRI.options[selRI.length]= new Option(i,i,false,(selRIsi == i));
        }
    }
    this.updateSelButtons();
 }
 
 function btnSortAddressesClick() {
    var whichSort=selectedValue(this.form.selSortAddresses);
    if (whichSort=="1") this.sortRouteAddresses();
    else if (whichSort=="2") this.sortSelectedAddresses();
    else alert("Bad sort selection " + whichSort);
 }
 
 function sortRouteAddresses() {
 
    var sorter = this.parentController.getSorter();
    var routeNum = selectedValue(this.form.selRouteInfo);
    if (routeNum>0) {
        var addrs=this.parentController.getWalklist().getRouteAddresses(routeNum);
        addrs.sort(this.parentController.getWalklist().sortByIconNum);
        sorter.sortAddresses(addrs);
    }
         this.updateSelButtons();
 }
 
  function sortSelectedAddresses() {

    var sorter = this.parentController.getSorter();
    var routeNum = selectedValue(this.form.selRouteInfo);
    if (routeNum>0) {
        var otherRouteSelected=false;
        var selAddrs=new Array();
        var min=null; var max=null;
        var addrs = this.parentController.getWalklist().addresses;
        for(var i=0;i<addrs.length;i++) {
            var addr=addrs[i];
            if (addr.selected) {
                if(addr.routeNum==routeNum) {
                    selAddrs[selAddrs.length]=addr;
                    if (min==null || addr.iconNum<min) min=addr.iconNum;
                    if (max==null || addr.iconNum>max) max=addr.iconNum;
                } else {
                    otherRouteSelected=true;
                }
            }
        }
        if (otherRouteSelected) {
            alert("Addresses in other routes have been selected.  These will not be sorted.");
        }
        if ((max - min) != (selAddrs.length - 1)) {
            alert("Selected points must have contiguous icon numbers. Unable to sort.");
        } else {
            selAddrs.sort(this.parentController.getWalklist().sortByIconNum);
            sorter.sortAddresses(selAddrs);
        }
        this.updateSelButtons();
    }
  }
 
 /**
  * Something of a 'catch-all' procedure for the changing status of the screen
  *  All functions that change mutiple points should call this function internally
  *  Call this function yourself if you only manipulate one point at a time
  * Source is optional and it contians info about the calling procedure
  * Valid string values for source, and their effects:
  *  region : keep the region markers on the map and whether undo should be tied to the region marker
  *  iframe : don't update the iFrame; would be redundant
  */
 function updateSelButtons(source) {
    var mode = this.parentController.mapMode;

    var hasUndoableItem = (this.parentController.getWalklist().hasUndoableAddress());
    this.form.btnUndo.disabled = !hasUndoableItem;
    this.undoWhat="order";
    
    this.form.btnExport.disabled=(typeof myAddrData =='undefined' || myAddrData==null)
    
    /** Check mode **/
    if (mode == "browse") {
        this.setStatusText("Move the map until you're at a comfortable zoom level and location." +
        " Then switch to another mode to select points.");
    }
    else if (mode == "click") {
        this.setStatusText("Click on address to toggle their selection.");
    } else if (mode == "region") {
      var region=this.parentController.getRegion();
      if (region==null) region=this.parentController.initializeRegion();
      
      var numOfMarkers = region.numOfMarkers;
      var connected = region.connected;
        
        if (numOfMarkers>0 && source == "region") {
            this.form.btnUndo.disabled=false;
            this.undoWhat="marker";
        }

        if (numOfMarkers==0) {
            this.setStatusText("Click anywhere on the map to begin to encircle the desired points.");
        } else if (numOfMarkers<3) {
            this.setStatusText("Click another point on the map to create your an outline segment.");
        }  else if (numOfMarkers>2) {
            this.setStatusText("Keep creating outline segments or click the first point again to complete the region.");
        }
    }

    /** Refresh the select route drop-down and select unassigned button**/
    var noRoutes=(this.parentController.getWalklist().maxRoute==0);
    var unaAddrs=(this.parentController.getWalklist().minRoute==0);

    //document.getElementById("selectRouteText").style.color = noRoutes ? "#C0C0C0 " : "#000000";
    //if (!noRoutes) {
        var routes = this.parentController.getWalklist().getRouteNums();
        var selSel=this.form.selSelRoute;
        selSel.length=0;
        selSel.options[0]= new Option("","",true,false);
        selSel.options[1]= new Option("unassigned","unassigned",false,false);
        selSel.options[2]= new Option("all points","all points",false,false);
        selSel.options[3]= new Option("no points","no points",false,false);
        for(var i =0; i<routes.length;i++)
            selSel.options[selSel.length]= new Option("route " + routes[i],"route " + routes[i],false,false);
    //}

    var noneSelected=(this.parentController.getWalklist().getSelectedAddresses().length==0);
    this.form.selAssignRoute.disabled=noneSelected;
    this.form.btnAssignRoute.disabled=noneSelected;
    /** TODO: clean up following line **/
    document.getElementById("assignRouteText").setAttribute("style",noneSelected ? "color: #C0C0C0 " : "color: #000000");
    
    /** Update IFrame if not redundant**/
    if (source != "iframe" && this.iframeController) this.iframeController.updateIFrameButtons("controlForm");
    
    /**Mark the undo reset flag as true, since we're starting a new operation**/
    if (source != "iframe") {
        this.parentController.getWalklist().undoResetFlag=true;
    }
    
 }
  
  function setMode(mode) {
    if (mode == "browse") {
        this.form.mapMode[0].checked=true;
    } else if (mode == "region") {
        this.form.mapMode[2].checked=true;
    } else if (mode == "click") {
        this.form.mapMode[1].checked=true;
    } else {
        alert("Illegal mode: " + mode);
    }
  }

  function setStatusText(txt) {
    this.parentController.getMapControl().setStatusText(txt);
  }
  
  function toggleOptions() {
      var optionsRow=document.getElementById("trOptions");
      var optionsArrow=document.getElementById("optionsArrow");
      if (optionsRow.nextSibling==null) {
        this.expandOptions();
        optionsArrow.src="images/downArrow.bmp";
      } else {
        this.shrinkOptions();
        optionsArrow.src="images/rightArrow.bmp";
      }
  }
  
  function shrinkOptions() {

    var optionsRow=document.getElementById("trOptions");
    var parentTable = optionsRow.parentNode;
    var begin=false; var stop=false;
    for(var i = 0; i<parentTable.childNodes.length && stop!=true; i++) {
        var node = parentTable.childNodes[i];
        if (begin && node.tagName != "TR") stop=true;
        /**TODO: fix kludge **/
        if (begin && !stop) {parentTable.removeChild(node); i--;}
        if (node==optionsRow) begin=true;
    }
  }
  
  function expandOptions() {
    var trAssignedTitle=document.createElement("tr");
    var tdAssignedTitle=document.createElement("td");
    tdAssignedTitle.appendChild(document.createTextNode("Display assigned points"));
    trAssignedTitle.appendChild(tdAssignedTitle);
    
    var trAssignedOptions=document.createElement("tr");
    var tdAssignedOptions=document.createElement("td");

    tdAssignedOptions.appendChild(document.createTextNode("  Always "));
    var tmp=document.createElement("INPUT");
    tmp.setAttribute("type","radio");
    tmp.setAttribute("name","radShowAssigned");
    tmp.setAttribute("value","Always");
    tmp.setAttribute("onclick","controlForm.setAsgDisplayMode('Always');");
    if (this.asgDisplay=="Always") tmp.checked=true;
    tdAssignedOptions.appendChild(tmp);

    tdAssignedOptions.appendChild(document.createTextNode("  Never "));
    var tmp=document.createElement("INPUT");
    tmp.setAttribute("type","radio");
    tmp.setAttribute("name","radShowAssigned");
    tmp.setAttribute("value","Never");
    tmp.setAttribute("onclick","controlForm.setAsgDisplayMode('Never');");
    if (this.asgDisplay=="Never") tmp.checked=true;
    tdAssignedOptions.appendChild(tmp);

    tdAssignedOptions.appendChild(document.createTextNode("  Only when active "));
    var tmp=document.createElement("INPUT");
    tmp.setAttribute("type","radio");
    tmp.setAttribute("name","radShowAssigned");
    tmp.setAttribute("value","Active");
    tmp.setAttribute("onclick","controlForm.setAsgDisplayMode('Active');");
    if (this.asgDisplay=="Active") tmp.checked=true;
    tdAssignedOptions.appendChild(tmp);
    trAssignedOptions.appendChild(tdAssignedOptions);
            
    var trUnassignedTitle=document.createElement("tr");
    var tdUnassignedTitle=document.createElement("td");
    tdUnassignedTitle.appendChild(document.createTextNode("Toggle display of unassigned points"));
    trUnassignedTitle.appendChild(tdUnassignedTitle);
    
    var trUnassignedOptions=document.createElement("tr");
    var tdUnassignedOptions=document.createElement("td");
    
    tdUnassignedOptions.appendChild(document.createTextNode("  Show "));
    var tmp=document.createElement("INPUT");
    tmp.setAttribute("type","radio");
    tmp.setAttribute("name","radShowUnassigned");
    tmp.setAttribute("value","Show");
    tmp.setAttribute("onclick","controlForm.setUnaDisplayMode('Show');");
    if (this.unaDisplay=="Show") tmp.checked=true;
    tdUnassignedOptions.appendChild(tmp);

    tdUnassignedOptions.appendChild(document.createTextNode("  Hide "));
    var tmp=document.createElement("INPUT");
    tmp.setAttribute("type","radio");
    tmp.setAttribute("name","radShowUnassigned");
    tmp.setAttribute("value","Hide");
    tmp.setAttribute("onclick","controlForm.setUnaDisplayMode('Hide');");
    if (this.unaDisplay=="Hide") tmp.checked=true;
    tdUnassignedOptions.appendChild(tmp);
    trUnassignedOptions.appendChild(tdUnassignedOptions);


    var optionsRow=document.getElementById("trOptions");
    var parentTable = optionsRow.parentNode;
    parentTable.appendChild(trAssignedTitle);
    parentTable.appendChild(trAssignedOptions);
    parentTable.appendChild(trUnassignedTitle);
    parentTable.appendChild(trUnassignedOptions);

  /*
    <tr><td>&nbsp;&nbsp;Always <INPUT type="radio" name="radShowAssigned" value="Always" onclick="controlForm.setAsgDisplayMode('Always');"/>
    Never <INPUT type="radio" name="radShowAssigned" value="Never" onclick="controlForm.setAsgDisplayMode('Never');"/>
    Only when active <INPUT type="radio" name="radShowAssigned" value="Active" onclick="controlForm.setAsgDisplayMode('Active');"/>
    </td></tr>
    <tr><td>Toggle display of unassigned points
    </td></tr>
    <tr><td>&nbsp;&nbsp;Show <INPUT type="radio" name="radShowUnassigned" value="Show" onclick="controlForm.setUnaDisplayMode('Show');"/>
        Hide <INPUT type="radio" name="radShowUnassigned" value="Hide" onclick="controlForm.setUnaDisplayMode('Hide');"/>
    </td></tr>
  
  */ 
    
  }

}


/*************************************************/

/**
 * Static object to order point
 */

function AutoAddressSorter(parentController) {
    this.parentController=parentController;
    this.nonStreetPenalty=NON_SAME_STREET_PENALTY;
    this.nonSameSideStreetPenalty=NON_SAME_SIDE_STREET_PENALTY;
    this.greedySort=greedySort;
    this.sortAddresses=sortAddresses;
    this.setProgress=setProgress;
    
    /**
     * Sort and assigns the addresses
     * Assignments begin at given number
     */
     function sortAddresses(addrs) {
        var firstNum=addrs[0].iconNum;
        var sorted=this.greedySort(addrs);
        for(var i=0; i<sorted.length;i++) {
            var addr=addrs[i];
            addr.setIFrameNode(null);
            addr.setIconNum(sorted[i]+firstNum,true,true);
        }
        this.parentController.getWalklist().refreshIcons();
        this.parentController.getControlForm().updateRouteInfoProperties();
     }
    
    /**
    * Takes array of addresses
    * Returns array of new order of addresses
    * Keeps first address in first position
    * private method
    */
    function greedySort(addrs) {
        var sorted=new Array();
                
        for(var i =0; i<addrs.length;i++) sorted[i]=-1;
        
        var curAddrIndex=0;
        var curPosition=0;
       
        while(curAddrIndex!=null) {
            var curAddr=addrs[curAddrIndex];
            sorted[curAddrIndex]=curPosition;
            
            var curAddrPoint = curAddr.getPoint();
            var curFullStreetName = curAddr.getFullStreetName();
            var curStreetNum = curAddr.getStreetNum();
            
            var minNonStreetIndex=null;
            var minOppoSideStreetIndex=null;
            var minThisSideStreetIndex=null;

            var minNonStreetDist=null;
            var minOppoSideStreetDist=null;
            var minThisSideStreetDist=null;
            
            for(var i=0;i<addrs.length;i++) {
                if(sorted[i]==-1) {
                    var addr=addrs[i];
                    var addrPoint = addr.getPoint();
                    var dist = curAddrPoint.distanceFrom(addrPoint);
                    if (addr.getFullStreetName() != curFullStreetName) {
                        if (minNonStreetIndex==null || dist < minNonStreetDist) {
                            minNonStreetIndex=i;
                            minNonStreetDist=dist;
                        }
                    } else if ((addr.getStreetNum() % 2) != (curStreetNum % 2)) {
                        if (minOppoSideStreetIndex==null || dist < minOppoSideStreetDist) {
                            minOppoSideStreetIndex=i;
                            minOppoSideStreetDist=dist;
                        }
                    } else {
                        if (minThisSideStreetIndex==null || dist < minThisSideStreetDist) {
                            minThisSideStreetIndex=i;
                            minThisSideStreetDist=dist;
                        }
                    }
                }
            }
                       
            /** Assess penalties **/
            var nullMax=99999;
            minNonStreetDist = (minNonStreetDist == null) ? 99999 : (minNonStreetDist * this.nonStreetPenalty);
            minOppoSideStreetDist = (minOppoSideStreetDist == null) ? 99999 : (minOppoSideStreetDist * this.nonSameSideStreetPenalty);
            minThisSideStreetDist = (minThisSideStreetDist == null) ? 99999 : minThisSideStreetDist;
            
            var minDist = Math.min(minNonStreetDist,minOppoSideStreetDist,minThisSideStreetDist);
            
            //alert((curAddrIndex + 1) + "(" + curFullStreetName  + "): " + minNonStreetDist + "," + minOppoSideStreetDist + "," + minThisSideStreetDist + "->" + minDist);
            
            if (minDist==nullMax) {
                curAddrIndex=null;
            } else if (minDist==minThisSideStreetDist) {
                curAddrIndex=minThisSideStreetIndex;
            } else if (minDist==minOppoSideStreetDist) {
                curAddrIndex=minOppoSideStreetIndex;
            } else if (minDist==minNonStreetDist) {
                curAddrIndex=minNonStreetIndex;
            }
            
            curPosition++;
            this.setProgress(curPosition,addrs.length)
        }
        return sorted;
            
    }
    
    function setProgress(done,total) {
        var controlForm = this.parentController.getControlForm();
        var pct = Math.round(done / total);
        var preamble = "Auto-sorting, but keeping the first point the same.";
        if (done!=total) {
            controlForm.setStatusText(preamble + "  Progress: Sorted " + done +
                ((done==1) ? " address" : " address(es)") + " out of " + total + " total (" + pct + ")");
        } else {
            controlForm.setStatusText(preamble + "  FINISHED.");           
        }
    }

}

/*************************************************/
/**
  * Walklist object: holds set of address
  */

function WalklistClass(parentController) {
    this.parentController = parentController;
    this.addresses = new Array();
   
    /** TODO: add a selected address array to improve speed **/
    this.displayAssigned=DEFAULT_DISPLAY_ASSIGNED;
    this.displayUnassigned=(DEFAULT_DISPLAY_UNASSIGNED=="Show");
    this.activeRouteNum=false;
    this.routeColor = new Array();
    this.routeLengths = new Array(); /** addresses per route; 0 for unassigned points **/
    this.maxRoute=0; /** Maximum route that has _been assigned_, not maximum on screen **/
    this.minRoute=0; /** Minimum route that has been assigned, 0 if unassigned points **/
    this.fieldMap=new Array(); /** fieldName -> fieldIndex in the data **/
    this.undoableAddresses=new Array(); /** addresses to undo **/
    this.undoIDs="";
    this.hhIDs="";
    this.undoResetFlag=true;
    this.rawData;
    this.rawDataFields;

    this.addAddress = addAddress;
    this.loadAddresses = loadAddresses;
    this.numOfAddresses = 0;
    this.selectRegion = selectRegion;
    this.getRegionInterior = getRegionInterior;
    this.selectRoute = selectRoute;
    this.toggleSelectPoint = toggleSelectPoint;
    this.deselectAll = deselectAll;
    this.getIndexOf = getIndexOf;
    this.getSelectedAddresses = getSelectedAddresses;
    this.getRouteAddresses = getRouteAddresses;
    this.getDisplayedAddresses = getDisplayedAddresses;
    this.getAddress = getAddress;
    this.getRouteNums = getRouteNums;
    this.getColorFromRoute = getColorFromRoute;
    this.setColorToRoute = setColorToRoute;
    this.assignSelectedTo = assignSelectedTo;
    this.deAssignSelected = deAssignSelected;
    this.undo = undo;
    this.addUndoAddress = addUndoAddress;
    this.clearAddressesUndoData = clearAddressesUndoData;
    this.hasUndoableAddress = hasUndoableAddress;
    this.refreshIcons = refreshIcons;
    this.refreshIconNums = refreshIconNums;
    this.refreshRouteLengths = refreshRouteLengths;
    this.offsetSelectedIconNums = offsetSelectedIconNums;
    this.updateDisplayOptions = updateDisplayOptions;
    this.exportToPHP = exportToPHP; //static
    this.sortByIconNum = sortByIconNum; //static


    /** Add addresses based on a 2-dimensional data array
      * and an array that maps column numbers to fields 
      * Adds only one address per householdID
      * Sets routeNum and iconNum is available
      **/
    function loadAddresses(data,fields)  {
        this.rawData = data;
        this.rawDataFields = fields;
        for(var i =0; i< fields.length; i++) {
            var field=fields[i];
            this.fieldMap[field]=i;
        }
        var hhIDfield = this.fieldMap["householdID"];
        if (this.fieldMap["uniqueID"] == -1) alert ("Missing ID field");
        if (hhIDfield == -1) alert ("Missing HHID field");

        for(var i = 0; i<data.length;i++) {
            var hhID=data[i][hhIDfield];
            var pos=this.hhIDs.indexOf(hhID + "|");
            if (pos==-1) {
                var newAddr=this.addAddress(data[i]);
                this.hhIDs+=hhID + "|";
                if (newAddr.routeNum>this.maxRoute) this.maxRoute=newAddr.routeNum;
            } else {
                var leftSide=this.hhIDs.substring(0,pos);
                var index=(pos==0) ? 0 : leftSide.split('|').length - 1;
                var addr=this.addresses[index];
                addr.addResident();
            }
        }
        //this.refreshRouteLengths();
        this.refreshIconNums(true);
        this.refreshIcons();
        this.parentController.getMapControl().centerMap();
        this.parentController.updateSelButtons();
    }

    /** Adds a new address from data and returns the new address added */
    function addAddress(dataRow) {
        var newAddr=new AddressClass(this,dataRow,this.numOfAddresses+1);
        this.addresses.push(newAddr);
        this.numOfAddresses++;
        return newAddr;
    }
    
    /**
     * Rerturn the route number and icon number information to an array suitable for passing to php
     **/
     function exportToPHP () {
        eraseCookie("exportDataRoute");
        eraseCookie("exportDataIcon");
        var routeArray=new Array();
        var iconArray=new Array();
        for(var i=0; i<this.numOfAddresses; i++) {
            var addr=this.addresses[i];
            routeArray[i]=addr.routeNum;
            iconArray[i]=addr.iconNum;
        }
        var retv=new Array();
        retv[0]=js_array_to_php_array(routeArray);
        retv[1]=js_array_to_php_array(iconArray);
        return retv;
    }
    
    /**
     * Add an address to the list of addresses to undo
     */
    function addUndoAddress(address) {
        if (this.undoResetFlag) {
            /** TODO: proper cleaning of first address **/
            for (var i=0; i<this.undoableAddresses.length;i++)
                if (this.undoableAddresses[i]!=address)
                    this.undoableAddresses[i].clearUndoData();
            this.undoIDs="";
            this.undoResetFlag=false;
            this.undoableAddresses=new Array();
        }
        if (this.undoIDs.indexOf(address.getUniqueID() + "|")==-1) {
            this.undoableAddresses.push(address);
            this.undoIDs += address.getUniqueID() + "|";
        }
        //this.parentController.getControlForm().setStatusText(this.undoableAddresses.length);
    }
        
    /**
     * Undo all addresses that have undo data
     */
    function undo() {
        for(var i=0; i<this.undoableAddresses.length; i++) {
            this.undoableAddresses[i].revert();
        }

        if (1==0) {
            this.refreshRouteLengths();
        } else { /**TODO: see if above is sufficient **/
            this.refreshIconNums();
        }
        this.refreshIcons();
        this.undoResetFlag=true;
        this.parentController.updateSelButtons();
    }

    function updateDisplayOptions() {
        var frmCon=this.parentController.getControlForm();
        this.displayAssigned=frmCon.getShowAssigned();
        this.displayUnassigned=(frmCon.getShowUnassigned()=="Show");
        this.activeRouteNum=frmCon.getActiveRoute();
    }

    /** Assign the points currently selected to the route routeNum **/
    function assignSelectedTo(routeNum) {
        var map = this.parentController.getMap();
        this.offsetSelectedIconNums(routeNum);
        for(var i=0; i<this.numOfAddresses; i++) {
            var addr=this.addresses[i];
            if (addr.selected) {
                //addr.deselect(map);
                addr.setRouteNum(routeNum,true);
            }
        }
        this.refreshIconNums();
        this.refreshIcons();
    }

    /** De-assign the points currently selected **/
    function deAssignSelected() {
        var map = this.parentController.getMap();
        this.offsetSelectedIconNums(0);
        for(var i=0; i<this.numOfAddresses; i++) {
            var addr=this.addresses[i];
            if (addr.selected) {
                addr.deselect(map);
                addr.setRouteNum(0,true);
            }
        }
        this.refreshIconNums();
        this.refreshIcons();
    }

    function clearAddressesUndoData() {
        for(var i=0; i<this.undoableAddresses.length; i++) {
            this.undoableAddresses[i].clearUndoData();
        }
    }
    
    function hasUndoableAddress() {
        return (this.undoableAddresses.length>0 && !this.undoResetFlag);
    }

    /**
     * This avoids iconNum collisions when appending points to a route 
     * Add the current number of addresses in routeNum to all selected icons
     **/
    function offsetSelectedIconNums(routeNum) {
        if (typeof this.routeLengths[routeNum] != 'undefined' && this.routeLengths[routeNum]>0) {
            for(var i=0; i<this.numOfAddresses; i++) {
                var addr=this.addresses[i];
                if (addr.selected && (!addr.routeNum || addr.routeNum != routeNum)) {
                    addr.setIconNum(addr.iconNum + this.routeLengths[routeNum],false,false);
                }
            }
        }
    }

    /** Refresh the icon numbers
      * Need to call every time the routes change
      * or route orderings change
     **/
    function refreshIconNums(inaugurate) {
        var oldRouteLengths= (!inaugurate) ? this.routeLengths.slice() : new Array();
        this.refreshRouteLengths();
        for(var i = 0; i<=this.maxRoute; i++) {
            if (inaugurate || typeof oldRouteLengths[i] =='undefined' ||
                (oldRouteLengths[i]<this.routeLengths[i]) ||
                (oldRouteLengths[i]>this.routeLengths[i]) ) { //we added or removed points

                var thresh=0; /** threshold: if we've appended points, don't touch old ones **/
                if (typeof oldRouteLengths[i] =='undefined') thresh=0;
                else if (oldRouteLengths[i]<this.routeLengths[i]) thresh=oldRouteLengths[i];
                /** First, collect all the new points **/
                var arrayToSort=new Array(); var index=0;
                for(var j=0; j<this.numOfAddresses; j++) {
                    var addr=this.addresses[j];
                    if (addr.routeNum==i && addr.iconNum>thresh) {
                        arrayToSort[index]=addr.iconNum;
                        index++;
                    }
                }
                
                /** Sort the points **/
                arrayToSort.sort(sortNumber);

                /** Assign the sorted points **/
                for(var j=0; j<this.numOfAddresses; j++) {
                    var addr=this.addresses[j];
                    if (addr.routeNum==i && addr.iconNum>thresh) {
                        index=binarySearchMin(arrayToSort,addr.iconNum);
                        arrayToSort[index] -= 0.1; //for ties
                        var newIconNum=thresh+index+1;//+1 because of 0-base
                        addr.setIconNum(newIconNum,true,true); 
                    }
                }
            }
        }
    }
    
    /** Refreshes this.routeLengths array,this.minRoute and this.maxRoute **/
    function refreshRouteLengths() {
       for(var i = 0; i<=this.maxRoute; i++) {
        this.routeLengths[i]=0;
       }
       this.maxRoute=0;
       this.minRoute=null;
        for(var i=0; i<this.numOfAddresses; i++) {
            var addr=this.addresses[i];
            var routeNum = addr.routeNum;
            if (routeNum>=0) {
                if (this.minRoute == null || routeNum < this.minRoute) this.minRoute = routeNum;
                if (routeNum > this.maxRoute) this.maxRoute = routeNum;
                if (typeof this.routeLengths[routeNum] == 'undefined') {
                    this.routeLengths[routeNum]=1;
                } else {
                    this.routeLengths[routeNum]++;
                }
            } else {
                alert("Error: illegal route number: " + routeNum + " for " + addr.getFullAddress());
            }
        }
        /** Update the legend **/
        this.parentController.getMapControl().legendControl.updateLegendTable(this);
    }

    function refreshIcons() {
        var map = this.parentController.getMap();
        for(var i=0; i<this.numOfAddresses; i++) {
            this.addresses[i].refreshIcon(map);
        }
    }

    function setColorToRoute(routeNum,color) {
        var map = this.parentController.getMap();
        this.routeColor[routeNum]=color;
        for(var i=0; i<this.numOfAddresses; i++) {
            if (this.addresses[i].routeNum==routeNum) {
                this.addresses[i].refreshIcon(map);
            }
        }
        /** Update the legend **/
        this.parentController.getMapControl().legendControl.updateLegendTable(this);
    }

    function getColorFromRoute(routeNum) {
        var assigned=this.routeColor[routeNum];
        if (assigned) return assigned;
        else {
            return COLORS_ASSIGN[(routeNum-1) % COLORS_ASSIGN.length];
        }
    }


    function getAddress(point) {
        for(var i=0; i<this.numOfAddresses; i++) {
            var addr=this.addresses[i];
            if (addr.getPoint() == point) {
                return addr;
            }
        }
        return false;
    }

    function toggleSelectPoint(point) {
        var addr=this.getAddress(point);
        if (addr) {
            if (addr.selected) addr.deselect();
            else addr.select();
        }
    }
    
    /**
     * selects the points assigned to routeNum
     * set routeNum=0 for unassigned points
     * set routeNum=-1 for all points
     * set routeNum=-2 for no points
     */
    function selectRoute(routeNum) {
        for(var i=0; i<this.numOfAddresses; i++) {
            var addr=this.addresses[i];
            if ((addr.routeNum == routeNum) ||
                (routeNum==0 && !addr.isAssigned()) ||
                routeNum==-1)
               addr.select();
            else addr.deselect();
        }
    }

    /**
     * Input: region object
     * Mutation: for all Addresses interior of region, mark these Addresses as selected, else deselect
     */
    function selectRegion(region) {
        var interiorArray=this.getRegionInterior(region);
        for(var i=0; i<this.numOfAddresses; i++) {
            var addr=this.addresses[i];
            if (interiorArray[i]) addr.select();
            else if (!interiorArray[i]) addr.deselect();
        }
    }
    
    /**
     * Input: region object
     * Returns: array of length this.numOfAddresses, with true if corresponding address is in the
     *          interior of region object, false otherwise
     * Code modifed from: http://alienryderflex.com/polygon/
     */
    function getRegionInterior(region) {
        var interiorArray=new Array();
        var polySides=region.numVertices();
        var points = region.getPoints();
        
        var polyX=new Array();
        var polyY=new Array();
        for(var i=0; i<polySides;i++) {
            polyX[i]=points[i].lng();
            polyY[i]=points[i].lat();
        }
        
        for(var index=0; index<this.numOfAddresses; index++) {
            var addr=this.addresses[index];
            var i=0; var j=0;
            var x=addr.marker.getPoint().lng();
            var y=addr.marker.getPoint().lat();
            var interior=false;
            for(i=0; i<polySides; i++) {
                j++;
                if (j==polySides) j=0;
                if (polyY[i]<y && polyY[j]>=y ||  polyY[j]<y && polyY[i]>=y) {
                  if (polyX[i]+(y-polyY[i])/(polyY[j]-polyY[i])*(polyX[j]-polyX[i])<x) {
                    interior=!interior; }}
            }
            if (interior) interiorArray.push(true);
            else interiorArray.push(false);
        }
        return interiorArray;
    
    } 
    
    function deselectAll() {
        this.selectRoute(-2);
    }

    /** Return array of addresses assigned to route routeNum
     * routeNum=0 to get unassigned routes
     **/
    function getRouteAddresses(routeNum) {
        var addrs=new Array(); var index=0;
        for(var i=0; i<this.numOfAddresses; i++) {
            if ((routeNum==0 && this.addresses[i].isAssigned()) ||
                (this.addresses[i].routeNum == routeNum)) {
                addrs[index]=this.addresses[i];
                index++;
            }
        }
        return(addrs);
    }

    function getIndexOf(addr) {
        var index=-1;
        for(var i=0; i<this.numOfAddresses && index==-1; i++) {
            if (this.addresses[i] == addr) {
                index=i;
            }
        }
        return(index);
    }

    /** Return array of selected addresses **/
    function getSelectedAddresses() {
        var sels=new Array(); var index=0;
        for(var i=0; i<this.numOfAddresses; i++) {
            if (this.addresses[i].selected) {
                sels[index]=this.addresses[i];
                index++;
            }
        }
        return(sels);
    }

    /** Return array of selected addresses **/
    function getDisplayedAddresses() {
        var dis=new Array(); var index=0;
        for(var i=0; i<this.numOfAddresses; i++) {
            if (this.addresses[i].displayed) {
                dis[index]=this.addresses[i];
                index++;
            }
        }
        return(dis);
    }

    /** Return array of route numbers with at least one
     *  address assigned
     **/
    function getRouteNums() {
        var rns=new Array(); var index=0;
        for(var i=0; i<this.numOfAddresses; i++) {
            var rn=this.addresses[i].routeNum;
            if (rn>0) {
                if (index==0 || binarySearch(rns,rn) < 0) {
                    /** TODO: fix with new binarySearch **/
                    rns[index]=this.addresses[i].routeNum;
                    index++;
                    if (index>1) rns.sort(sortNumber);
                }
            }
        }
        return(rns);
    }

    /** For sorting arrays addresses **/
    //Static function
    function sortByIconNum(a,b)
    {
        return a.iconNum - b.iconNum;
    }
   
}
/*********************/

/**
  * Address object: holds address string, long, lat, walk route #,
  * walk route order, and image
  */

function AddressClass(parentWL,dataRow,iconNum) {
    this.parentWL = parentWL;
    this.fieldMap = this.parentWL.fieldMap;
    /**TODO: clean data **/
    this.uniqueID = dataRow[this.fieldMap["uniqueID"]];
    this.householdID = dataRow[this.fieldMap["householdID"]];
    this.fullName = dataRow[this.fieldMap["fullName"]];
    this.fullAddress = dataRow[this.fieldMap["fullAddress"]];
    this.fullStreetName=dataRow[this.fieldMap["streetName"]];
    this.streetNum=dataRow[this.fieldMap["streetNum"]];
    this.latitude=dataRow[this.fieldMap["latitude"]];
    this.longitude=dataRow[this.fieldMap["longitude"]];
    //alert(this.uniqueID + " " + this.fullAddress + " " + this.latitude + " " + this.longitude);
        
    this.selected=false;
    this.oldSelect=null;
    this.displayed=false;
    /** routeNum starts as unassigned unless previously specified**/
    this.routeNum=parseInt(dataRow[this.fieldMap["routeNumber"]])>0 ? parseInt(dataRow[this.fieldMap["routeNumber"]]) : 0;
    this.oldRouteNum=null;
    this.iconNum=parseInt(dataRow[this.fieldMap["routeOrder"]]>0) ? parseInt(dataRow[this.fieldMap["routeOrder"]]) : iconNum;
    this.oldIconNum=null;
    this.iframeNode=null; /**Analagous address in the IFrame points container**/
    this.residents=1; /**Number of residents at address **/
    
    this.getPoint = getPoint;
    this.setIconNum = setIconNum;
    this.setRouteNum = setRouteNum;
    this.refreshIcon = refreshIcon;
    this.setCenterOn = setCenterOn;
    this.select = select;
    this.deselect = deselect;
    this.enactSelection = enactSelection;
    this.isAssigned = isAssigned;
    this.revert = revert;
    this.clearUndoData = clearUndoData;
    this.setIFrameNode = setIFrameNode
    this.getUniqueID = getUniqueID;
    this.getHouseholdID = getHouseholdID;
    this.getFullStreetName = getFullStreetName;
    this.getFullAddress = getFullAddress;
    this.getFullName = getFullName;
    this.getStreetNum = getStreetNum;
    this.getInfoWindow = getInfoWindow;
    this.addResident=addResident;
    this.initialize = initialize;
    this.updateWindowInfo = updateWindowInfo;

    this.initialize();

    function getUniqueID() {return this.uniqueID;}
    function getHouseholdID() {return this.householdID;}
    function getFullStreetName() {return this.fullStreetName;}
    function getFullName() {return this.fullName;}
    function getFullAddress() {return this.fullAddress;}
    function getStreetNum() {return this.streetNum;}
    function getInfoWindow() {return this.infoWindow;}
    
    function isAssigned() {return (!(this.routeNum==0));}  
    function addResident() {
        this.residents++;
        this.updateWindowInfo();
    }
    
    function updateWindowInfo() {
        this.infoWindow=document.createElement("div");
        this.infoWindow.setAttribute("class","addressWindow");
        var tmp = document.createElement("div");
        var resString = (this.residents>1) ? (" (+" + (this.residents - 1) + ")") : "";
        tmp.appendChild(document.createTextNode(this.getFullName() + resString));
        this.infoWindow.appendChild(tmp);
        tmp = document.createElement("div");
        tmp.appendChild(document.createTextNode(this.getFullAddress()));
        this.infoWindow.appendChild(tmp);
    }
    
    function initialize() {
        this.updateWindowInfo();
        
        if (this.latitude.length>0 && this.longitude.length>0 ) {
            this.marker=new GMarker(new GLatLng(this.latitude,this.longitude));
            this.refreshIcon(this.parentWL.parentController.getMap());
        } else {
            this.marker=null;
        }
    }
   
   function setIFrameNode(iframeNode) {
      this.iframeNode=iframeNode;
   }
    
    /** returns true if successful **/
    function setCenterOn(map,zoom) {
        if (!this.marker) return false;
        else if (this.displayed) {
            map.setCenter(this.marker.getPoint(),zoom);
            return true;
        }
    }
       
    function getPoint(pt) {
        if (!this.marker) {
            return false;
        } else {
            return this.marker.getPoint();
        }
    }

    function select() {
        this.enactSelection(true,true);
    }

    function deselect() {
       this.enactSelection(false,true);
    }
    
    /*
     * Private method.
     * selMode: true to select, false for deselect
     * saveState: store old state
     */
    function enactSelection(selMode,saveState) {
        if ((selMode ^ this.selected) == 1) {
            if (saveState) {
                this.oldSelected=this.selected;
                this.parentWL.addUndoAddress(this);
            } 
    
            this.selected=selMode;
            var cont= this.parentWL.parentController.getControlForm().addressContainer;
            if(typeof cont !='undefined' && cont != null) {
                if (selMode) cont.selectAddressNode(this.routeNum,this.iconNum);
                else cont.deselectAddressNode(this.routeNum,this.iconNum);
            }
            this.refreshIcon(this.parentWL.parentController.getMap());
        } else this.oldSelected=null;
    }
       
    function setRouteNum(num,saveState) {
        if (this.routeNum!=num) {
            if (saveState) {
                this.oldRouteNum=this.routeNum;
                this.parentWL.addUndoAddress(this);
            }
            this.routeNum=num;
        } else {
            this.oldRouteNum=null;
        }
    }
    
    function setIconNum(num,saveState,passState) {
        //if (this.routeNum>0) alert(this.routeNum + ":" + this.iconNum  + "->" + num + " " + saveState);
        if (this.iconNum!=num) {
            if (saveState) {
                this.oldIconNum=this.iconNum;
                this.parentWL.addUndoAddress(this);
            }
            this.iconNum=num;
            if(this.iframeNode != null && passState) {
                this.iframeNode.setIconNum(num,saveState,false);
            }
        } else this.oldIconNum=null;
    }
   
    function revert() {
        if (this.oldRouteNum!=null && this.oldRouteNum != this.routeNum) this.setRouteNum(this.oldRouteNum,false);
        if (this.oldIconNum!=null && this.oldIconNum != this.iconNum) this.setIconNum(this.oldIconNum,false,true);
        if (this.oldSelected!=null && this.oldSelected != this.selected) this.enactSelection(this.oldSelected,false);
        this.clearUndoData();
    }
    
    function clearUndoData() {
        this.oldRouteNum=null;
        this.oldIconNum=null;
        this.oldSelected=null;
    }   
      
    /**
      * Call whenever the icon needs changing -- also re-maps point
      */
    function refreshIcon(map) {
        var oldDisplayed = this.displayed;
        this.displayed=false;
        if ((this.routeNum>0 && (this.parentWL.displayAssigned=="Always" ||
                                (this.parentWL.displayAssigned=="Active" &&
                                this.routeNum==this.parentWL.activeRouteNum))) ||
            (this.routeNum==0 && this.parentWL.displayUnassigned))
            this.displayed= this.marker ? true : false;

        /**can't create marker without a point, so do nothing if no marker exists**/
        if (this.marker) {
            var oldMarker=this.marker;
            var ico;
            if (this.selected) {
                ico = newSelectedIcon(this.iconNum);
            } else if (this.routeNum) {
                col = this.parentWL.getColorFromRoute(this.routeNum);
                ico=newColoredIcon(col,this.iconNum);
            } else {
                ico = newUnselectedIcon(this.iconNum);
            }
            /**replace icon only if we need to**/
            if (ico.image != oldMarker.getIcon().image) this.marker = new GMarker(this.marker.getPoint(),ico);
            
            /**Refresh if we changed icon or display status **/
            if (map && (ico.image != oldMarker.getIcon().image || oldDisplayed != this.displayed)) {
                var rem=false;
                if (oldDisplayed) rem=map.removeOverlay(oldMarker);
                if (this.displayed) map.addOverlay(this.marker);
                //alert(this.parentWL.activeRouteNum + " " + this.parentWL.displayAssigned + " " + oldDisplayed + "->" + this.displayed + " " + map + "\n" + "Out: " + oldMarker.getIcon().image + "\n In: " + this.marker.getIcon().image + "\n" + rem);
            }
        }   
    }

}


/************************/
/* Walklist object specific helper functions */

function getAPIKey() {
    baseURL = document.location.href;
    if (baseURL.indexOf("localhost")>-1) {
        return "ABQIAAAAN9-M2_p7_kezNX_ZV8jCuxT2yXp_ZAY8_ufC3CFXhHIE1NvwkxRLxVN-SezmmgXB7bcYmnUsg21IpA";
    }
        else return "ABQIAAAAN9-M2_p7_kezNX_ZV8jCuxTuCAnQjkTOwyW1LgpVGCdGo0IK2BSumshbRPVxc138uqVCDY2zF_7rtw";
}

function defaultIcon() {
    var ico = new GIcon();
    ico.image = getBaseURL() + "icons/marker.png";
    ico.iconSize = new GSize(20, 34);
    ico.iconAnchor = new GPoint(6, 20);
    ico.infoWindowAnchor = new GPoint(5, 1);
    return ico;
}

function newStartingIcon() {
    var ico = defaultIcon();
    ico.image = getBaseURL() + "icons/markerS.png";
    return ico;
}

function newMiddleIcon() {
    var ico = defaultIcon();
    return ico;
}

/** Standard Icon for UnSelected Points **/
function newUnselectedIcon(num) {
    var ico = defaultIcon();
    var name=(num==0 || num>99) ? "blank" : ("marker" + num);
    var image = getBaseURL() + "icons/largeTD" + COLOR_UNASSIGNED + "Icons/" + name + ".png";
    return new GIcon(ico,image);
}

/** Standard Icon for Selected Points **/
function newSelectedIcon(num) {
    var ico = defaultIcon();
    var name=(num==0 || num>99) ? "blank" : ("marker" + num);
    var image = getBaseURL() + "icons/largeTD" + COLOR_SELECTED + "Icons/" + name + ".png";
    return new GIcon(ico,image);
}

/** Standard Icon for Routed Points **/
function newColoredIcon(col,num) {
    var ico = defaultIcon();
    var name=(num==0 || num>99) ? "blank" : ("marker" + num);
    var image = getBaseURL() + "icons/largeTD" + col + "Icons/" + name + ".png";
    return new GIcon(ico,image);
}

/** CONSTANTS 
 * Mostly used for initialization/defaults
*/
 
 
 var DEFAULT_NUM_ROUTES = 5;
 var DEFAULT_MAP_MODE = "browse";
 var DEFAULT_DISPLAY_ASSIGNED = "Always";
 var DEFAULT_DISPLAY_UNASSIGNED = "Show";
 /** Colors possible to assign **/
 var COLORS_ASSIGN = new Array("Blue","Green","Cyan","Grey","Purple","Gold","Orange","Pink","Red","BlueRed","GreenRed");
 var COLOR_UNASSIGNED = "AntWhite";
 var COLOR_SELECTED = "Yellow";
 var PAN_THRESHHOLD = 0.5;
 var PAN_PARTIAL = 0.3;
 /**Auto-sort varants **/
 var NON_SAME_STREET_PENALTY=4.0;
 var NON_SAME_SIDE_STREET_PENALTY=2.0;

/** Returns the point part of the way from
 * latLng1  to latLng2 depending on alpha
 * alpha: [0,1]
 */
function partWayThere(latLng1,latLng2,alpha) {
    return(new GLatLng(latLng1.lat()*(1-alpha) + latLng2.lat()*alpha,
                       latLng1.lng()*(1-alpha) + latLng2.lng()*alpha));
}

/**
 * Pan to the point panPoint if necessary,
 * and only partially pan there. Depends on
 * constants PAN_THRESHHOLD and PAN_PARTIAL
 */
 
 function conditionalPan(map,panPoint) {
    var mapCenter=map.getCenter();
    var mapCorner=map.getBounds().getNorthEast();
    var ptDist = panPoint.distanceFrom(mapCenter);
    var thDist = PAN_THRESHHOLD * mapCorner.distanceFrom(mapCenter);
    if ( ptDist > thDist) {
        map.panTo(partWayThere(mapCenter,panPoint,PAN_PARTIAL));
    }
 }

