/**
 * jQuery goMap
 *
 * @url    http://www.pittss.lv/jquery/gomap/
 * @author  Jevgenijs Shtrauss <pittss@gmail.com>
 * @version  1.3.2 2011.07.01
 * This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
 */

(function($) {
  var geocoder = new google.maps.Geocoder();

  function MyOverlay(map) { this.setMap(map); };
  MyOverlay.prototype = new google.maps.OverlayView();
  MyOverlay.prototype.onAdd = function() { };
  MyOverlay.prototype.onRemove = function() { };
  MyOverlay.prototype.draw = function() { };

  $.goMap = {};

  $.fn.goMap = function(options) {
    return this.each(function() {
      var goMap = $(this).data('goMap');
      if(!goMap) {
        var goMapBase = $.extend(true, {}, $.goMapBase);
        $(this).data('goMap', goMapBase.init(this, options));
        $.goMap = goMapBase;
      }
      else {
        $.goMap = goMap;
      }
    });
  };

  $.goMapBase = {
    defaults: {
      address:          '', // Street, City, Country
      latitude:          56.9,
      longitude:          24.1,
      zoom:            4,
      delay:            200,
      hideByClick:        true,
      oneInfoWindow:        true,
      prefixId:          'gomarker',
      polyId:            'gopoly',
      groupId:          'gogroup',
        navigationControl:      true, // Show or hide navigation control
      navigationControlOptions:  {
        position:  'TOP_LEFT', // TOP, TOP_LEFT, TOP_RIGHT, BOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT, LEFT, RIGHT
        style:    'DEFAULT'   // DEFAULT, ANDROID, SMALL, ZOOM_PAN
      },
        mapTypeControl:       true, // Show or hide map control
      mapTypeControlOptions:    {
        position:   'TOP_RIGHT', // TOP, TOP_LEFT, TOP_RIGHT, BOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT, LEFT, RIGHT
        style:     'DEFAULT'// DEFAULT, DROPDOWN_MENU, HORIZONTAL_BAR
      },
        scaleControl:         false, // Show or hide scale
      scrollwheel:        true, // Mouse scroll whell
        directions:         false,
        directionsResult:       null,
      disableDoubleClickZoom:    false,
      streetViewControl:      false,
      markers:          [],
      overlays:          [],
      polyline:          {
        color:    '#FF0000',
        opacity:  1.0,
        weight:    2
      },
      polygon:          {
        color:      '#FF0000',
        opacity:    1.0,
        weight:      2,
        fillColor:    '#FF0000',
        fillOpacity:  0.2
      },
      circle:            {
        color:      '#FF0000',
        opacity:    1.0,
        weight:      2,
        fillColor:    '#FF0000',
        fillOpacity:  0.2
      },
      rectangle:          {
        color:      '#FF0000',
        opacity:    1.0,
        weight:      2,
        fillColor:    '#FF0000',
        fillOpacity:  0.2
      },
      maptype:          'HYBRID', // Map type - HYBRID, ROADMAP, SATELLITE, TERRAIN
      html_prepend:        '<div class=gomapMarker>',
      html_append:        '</div>',
      addMarker:          false
    },    
    map:      null,
    count:      0,
    markers:    [],
    polylines:    [],
    polygons:    [],
    circles:    [],
    rectangles:    [],
    tmpMarkers:    [],
    geoMarkers:    [],
    lockGeocode:  false,
    bounds:      null,
    overlays:    null,
    overlay:    null,
    mapId:      null,
    plId:      null,
    pgId:      null,
    cId:      null,
    rId:      null,
    opts:      null,
    centerLatLng:  null,

    init: function(el, options) {
      var opts   = $.extend(true, {}, $.goMapBase.defaults, options);
      this.mapId  = $(el);
      this.opts  = opts;

      if (opts.address)
        this.geocode({address: opts.address, center: true});
//      else if (opts.latitude != $.goMapBase.defaults.latitude && opts.longitude != $.goMapBase.defaults.longitude)
//        this.centerLatLng = new google.maps.LatLng(opts.latitude, opts.longitude);
      else if ($.isArray(opts.markers) && opts.markers.length > 0) {
        if (opts.markers[0].address)
          this.geocode({address: opts.markers[0].address, center: true});
        else
          this.centerLatLng = new google.maps.LatLng(opts.markers[0].latitude, opts.markers[0].longitude);
      }
      else
        this.centerLatLng = new google.maps.LatLng(opts.latitude, opts.longitude);

      var myOptions = {
        center:         this.centerLatLng,
        disableDoubleClickZoom:  opts.disableDoubleClickZoom,
            mapTypeControl:      opts.mapTypeControl,
        streetViewControl:    opts.streetViewControl,
        mapTypeControlOptions:  {
          position:  google.maps.ControlPosition[opts.mapTypeControlOptions.position.toUpperCase()],
          style:    google.maps.MapTypeControlStyle[opts.mapTypeControlOptions.style.toUpperCase()]
        },
        mapTypeId:        google.maps.MapTypeId[opts.maptype.toUpperCase()],
            navigationControl:    opts.navigationControl,
        navigationControlOptions: {
          position:  google.maps.ControlPosition[opts.navigationControlOptions.position.toUpperCase()],
          style:    google.maps.NavigationControlStyle[opts.navigationControlOptions.style.toUpperCase()]
        },
            scaleControl:      opts.scaleControl,
            scrollwheel:      opts.scrollwheel,
        zoom:          opts.zoom
      };

      this.map     = new google.maps.Map(el, myOptions);
      this.overlay  = new MyOverlay(this.map);

      this.overlays = { 
        polyline:  { id: 'plId', array: 'polylines',   create: 'createPolyline' },
        polygon:  { id: 'pgId', array: 'polygons',   create: 'createPolygon' },
        circle:    { id: 'cId',  array: 'circles',    create: 'createCircle' },
        rectangle:  { id: 'rId',  array: 'rectangles',  create: 'createRectangle' }
      };

      this.plId = $('<div style="display:none;"/>').appendTo(this.mapId);
      this.pgId = $('<div style="display:none;"/>').appendTo(this.mapId);
      this.cId  = $('<div style="display:none;"/>').appendTo(this.mapId);
      this.rId  = $('<div style="display:none;"/>').appendTo(this.mapId);

      for (var j = 0, l = opts.markers.length; j < l; j++)
        this.createMarker(opts.markers[j]);

      for (var j = 0, l = opts.overlays.length; j < l; j++)
        this[this.overlays[opts.overlays[j].type].create](opts.overlays[j]);

      var goMap = this;
      if (opts.addMarker == true || opts.addMarker == 'multi') {
        google.maps.event.addListener(goMap.map, 'click', function(event) {
          var options = {
            position:  event.latLng,
            draggable: true
          };

          var marker = goMap.createMarker(options);

          google.maps.event.addListener(marker, 'dblclick', function(event) {
            marker.setMap(null);
            goMap.removeMarker(marker.id);
          });

        });
      }
      else if (opts.addMarker == 'single') {
        google.maps.event.addListener(goMap.map, 'click', function(event) {
          if(!goMap.singleMarker) {
            var options = {
              position:  event.latLng,
              draggable: true
            };

            var marker = goMap.createMarker(options);
            goMap.singleMarker = true;

            google.maps.event.addListener(marker, 'dblclick', function(event) {
              marker.setMap(null);
              goMap.removeMarker(marker.id);
              goMap.singleMarker = false;
            });
          }
        });
      }
      delete opts.markers;
      delete opts.overlays;

      return this;
    },

    ready: function(f) {
      google.maps.event.addListenerOnce(this.map, 'bounds_changed', function() { 
        return f();
        }); 
    },

    geocode: function(address, options) {
      var goMap = this;
      setTimeout(function() {
        geocoder.geocode({'address': address.address}, function(results, status) {
              if (status == google.maps.GeocoderStatus.OK && address.center)
            goMap.map.setCenter(results[0].geometry.location);

          if (status == google.maps.GeocoderStatus.OK && options && options.markerId)
            options.markerId.setPosition(results[0].geometry.location);

          else if (status == google.maps.GeocoderStatus.OK && options) {
            if(goMap.lockGeocode) {
              goMap.lockGeocode = false;
              options.position  = results[0].geometry.location;
              options.geocode   = true;
              goMap.createMarker(options);
            }
          }
          else if(status == google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
            goMap.geocode(address, options);
          }
              });
      }, this.opts.delay);
    },

    geoMarker: function() {
      if(this.geoMarkers.length > 0 && !this.lockGeocode) {
        this.lockGeocode = true;
        var current = this.geoMarkers.splice(0, 1);
        this.geocode({address:current[0].address}, current[0]);
      }
      else if(this.lockGeocode) {
        var goMap = this;
        setTimeout(function() {
          goMap.geoMarker();
        }, this.opts.delay);
      }
    },

    setMap: function(options) {
      delete options.mapTypeId;

      if (options.address) {
        this.geocode({address: options.address, center: true});
        delete options.address;
      }
      else if (options.latitude && options.longitude) {
        options.center = new google.maps.LatLng(options.latitude, options.longitude);
        delete options.longitude;
        delete options.latitude;
      }

      if(options.mapTypeControlOptions && options.mapTypeControlOptions.position)
        options.mapTypeControlOptions.position = google.maps.ControlPosition[options.mapTypeControlOptions.position.toUpperCase()];

      if(options.mapTypeControlOptions && options.mapTypeControlOptions.style)
        options.mapTypeControlOptions.style = google.maps.MapTypeControlStyle[options.mapTypeControlOptions.style.toUpperCase()];

      if(options.navigationControlOptions && options.navigationControlOptions.position)
        options.navigationControlOptions.position = google.maps.ControlPosition[options.navigationControlOptions.position.toUpperCase()];

      if(options.navigationControlOptions && options.navigationControlOptions.style)
        options.navigationControlOptions.style = google.maps.NavigationControlStyle[options.navigationControlOptions.style.toUpperCase()];

      this.map.setOptions(options);
    },

    getMap: function() {
       return this.map;
    },

    createListener: function(type, event, data) {
      var target;

      if(typeof type != 'object')
        type = {type:type};

      if(type.type == 'map')
        target = this.map;
      else if(type.type == 'marker' && type.marker)
        target = $(this.mapId).data(type.marker);
      else if(type.type == 'info' && type.marker)
        target = $(this.mapId).data(type.marker + 'info');

      if(target)
        return google.maps.event.addListener(target, event, data);
      else if((type.type == 'marker' || type.type == 'info') && this.getMarkerCount() != this.getTmpMarkerCount())
        var goMap = this;
        setTimeout(function() {
          goMap.createListener(type, event, data);
        }, this.opts.delay);
    },

    removeListener: function(listener) {
      google.maps.event.removeListener(listener);
    },

    setInfoWindow: function(marker, html) {
      var goMap = this;
      html.content    = goMap.opts.html_prepend + html.content + goMap.opts.html_append;
      var infowindow  = new google.maps.InfoWindow(html);
      infowindow.show = false;

      $(goMap.mapId).data(marker.id + 'info',infowindow);

      if (html.popup) {
        goMap.openWindow(infowindow, marker, html);
        infowindow.show = true;
      }

      google.maps.event.addListener(marker, 'click', function() {
        if (infowindow.show && goMap.opts.hideByClick) {
          infowindow.close();
          infowindow.show = false;
        }
        else {
          goMap.openWindow(infowindow, marker, html);
          infowindow.show = true;
        }
      });
    },

    openWindow: function(infowindow, marker, html) {
      if(this.opts.oneInfoWindow)
        this.clearInfo();

      if (html.ajax) {
        infowindow.open(this.map, marker);
        $.ajax({
          url: html.ajax,
          success: function(html) {
            infowindow.setContent(html);
          }
        });
      }
      else if (html.id) {
        infowindow.setContent($(html.id).html());
        infowindow.open(this.map, marker);
      }
      else
        infowindow.open(this.map, marker);
    },

    setInfo: function(id, text) {
      var info = $(this.mapId).data(id + 'info');

      if(typeof text == 'object')
        info.setOptions(text);
      else
        info.setContent(text);
    },

    getInfo: function(id, hideDiv) {
       var info = $(this.mapId).data(id + 'info').getContent();
      if(hideDiv)
        return $(info).html();
      else
        return info;
    },

    clearInfo: function() {
      for (var i = 0, l = this.markers.length; i < l; i++) {
        var info = $(this.mapId).data(this.markers[i] + 'info');
        if(info) {
          info.close();
          info.show = false;
        }
      }
    },

    fitBounds: function(type, markers) {
      var goMap = this;
      if(this.getMarkerCount() != this.getTmpMarkerCount())
        setTimeout(function() {
          goMap.fitBounds(type, markers);
        }, this.opts.delay);
      else {
        this.bounds = new google.maps.LatLngBounds();

        if(!type || (type && type == 'all')) {
          for (var i = 0, l = this.markers.length; i < l; i++) {
            this.bounds.extend($(this.mapId).data(this.markers[i]).position);
          }
        }
        else if (type && type == 'visible') {
          for (var i = 0, l = this.markers.length; i < l; i++) {
            if(this.getVisibleMarker(this.markers[i]))
              this.bounds.extend($(this.mapId).data(this.markers[i]).position);
          }
  
        }
        else if (type && type == 'markers' && $.isArray(markers)) {
          for (var i = 0, l = markers.length; i < l; i++) {
            this.bounds.extend($(this.mapId).data(markers[i]).position);
          }
        }
        this.map.fitBounds(this.bounds);
      }
    },

    getBounds: function() {
      return this.map.getBounds();
    },

    createPolyline: function(poly) {
      poly.type = 'polyline';
      return this.createOverlay(poly);
    },

    createPolygon: function(poly) {
      poly.type = 'polygon';
      return this.createOverlay(poly);
    },

    createCircle: function(poly) {
      poly.type = 'circle';
      return this.createOverlay(poly);
    },

    createRectangle: function(poly) {
      poly.type = 'rectangle';
      return this.createOverlay(poly);
    },

    createOverlay: function(poly) {
      var overlay = [];
      if (!poly.id) {
        this.count++;
        poly.id = this.opts.polyId + this.count;
      }
      switch(poly.type) {
        case 'polyline':
          if (poly.coords.length > 0) {
            for (var j = 0, l = poly.coords.length; j < l; j++)
              overlay.push(new google.maps.LatLng(poly.coords[j].latitude, poly.coords[j].longitude));

            overlay = new google.maps.Polyline({
              map: this.map,
              path: overlay,
              strokeColor:   poly.color ? poly.color : this.opts.polyline.color,
              strokeOpacity:  poly.opacity ? poly.opacity : this.opts.polyline.opacity,
              strokeWeight:  poly.weight ? poly.weight : this.opts.polyline.weight
            });
          }
          else
            return false;
          break;
        case 'polygon':
          if (poly.coords.length > 0) {
            for (var j = 0, l = poly.coords.length; j < l; j++)
              overlay.push(new google.maps.LatLng(poly.coords[j].latitude, poly.coords[j].longitude));

            overlay = new google.maps.Polygon({
              map: this.map,
              path: overlay,
              strokeColor: poly.color ? poly.color : this.opts.polygon.color,
              strokeOpacity: poly.opacity ? poly.opacity : this.opts.polygon.opacity,
              strokeWeight: poly.weight ? poly.weight : this.opts.polygon.weight,
              fillColor: poly.fillColor ? poly.fillColor : this.opts.polygon.fillColor,
              fillOpacity: poly.fillOpacity ? poly.fillOpacity : this.opts.polygon.fillOpacity
            });
          }
          else
            return false;
          break;
        case 'circle':
          overlay = new google.maps.Circle({
            map: this.map,
            center: new google.maps.LatLng(poly.latitude, poly.longitude),
            radius: poly.radius,
            strokeColor: poly.color ? poly.color : this.opts.circle.color,
            strokeOpacity: poly.opacity ? poly.opacity : this.opts.circle.opacity,
            strokeWeight: poly.weight ? poly.weight : this.opts.circle.weight,
            fillColor: poly.fillColor ? poly.fillColor : this.opts.circle.fillColor,
            fillOpacity: poly.fillOpacity ? poly.fillOpacity : this.opts.circle.fillOpacity
          });
          break;
        case 'rectangle':
          overlay = new google.maps.Rectangle({
            map: this.map,
            bounds: new google.maps.LatLngBounds(new google.maps.LatLng(poly.sw.latitude, poly.sw.longitude), new google.maps.LatLng(poly.ne.latitude, poly.ne.longitude)),
            strokeColor: poly.color ? poly.color : this.opts.circle.color,
            strokeOpacity: poly.opacity ? poly.opacity : this.opts.circle.opacity,
            strokeWeight: poly.weight ? poly.weight : this.opts.circle.weight,
            fillColor: poly.fillColor ? poly.fillColor : this.opts.circle.fillColor,
            fillOpacity: poly.fillOpacity ? poly.fillOpacity : this.opts.circle.fillOpacity
          });
          break;
        default:
          return false;
          break;
      }
      this.addOverlay(poly, overlay);
      return overlay;
    },

    addOverlay: function(poly, overlay) {
      $(this[this.overlays[poly.type].id]).data(poly.id, overlay);
      this[this.overlays[poly.type].array].push(poly.id);
    },

    setOverlay: function(type, overlay, options) {
      overlay = $(this[this.overlays[type].id]).data(overlay);

      if (options.coords && options.coords.length > 0) {
        var array = [];
        for (var j = 0, l = options.coords.length; j < l; j++)
          array.push(new google.maps.LatLng(options.coords[j].latitude, options.coords[j].longitude));

        options.path = array;
        delete options.coords;
      }
      else if (options.ne && options.sw) {
        options.bounds = new google.maps.LatLngBounds(new google.maps.LatLng(options.sw.latitude, options.sw.longitude), new google.maps.LatLng(options.ne.latitude, options.ne.longitude));
        delete options.ne;
        delete options.sw;
      }
      else if (options.latitude && options.longitude) {

        options.center = new google.maps.LatLng(options.latitude, options.longitude);
        delete options.latitude;
        delete options.longitude;
      }
      overlay.setOptions(options);
    },

    showHideOverlay: function(type, overlay, display) {
      if(typeof display === 'undefined') {
        if(this.getVisibleOverlay(type, overlay))
          display = false;
        else
          display = true;
      }

      if(display)
        $(this[this.overlays[type].id]).data(overlay).setMap(this.map);
      else
        $(this[this.overlays[type].id]).data(overlay).setMap(null);
    },

    getVisibleOverlay: function(type, overlay) {
      if($(this[this.overlays[type].id]).data(overlay).getMap())
        return true;
      else
        return false;
    },

    getOverlaysCount: function(type) {
      return this[this.overlays[type].array].length;
    },

    removeOverlay: function(type, overlay) {
      var index = $.inArray(overlay, this[this.overlays[type].array]), current;
      if (index > -1) {
        current = this[this.overlays[type].array].splice(index, 1);
        var markerId = current[0];
        $(this[this.overlays[type].id]).data(markerId).setMap(null);
        $(this[this.overlays[type].id]).removeData(markerId);

        return true;
      }
      return false;
    },

    clearOverlays: function(type) {
      for (var i = 0, l = this[this.overlays[type].array].length; i < l; i++) {
        var markerId = this[this.overlays[type].array][i];
        $(this[this.overlays[type].id]).data(markerId).setMap(null);
        $(this[this.overlays[type].id]).removeData(markerId);
      }
      this[this.overlays[type].array] = [];
    },

    showHideMarker: function(marker, display) {
      if(typeof display === 'undefined') {
        if(this.getVisibleMarker(marker)) {
          $(this.mapId).data(marker).setVisible(false);
          var info = $(this.mapId).data(marker + 'info');
          if(info && info.show) {
            info.close();
            info.show = false;
          }
        }
        else
          $(this.mapId).data(marker).setVisible(true);
      }
      else
        $(this.mapId).data(marker).setVisible(display);
    },

    showHideMarkerByGroup: function(group, display) {
      for (var i = 0, l = this.markers.length; i < l; i++) {
        var markerId = this.markers[i];
        var marker   = $(this.mapId).data(markerId);
        if(marker.group == group) {
          if(typeof display === 'undefined') {
            if(this.getVisibleMarker(markerId)) {
              marker.setVisible(false);
              var info = $(this.mapId).data(markerId + 'info');
              if(info && info.show) {
                info.close();
                info.show = false;
              }
            }
            else
              marker.setVisible(true);
          }
          else
            marker.setVisible(display);
        }
      }
    },

    getVisibleMarker: function(marker) {
      return $(this.mapId).data(marker).getVisible();
    },

    getMarkerCount: function() {
      return this.markers.length;
    },

    getTmpMarkerCount: function() {
      return this.tmpMarkers.length;
    },

    getVisibleMarkerCount: function() {
      return this.getMarkers('visiblesInMap').length;
    },

    getMarkerByGroupCount: function(group) {
      return this.getMarkers('group', group).length;
    },

    getMarkers: function(type, name) {
      var array = [];
      switch(type) {
        case "json":
          for (var i = 0, l = this.markers.length; i < l; i++) {
            var temp = "'" + i + "': '" + $(this.mapId).data(this.markers[i]).getPosition().toUrlValue() + "'";
            array.push(temp);
          }
          array = "{'markers':{" + array.join(",") + "}}";
          break;
        case "data":
          for (var i = 0, l = this.markers.length; i < l; i++) {
            var temp = "marker[" + i + "]=" + $(this.mapId).data(this.markers[i]).getPosition().toUrlValue();
            array.push(temp);
          }
          array = array.join("&");           
          break;
        case "visiblesInBounds":
          for (var i = 0, l = this.markers.length; i < l; i++) {
            if (this.isVisible($(this.mapId).data(this.markers[i]).getPosition()))
              array.push(this.markers[i]);
          }
          break;
        case "visiblesInMap":
          for (var i = 0, l = this.markers.length; i < l; i++) {
            if(this.getVisibleMarker(this.markers[i]))
              array.push(this.markers[i]);
          }
          break;
        case "group":
          if(name)
            for (var i = 0, l = this.markers.length; i < l; i++) {
              if($(this.mapId).data(this.markers[i]).group == name)
                array.push(this.markers[i]);
            }
          break;
        case "markers":
          for (var i = 0, l = this.markers.length; i < l; i++) {
            var temp = $(this.mapId).data(this.markers[i]);
            array.push(temp);
          }
          break;
        default:
          for (var i = 0, l = this.markers.length; i < l; i++) {
            var temp = $(this.mapId).data(this.markers[i]).getPosition().toUrlValue();
            array.push(temp);
          }
          break;
      }
      return array;
    },

    getVisibleMarkers: function() {
      return this.getMarkers('visiblesInBounds');
    },

    createMarker: function(marker) {
      if (!marker.geocode) {
        this.count++;
        if (!marker.id)
          marker.id = this.opts.prefixId + this.count;
        this.tmpMarkers.push(marker.id);
      }
      if (marker.address && !marker.geocode) {
        this.geoMarkers.push(marker);
        this.geoMarker();
      }
      else if (marker.latitude && marker.longitude || marker.position) {
        var options = { map:this.map };
        options.id       = marker.id;
        options.group    = marker.group ? marker.group : this.opts.groupId; 
        options.zIndex     = marker.zIndex ? marker.zIndex : 0;
        options.zIndexOrg  = marker.zIndexOrg ? marker.zIndexOrg : 0;

        if (marker.visible == false)
          options.visible = marker.visible;

        if (marker.title)
          options.title = marker.title;

        if (marker.draggable)
          options.draggable = marker.draggable;

        if (marker.icon && marker.icon.image) {
          options.icon = marker.icon.image;
          if (marker.icon.shadow)
            options.shadow = marker.icon.shadow;
        }
        else if (marker.icon)
          options.icon = marker.icon;

        else if (this.opts.icon && this.opts.icon.image) {
          options.icon = this.opts.icon.image;
          if (this.opts.icon.shadow)
            options.shadow = this.opts.icon.shadow;
        }
        else if (this.opts.icon)
          options.icon = this.opts.icon;

        options.position = marker.position ? marker.position : new google.maps.LatLng(marker.latitude, marker.longitude);

        var cmarker = new google.maps.Marker(options);

        if (marker.html) {
          if (!marker.html.content && !marker.html.ajax && !marker.html.id)
            marker.html = { content:marker.html };
          else if (!marker.html.content)
            marker.html.content = null;

          this.setInfoWindow(cmarker, marker.html);
        }
        this.addMarker(cmarker);
        return cmarker;
      }
    },

    addMarker: function(marker) {
      $(this.mapId).data(marker.id, marker);
      this.markers.push(marker.id);
    },

    setMarker: function(marker, options) {
      var tmarker = $(this.mapId).data(marker);

      delete options.id;
      delete options.visible;

      if(options.icon) {
        var toption = options.icon;
        delete options.icon;

        if(toption && toption == 'default') {
          if (this.opts.icon && this.opts.icon.image) {
            options.icon = this.opts.icon.image;
            if (this.opts.icon.shadow)
              options.shadow = this.opts.icon.shadow;
          }
          else if (this.opts.icon)
            options.icon = this.opts.icon;
        }
        else if(toption && toption.image) {
          options.icon = toption.image;
          if (toption.shadow)
            options.shadow = toption.shadow;
        }
        else if (toption)
          options.icon = toption;
      }

      if (options.address) {
        this.geocode({address: options.address}, {markerId:tmarker});
        delete options.address;
        delete options.latitude;
        delete options.longitude;
        delete options.position;
      }
      else if (options.latitude && options.longitude || options.position) {
        if (!options.position)
          options.position = new google.maps.LatLng(options.latitude, options.longitude);
      }
      tmarker.setOptions(options);
    },

    removeMarker: function(marker) {
      var index = $.inArray(marker, this.markers), current;
      if (index > -1) {
        this.tmpMarkers.splice(index,1);
        current = this.markers.splice(index,1);
        var markerId = current[0];
        var marker   = $(this.mapId).data(markerId);
        var info     = $(this.mapId).data(markerId + 'info');

        marker.setVisible(false);
        marker.setMap(null);
        $(this.mapId).removeData(markerId);

        if(info) {
          info.close();
          info.show = false;
          $(this.mapId).removeData(markerId + 'info');
        }
        return true;
      }
      return false;
    },

    clearMarkers: function() {
      for (var i = 0, l = this.markers.length; i < l; i++) {
        var markerId = this.markers[i];
        var marker   = $(this.mapId).data(markerId);
        var info     = $(this.mapId).data(markerId + 'info');

        marker.setVisible(false);
        marker.setMap(null);
        $(this.mapId).removeData(markerId);

        if(info) {
          info.close();
          info.show = false;
          $(this.mapId).removeData(markerId + 'info');
        }
      }
      this.singleMarker = false;
      this.lockGeocode = false;
      this.markers = [];
      this.tmpMarkers = [];
      this.geoMarkers = [];
    },

    isVisible: function(latlng) {
      return this.map.getBounds().contains(latlng);
    }
  }
})(jQuery);