(function() {

	'use strict';



	var L = require('leaflet');

	var GeocoderElement = require('./geocoder-element');

	var Waypoint = require('./waypoint');



	module.exports = (L.Layer || L.Class).extend({

		includes: ((typeof L.Evented !== 'undefined' && L.Evented.prototype) || L.Mixin.Events),



		options: {

			dragStyles: [

				{color: 'black', opacity: 0.15, weight: 9},

				{color: 'white', opacity: 0.8, weight: 6},

				{color: 'red', opacity: 1, weight: 2, dashArray: '7,12'}

			],

			draggableWaypoints: true,

			routeWhileDragging: false,

			addWaypoints: true,

			reverseWaypoints: false,

			addButtonClassName: '',

			language: 'en',

			createGeocoderElement: function(wp, i, nWps, plan) {

				return new GeocoderElement(wp, i, nWps, plan);

			},

			createMarker: function(i, wp) {

				var options = {

						draggable: this.draggableWaypoints

					},

				    marker = L.marker(wp.latLng, options);



				return marker;

			},

			geocodersClassName: ''

		},



		initialize: function(waypoints, options) {

			L.Util.setOptions(this, options);

			this._waypoints = [];

			this.setWaypoints(waypoints);

		},



		isReady: function() {

			var i;

			for (i = 0; i < this._waypoints.length; i++) {

				if (!this._waypoints[i].latLng) {

					return false;

				}

			}



			return true;

		},



		getWaypoints: function() {

			var i,

				wps = [];



			for (i = 0; i < this._waypoints.length; i++) {

				wps.push(this._waypoints[i]);

			}



			return wps;

		},



		setWaypoints: function(waypoints) {

			var args = [0, this._waypoints.length].concat(waypoints);

			this.spliceWaypoints.apply(this, args);

			return this;

		},



		spliceWaypoints: function() {

			var args = [arguments[0], arguments[1]],

			    i;



			for (i = 2; i < arguments.length; i++) {

				args.push(arguments[i] && arguments[i].hasOwnProperty('latLng') ? arguments[i] : new Waypoint(arguments[i]));

			}



			[].splice.apply(this._waypoints, args);



			// Make sure there's always at least two waypoints

			while (this._waypoints.length < 2) {

				this.spliceWaypoints(this._waypoints.length, 0, null);

			}



			this._updateMarkers();

			this._fireChanged.apply(this, args);

		},



		onAdd: function(map) {

			this._map = map;

			this._updateMarkers();

		},



		onRemove: function() {

			var i;

			this._removeMarkers();



			if (this._newWp) {

				for (i = 0; i < this._newWp.lines.length; i++) {

					this._map.removeLayer(this._newWp.lines[i]);

				}

			}



			delete this._map;

		},



		createGeocoders: function() {

			var container = L.DomUtil.create('div', 'leaflet-routing-geocoders ' + this.options.geocodersClassName),

				waypoints = this._waypoints,

			    addWpBtn,

			    reverseBtn;



			this._geocoderContainer = container;

			this._geocoderElems = [];





			if (this.options.addWaypoints) {

				addWpBtn = L.DomUtil.create('button', 'leaflet-routing-add-waypoint ' + this.options.addButtonClassName, container);

				addWpBtn.setAttribute('type', 'button');

				L.DomEvent.addListener(addWpBtn, 'click', function() {

					this.spliceWaypoints(waypoints.length, 0, null);

				}, this);

			}



			if (this.options.reverseWaypoints) {

				reverseBtn = L.DomUtil.create('button', 'leaflet-routing-reverse-waypoints', container);

				reverseBtn.setAttribute('type', 'button');

				L.DomEvent.addListener(reverseBtn, 'click', function() {

					this._waypoints.reverse();

					this.setWaypoints(this._waypoints);

				}, this);

			}



			this._updateGeocoders();

			this.on('waypointsspliced', this._updateGeocoders);



			return container;

		},



		_createGeocoder: function(i) {

			var geocoder = this.options.createGeocoderElement(this._waypoints[i], i, this._waypoints.length, this.options);

			geocoder

			.on('delete', function() {

				if (i > 0 || this._waypoints.length > 2) {

					this.spliceWaypoints(i, 1);

				} else {

					this.spliceWaypoints(i, 1, new Waypoint());

				}

			}, this)

			.on('geocoded', function(e) {

				this._updateMarkers();

				this._fireChanged();

				this._focusGeocoder(i + 1);

				this.fire('waypointgeocoded', {

					waypointIndex: i,

					waypoint: e.waypoint

				});

			}, this)

			.on('reversegeocoded', function(e) {

				this.fire('waypointgeocoded', {

					waypointIndex: i,

					waypoint: e.waypoint

				});

			}, this);



			return geocoder;

		},



		_updateGeocoders: function() {

			var elems = [],

				i,

			    geocoderElem;



			for (i = 0; i < this._geocoderElems.length; i++) {

				this._geocoderContainer.removeChild(this._geocoderElems[i].getContainer());

			}



			for (i = this._waypoints.length - 1; i >= 0; i--) {

				geocoderElem = this._createGeocoder(i);

				this._geocoderContainer.insertBefore(geocoderElem.getContainer(), this._geocoderContainer.firstChild);

				elems.push(geocoderElem);

			}



			this._geocoderElems = elems.reverse();

		},



		_removeMarkers: function() {

			var i;

			if (this._markers) {

				for (i = 0; i < this._markers.length; i++) {

					if (this._markers[i]) {

						this._map.removeLayer(this._markers[i]);

					}

				}

			}

			this._markers = [];

		},



		_updateMarkers: function() {

			var i,

			    m;



			if (!this._map) {

				return;

			}



			this._removeMarkers();



			for (i = 0; i < this._waypoints.length; i++) {

				if (this._waypoints[i].latLng) {

					m = this.options.createMarker(i, this._waypoints[i], this._waypoints.length);

					if (m) {

						m.addTo(this._map);

						if (this.options.draggableWaypoints) {

							this._hookWaypointEvents(m, i);

						}

					}

				} else {

					m = null;

				}

				this._markers.push(m);

			}

		},



		_fireChanged: function() {

			this.fire('waypointschanged', {waypoints: this.getWaypoints()});



			if (arguments.length >= 2) {

				this.fire('waypointsspliced', {

					index: Array.prototype.shift.call(arguments),

					nRemoved: Array.prototype.shift.call(arguments),

					added: arguments

				});

			}

		},



		_hookWaypointEvents: function(m, i, trackMouseMove) {

			var eventLatLng = function(e) {

					return trackMouseMove ? e.latlng : e.target.getLatLng();

				},

				dragStart = L.bind(function(e) {

					this.fire('waypointdragstart', {index: i, latlng: eventLatLng(e)});

				}, this),

				drag = L.bind(function(e) {

					this._waypoints[i].latLng = eventLatLng(e);

					this.fire('waypointdrag', {index: i, latlng: eventLatLng(e)});

				}, this),

				dragEnd = L.bind(function(e) {

					this._waypoints[i].latLng = eventLatLng(e);

					this._waypoints[i].name = '';

					if (this._geocoderElems) {

						this._geocoderElems[i].update(true);

					}

					this.fire('waypointdragend', {index: i, latlng: eventLatLng(e)});

					this._fireChanged();

				}, this),

				mouseMove,

				mouseUp;



			if (trackMouseMove) {

				mouseMove = L.bind(function(e) {

					this._markers[i].setLatLng(e.latlng);

					drag(e);

				}, this);

				mouseUp = L.bind(function(e) {

					this._map.dragging.enable();

					this._map.off('mouseup', mouseUp);

					this._map.off('mousemove', mouseMove);

					dragEnd(e);

				}, this);

				this._map.dragging.disable();

				this._map.on('mousemove', mouseMove);

				this._map.on('mouseup', mouseUp);

				dragStart({latlng: this._waypoints[i].latLng});

			} else {

				m.on('dragstart', dragStart);

				m.on('drag', drag);

				m.on('dragend', dragEnd);

			}

		},



		dragNewWaypoint: function(e) {

			var newWpIndex = e.afterIndex + 1;

			if (this.options.routeWhileDragging) {

				this.spliceWaypoints(newWpIndex, 0, e.latlng);

				this._hookWaypointEvents(this._markers[newWpIndex], newWpIndex, true);

			} else {

				this._dragNewWaypoint(newWpIndex, e.latlng);

			}

		},



		_dragNewWaypoint: function(newWpIndex, initialLatLng) {

			var wp = new Waypoint(initialLatLng),

				prevWp = this._waypoints[newWpIndex - 1],

				nextWp = this._waypoints[newWpIndex],

				marker = this.options.createMarker(newWpIndex, wp, this._waypoints.length + 1),

				lines = [],

				draggingEnabled = this._map.dragging.enabled(),

				mouseMove = L.bind(function(e) {

					var i,

						latLngs;

					if (marker) {

						marker.setLatLng(e.latlng);

					}

					for (i = 0; i < lines.length; i++) {

						latLngs = lines[i].getLatLngs();

						latLngs.splice(1, 1, e.latlng);

						lines[i].setLatLngs(latLngs);

					}



					L.DomEvent.stop(e);

				}, this),

				mouseUp = L.bind(function(e) {

					var i;

					if (marker) {

						this._map.removeLayer(marker);

					}

					for (i = 0; i < lines.length; i++) {

						this._map.removeLayer(lines[i]);

					}

					this._map.off('mousemove', mouseMove);

					this._map.off('mouseup', mouseUp);

					this.spliceWaypoints(newWpIndex, 0, e.latlng);

					if (draggingEnabled) {

						this._map.dragging.enable();

					}



					L.DomEvent.stop(e);

				}, this),

				i;



			if (marker) {

				marker.addTo(this._map);

			}



			for (i = 0; i < this.options.dragStyles.length; i++) {

				lines.push(L.polyline([prevWp.latLng, initialLatLng, nextWp.latLng],

					this.options.dragStyles[i]).addTo(this._map));

			}



			if (draggingEnabled) {

				this._map.dragging.disable();

			}



			this._map.on('mousemove', mouseMove);

			this._map.on('mouseup', mouseUp);

		},



		_focusGeocoder: function(i) {

			if (this._geocoderElems[i]) {

				this._geocoderElems[i].focus();

			} else {

				document.activeElement.blur();

			}

		}

	});

})();

