
/**
 * @file
 * Drupal to Google Maps API bridge.
 */

/*global jQuery, Drupal, GLatLng, GSmallZoomControl, GLargeMapControl, GMap2 */
/*global GMapTypeControl, GSmallMapControl, G_HYBRID_MAP, G_NORMAL_MAP */
/*global G_PHYSICAL_MAP, G_SATELLITE_MAP, GHierarchicalMapTypeControl */
/*global GKeyboardHandler, GLatLngBounds, GMenuMapTypeControl, GEvent */
/*global GOverviewMapControl, GScaleControl, GUnload */

(function () { // BEGIN closure
  var handlers = {};
  var maps = {};
  var ajaxoffset = 0;

  Drupal.gmap = {

    /**
     * Retrieve a map object for use by a non-widget.
     * Use this if you need to be able to fire events against a certain map
     * which you have the mapid for.
     * Be a good GMap citizen! Remember to send change()s after modifying variables!
     */
    getMap: function (mapid) {
      if (maps[mapid]) {
        return maps[mapid];
      }
      else {
        // Perhaps the user passed a widget id instead?
        mapid = mapid.split('-').slice(1, -1).join('-');
        if (maps[mapid]) {
          return maps[mapid];
        }
      }
      return false;
    },

    unloadMap: function (mapid) {
      delete maps[mapid];
    },

    addHandler: function (handler, callback) {
      if (!handlers[handler]) {
        handlers[handler] = [];
      }
      handlers[handler].push(callback);
    },

    globalChange: function (name, userdata) {
      for (var mapid in Drupal.settings.gmap) {
        if (Drupal.settings.gmap.hasOwnProperty(mapid)) {
          // Skip maps that are set up but not shown, etc.
          if (maps[mapid]) {
            maps[mapid].change(name, -1, userdata);
          }
        }
      }
    },

    setup: function (settings) {
      var obj = this;

      var initcallback = function (mapid) {
        return (function () {
          maps[mapid].change("bootstrap_options", -1);
          maps[mapid].change("boot", -1);
          maps[mapid].change("init", -1);
          // Send some changed events to fire up the rest of the initial settings..
          maps[mapid].change("maptypechange", -1);
          maps[mapid].change("controltypechange", -1);
          maps[mapid].change("alignchange", -1);
          // Set ready to put the event system into action.
          maps[mapid].ready = true;
          maps[mapid].change("ready", -1);
        });
      };

      if (settings || (Drupal.settings && Drupal.settings.gmap)) {
        var mapid = obj.id.split('-');
        if (Drupal.settings['gmap_remap_widgets']) {
          if (Drupal.settings['gmap_remap_widgets'][obj.id]) {
            jQuery.each(Drupal.settings['gmap_remap_widgets'][obj.id].classes, function() {
              jQuery(obj).addClass(this);
            });
            mapid = Drupal.settings['gmap_remap_widgets'][obj.id].id.split('-');
          }
        }
        var instanceid = mapid.pop();
        mapid.shift();
        mapid = mapid.join('-');
        var control = instanceid.replace(/\d+$/, '');

        // Lazy init the map object.
        if (!maps[mapid]) {
          if (settings) {
            maps[mapid] = new Drupal.gmap.map(settings);
          }
          else {
            maps[mapid] = new Drupal.gmap.map(Drupal.settings.gmap[mapid]);
          }
          // Prepare the initialization callback.
          var callback = initcallback(mapid);
          setTimeout(callback, 0);
        }

        if (handlers[control]) {
          for (var i = 0; i < handlers[control].length; i++) {
            handlers[control][i].call(maps[mapid], obj);
          }
        }
        else {
          // Element with wrong class?
        }
      }
    }
  };

  jQuery.fn.createGMap = function (settings, mapid) {
    return this.each(function () {
      if (!mapid) {
        mapid = 'auto' + ajaxoffset + 'ajax';
        ajaxoffset++;
      }
      settings.id = mapid;
      jQuery(this)
        .attr('id', 'gmap-' + mapid + '-gmap0')
        .css('width', settings.width)
        .css('height', settings.height)
        .addClass('gmap-control')
        .addClass('gmap-gmap')
        .addClass('gmap')
        .addClass('gmap-map')
        .addClass('gmap-' + mapid + '-gmap')
        .addClass('gmap-processed')
        .each(function() {Drupal.gmap.setup.call(this, settings)});
    });
  };

})(); // END closure

Drupal.gmap.factory = {};

Drupal.gmap.map = function (v) {
  this.vars = v;
  this.map = undefined;
  this.ready = false;
  var _bindings = {};

  /**
   * Register interest in a change.
   */
  this.bind = function (name, callback) {
    if (!_bindings[name]) {
      _bindings[name] = [];
    }
    return _bindings[name].push(callback) - 1;
  };

  /**
   * Change notification.
   * Interested parties can act on changes.
   */
  this.change = function (name, id, userdata) {
    var c;
    if (_bindings[name]) {
      for (c = 0; c < _bindings[name].length; c++) {
        if (c !== id) {
          _bindings[name][c](userdata);
        }
      }
    }
    if (name !== 'all') {
      this.change('all', -1, name, userdata);
    }
  };

  /**
   * Deferred change notification.
   * This will cause a change notification to be tacked on to the *end* of the event queue.
   */
  this.deferChange = function (name, id, userdata) {
    var obj = this;
    // This will move the function call to the end of the event loop.
    setTimeout(function () {
      obj.change(name, id, userdata);
    }, 0);
  };
  
  this.getMapTypeName = function(type) {
    if (type == 'map' || type == 'roadmap') return 'Map';
    if (type == 'hybrid') return 'Hybrid';
    if (type == 'physical' || type == 'terrain') return 'Physical';
    if (type == 'satellite') return 'Satellite';
  };  
  
  this.getMapTypeId = function(type) {
    if (type == 'Map' || type == 'Roadmap') return google.maps.MapTypeId.ROADMAP;
    if (type == 'Hybrid') return google.maps.MapTypeId.HYBRID;
    if (type == 'Physical' || type == 'Terrain') return google.maps.MapTypeId.TERRAIN;
    if (type == 'Satellite') return google.maps.MapTypeId.SATELLITE;
  };  
};

////////////////////////////////////////
//             Map widget             //
////////////////////////////////////////
Drupal.gmap.addHandler('gmap', function (elem) {
  var obj = this;
  var _ib = {};

  // Respond to incoming zooms
  _ib.zoom = obj.bind("zoom", function (zoom) {
    obj.map.setZoom(obj.vars.zoom);
  });

  // Respond to incoming moves
  _ib.move = obj.bind("move", function () {
    obj.map.panTo(new google.maps.LatLng(obj.vars.latitude, obj.vars.longitude));
  });

  // Respond to incoming width changes.
  _ib.width = obj.bind("widthchange", function (w) {
    obj.map.getDiv().style.width = w;
    google.maps.event.trigger(obj.map);
  });
  // Send out outgoing width changes.
  // N/A
  // Respond to incoming height changes.
  _ib.height = obj.bind("heightchange", function (h) {
    obj.map.getDiv().style.height = h;
    google.maps.event.trigger(obj.map);
  });
  // Send out outgoing height changes.
  // N/A

  // Respond to incoming control type changes.
  _ib.ctc = obj.bind("controltypechange", function () {
    if (obj.vars.controltype === 'Small') {
      obj.map.setOptions({navigationControlOptions: {style: google.maps.NavigationControlStyle.SMALL}});
    }
    else if (obj.vars.controltype === 'Large') {
      obj.map.setOptions({navigationControlOptions: {style: google.maps.NavigationControlStyle.ZOOM_PAN}});
    }
    else if (obj.vars.controltype === 'Android') {
      obj.map.setOptions({navigationControlOptions: {style: google.maps.NavigationControlStyle.ANDROID}});
    }
  });
  // Send out outgoing control type changes.
  // N/A
  
  // Respond to incoming map type changes.
  _ib.mtc = obj.bind("maptypechange", function () {
    obj.map.setMapTypeId(obj.getMapTypeId(obj.vars.maptype));
  });
  // Send out outgoing map type changes.
  // N/A  

  obj.bind("bootstrap_options", function () {
    // Bootup options.
    var opts = {}; // Object literal google.maps.MapOptions
    obj.opts = opts;

    // Disable default UI for custom options
    opts.disableDefaultUI = true;
    
    // Set draggable property
    if (obj.vars.behavior.nodrag) {
      opts.draggable = false;
    }
    else if (obj.vars.behavior.nokeyboard) {
      opts.keyboardShortcuts = false;
    }

    // Set default map type (set to road map if nothing selected)
    switch (obj.vars.maptype) {
      case 'Hybrid':
        opts.mapTypeId = google.maps.MapTypeId.HYBRID;
        break;
      case 'Physical':
        opts.mapTypeId = google.maps.MapTypeId.TERRAIN;
        break;
      case 'Satellite':
        opts.mapTypeId = google.maps.MapTypeId.SATELLITE;
        break;
      case 'Map':
      default:
        opts.mapTypeId = google.maps.MapTypeId.ROADMAP;
        break;
    }

    // Null out the enabled types.
    opts.mapTypeIds = [];

    if (obj.vars.baselayers.Map) {
      opts.mapTypeIds.push(google.maps.MapTypeId.ROADMAP);
    }
    if (obj.vars.baselayers.Hybrid) {
      opts.mapTypeIds.push(google.maps.MapTypeId.HYBRID);
    }
    if (obj.vars.baselayers.Physical) {
      opts.mapTypeIds.push(google.maps.MapTypeId.TERRAIN);
    }
    if (obj.vars.baselayers.Satellite) {
      opts.mapTypeIds.push(google.maps.MapTypeId.SATELLITE);
    }    

    if (obj.vars.draggableCursor) {
      opts.draggableCursor = obj.vars.draggableCursor;
    }
    if (obj.vars.draggingCursor) {
      opts.draggingCursor = obj.vars.draggingCursor;
    }
    if (obj.vars.backgroundColor) {
      opts.backgroundColor = obj.vars.backgroundColor;
    }

    // Map type control
    opts.mapTypeControl = true;
    opts.mapTypeControlOptions = {};    
    if (obj.vars.mtc === 'standard') {
      opts.mapTypeControlOptions.style = google.maps.MapTypeControlStyle.DEFAULT;
    }
    else if (obj.vars.mtc === 'horiz') {
      opts.mapTypeControlOptions.style = google.maps.MapTypeControlStyle.HORIZONTAL_BAR;
    }
    else if (obj.vars.mtc === 'menu') {
      opts.mapTypeControlOptions.style = google.maps.MapTypeControlStyle.DROPDOWN_MENU;
    }

    // Navigation control type
    if (obj.vars.controltype !== 'None') {
      opts.navigationControl = true;
    }
    if (obj.vars.controltype === 'Small') {
      opts.navigationControlOptions = {style: google.maps.NavigationControlStyle.SMALL};
    }
    else if (obj.vars.controltype === 'Large') {
      opts.navigationControlOptions = {style: google.maps.NavigationControlStyle.ZOOM_PAN};
    }

    // Set scale control visibility
    opts.scaleControl = obj.vars.behavior.scale;

    // Scroll wheel control
    if (obj.vars.behavior.nomousezoom) {
      opts.scrollwheel = false;
    }
    // Disable double-click zoom
    if (obj.vars.behavior.nocontzoom) {
      opts.disableDoubleClickZoom = true;
    }

  });

  obj.bind("boot", function () {
    obj.map = new google.maps.Map(elem, obj.opts);
    //console.log(obj.map);
  });

  obj.bind("init", function () {
    var map = obj.map;

    // Not implemented in API v3
    // if (obj.vars.behavior.overview) {
    //   map.addControl(new GOverviewMapControl());
    // }
    // if (obj.vars.behavior.googlebar) {
    //   map.enableGoogleBar();
    // }
   
    if (obj.vars.extent) {
      var c = obj.vars.extent;
      var extent = new google.maps.LatLngBounds(new google.maps.LatLng(c[0][0], c[0][1]), new google.maps.LatLng(c[1][0], c[1][1]));
      obj.vars.latitude = extent.getCenter().lat();
      obj.vars.longitude = extent.getCenter().lng();
      obj.vars.zoom = map.getBoundsZoomLevel(extent);
    }
    if (obj.vars.behavior.collapsehack) {
      // Modify collapsable fieldsets to make maps check dom state when the resize handle
      // is clicked. This may not necessarily be the correct thing to do in all themes,
      // hence it being a behavior.
      setTimeout(function () {
        var r = function () {
          google.maps.event.trigger(map);
          map.setCenter(new google.maps.LatLng(obj.vars.latitude, obj.vars.longitude), obj.vars.zoom);
        };
        jQuery(elem).parents('fieldset.collapsible').children('legend').children('a').click(r);
        jQuery('.vertical-tab-button', jQuery(elem).parents('.vertical-tabs')).children('a').click(r);
        jQuery(window).bind('hashchange', r);
        // Would be nice, but doesn't work.
        //$(elem).parents('fieldset.collapsible').children('.fieldset-wrapper').scroll(r);
      }, 0);
    }
    map.setCenter(new google.maps.LatLng(obj.vars.latitude, obj.vars.longitude));
    map.setZoom(obj.vars.zoom);

    // Send out outgoing zooms
    google.maps.event.addListener(map, "zoom_changed", function () {
      obj.vars.zoom = map.getZoom();
      obj.change("zoom", _ib.zoom);
    });

    // Send out outgoing moves
    google.maps.event.addListener(map, "center_changed", function () {
      var coord = map.getCenter();
      obj.vars.latitude = coord.lat();
      obj.vars.longitude = coord.lng();
      obj.change("move", _ib.move);
    });

    // Send out outgoing map type changes.
    google.maps.event.addListener(map, "maptypeid_changed", function () {
      // If the map isn't ready yet, ignore it.
      if (obj.ready) {
        obj.vars.maptype = obj.getMapTypeName(map.getMapTypeId());
        obj.change("maptypechange", _ib.mtc);
      }
    });
    
    /*
    google.maps.event.addListener(map, 'click', function(event) {
      alert(Drupal.gmap.getIcon("big blue", 0));
      var marker = new google.maps.Marker({
        position: event.latLng, 
        map: map
      });
      google.maps.event.addListener(marker, 'click', function() {
        marker.setMap(null);
      });
    });
    */
  });  
});

////////////////////////////////////////
//            Zoom widget             //
////////////////////////////////////////
Drupal.gmap.addHandler('zoom', function (elem) {
  var obj = this;
  // Respond to incoming zooms
  var binding = obj.bind("zoom", function () {
    elem.value = obj.vars.zoom;
  });
  // Send out outgoing zooms
  jQuery(elem).change(function () {
    obj.vars.zoom = parseInt(elem.value, 10);
    obj.change("zoom", binding);
  });
});

////////////////////////////////////////
//          Latitude widget           //
////////////////////////////////////////
Drupal.gmap.addHandler('latitude', function (elem) {
//  var obj = this;
//  // Respond to incoming movements.
//  var binding = obj.bind("move", function () {
//    elem.value = '' + obj.vars.latitude;
//  });
//  // Send out outgoing movements.
//  $(elem).change(function () {
//    obj.vars.latitude = Number(this.value);
//    obj.change("move", binding);
//  });
});

////////////////////////////////////////
//         Longitude widget           //
////////////////////////////////////////
Drupal.gmap.addHandler('longitude', function (elem) {
//  var obj = this;
//  // Respond to incoming movements.
//  var binding = obj.bind("move", function () {
//    elem.value = '' + obj.vars.longitude;
//  });
//  // Send out outgoing movements.
//  $(elem).change(function () {
//    obj.vars.longitude = Number(this.value);
//    obj.change("move", binding);
//  });
});

////////////////////////////////////////
//          Latlon widget             //
////////////////////////////////////////
Drupal.gmap.addHandler('latlon', function (elem) {
  var obj = this;
  // Respond to incoming movements.
  var binding = obj.bind("move", function () {
    elem.value = '' + obj.vars.latitude + ',' + obj.vars.longitude;
  });
  // Send out outgoing movements.
  jQuery(elem).change(function () {
    var t = this.value.split(',');
    obj.vars.latitude = Number(t[0]);
    obj.vars.longitude = Number(t[1]);
    obj.change("move", binding);
  });
});

////////////////////////////////////////
//          Maptype widget            //
////////////////////////////////////////
Drupal.gmap.addHandler('maptype', function (elem) {
  var obj = this;
  // Respond to incoming movements.
  var binding = obj.bind("maptypechange", function () {
    elem.value = obj.vars.maptype;
  });
  // Send out outgoing movements.
  jQuery(elem).change(function () {
    obj.vars.maptype = elem.value;
    obj.change("maptypechange", binding);
  });
});
 
(function () { // BEGIN CLOSURE
  var re = /([0-9.]+)\s*(em|ex|px|in|cm|mm|pt|pc|%)/;
  var normalize = function (str) {
    var ar;
    if ((ar = re.exec(str.toLowerCase()))) {
      return ar[1] + ar[2];
    }
    return null;
  };

  ////////////////////////////////////////
  //           Width widget             //
  ////////////////////////////////////////
  Drupal.gmap.addHandler('width', function (elem) {
    var obj = this;
    // Respond to incoming width changes.
    var binding = obj.bind("widthchange", function (w) {
      elem.value = normalize(w);
    });
    // Send out outgoing width changes.
    jQuery(elem).change(function () {
      var n;
      if ((n = normalize(elem.value))) {
        elem.value = n;
        obj.change('widthchange', binding, n);
      }
    });
    obj.bind('init', function () {
      jQuery(elem).change();
    });
  });
 
  ////////////////////////////////////////
  //           Height widget            //
  ////////////////////////////////////////
  Drupal.gmap.addHandler('height', function (elem) {
    var obj = this;
    // Respond to incoming height changes.
    var binding = obj.bind("heightchange", function (h) {
      elem.value = normalize(h);
    });
    // Send out outgoing height changes.
    jQuery(elem).change(function () {
      var n;
      if ((n = normalize(elem.value))) {
        elem.value = n;
        obj.change('heightchange', binding, n);
      }
    });
    obj.bind('init', function () {
      jQuery(elem).change();
    });
  });
})(); // END CLOSURE

////////////////////////////////////////
//        Control type widget         //
////////////////////////////////////////
Drupal.gmap.addHandler('controltype', function (elem) {
  var obj = this;
  // Respond to incoming height changes.
  var binding = obj.bind("controltypechange", function () {
    elem.value = obj.vars.controltype;
  });
  // Send out outgoing height changes.
  jQuery(elem).change(function () {
    obj.vars.controltype = elem.value
    obj.change("controltypechange", binding);
  });
});

// // Map cleanup.
// if (Drupal.jsEnabled) {
//   $(document).unload(GUnload);
// }

Drupal.behaviors.GMap = {
  attach: function (context, settings) {
  if (Drupal.settings && Drupal.settings['gmap_remap_widgets']) {
    jQuery.each(Drupal.settings['gmap_remap_widgets'], function(key, val) {
        jQuery('#'+ key).addClass('gmap-control');
    });
  }
    jQuery('.gmap-gmap:not(.gmap-processed)', context).addClass('gmap-processed').each(function () {Drupal.gmap.setup.call(this)});
    jQuery('.gmap-control:not(.gmap-processed)', context).addClass('gmap-processed').each(function () {Drupal.gmap.setup.call(this)});
  },
  detach: function (context, settings) {
    jQuery('.gmap-processed', context).each(function (element) {
      //find mapid
      var id = jQuery(this).attr('id');
      var mapid = id.split('-', 2);

      //unload map
      Drupal.gmap.unloadMap(mapid[1]);
    });
  }
};
