cordova.define("cordova-plugin-googlemaps.PluginMap", function(require, exports, module) { 


var utils = require('cordova/utils'),
  event = require('cordova-plugin-googlemaps.event'),
  BaseClass = require('cordova-plugin-googlemaps.BaseClass'),
  LatLng = require('cordova-plugin-googlemaps.LatLng'),
  MapTypeId = require('cordova-plugin-googlemaps.MapTypeId');

var MAP_TYPES = {};
MAP_TYPES[MapTypeId.NORMAL] = 'roadmap';
MAP_TYPES[MapTypeId.ROADMAP] = 'roadmap';
MAP_TYPES[MapTypeId.SATELLITE] = 'satellite';
MAP_TYPES[MapTypeId.HYBRID] = 'hybrid';
MAP_TYPES[MapTypeId.TERRAIN] = 'terrain';
MAP_TYPES[MapTypeId.NONE] = 'none';

var LOCATION_ERROR = {};
LOCATION_ERROR[1] = 'service_denied';
LOCATION_ERROR[2] = 'not_available';
LOCATION_ERROR[3] = 'cannot_detect';
var LOCATION_ERROR_MSG = {};
LOCATION_ERROR_MSG[1] = 'Location service is rejected by user.';
LOCATION_ERROR_MSG[2] = 'Since this device does not have any location provider, this app can not detect your location.';
LOCATION_ERROR_MSG[3] = 'Can not detect your location. Try again.';

function displayGrayMap(container) {
  var gmErrorContent = document.querySelector('.gm-err-container');
  var gmnoprint = document.querySelector('.gmnoprint');
  if (!gmErrorContent && !gmnoprint) {
    container.innerHTML = [
      '<div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color:rgb(229, 227, 223)">',
      '<div style="text-align: center; position: absolute; left: 0; top: 0; bottom: 0; right: 0; width: 80%; height: 100px; margin: auto; color: #616161">',
      '<img src="https://maps.gstatic.com/mapfiles/api-3/images/icon_error.png"><br>',
      '<h3>Can not display map.<br>Check the developer console.</h3>',
      '</div>',
      '</div>'
    ].join('\n');
  }
}

function PluginMap(mapId, options) {
  var self = this;
  BaseClass.apply(this);
  var mapDiv = document.querySelector('[__pluginMapId=\'' + mapId + '\']');
  mapDiv.style.backgroundColor = 'rgb(229, 227, 223)';

  var container = document.createElement('div');
  container.style.userSelect='none';
  container.style['-webkit-user-select']='none';
  container.style['-moz-user-select']='none';
  container.style['-ms-user-select']='none';
  mapDiv.style.position = 'relative';
  container.style.position = 'absolute';
  container.style.top = 0;
  container.style.bottom = 0;
  container.style.right = 0;
  container.style.left = 0;
  mapDiv.insertBefore(container, mapDiv.firstElementChild);

  self.set('isGoogleReady', false);
  self.set('container', container);
  self.PLUGINS = {};

  Object.defineProperty(self, '__pgmId', {
    value: mapId,
    writable: false
  });
  Object.defineProperty(self, 'objects', {
    value: {},
    writable: false
  });
  Object.defineProperty(self, 'activeMarker', {
    value: null,
    writable: true
  });
  self.set('clickable', true);


  self.one('googleready', function() {
    self.set('isGoogleReady', true);

    var mapTypeReg = new google.maps.MapTypeRegistry();
    mapTypeReg.set('none', new google.maps.ImageMapType({
      'getTileUrl': function() { return null; },
      'name': 'none_type',
      'tileSize': new google.maps.Size(256, 256),
      'minZoom': 0,
      'maxZoom': 25
    }));

    var mapInitOptions = {
      mapTypes: mapTypeReg,
      mapTypeId: google.maps.MapTypeId.ROADMAP,
      noClear: true,
      zoom: 2,
      minZoom: 2,
      disableDefaultUI: true,
      zoomControl: true,
      center: {lat: 0, lng: 0}
    };

    if (options) {
      if (options.mapType) {
        mapInitOptions.mapTypeId = MAP_TYPES[options.mapType];
      }
      if (options.styles) {
        mapInitOptions.styles = JSON.parse(options.styles);
      }

      if (options.controls) {
        if (options.controls.zoom !== undefined) {
          mapInitOptions.zoomControl = options.controls.zoom == true;
        }
      }
      if (options.preferences) {
        if (options.preferences.zoom) {
          mapInitOptions.minZoom = options.preferences.zoom.minZoom;
          if (options.preferences.zoom.maxZoom) {
            mapInitOptions.maxZoom = options.preferences.zoom.maxZoom;
          }
        }
      }
    }

    var map = new google.maps.Map(container, mapInitOptions);
    map.mapTypes = mapTypeReg;
    self.set('map', map);

    var boundsLimit = null;
    if (options.preferences && options.preferences.gestureBounds &&
        options.preferences.gestureBounds.length > 0) {
      boundsLimit = new google.maps.LatLngBounds();
      options.preferences.gestureBounds.forEach(function(pos) {
        boundsLimit.extend(pos);
      });
    }
    map.set('boundsLimit', boundsLimit);

    var timeoutError = setTimeout(function() {
      self.trigger('load_error');
      displayGrayMap(mapDiv);
    }, 3000);

    map.addListener('bounds_changed', function() {
      var boundsLimit = map.get('boundsLimit');
      if (!boundsLimit) {
        return;
      }
      var visibleBounds = map.getBounds();
      if (boundsLimit.intersects(visibleBounds) ||
          visibleBounds.contains(boundsLimit.getNorthEast()) && visibleBounds.contains(boundsLimit.getSouthWest()) ||
          boundsLimit.contains(visibleBounds.getNorthEast()) && boundsLimit.contains(visibleBounds.getSouthWest())) {
        return;
      }
      var center = map.getCenter();
      var dummyLat = center.lat(),
        dummyLng = center.lng();
      var ne = boundsLimit.getNorthEast(),
        sw = boundsLimit.getSouthWest();
      if (dummyLat < sw.lat() ) {
        dummyLat = sw.lat();
      } else if (dummyLat > ne.lat()) {
        dummyLat = ne.lat();
      }
      if (dummyLng < 0) {
        // the Western Hemisphere
        if (dummyLng > ne.lng()) {
          dummyLng = ne.lng();
        } else if (dummyLng < sw.lng()) {
          dummyLng = sw.lng();
        }
      } else {
        // the Eastern Hemisphere
        if (dummyLng > ne.lng()) {
          dummyLng = ne.lng();
        } else if (dummyLng < sw.lng()) {
          dummyLng = sw.lng();
        }
      }
      var dummyLatLng = new google.maps.LatLng(dummyLat, dummyLng);
      map.panTo(dummyLatLng);
    });

    google.maps.event.addListenerOnce(map, 'projection_changed', function() {
      clearTimeout(timeoutError);

      self.trigger(event.MAP_READY);
      map.addListener('idle', self._onCameraEvent.bind(self, 'camera_move_end'));
      //map.addListener("bounce_changed", self._onCameraEvent.bind(self, 'camera_move'));
      map.addListener('drag', self._onCameraEvent.bind(self, event.CAMERA_MOVE));
      map.addListener('dragend', self._onCameraEvent.bind(self, event.CAMERA_MOVE_END));
      map.addListener('dragstart', self._onCameraEvent.bind(self, event.CAMERA_MOVE_START));

      map.addListener('click', function(evt) {
        self._onMapEvent.call(self, event.MAP_CLICK, evt);
      });
      map.addListener('mousedown', function() {
        map.set('mousedown_time', Date.now());
      });
      map.addListener('mouseup', function(evt) {
        if (Date.now() - (map.get('mousedown_time') || Date.now()) > 500) {
          self._onMapEvent.call(self, event.MAP_LONG_CLICK, evt);
        }
      });
      map.addListener('drag', function(evt) {
        self._onMapEvent.call(self, event.MAP_DRAG, evt);
      });
      map.addListener('dragend', function(evt) {
        self._onMapEvent.call(self, event.MAP_DRAG_END, evt);
      });
      map.addListener('dragstart', function(evt) {
        map.set('mousedown_time', undefined);
        self._onMapEvent.call(self, event.MAP_DRAG_START, evt);
      });
    });

    if (options) {
      if (options.camera) {
        if (options.camera.target) {

          if (Array.isArray(options.camera.target)) {
            var bounds = new google.maps.LatLngBounds();
            options.camera.target.forEach(function(pos) {
              bounds.extend(pos);
            });
            map.fitBounds(bounds, 5);
          } else {
            map.setCenter(options.camera.target);
          }
          if (typeof options.camera.tilt === 'number') {
            map.setTilt(options.camera.tilt);
          }
          if (typeof options.camera.bearing === 'number') {
            map.setHeading(options.camera.bearing);
          }
          if (typeof options.camera.zoom === 'number') {
            map.setZoom(options.camera.zoom);
          }
        }
      } else {
        map.setCenter({lat: 0, lng: 0});
      }
    } else {
      map.setCenter({lat: 0, lng: 0});
    }

  });

}

utils.extend(PluginMap, BaseClass);

PluginMap.prototype.setOptions = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map'),
    options = args[0];

  var mapInitOptions = {};

  if (options) {
    if (options.mapType) {
      mapInitOptions.mapTypeId = MAP_TYPES[options.mapType];
    }
    if (options.styles) {
      mapInitOptions.styles = JSON.parse(options.styles);
    }

    if (options.controls) {
      if (options.controls.zoom !== undefined) {
        mapInitOptions.zoomControl = options.controls.zoom == true;
      }
    }
    if (options.preferences) {
      if (options.preferences.zoom) {
        mapInitOptions.minZoom = Math.max(options.preferences.zoom || 2, 2);
        if (options.preferences.zoom.maxZoom) {
          mapInitOptions.maxZoom = options.preferences.zoom.maxZoom;
        }
      }

      if ('gestureBounds' in options.preferences) {
        var boundsLimit = null;
        if (options.preferences.gestureBounds && options.preferences.gestureBounds.length > 0) {
          boundsLimit = new google.maps.LatLngBounds();
          options.preferences.gestureBounds.forEach(function(pos) {
            boundsLimit.extend(pos);
          });
        }
        map.set('boundsLimit', boundsLimit);
      }

    }
  }
  map.setOptions(mapInitOptions);

  if (options) {
    if (options.camera) {
      if (options.camera.target) {

        if (Array.isArray(options.camera.target)) {
          var bounds = new google.maps.LatLngBounds();
          options.camera.target.forEach(function(pos) {
            bounds.extend(pos);
          });
          map.fitBounds(bounds, 5);
        } else {
          map.setCenter(options.camera.target);
        }
        if (typeof options.camera.tilt === 'number') {
          map.setTilt(options.camera.tilt);
        }
        if (typeof options.camera.bearing === 'number') {
          map.setHeading(options.camera.bearing);
        }
        if (typeof options.camera.zoom === 'number') {
          map.setZoom(options.camera.zoom);
        }
      }
    } else {
      map.setCenter({lat: 0, lng: 0});
    }
  } else {
    map.setCenter({lat: 0, lng: 0});
  }

  onSuccess();
};

PluginMap.prototype.setActiveMarkerId = function(onSuccess, onError, args) {
  var self = this,
    markerId = args[0];
  self.activeMarker = self.objects[markerId];
  onSuccess();
};
PluginMap.prototype.clear = function(onSuccess) {
  this.activeMarker = null;
  onSuccess();
};

PluginMap.prototype.getFocusedBuilding = function(onSuccess) {
  // stub
  onSuccess(-1);
};

PluginMap.prototype.setDiv = function(onSuccess, onError, args) {
  var self = this,
    map = self.get('map'),
    container = self.get('container');

  if (args.length === 0) {
    if (container && container.parentNode) {
      container.parentNode.removeAttribute('__pluginMapId');
      container.parentNode.removeChild(container);
    }
  } else {
    var domId = args[0];
    var mapDiv = document.querySelector('[__pluginDomId=\'' + domId + '\']');
    mapDiv.style.position = 'relative';
    mapDiv.insertBefore(container, mapDiv.firstElementChild);
    mapDiv.setAttribute('__pluginMapId', self.__pgmId);
  }

  google.maps.event.trigger(map, 'resize');
  onSuccess();
};
PluginMap.prototype.resizeMap = function(onSuccess) {
  var self = this;
  var map = self.get('map');

  google.maps.event.trigger(map, 'resize');
  onSuccess();
};

PluginMap.prototype.panBy = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');
  map.panBy.apply(map, args);
  onSuccess();
};

PluginMap.prototype.setCameraBearing = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');
  var heading = args[0];

  map.setHeading(heading);
  onSuccess();
};

PluginMap.prototype.setCameraZoom = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');
  var zoom = args[0];

  map.setZoom(zoom);
  onSuccess();
};

PluginMap.prototype.setCameraTarget = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');
  var lat = args[0],
    lng = args[1];

  map.setCenter(new google.maps.LatLng(lat, lng));
  onSuccess();
};

PluginMap.prototype.setCameraTilt = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');
  var tilt = args[0];

  map.setTilt(tilt);
  onSuccess();
};
PluginMap.prototype.setMyLocationEnabled = function(onSuccess) {
  // stub
  onSuccess();
};

PluginMap.prototype.animateCamera = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');

  var options = args[0];
  var padding = 'padding' in options ? options.padding : 5;
  var bounds;
  if (Array.isArray(options.target)) {
    bounds = new google.maps.LatLngBounds();
    options.target.forEach(function(pos) {
      bounds.extend(pos);
    });
    map.fitBounds(bounds, padding);
  } else {
    var zoomFlag = typeof options.zoom === 'number';
    var targetFlag = !!options.target;

    if (zoomFlag && targetFlag) {
      var projection = map.getProjection();
      var centerLatLng = new google.maps.LatLng(options.target.lat, options.target.lng, true);
      var centerPoint = projection.fromLatLngToPoint(centerLatLng);

      var scale = Math.pow(2, options.zoom);

      var div = map.getDiv();
      var harfWidth = div.offsetWidth / 2;
      var harfHeight = div.offsetHeight / 2;
      var swPoint = new google.maps.Point((centerPoint.x * scale - harfWidth) / scale, (centerPoint.y * scale + harfHeight) / scale );
      var nePoint = new google.maps.Point((centerPoint.x * scale + harfWidth) / scale, (centerPoint.y * scale - harfHeight)  / scale);
      var sw = projection.fromPointToLatLng(swPoint);
      var ne = projection.fromPointToLatLng(nePoint);
      bounds = new google.maps.LatLngBounds(sw, ne);
      map.fitBounds(bounds, padding);

    } else if (zoomFlag) {
      map.setZoom(options.zoom);
    } else if (targetFlag) {
      map.panTo(options.target);
    }
  }
  if (typeof options.tilt === 'number') {
    map.setTilt(options.tilt);
  }
  if (typeof options.bearing === 'number') {
    map.setHeading(options.bearing);
  }
  onSuccess();

};

PluginMap.prototype.moveCamera = function(onSuccess, onError, args) {
  this.animateCamera.call(this, onSuccess, onError, args);
};

PluginMap.prototype.setMapTypeId = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');
  var mapTypeId = args[0];
  map.setMapTypeId(MAP_TYPES[mapTypeId]);
  onSuccess();
};
PluginMap.prototype.setClickable = function(onSuccess, onError, args) {
  var self = this;
  var clickable = args[0];
  self.set('clickable', clickable);
  onSuccess();
};
PluginMap.prototype.setVisible = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');
  var visibility = args[0];
  var mapDiv = map.getDiv();
  if (mapDiv) {
    mapDiv.style.visibility = visibility === true ? 'visible' : 'hidden';
  }
  onSuccess();
};

PluginMap.prototype.setPadding = function(onSuccess) {
  // stub
  onSuccess();
};
PluginMap.prototype.setAllGesturesEnabled = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');
  var enabled = args[0];
  map.setOptions({
    gestureHandling: enabled === true ? 'auto': 'none'
  });

  onSuccess();
};
PluginMap.prototype.setCompassEnabled = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');
  var enabled = args[0];
  map.setOptions({
    rotateControl: enabled === true
  });
  var mapTypeId = map.getMapTypeId();
  if (mapTypeId !== google.maps.MapTypeId.SATELLITE &&
    mapTypeId !== google.maps.MapTypeId.HYBRID) {
    console.warn('map.setCompassEnabled() works only HYBRID or SATELLITE for this platform.');
  }
  onSuccess();
};
PluginMap.prototype.setTrafficEnabled = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');
  var enabled = args[0];

  var trafficLayer = map.get('trafficLayer');
  if (!trafficLayer) {
    trafficLayer = new google.maps.TrafficLayer();
    map.set('trafficLayer', trafficLayer);
  }
  if (enabled) {
    trafficLayer.setMap(map);
  } else {
    trafficLayer.setMap(null);
  }

  onSuccess();

};



PluginMap.prototype.fromLatLngToPoint = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');
  var lat = args[0],
    lng = args[1];

  var projection = map.getProjection(),
    bounds = map.getBounds(),
    ne = bounds.getNorthEast(),
    sw = bounds.getSouthWest(),
    zoom = map.getZoom(),
    north = ne.lat(),
    west = sw.lng();

  var nowrapFlag = !bounds.contains(new google.maps.LatLng(north, 179));

  var scale = Math.pow(2, zoom),
    topLeft = projection.fromLatLngToPoint(new google.maps.LatLng(north, west + 360, nowrapFlag)),
    worldPoint = projection.fromLatLngToPoint(new google.maps.LatLng(lat, lng + 360, true));
  onSuccess([(worldPoint.x - topLeft.x) * scale, (worldPoint.y - topLeft.y) * scale]);
};

PluginMap.prototype.fromPointToLatLng = function(onSuccess, onError, args) {
  var self = this;
  var map = self.get('map');
  var x = args[0],
    y = args[1];

  var projection = map.getProjection(),
    bounds = map.getBounds(),
    ne = bounds.getNorthEast(),
    sw = bounds.getSouthWest(),
    zoom = map.getZoom();

  var topRight = projection.fromLatLngToPoint(ne);
  var bottomLeft = projection.fromLatLngToPoint(sw);
  var scale = Math.pow(2, zoom);
  var worldPoint = new google.maps.Point(x / scale + bottomLeft.x, y / scale + topRight.y);
  var latLng = map.getProjection().fromPointToLatLng(worldPoint);
  onSuccess([latLng.lat(), latLng.lng()]);

};

PluginMap.prototype.setIndoorEnabled = function(onSuccess) {
  // stub
  onSuccess();
};

PluginMap.prototype.toDataURL = function(onSuccess) {
  // stub
  onSuccess();
};

PluginMap.prototype._syncInfoWndPosition = function() {
  var self = this;

  if (!self.activeMarker) {
    return;
  }

  var latLng = self.activeMarker.getPosition();
  self.fromLatLngToPoint(function(point) {

    plugin.google.maps[self.__pgmId]({
      'evtName': 'syncPosition',
      'callback': '_onSyncInfoWndPosition',
      'args': [{'x': point[0], 'y': point[1]}]
    });

  }, null, [latLng.lat(), latLng.lng()]);

};

PluginMap.prototype._onMapEvent = function(evtName, evt) {
  var self = this;

  if (self.get('clickable') === false &&
    (evtName === event.MAP_CLICK || evtName === event.MAP_LONG_CLICK)) {
    evt.stop();
    return;
  }
  if (self.__pgmId in plugin.google.maps) {
    if (evt) {
      if (evtName === event.MAP_CLICK) {
        if (evt.placeId) {
          evt.stop();
          plugin.google.maps[self.__pgmId]({
            'evtName': event.POI_CLICK,
            'callback': '_onMapEvent',
            'args': [evt.placeId, undefined, new LatLng(evt.latLng.lat(), evt.latLng.lng())]
          });
          return;
        }
      }
      plugin.google.maps[self.__pgmId]({
        'evtName': evtName,
        'callback': '_onMapEvent',
        'args': [new LatLng(evt.latLng.lat(), evt.latLng.lng())]
      });
    } else {
      plugin.google.maps[self.__pgmId]({
        'evtName': evtName,
        'callback': '_onMapEvent',
        'args': []
      });
    }
  }

};

PluginMap.prototype._onCameraEvent = function(evtName) {
  var self = this,
    map = self.get('map'),
    center = map.getCenter(),
    bounds = map.getBounds(),
    ne = bounds.getNorthEast(),
    sw = bounds.getSouthWest();

  self._syncInfoWndPosition();

  var cameraInfo = {
    'target': {'lat': center.lat(), 'lng': center.lng()},
    'zoom': map.getZoom(),
    'tilt': map.getTilt() || 0,
    'bearing': map.getHeading() || 0,
    'northeast': {'lat': ne.lat(), 'lng': ne.lng()},
    'southwest': {'lat': sw.lat(), 'lng': sw.lng()},
    'farLeft': {'lat': ne.lat(), 'lng': sw.lng()},
    'farRight': {'lat': ne.lat(), 'lng': ne.lng()}, // = northEast
    'nearLeft': {'lat': sw.lat(), 'lng': sw.lng()}, // = southWest
    'nearRight': {'lat': sw.lat(), 'lng': ne.lng()}
  };
  if (self.__pgmId in plugin.google.maps) {
    plugin.google.maps[self.__pgmId]({
      'evtName': evtName,
      'callback': '_onCameraEvent',
      'args': [cameraInfo]
    });
  }
};

PluginMap.prototype.loadPlugin = function(onSuccess, onError, args) {
  var self = this;
  var className = args[0];

  var plugin;
  if (className in self.PLUGINS) {
    plugin = self.PLUGINS[className];
  } else {
    var OverlayClass = require('cordova-plugin-googlemaps.Plugin' + className);
    plugin = new OverlayClass(this);
    self.PLUGINS[className] = plugin;

    // Since Cordova involes methods as Window,
    // the `this` keyword of involved method is Window, not overlay itself.
    // In order to keep indicate the `this` keyword as overlay itself,
    // wrap the method.
    var dummyObj = {};
    for (var key in OverlayClass.prototype) {
      if (typeof OverlayClass.prototype[key] === 'function') {
        dummyObj[key] = plugin[key].bind(plugin);
      } else {
        dummyObj[key] = plugin[key];
      }
    }
    require('cordova/exec/proxy').add(self.__pgmId + '-' + className.toLowerCase(), dummyObj);
  }

  plugin._create.call(plugin, onSuccess, onError, args);
};

module.exports = PluginMap;

});