/**
 * jQuery goMap
 *
 * @url   http://www.pittss.lv/jquery/gomap/
 * @author  Jevgenijs Shtrauss <pittss@gmail.com>
 * @version 1.3.0 2011.02.28
 * 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',
      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:          [],
      maptype:          'HYBRID', // Map type - HYBRID, ROADMAP, SATELLITE, TERRAIN
      html_prepend:       '<div class=gomapMarker>',
      html_append:        '</div>',
      addMarker:          false
    },    
    map:      null,
    count:      0,
    markers:    [],
    tmpMarkers:   [],
    geoMarkers:   [],
    lockGeocode:  false,
    bounds:     null,
    overlay:    null,
    mapId:      null,
    opts:     null,
    centerLatLng: null,

    init: function(el, options) {
      var opts  = $.extend({}, $.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: eval('google.maps.ControlPosition.' + opts.mapTypeControlOptions.position.toUpperCase()),
          style:    eval('google.maps.MapTypeControlStyle.' + opts.mapTypeControlOptions.style.toUpperCase())
        },
        mapTypeId:        eval('google.maps.MapTypeId.' + opts.maptype.toUpperCase()),
            navigationControl:    opts.navigationControl,
        navigationControlOptions: {
          position: eval('google.maps.ControlPosition.' + opts.navigationControlOptions.position.toUpperCase()),
          style:    eval('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);

      for (var j = 0; j < opts.markers.length; j++)
        this.createMarker(opts.markers[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;
            });
          }
        });
      }
      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 = eval('google.maps.ControlPosition.' + options.mapTypeControlOptions.position.toUpperCase());

      if(options.mapTypeControlOptions && options.mapTypeControlOptions.style)
        options.mapTypeControlOptions.style = eval('google.maps.MapTypeControlStyle.' + options.mapTypeControlOptions.style.toUpperCase());

      if(options.navigationControlOptions && options.navigationControlOptions.position)
        options.navigationControlOptions.position = eval('google.maps.ControlPosition.' + options.navigationControlOptions.position.toUpperCase());

      if(options.navigationControlOptions && options.navigationControlOptions.style)
        options.navigationControlOptions.style = eval('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())
        setTimeout(function() {
          this.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 in this.markers) {
        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 in this.markers) {
            this.bounds.extend($(this.mapId).data(this.markers[i]).position);
          }
        }
        else if (type && type == 'visible') {
          for (var i in this.markers) {
            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 in markers) {
            this.bounds.extend($(this.mapId).data(markers[i]).position);
          }
        }
        this.map.fitBounds(this.bounds);
      }
    },

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

    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 in this.markers) {
        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 in this.markers) {
            var temp = "'" + i + "': '" + $(this.mapId).data(this.markers[i]).getPosition().toUrlValue() + "'";
            array.push(temp);
          }
          array = "{'markers':{" + array.join(",") + "}}";
          break;
        case "data":
          for (var i in this.markers) {
            var temp = "marker[" + i + "]=" + $(this.mapId).data(this.markers[i]).getPosition().toUrlValue();
            array.push(temp);
          }
          array = array.join("&");          
          break;
        case "visiblesInBounds":
          for (var i in this.markers) {
            if (this.isVisible($(this.mapId).data(this.markers[i]).getPosition()))
              array.push(this.markers[i]);
          }
          break;
        case "visiblesInMap":
          for (var i in this.markers) {
            if(this.getVisibleMarker(this.markers[i]))
              array.push(this.markers[i]);
          }
          break;
        case "group":
          if(name)
            for (var i in this.markers) {
              if($(this.mapId).data(this.markers[i]).group == name)
                array.push(this.markers[i]);
            }
          break;
        case "markers":
          for (var i in this.markers) {
            var temp = $(this.mapId).data(this.markers[i]);
            array.push(temp);
          }
          break;
        default:
          for (var i in this.markers) {
            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 in this.markers) {
        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);
