google.load("maps", CI_MAP_VERSION);
var countyZoom = 9;  //default zoom level for all counties
var cityZoom = 11;  //default zoom level for all cities
var zipZoom = 12;  //default zoom level for all zip codes
var addressZoom = 15;  //default zoom level for a specific address
var moreZoom = 16;  //zoom level at which to show tabs / bertha
var maxTabs = 5;  //maximum number of tabs to allow before making bertha
var shiftThreshold = 0.20;  //maximum distance (percentage) to move before reloading
var map;
var newZoom;
var newCenter;
var balloonMarker = null;
var oldOfd = new Array(0);
var loadCounter = 0;
var imgOffender = new Array(5);
imgOffender["MORE"]     = "images/icons/purpleShape.png";
imgOffender["VIOLENT"]  = "images/icons/redShape.png";
imgOffender["FAILED"]   = "images/icons/greenShape.png";
imgOffender["MULTIPLE"] = "images/icons/blueShape.png";
imgOffender["FELON"]    = "images/icons/orangeShape.png";


//Google's Geocoder
var geocoder;
//Lat, log boundaries of the state of Colorado
var boundaries;

var jsMonth = new Array(12);
jsMonth[0] = "January";
jsMonth[1] = "February";
jsMonth[2] = "March";
jsMonth[3] = "April";
jsMonth[4] = "May";
jsMonth[5] = "June";
jsMonth[6] = "July";
jsMonth[7] = "August";
jsMonth[8] = "September";
jsMonth[9] = "October";
jsMonth[10] = "November";
jsMonth[11] = "December";


//should be called whenever motion is detected on the page
function handleMotion() {
  var bounds = map.getBounds();
  var sw = bounds.getSouthWest();
  var ne = bounds.getNorthEast();
  var shift = Math.max(Math.abs(map.getCenter().lat() - newCenter.lat())/(ne.lat() - sw.lat()),
    Math.abs(map.getCenter().lng() - newCenter.lng())/(ne.lng() - sw.lng()));
  if (newZoom != map.getZoom() || shift > shiftThreshold ) {
    newZoom = map.getZoom();
    newCenter = map.getCenter();
    retrieveOffenders();
  } else {
    newZoom = map.getZoom();
    newCenter = map.getCenter();
  }
}
//retrieve a full address, calling changeCenter once geocoded
function setAddress(address,city,zip) {
  city = city.replace(/\s+/g, '').toLowerCase();
  zip = zip.replace(/\s+/g, '').toLowerCase();
  if (zip == "zip") zip = "";
  if (city == "city") city = "";
  if (city == "" && zip == "")
    return;
  newZoom = addressZoom;
  setLoading(true);
  geocoder.getLatLng(address.toLowerCase() + " " + city
    + ", CO " + zip, changeCenter);
}
//retrieve a city, calling changeCenter once geocoded
function setCity(city) {
  city = city.replace(/\s+/g, '').toLowerCase();
  if (city == "city") city = "";
  if (city == "")
    return;
  newZoom = cityZoom;
  setLoading(true);
  geocoder.getLatLng(city  + ", CO", changeCenter);
}
//retrieve a county, calling changeCenter once geocoded
function setCounty(county) {
  if (county == "")
    return;
  newZoom = countyZoom;
  setLoading(true);
  var countyObj = CI_COUNTIES[county];
  var countyLatLng = new google.maps.LatLng(countyObj.lat, countyObj.lng);
  changeCenter(countyLatLng);
}
//retrieve a zip, calling changeCenter once geocoded
function setZip(zip) {
  zip = zip.replace(/\s+/g, '').toLowerCase();
  if (zip == "zip") zip = "";
  if (zip == "")
    return;
  newZoom = zipZoom;
  setLoading(true);
  geocoder.getLatLng("CO " + zip, changeCenter);
}
//changes the google map center based on the DWR callback.
//this function should only be used as a callback from the web tier.
function changeCenter(latlng) {
  setLoading(false);
  if (latlng != null && boundaries.containsLatLng(latlng)) {
    map.setCenter(latlng,newZoom);
    setLoading(false);
  } else {
    setMessage("We cannot find the location requested, please try again.");
  }
}
//requests offenders from the database, based on the current bounds of the map
//may call load offenders when completed, depending on circumstance
function retrieveOffenders() {
  var bounds = map.getBounds();
  var sw = bounds.getSouthWest();
  var ne = bounds.getNorthEast();
  var zoom = map.getZoom();
  setLoading(true);
  var load = ""+sw.lat()+sw.lng();
  SomaController.getOffenders(sw.lat(),sw.lng(),ne.lat(),ne.lng(),zoom,(zoom>=moreZoom),{
    callback:function(data) {
      loadOffenders(data, load);
    }
  });
}
//loads the offenders as a response from the database
//load determines the viewport from the initial request, and will
//disregard the request if it does not match the current viewport.
function loadOffenders(ofd,load) {
  var sw = map.getBounds().getSouthWest();
  if (load != ""+sw.lat()+sw.lng()) return;
  var mrkrOfdrContainer = new Array(ofd.length);
  var zoom = map.getZoom();

  for (x=0;x<oldOfd.length;x++) {
    if (oldOfd[x] != null && balloonMarker != oldOfd[x].marker) {
      map.removeOverlay(oldOfd[x].marker);
    }
  }
  
  oldOfd = new Array(ofd.length+1);
  
  for (x=0;x<ofd.length;x++) {
    if (balloonMarker == null ||
        balloonMarker.getPoint().lat() != ofd[x].latitude && 
        balloonMarker.getPoint().lng() != ofd[x].longitude) {
      processOfd(ofd[x],mrkrOfdrContainer,x);
    }
  }
  
  if (balloonMarker == null) {
    oldOfd[ofd.length] = null
  } else {
    oldOfd[ofd.length] = {
      point: balloonMarker.getPoint(),
      marker: balloonMarker
    };
  }
  
  setLoading(false);
}
//process an offender into a specific icon
//if it is of type "MORE", then it will make it a tabbed panel
//bertha balloon, or link to zoom in depending on the quantity
//of offenders and the zoom level.
function processOfd(ofd,mrkrOfdrContainer,x) {
  var type = ofd.icon;
  var markerOptions = { title: ofd.name, icon:getIcon(type)};
  var point = new google.maps.LatLng(ofd.latitude, ofd.longitude);
  var currentMarker = new google.maps.Marker(point,markerOptions);
  if (type == "MORE") {
    //more at a close zoom level
    if (map.getZoom() >= moreZoom) {
      var ofds = [];
      for (var i=0;i<ofd.offenders.length;i++) {
        ofds.push(getJSOffenderObject(ofd.offenders[i]));
      }
      if (ofd.offenders.length > maxTabs) {
        mrkrOfdrContainer[x] = {
          ofd: ofds,
          callback: getBalloon,
          marker: currentMarker
        };
      }
      else {
        mrkrOfdrContainer[x] = {
          ofd: ofds,
          callback: getBalloonTabs,
          marker: currentMarker
        };
      }
    //more at a far away zoom level
    } else {
      var ofds = [];
      mrkrOfdrContainer[x] = {
        ofd: ofds,
        callback: getBalloon,
        marker: currentMarker
      };
    }
  } else {
    mrkrOfdrContainer[x] = {
      ofd: new Array(getJSOffenderObject(ofd)),
      callback: getBalloon,
      marker: currentMarker
    };
  }
  google.maps.Event.bind(mrkrOfdrContainer[x].marker, "click", mrkrOfdrContainer[x], mrkrOfdrContainer[x].callback);
  google.maps.Event.addListener(mrkrOfdrContainer[x].marker, "infowindowopen", function() {balloonMarker = currentMarker});
  google.maps.Event.addListener(mrkrOfdrContainer[x].marker, "infowindowclose", function() {balloonMarker = null});
  map.addOverlay(currentMarker);
  oldOfd[x] = {
    point: point,
    marker: currentMarker
  };
}
//creates an icon based on the offender type, and returns it.
function getIcon(type) {
  var icon = new GIcon();
  icon.image = imgOffender[type];
  icon.iconAnchor = new GPoint(11, 25);
  icon.infoWindowAnchor = new GPoint(11,13);
  icon.iconSize = new GSize(24, 26);
  return icon;
}
//creates an offender for the client tier based on the web tier.
//this is a slightly different offender than the one defined
//in the model, but should be very similar.
function getJSOffenderObject(ofd) {
  return{
    idx: ofd.idx,
    img: ofd.img,
    icon: ofd.icon,
    name: ofd.name,
    address1: ofd.address_1,
    address2: ofd.address_2,
    dob:      jsMonth[ofd.dob.getMonth()]+" "+ofd.dob.getDate()+" "+ofd.dob.getFullYear(),
    height:   ofd.height.substring(0,1)+"'"+ofd.height.substring(1)+'"',
    weight:   ofd.weight
  }
}
//opens a balloon with tabs, called off a marker object
function getBalloonTabs() {
  var tabs = [];
  for (var i=0; i<this.ofd.length; i++) {
    var innerHtml = getBalloonInnerHtml(this.ofd[i],true);
    if (this.ofd.length > 3)
      innerHtml = '<div style="width:'+this.ofd.length*88+'px">' + innerHtml + '</div>';
    tabs.push(new GInfoWindowTab(""+(i+1),innerHtml));
  }
  this.marker.openInfoWindowTabsHtml(tabs);
}
//opens a balloon without tabs, called off a marker object
function getBalloon() {
  if (this.ofd.length >1)
    this.marker.openInfoWindowHtml(getBalloonInnerHtmlBertha(this.ofd));
  else if (this.ofd.length <1)
    this.marker.openInfoWindowHtml(getBalloonInnerHtmlForZoomIn(this.marker),false);
  else
    this.marker.openInfoWindowHtml(getBalloonInnerHtml(this.ofd[0]),false);
}
//creates the inner content of a balloon or tab with or without an image
function getBalloonInnerHtml(ofd,showIcon) {
  var content = "";
  if (ofd.img > 0) {
    var img = (gup('zapp') == "stopexplodingyoucowards")?
      'src="images/zapp.jpg" ':'src="image?id='+ofd.img+'" ';
    content =  '<p style="width:300px;text-align:left;font-size:12px"> '+
      '<img style="float:left; padding-right:5px;" '+img+
      'width=100 height=125 /> '+
      '<br/>';
  } else {
    content = '<p style="width:200px;text-align:left;font-size:12px">';
  }
  
  if (showIcon == true) {
    content+='<img src="'+imgOffender[ofd.icon]+'" />';
  }
  content += '<a style="font-size:120%;font-weight:bold;" target="_OFD" '+
  'href="http://sor.state.co.us/index.cfm?SOR=offender.detail&id='+ofd.idx+
  '">'+ofd.name+'</a><br/><br/> '+ofd.address1+'<br/> '+ofd.address2+'<br/> '+
  '<strong>DOB:</strong>'+ofd.dob+'<br/> '+
  '<strong>Height:</strong> '+ofd.height+'<br/> '+
  '<strong>Weight:</strong> '+ofd.weight+' lbs<br/> '+
  '</p>';
  return content;
}
//creates a list of offenders for the case when too many exist at a single
//location to fit within tabs
function getBalloonInnerHtmlBertha(ofd) {
  var content = '<br/><div style="height:300px;overflow:auto;"';
  for (var i=0;i<ofd.length;i++) {
    content += '<p style="text-align:left"><img src="'+imgOffender[ofd[i].icon]+'" />'+
      '<a style="font-weight:bold" target="_OFD" '+
      'href="http://sor.state.co.us/index.cfm?SOR=offender.detail&id='+ofd[i].idx+
      '">'+ofd[i].name+'</a></p>';
  }
  return content+'</div>';
}
//creates a balloon displaying simple information and an option to zoom in
function getBalloonInnerHtmlForZoomIn(marker) {
  var coordinates = marker.getLatLng();
  return '<br/><p>'+marker.getTitle()+'</p>'+
    '<p><a onclick="map.closeInfoWindow();'+
    'map.setCenter(new google.maps.LatLng('+
    coordinates.lat()+','+coordinates.lng()+
    '),map.getZoom()+1);" '+
    'style="cursor:pointer;">'+
    'Zoom in</a> to view them.</a>';
}
//creates the map and loads the initial offenders.
//this is called only once upon the load of the page
function load() {
  if (google.maps.BrowserIsCompatible()) {
    map = new google.maps.Map2(document.getElementById("map"));
    newZoom = 7;
    newCenter = new google.maps.LatLng(38.993572, -105.46916);
    map.setCenter(newCenter, newZoom);
    map.addControl(new google.maps.LargeMapControl());
    map.addControl(new google.maps.MapTypeControl());
    map.enableContinuousZoom();
    google.maps.Event.addListener(map, "moveend", handleMotion);
    // Set up state of Colorado boundaries
    boundaries = new google.maps.LatLngBounds(
      new google.maps.LatLng(36.998528,-109.05),
      new google.maps.LatLng(41.002942,-102.04));
    // Setup Google's Geocoder
    geocoder = new google.maps.ClientGeocoder();
    geocoder.setBaseCountryCode("us");
    geocoder.setViewport();
  }
  retrieveOffenders();
}
//sets or clears the loading message on the page
function setLoading(loader) {
  if (loader)
    dwr.util.setValue("loaderMessage", "Loading...");
  else
    dwr.util.setValue("loaderMessage", " ");
}
//sets an arbitrary message on the page, negating whatever was there
function setMessage(msg) {
  dwr.util.setValue("loaderMessage", msg);
}
//grabs a user parameter from the URI
function gup(name) {
  name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  var regexS = "[\\?&]"+name+"=([^&#]*)";
  var regex = new RegExp( regexS );
  var results = regex.exec( window.location.href );
  if( results == null )
    return "";
  else
    return results[1];
}
google.setOnLoadCallback(load);

