/**
 * @file GoogleMaps plugin for CKEditor
 *	Version 3.6.0
 *	Allows to insert and edit GoogleMaps inside CKEditor with markers, polygons,...
 *
 * Copyright (C) 2012-16 Alfonso Martínez de Lizarrondo
 *
 */

/* global CKEDITOR */

(function() {
	'use strict';

	// Object that handles the common functions about all the maps
	function GoogleMapsHandler(editor) {
		this.editor = editor;
		// Clean up
		editor.on( 'destroy', function() { this.maps = {}; this.editor = null;});

		// Object to store a reference to each map
		this.maps = {};

		// We will use this to track the number of maps that are generated
		// This way we know if we must add the Google Script or not.
		// We store their names so they are called properly from BuildEndingScript
		this.CreatedMapsNames = [];

		return this;
	}

	GoogleMapsHandler.prototype  = {
		majorVersion : 3,
		minorVersion : 6,

		isStaticImage: function( node ) {
			return node.getAttribute( 'mapnumber' ) || (/^(https?\:)?\/\/maps\.google(apis)?\.com\/(maps\/api\/)?staticmap/).test( node.src );
		},

		getMap: function( id ) {
			return this.maps[id];
		},

		// Verify that the node is a script generated by this plugin.
		detectMapScript: function( script ) {
			if ( !/CK googlemaps v(\d)\.(\d+)/.test(script) )
				return false;

			var version = parseInt(RegExp.$1, 10),
				revision = parseInt(RegExp.$2, 10);

			if ( version > this.majorVersion || (version == this.majorVersion && revision > this.minorVersion) )
				return false;

			return true;
		},

		// Detects both the google script as well as our ending block
		// both must be removed and then added later only if necessary
		detectGoogleScript: function( script ) {
			// Our final script
			if (/CK googlemapsEnd v(\d)\.(\d+)/.test(script) ) {
				var version = parseInt(RegExp.$1, 10),
					revision = parseInt(RegExp.$2, 10);

				if ( version > this.majorVersion || (version == this.majorVersion && revision > this.minorVersion) )
					return false;

				if (version == 2) {
					if ( !/script.src\s*=\s*"http:\/\/www\.google\.com\/.*key=(.*?)("|&)/.test(script) )
						return false;
				}
				return true;
			}
			// Check if it is the Google Maps script (but we don't need the key)
			if ( !(/^<script src="http:\/\/maps\.google\.com\/.*key=(.*?)("|&)/).test(script) )
				return false;

			return true;
		},

		// This can be called from the dialog
		createNew: function() {
			var map = new CKEGoogleMap( this.editor );
			this.maps[ map.number ] = map;
			return map;
		},

		BuildEndingScript: function() {
			var aScript = [],
				path = this.editor.config.googleMaps_endScriptPath;

			// Empty string: don't insert it at all. It must be inserted in the page template
			if (path === '')
				return '';

			// Not defined: use default path
			if (typeof path == 'undefined')
				path = this.editor.plugins.googlemaps.path + 'scripts/cke_gmaps_end.js';

			aScript.push('\r\n<script type="text/javascript" src="' + path + '">');
			aScript.push( '/* CK googlemapsEnd v' + this.majorVersion + '.' + this.minorVersion + ' */' );
			aScript.push('</script>');

			return aScript.join('');
		},

		// Function that will test if the divs and images are correctly wrapped
		// returns true if it was OK, false if it has modified the DOM
		validateDOM: function() {
			// if no maps have been used, then exit
			var tmpMap, aMaps = [], i, j, imgs, mapId, map, div, img, element,
				modified = false,
				maps = this.maps;

			for (tmpMap in maps)
				aMaps.push( tmpMap );

			if (aMaps.length === 0)
				return true;

			var theDoc = this.editor.document.$;
			// the user might have moved an img outside its container div.
			for (i = 0; i < aMaps.length; i++) {
				mapId = aMaps[i];
				map = maps[mapId];
				img = theDoc.getElementById( mapId );
				if ( img ) {
					if (img.parentNode.nodeName != 'DIV' || img.parentNode.id != 'd' + map.number) {
						modified = true;
						map.createDivs( img );
					}
					map.updateDimensions( img );
				} else {
					div = theDoc.getElementById( 'd' + mapId );
					if (div) {
						element = new CKEDITOR.dom.element( div );
						element.remove( true );
						modified = true;
					}
					delete maps[ mapId ];
				}
			}

			// Try to check if the container divs have been duplicated (for example by drag&drop)
			if ( document.querySelectorAll ) {
				for (i = 0; i < aMaps.length; i++) {
					mapId = aMaps[i];
					var divs = theDoc.querySelectorAll('div#d' + mapId);
					if (divs.length <= 1)
						continue;

					//Ups, there are two or more divs, only one must remain
					for (j = divs.length - 1; j >= 0; j--) {
						div = divs[j];
						imgs = div.getElementsByTagName( 'IMG' );
						if ( !imgs || !imgs[0] || imgs[0].id != mapId) {
							// If it doesn't have an image inside, remove that div
							// but leave whatever it's inside, just in case...
							element = new CKEDITOR.dom.element( div );
							element.remove( true );
							modified = true;
						}
					}
				}
			}

			return !modified;
		}
	};




	// Our object that will handle parsing of the script and creating the new one.
	function CKEGoogleMap( editor ) {
		this.editor = editor;

		var now = new Date();
		this.number = 'gmap' + now.getFullYear() + now.getMonth() + now.getDate() + now.getHours() + now.getMinutes() + now.getSeconds();

		this.width = editor.config.googleMaps_Width || 900;
		this.height = editor.config.googleMaps_Height || 250;
		this.responsive = true; //editor.config.googleMaps_Responsive;

		this.centerLat = editor.config.googleMaps_CenterLat || 37.4419;
		this.centerLon =  editor.config.googleMaps_CenterLon || -122.1419;
		this.zoom = editor.config.googleMaps_Zoom || 11;
		this.tilt = 0;
		this.heading = 0;

		this.markerPoints = [];
		this.textsData = [];
		this.linesData = [];
		this.areasData = [];
		this.circlesData = [];

		this.mapType = 0; // map, satellite, hybrid...
		this.generatedType = 3; // 1: only static, 2: static + click to load, 3: load with page, 4: loaded on demand by call to initGmapsLoader

		this.kmlOverlay = '';
		this.zoomControl = 'Default';
		this.mapTypeControl = 'Default';
		this.scaleControl = false;

		this.overviewMapControl = false;
		this.overviewMapControlOpened = true;
		this.googleBar = false;

		this.weather = false;
		this.weatherUsaUnits = editor.config.googleMaps_weatherUsaUnits || false;

		this.pathType = 'Default';

		this.WrapperClass = editor.config.googleMaps_WrapperClass || '';

		this.key = editor.config.googleMaps_ApiKey || '';
	}


	CKEGoogleMap.prototype.createHtmlElement = function() {
		// let's assure that the map exists in the list
		this.editor.plugins.googleMapsHandler.maps[ this.number ] = this;

		var oFakeNode = this.editor.document.createElement( 'IMG' );
		oFakeNode.setAttribute( 'mapnumber', this.number );
		oFakeNode.setAttribute( 'id', this.number );

		var oDiv = this.editor.document.createElement( 'DIV' );
		oDiv.setAttribute( 'id', 'd' + this.number);

		if (this.WrapperClass !== '') {
			var oWrapper = this.editor.document.createElement( 'DIV' );
			oWrapper.setAttribute( 'class', this.WrapperClass );
			this.editor.insertElement( oWrapper );
			oWrapper.append( oDiv );
		} else
			this.editor.insertElement( oDiv );

		oDiv.append( oFakeNode );
		return oFakeNode.$;
	};

	CKEGoogleMap.prototype.createDivs = function( oFakeNode ) {
		var oWrapper,
			oDiv = this.editor.document.$.createElement( 'DIV' );
		oDiv.setAttribute( 'id', 'd' + this.number);
		oFakeNode.parentNode.insertBefore( oDiv, oFakeNode );

		if (this.WrapperClass !== '') {
			oWrapper = this.editor.document.$.createElement( 'DIV' );
			oWrapper.className = this.WrapperClass;
			oDiv.parentNode.insertBefore( oWrapper, oDiv );
			oWrapper.appendChild( oDiv );
		}

		oDiv.appendChild( oFakeNode );
	};

	CKEGoogleMap.prototype.updateHTMLElement = function( oFakeNode ) {
		// let's assure that the map exists in the list
		this.editor.plugins.googleMapsHandler.maps[ this.number ] = this;

		oFakeNode.setAttribute('width', this.width);
		oFakeNode.setAttribute('height', this.height);

		// Static maps preview :-)
		var staticMap = this.generateStaticMap();
		oFakeNode.setAttribute( 'src', staticMap);
		oFakeNode.setAttribute( 'data-cke-saved-src', staticMap);
		oFakeNode.setAttribute( 'alt', '');

		if (this.responsive) {
			oFakeNode.style.width = '100%';
			oFakeNode.style.maxWidth = this.cssWidth();
			oFakeNode.style.height = this.height; //'auto';
		} else {
			oFakeNode.style.width = '';
			oFakeNode.style.height = '';
		}
	};

	CKEGoogleMap.prototype.getMapTypeIndex = function( type ) {
		return CKEDITOR.tools.indexOf([ 'roadmap', 'satellite', 'hybrid', 'terrain' ], type);
	};

	CKEGoogleMap.prototype.parseStaticMap = function( oImage ) {
		var reStatic = /center=(-?\d+\.\d*),(-?\d+\.\d*)&zoom=(\d+)&size=(\d+)x(\d+)&maptype=(.*?)(?:&markers=(.*?))?(&path=(?:.*?))?/,
			markers, paths, result,
			reMarks = /(-?\d+\.\d*),(-?\d+\.\d*),(\w+)\|?/g,
			reLines = /rgba:0x(\w{6})(\w\w),weight:(\d)(.*?)(&|$)/g;

		if (reStatic.test( oImage.src ) ) {
			this.generatedType = 1;

			this.centerLat = RegExp.$1;
			this.centerLon = RegExp.$2;
			this.zoom = RegExp.$3;
			this.width = oImage.width;
			this.height = oImage.height; alert( this.height );
			this.mapType = this.getMapTypeIndex(RegExp.$6);
			// markers
			markers = RegExp.$7;
			// paths
			paths = RegExp.$8;

			while ( (result = reMarks.exec( markers )) ) {
				this.markerPoints.push( { lat:result[1], lon:result[2], text:'', color:result[3], title:'', maxWidth:200 } );
			}
			while ( (result = reLines.exec( paths )) ) {
				this.linesData.push( { color:'#' + result[1], opacity:parseInt(result[2], 16) / 255, weight:parseInt(result[3], 10), PointsData:result[4], points:null, text:'', maxWidth:200 } );
			}

		}
	};

	CKEGoogleMap.prototype.parseStaticMap2 = function( oImage ) {
		var reStatic = /center=(-?\d+\.\d*),(-?\d+\.\d*)&zoom=(\d+)&size=(\d+)x(\d+)&maptype=(\w*?)(&markers=.*?)?(&path=.*?)?&?/,
			markers, paths, result, line,
			reMarks = /markers=color:(\w*)\|(-?\d+\.\d*),(-?\d+\.\d*)(&|$)/g,
			reLines = /path=color:0x(\w{6})(\w\w)\|weight:(\d)\|(fillcolor:0x(\w{6})(\w\w)\|)?enc:(.*?)&pathData=zf:(\d*)\|nl:(\d*)\|lvl:(.*?)(&|$)/g;

		if (reStatic.test( oImage.src ) ) {
			this.generatedType = 1;

			this.centerLat = RegExp.$1;
			this.centerLon = RegExp.$2;
			this.zoom = RegExp.$3;
			this.width = oImage.width;
			this.height = oImage.height;
			this.mapType = this.getMapTypeIndex(RegExp.$6);
			// markers
			markers = RegExp.$7;
			// paths
			paths = RegExp.$8;

			while ( (result = reMarks.exec( markers )) ) {
				this.markerPoints.push( { lat:result[2], lon:result[3], text:'', color:result[1], title:'', maxWidth:200 } );
			}

			while ( (result = reLines.exec( paths )) ) {
				line = { color:'#' + result[1], opacity:parseInt(result[2], 16) / 255, weight:parseInt(result[3], 10), points:unescape(result[7]) };

				if (result[4]) {
					//polygons
					this.areasData.push( { polylines: [ line ], fill:line.color, color:'#' + result[5], opacity:parseInt(result[6], 16) / 255, text:'', maxWidth:200 } );
				} else {
					// lines
					line.text = '';
					line.maxWidth = 200;
					this.linesData.push( line );
				}
			}
		} else {
			// Static Maps V1
			this.parseStaticMap( oImage );
		}
	};

	CKEGoogleMap.prototype.generateStaticMap = function() {
		var w = Math.min(this.width, 640),
			h = Math.min(this.height, 640),
			staticMapTypes = [ 'roadmap', 'satellite', 'hybrid', 'terrain' ];
      
		if (this.panorama) {
			var fov = 180 / Math.pow(2, this.panorama.zoom);
			return 'https://maps.googleapis.com/maps/api/streetview?location=' + this.panorama.lat + ',' + this.panorama.lng +
				'&size=' + w + 'x' + h + '&heading=' + this.panorama.heading + '&pitch=' + this.panorama.pitch + '&fov=' + fov +
				'&key=' + this.key;
		}

		var part1 = 'https://maps.googleapis.com/maps/api/staticmap?center=' + this.centerLat + ',' + this.centerLon +
				'&zoom=' + this.zoom + '&size=' + w + 'x' + h +
				'&maptype=' + staticMapTypes[ this.mapType ],
			part2 = this.generateStaticMarkers(),
			part3 = this.generateStaticPaths() +
				'&key=' + this.key;
		// If the URL is too large Google won't generate the image. Try to trim it using default icons
		if ((part1 + part2 + part3).length >= 1950) {
			part2 = this.generateStaticMarkers(true);
		}

		return part1 + part2 + part3;
	};

	CKEGoogleMap.prototype.generateStaticMarkers = function(shortened) {
		if (this.markerPoints.length === 0)
			return '';

		var i = 0, point,
			aPoints = [],
			googleMaps_Icons = this.editor.config.googleMaps_Icons;

		for (; i < this.markerPoints.length; i++) {
			point = this.markerPoints[i];
			aPoints.push('&markers=');
			// Custom icons
			if (!shortened) {
				if ( googleMaps_Icons && googleMaps_Icons[ point.color ] ) {
					var url = googleMaps_Icons[ point.color ].marker.image;
					// avoid linking to localhost or the map generator will return an error
					if (/:\/\/.*\..*\//.test( url ))
						aPoints.push('icon:' + url);
					else
						aPoints.push('icon:' + point.color);
				} else
					aPoints.push('color:' + point.color);
			}

			aPoints.push('|' + point.lat + ',' + point.lon );
		}
		return aPoints.join('');
	};

	CKEGoogleMap.prototype.generateStaticPaths = function() {
		function hex(n) {
			var h = Math.round(255 * n).toString(16);
			if (h.length == 1)
				h = '0' + h;
			return h;
		}

		var strings = '', i = 0, line, area;
		for (; i < this.linesData.length; i++) {
			// transform the color to rgba....
			line = this.linesData[i];
			strings += '&path=' + 'color:0x' + line.color.replace('#', '') + hex(line.opacity) + '|weight:' + line.weight + '|enc:' + line.points;
		}

		// Polygons
		for (i = 0; i < this.areasData.length; i++) {
			area = this.areasData[i];
			line = area.polylines[0];
			strings += '&path=' + 'color:0x' + line.color.replace('#', '') + hex(line.opacity) + '|weight:' + line.weight + '|fillcolor:0x' + area.color.replace('#', '') + hex(area.opacity) + '|enc:' + line.points;
		}

		return strings;
	};

	// Read the dimensions back from the fake node (the user might have manually resized it)
	CKEGoogleMap.prototype.updateDimensions = function( oFakeNode ) {
		var iWidth, iHeight,
			regexSize = /^\s*(\d+)px\s*$/i,
			aMatchW, aMatchH;

		if ( oFakeNode.style.width ) {
			aMatchW  = oFakeNode.style.width.match( regexSize );
			if ( aMatchW ) {
				iWidth = aMatchW[1];
				oFakeNode.style.width = '';
				oFakeNode.width = iWidth;
			}
		}

		if ( oFakeNode.style.height ) {
			aMatchH  = oFakeNode.style.height.match( regexSize );
			if ( aMatchH ) {
				iHeight = aMatchH[1];
				oFakeNode.style.height = '';
				oFakeNode.height = iHeight;
			}
		}
    
		this.width	= iWidth ? iWidth : oFakeNode.width;
		this.height	= iHeight ? iHeight : oFakeNode.height;
	};

	CKEGoogleMap.prototype.decodeText = function(string) {
		return string.replace(/<\\\//g, '</').replace(/\\n/g, '').replace(/\\'/g, '\'').replace(/\\"/g, '\"').replace(/\\\\/g, '\\');
	};

	CKEGoogleMap.prototype.encodeText = function(string) {
		return string.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/'/g, '\\\'').replace(/\n/g, '').replace(/<\//g, '<\\/');
	};

	CKEGoogleMap.prototype.parse = function( script ) {
		// Get version info:
		if ( !/CK googlemaps v(\d+)\.(\d+)/.test(script) )
			return false;

		var regexpKml = /google\.maps\.GeoXml\("([^"]*?)"\)/ ,
			majorVersion = parseInt(RegExp.$1, 10) ,
			minorVersion = parseInt(RegExp.$2, 10) ,
			regexpId = /dMap\s=\sdocument\.getElementById\("(.*?)"\)/ ,
			regexpWidth = /dMap\.style\.width\s=\s"(.*?)"/ ,
			regexpHeight = /dMap\.style\.height\s=\s"(.*?)"/ ,
			regexpType = /\/(?:\/|\*)generatedType = (\d)/ ,
			maxWidth ,
			regexpPosition = /map\.setCenter\(new google\.maps\.LatLng\((-?\d{1,3}\.\d{1,6}),(-?\d{1,3}\.\d{1,6})\), (\d{1,2})\);/ ,
			markerText, markerLat = 0, markerLon = 0 ,
			regexpText = /var text\s*=\s*("|')(.*)\1;\s*\n/ ,
			regexpDimensions = /<div id="(.*)" style="width\:\s*(\d+)px; height\:\s*(\d+)px;/ ,
			regexpMarker = /var point\s*=\s*new GLatLng\((-?\d{1,3}\.\d{1,6}),(-?\d{1,3}\.\d{1,6})\)/, // v< 1.5
			regexpMarkers = /\{lat\:(-?\d{1,3}\.\d{1,6}),\s*lon\:(-?\d{1,3}\.\d{1,6}),\s*text\:("|')(.*?)\3(?:,\s*color:"(.*?)"(?:,\s*title:"(.*?)"(?:,\s*maxWidth:(\d+))?)?)?}(?:,|])/g ,
			linePoints = '' ,
			lineLevels = '' ,
			regexpLinePoints = /var encodedPoints\s*=\s*("|')(.*)\1;\s*\n/ ,
			regexpLineLevels = /var encodedLevels\s*=\s*("|')(.*)\1;\s*\n/ ,
			color = 'red' ,
			title = '' ,
			regexpLines = /\{color\:'(.*)',\s*weight:(\d+),\s*opacity:(-?\d\.\d{1,2}),\s*points\:'(.*?)',\s*levels\:'(.*?)',\s*zoomFactor:(\d+),\s*numLevels:(\d+),\s*text\:'(.*?)'(?:,\s*maxWidth:(\d+))?}(?:,|])/g ,
			regexpAreas = /\{polylines:\s*\[\{color\:'(.*)',\s*opacity:(-?\d\.\d{1,2}),\s*weight:(\d+),\s*points\:'(.*)',\s*levels\:'(.*)',\s*zoomFactor:(\d+),\s*numLevels:(\d+)\}\],\s*fill:(.*),\s*color:'(.*)',\s*opacity:(-?\d\.\d{1,2}),\s*outline:(.*?),\s*text\:'(.*?)'(?:,\s*maxWidth:(\d+))?\}(?:,|])/g ,
			lines = [] ,
			regexpTexts = /\{lat\:(-?\d{1,3}\.\d{1,6}),\s*lon\:(-?\d{1,3}\.\d{1,6}),\s*title\:"(.*?)",\s*className:"(.*?)"}(?:,|])/g,
			result;

		var handler = this.editor.plugins.googleMapsHandler;

		if (majorVersion > 3)
			return false;

		if (majorVersion > 2)
			regexpKml =  /AddKml\("([^"]*)"\)/;

		if (majorVersion >= 3 && minorVersion >= 3)
			regexpMarkers = /\{lat\:(-?\d{1,3}\.\d{1,6}),\s*lon\:(-?\d{1,3}\.\d{1,6}),\s*text\:("|')(.*?)\3,\s*color:"(.*?)",\s*title:"(.*?)",\s*maxWidth:(\d+),\s*open:(\d)}(?:,|])/g ;

		// dimensions:
		//	document.writeln('<div id="gmap1" style="width: 544px; height: 350px;">.</div>');
		if (majorVersion < 2) {
			if (regexpDimensions.test( script ) ) {
				delete handler.maps[this.number];
				this.number = RegExp.$1;
				handler.maps[this.number] = this;

				this.width = RegExp.$2;
				this.height = RegExp.$3;
			}
		} else {
		//			var dMap = document.getElementById("gmap200893233020");
		//			dMap.style.width = "300px";
		//			dMap.style.height = "200px";
			if (regexpId.test( script ) ) {
				delete handler.maps[this.number];
				this.number = RegExp.$1;
				handler.maps[this.number] = this;
			}

			if (regexpWidth.test( script ) ) {
				this.width = RegExp.$1.replace(/px$/, '');
				//if (this.width == '100%')
				this.responsive = true;
			}
			if (regexpHeight.test( script ) )
				this.height = RegExp.$1.replace(/px$/, '');

			if (regexpType.test( script ) )
				this.generatedType = RegExp.$1;
		}

		//	map.setCenter(new GLatLng(42.4298,-8.07756), 8);
		if (majorVersion == 1)
			regexpPosition = /map\.setCenter\(new GLatLng\((-?\d{1,3}\.\d{1,6}),(-?\d{1,3}\.\d{1,6})\), (\d{1,2})\);/;

		if (regexpPosition.test( script ) ) {
			this.centerLat = RegExp.$1;
			this.centerLon = RegExp.$2;
			this.zoom = RegExp.$3;
		}

		// v <= 1.5
		if (majorVersion == 1 && minorVersion <= 5 ) {
			//	var text = 'En O Carballino ha estado la d\'elegacion diplomatica japonesa';
			if (regexpText.test( script ) ) {
				markerText = RegExp.$2;
			}

			//	var point = new GLatLng(42.4298,-8.07756);
			if (regexpMarker.test( script ) ) {
				markerLat = RegExp.$1;
				markerLon = RegExp.$2;
			}
			if (markerLat !== 0 && markerLon !== 0)
				this.markerPoints.push( { lat:markerLat, lon:markerLon, text:this.decodeText(markerText), color:'red', title:'' } );
		} else {
		// v > 1.5. multiple points.

		// AddMarkers( [{lat:37.45088, lon:-122.21123, text:'Write your text'}] );
			while ( (result = regexpMarkers.exec(script)) ) {
				maxWidth = 200;
				var opened = false;
				if (result[5])
					color = result[5];
				if (result[6])
					title = result[6];
				if (result[7])
					maxWidth = result[7];
				if (result[8])
					opened = result[8] == '1';
				this.markerPoints.push( { lat:result[1], lon:result[2], text:this.decodeText(result[4]), color:color, title:this.decodeText(title), maxWidth:maxWidth, open:opened } );
			}

			// Texts v>=2.1
			while ( (result = regexpTexts.exec(script)) ) {
				this.textsData.push( { lat:result[1], lon:result[2], title:this.decodeText(result[3]), className:this.decodeText(result[4]) } );
			}
		}

		if (majorVersion == 1) {
			// Notify back to parser that it must insert an img
			this.requiresImage = true;

			if (regexpLinePoints.test( script ) )
				linePoints = RegExp.$2;

			if (regexpLineLevels.test( script ) )
				lineLevels = RegExp.$2;

			if (linePoints !== '' && lineLevels !== '') {
				this.linesData.push({ color:'#3333cc', weight:5, opacity:0.45, points:linePoints, text:'', maxWidth:200 });
			}
		} else {
			// V2: multiple lines and areas.
		//			aLines.push('{color:\'' + line.color + '\', weight:' + line.weight + ', points:\'' + line.points + '\', levels:\'' + line.levels + '\'}');
		//		}
		//		aScript.push('	AddLines( map, [' + aLines.join(',\r\n') + '] );');

			if (majorVersion < 3 || (majorVersion == 3 && minorVersion === 0)) {
				while ( (result = regexpLines.exec(script)) ) {
					maxWidth = 200;
					if (result[9])
						maxWidth = result[9];
					this.linesData.push( { color:result[1], weight:result[2], opacity:result[3], points:this.decodeText(result[4]) } );
				}

				// only one line in each area
				// we reverse the opacity and weight to be able to avoid parsing them as normal polylines
				//	AddAreas( map, [{polylines: [{color:'#008000', opacity:0.7, weight:2, points:'iatcFnzrhVpfA{nOtuExkJnd@ffOwbIecJ', levels:'BBBBB'}], fill: true, color:'#008000', opacity:0.2, outline:true}] );
				while ( (result = regexpAreas.exec(script)) ) {
					lines = [ { color:result[1], weight:parseInt(result[3],10), opacity:parseFloat(result[2]), points:this.decodeText(result[4]) } ];

					maxWidth = 200;
					if (result[13])
						maxWidth = parseInt(result[13], 10);
					this.areasData.push( { polylines: lines, fill:true, color:result[9], opacity:parseFloat(result[10]) } );
				}
			} else {
				if (majorVersion == 3 && minorVersion < 6) {
					// withouth levels, zoomfactor and numLevels params, but always with maxWidth
					regexpLines = /\{color\:("|')(.*)\1,\s*weight:(\d+),\s*opacity:(-?\d\.\d{1,2}),\s*points\:("|')(.*?)\5,\s*text\:("|')(.*?)\7,\s*maxWidth:(\d+)}(?:,|])/g ;
					regexpAreas = /\{polylines:\s*\[\{color\:("|')(.*)\1,\s*opacity:(-?\d\.\d{1,2}),\s*weight:(\d+),\s*points\:("|')(.*)\5}\],\s*fill:(.*),\s*color:("|')(.*)\8,\s*opacity:(-?\d\.\d{1,2}),\s*text\:("|')(.*?)\11,\s*maxWidth:(\d+)\}(?:,|])/g ;

					while ( (result = regexpLines.exec(script)) ) {
						this.linesData.push( { color:result[2], weight:parseInt(result[3],10), opacity:parseFloat(result[4]), points:this.decodeText(result[6]) } );
					}

					while ( (result = regexpAreas.exec(script)) ) {
						lines = [ { color:result[2], opacity:parseFloat(result[3]), weight:parseInt(result[4],10), points:this.decodeText(result[6]) } ];
						this.areasData.push( { polylines:lines, color:result[9], opacity:parseFloat(result[10]) } );
					}
				} else {
					regexpLines = /\{color\:"(.*)",\s*weight:(\d+),\s*opacity:(-?\d\.\d{1,2}),\s*points\:"(.*?)"}(?:,|])/g ;
					while ( (result = regexpLines.exec(script)) ) {
						this.linesData.push( { color:result[1], weight:parseInt(result[2],10), opacity:parseFloat(result[3]), points:this.decodeText(result[4]) } );
					}

					regexpAreas = /\{polylines:\s*\[\{color\:"(.*)",\s*opacity:(-?\d\.\d{1,2}),\s*weight:(\d+),\s*points\:"(.*)"}\],\s*color:"(.*)",\s*opacity:(-?\d\.\d{1,2})\}(?:,|])/g ;
					while ( (result = regexpAreas.exec(script)) ) {
						lines = [ { color:result[1], opacity:parseFloat(result[2]), weight:parseInt(result[3],10), points:this.decodeText(result[4]) } ];
						this.areasData.push( { polylines:lines, color:result[5], opacity:parseFloat(result[6]) } );
					}

					var regexpCircles = /\{lat\:(-?\d{1,3}\.\d{1,6}),\s*lon\:(-?\d{1,3}\.\d{1,6}),radius:(\d+),color\:"(.*)",\s*weight:(\d+),\s*opacity:(-?\d\.\d{1,2}),\s*fillColor:"(.*)",\s*fillOpacity:(-?\d\.\d{1,2})\}(?:,|])/g ;
					while ( (result = regexpCircles.exec(script)) ) {
						this.circlesData.push( { lat:result[1], lon:result[2], radius:parseInt(result[3], 10), color:result[4], weight:parseInt(result[5],10), opacity:parseFloat(result[6]), fillColor:result[7], fillOpacity:parseFloat(result[8]) } );
					}

					// panorama
					var regexpPanorama = /panorama:\{lat:(-?\d{1,3}\.\d+),lng:(-?\d{1,3}\.\d+),pov1:(\d+\.?\d*),pov2:(\d+\.?\d*),pov3:(\d+\.?\d*)\}/;
					if (regexpPanorama.test( script ) ) {
						var panorama = {};
						panorama.lat = parseFloat(RegExp.$1);
						panorama.lng = parseFloat(RegExp.$2);
						panorama.heading = parseFloat(RegExp.$3);
						panorama.pitch = parseFloat(RegExp.$4);
						panorama.zoom = parseFloat(RegExp.$5);
						this.panorama = panorama;
					}

				}
			}

		}

		// 1.8 mapType
		//	map.setMapType( allMapTypes[ 1 ] );
		if (/setMapType\([^\[]*\[\s*(\d+)\s*\]\s*\)/.test( script ) ) {
			this.mapType = RegExp.$1;
		}

		// 1.9 wrapper div with custom class
		if ( majorVersion == 1 && minorVersion >= 9 ) {
			if (/<div class=("|')(.*)\1.*\/\/wrapper/.test( script ) )
				this.WrapperClass = RegExp.$2;
			else
				this.WrapperClass = '';
		}

		if (regexpKml.test( script ) ) {
			this.kmlOverlay = RegExp.$1;
		}

		// controls:
		if ( majorVersion == 2 ) {
			if (/google\.maps\.(.*)\(\)\);\/\*zoom\*\//.test( script ) )
				this.zoomControl = RegExp.$1;
			else
				this.zoomControl = '';

			if (/google\.maps\.(.*)\(\)\);\/\*type\*\//.test( script ) )
				this.mapTypeControl = RegExp.$1;
			else
				this.mapTypeControl = '';

			this.overviewMapControl = script.indexOf('OverviewMapControl') >= 0;
			this.scaleControl = script.indexOf('ScaleControl') >= 0;
			this.googleBar = script.indexOf('enableGoogleBar') >= 0;

			// Remap them to v3:
			switch (this.zoomControl) {
				case 'SmallZoomControl':
					this.zoomControl = 'Small';
					break;
				case 'SmallMapControl':
					this.zoomControl = 'Small';
					break;
				case 'LargeMapControl':
					this.zoomControl = 'Full';
					break;
				default:
					this.zoomControl = 'None';
					break;
			}

			switch (this.mapTypeControl) {
				case 'MapTypeControl':
					this.mapTypeControl = 'Full';
					break;
				case 'HierarchicalMapTypeControl':
					this.mapTypeControl = 'Menu';
					break;
				default:
					this.mapTypeControl = 'None';
					break;
			}
		}
		// controls:
		if ( majorVersion == 3 ) {
		/*
			var mapOptions = {
				zoom: 7,
				center: [42.7228,-1.72485],
				mapType: 0,
				zoomControl: 'Full',
				mapsControl: 'Menu',
				overviewMapControl: false,
				scaleControl: false,
				googleBar: false
			};
		*/
			if (/center:\s*\[(-?\d{1,3}\.\d{1,6}),(-?\d{1,3}\.\d{1,6})\],/.test( script ) ) {
				this.centerLat = RegExp.$1;
				this.centerLon = RegExp.$2;
			}

			if (/zoom: (\d{1,2}),/.test( script ) )
				this.zoom = parseInt(RegExp.$1, 10);

			if (/mapType: (\d{1,2}),/.test( script ) )
				this.mapType = parseInt(RegExp.$1, 10);

			// old
			if (/navigationControl: "(.*?)",/.test( script ) )
				this.zoomControl = RegExp.$1;

			if (/zoomControl: "(.*?)",/.test( script ) )
				this.zoomControl = RegExp.$1;

			if (/mapsControl: "(.*?)",/.test( script ) )
				this.mapTypeControl = RegExp.$1;

			if (/overviewMapControl: (.*?),/.test( script ) )
				this.overviewMapControl = RegExp.$1 == 'true';

			if (/overviewMapControlOptions: \{opened:(.*?)\},/.test( script ) )
				this.overviewMapControlOpened = RegExp.$1 == 'true';

			if (/scaleControl: (.*?),/.test( script ) )
				this.scaleControl = RegExp.$1 == 'true';

			if (/traffic: (.*?),/.test( script ) ) {
				if (RegExp.$1 == 'true')
					this.pathType = 'Traffic';
			}

			if (/transit: (.*?),/.test( script ) ) {
				if (RegExp.$1 == 'true')
					this.pathType = 'Transit';
			}
			if (/pathType: "(.*?)",/.test( script ) )
				this.pathType = RegExp.$1;

			if (/weather: (.*?),/.test( script ) )
				this.weather = RegExp.$1 == 'true';

			if (/weatherUsaUnits: (.*?),/.test( script ) )
				this.weatherUsaUnits = RegExp.$1 == 'true';

			// last one without trailing comma
			if (/googleBar: (.*?)\n/.test( script ) )
				this.googleBar = RegExp.$1 == 'true';

			if (/heading: (\d{1,3}),/.test( script ) )
				this.heading = parseInt(RegExp.$1, 10);

			if (/tilt: (\d{1,2}),/.test( script ) )
				this.tilt = parseInt(RegExp.$1, 10);

			// extra icons
			if ( minorVersion >= 3 ) {
				if (!this.editor.config.googleMaps_Icons)
					this.editor.config.googleMaps_Icons = {};
				var reIcons = /googleMaps_Icons\["(.*?)"\] = ({.*?});/g;
				while ( (result = reIcons.exec(script)) ) {
					// Simulate that it was included in the config:
					this.editor.config.googleMaps_Icons[ result[1] ] = JSON.parse( result[2] );
				}
			}
		}

		return true;
	};

	// fixme: allow %
	CKEGoogleMap.prototype.cssWidth = function() {
		if (/\d+\D+/.test(this.width))
			return this.width;

		return this.width + 'px';
	};

	CKEGoogleMap.prototype.cssHeight = function() {
		if (/\d+\D+/.test(this.height))
			return this.height;

		return this.height + 'px';
	};

	CKEGoogleMap.prototype.BuildScript = function() {
		var handler = this.editor.plugins.googleMapsHandler,
			aScript = [], i,
			aPoints = [], point,
			aTexts = [],
			aLines = [], line,
			aAreas = [], area, areaLine,
			aCircles = [], circle;

		aScript.push('\r\n<script type="text/javascript">');
		aScript.push('/*<![CDATA[*/');
		aScript.push( '/* CK googlemaps v' + handler.majorVersion + '.' + handler.minorVersion + ' */' );

		aScript.push('var imgMap = document.getElementById("' + this.number + '"),');
		aScript.push('	dMap = document.createElement("div"),');
		aScript.push('	canvas = document.createElement("canvas"),');
		aScript.push('	dGMap = imgMap.parentNode;');
		aScript.push('dMap.id="w' + this.number + '";');
		aScript.push('dGMap.insertBefore( dMap, imgMap);');
		aScript.push('dMap.appendChild(imgMap);');

		aScript.push('if (dGMap.style.textAlign == "center") dGMap.style.margin="0 auto";');

		if (this.responsive) {
			aScript.push('dGMap.style.maxWidth = "' + this.cssWidth() + '";');

			aScript.push('dGMap.insertBefore( canvas, dMap);');
			aScript.push('canvas.style.width = "100%";');
			aScript.push('canvas.style.height = "auto";');
			aScript.push('canvas.width = "' + this.width + '";');
			aScript.push('canvas.height = "' + this.height + '";');

			aScript.push('dMap.style.width = "100%";');
			aScript.push('dMap.style.height = "100%";');
			aScript.push('dMap.style.left = 0;');
			aScript.push('dMap.style.top = 0;');
			aScript.push('dMap.style.position = "absolute";');
			aScript.push('dGMap.style.position = "relative";');
		} else {
			aScript.push('dMap.style.width = "' + this.cssWidth() + '";');
			aScript.push('dMap.style.height = "' + this.cssHeight() + '";');
		}
		aScript.push('var generatedType = ' + this.generatedType + ';');
		aScript.push('if (generatedType==3 && navigator.userAgent.indexOf("Mobile")>0) generatedType=2;');
		aScript.push('if (generatedType==2) {');
		//aScript.push('dMap.style.position = "relative";');
		aScript.push('dMap.style.cursor = "pointer";');
		aScript.push('dMap.onclick = function(e) {initGmapsLoader(e||event)};');
		aScript.push('var t = document.createTextNode("' + this.editor.lang.googlemaps.clickToLoad + '"),');
		aScript.push('	d = document.createElement("div");');
		aScript.push('d.appendChild(t);');
		aScript.push('d.style.cssText="background-color:#e5e5e5; filter:alpha(opacity=80); opacity:0.8; padding:1em; font-weight:bold; text-align:center; position:absolute; left:0; box-sizing:border-box; width:100%; max-width:' + this.width + 'px; top:0";');
		aScript.push('dMap.appendChild(d);');
		aScript.push('}');

		aScript.push('function CreateGMap' + this.number + '() {');
		aScript.push('	var dMap = document.getElementById("' + this.number + '");');
		aScript.push('	if (dMap) ');
		aScript.push('		dMap = dMap.parentNode;');
		aScript.push('	else ');
		aScript.push('		dMap = document.getElementById("w' + this.number + '");');
		aScript.push('	if (!dMap) return;');

		aScript.push('	if (dMap.ckemap) {');
		aScript.push('		var map = dMap.ckemap.map, center = map.getCenter();');
		aScript.push('		google.maps.event.trigger(map, "resize");');
		aScript.push('		map.setCenter(center);');
		aScript.push('		return; ');
		aScript.push('	} ');

		aScript.push('	dMap.onclick = null;');

		aScript.push('	var mapOptions = {');
		aScript.push('		zoom: ' + this.zoom + ',');
		aScript.push('		center: [' + this.centerLat + ',' + this.centerLon + '],');
		aScript.push('		mapType: ' + this.mapType + ',' );
		aScript.push('		zoomControl: "' + this.zoomControl + '",' );
		aScript.push('		mapsControl: "' + this.mapTypeControl + '",' );
		aScript.push('		heading: ' + this.heading + ',');
		aScript.push('		tilt: ' + this.tilt + ',');
		if (this.overviewMapControl) aScript.push('		overviewMapControl: ' + this.overviewMapControl + ',' );
		if (this.overviewMapControlOpened) aScript.push('		overviewMapControlOptions: {opened:' + this.overviewMapControlOpened + '},' );
		if (this.scaleControl) aScript.push('		scaleControl: ' + this.scaleControl + ',' );
		if (this.weather) aScript.push('		weather: ' + this.weather + ',' );
		if (this.weatherUsaUnits) aScript.push('		weatherUsaUnits: ' + this.weatherUsaUnits + ',' );
		aScript.push('		pathType: "' + this.pathType + '",' );
		aScript.push('		googleBar: ' + this.googleBar );

		if (this.panorama) {
			var pan = this.panorama;
			aScript.push(', panorama:{' +
				'lat:' + pan.lat + ',' +
				'lng:' + pan.lng + ',' +
				'pov1:' + pan.heading + ',' +
				'pov2:' + pan.pitch + ',' +
				'pov3:' + pan.zoom + '' +
			'}');
		}

		aScript.push('	};');

		aScript.push('	var myMap = new CKEMap(dMap, mapOptions);');
		aScript.push('	dMap.ckemap=myMap;');

		var googleMaps_Icons = this.editor.config.googleMaps_Icons,
			usedIcons = {};

		if (this.markerPoints.length) {
			for (i = 0; i < this.markerPoints.length; i++) {
				point = this.markerPoints[i];
				aPoints.push('{lat:' + point.lat + ', lon:' + point.lon + ', text:\"' + this.encodeText(point.text) + '\",color:\"' + point.color +
					'\", title:\"' + this.encodeText(point.title) + '\", maxWidth:' + point.maxWidth + ', open:' + (point.open ? 1 : 0) + '}');

				if ( googleMaps_Icons && googleMaps_Icons[ point.color ] )
					usedIcons[ point.color ] = googleMaps_Icons[ point.color ];
			}
			aScript.push('	myMap.AddMarkers( [' + aPoints.join(',\r\n') + '] );');
		}

		if (this.textsData.length) {
			for (i = 0; i < this.textsData.length; i++) {
				point = this.textsData[i];
				aTexts.push('{lat:' + point.lat + ', lon:' + point.lon + ', title:\"' + this.encodeText(point.title) + '\", className:\"' + this.encodeText(point.className) + '\"}');
			}
			aScript.push('	myMap.AddTexts( [' + aTexts.join(',\r\n') + '] );');
		}

		if (this.linesData.length) {
			for (i = 0; i < this.linesData.length; i++) {
				line = this.linesData[i];
				aLines.push('{color:\"' + line.color + '\", weight:' + line.weight + ', opacity:' + line.opacity + ', points:\"' + this.encodeText(line.points) + '\"}');
			}
			aScript.push('	myMap.AddLines( [' + aLines.join(',\r\n') + '] );');
		}

		if (this.areasData.length) {
			for (i = 0; i < this.areasData.length; i++) {
				area = this.areasData[i];
				areaLine = area.polylines[0];
				aAreas.push('{polylines: [{color:"' + areaLine.color + '", opacity:' + areaLine.opacity + ', weight:' + areaLine.weight + ', points:"' + this.encodeText(areaLine.points) + '"}], color:"' + area.color + '", opacity:' + area.opacity + '}');
			}
			aScript.push('	myMap.AddAreas( [' + aAreas.join(',\r\n') + '] );');
		}

		if (this.circlesData.length) {
			for (i = 0; i < this.circlesData.length; i++) {
				circle = this.circlesData[i];
				aCircles.push('{lat:' + circle.lat + ',lon:' + circle.lon + ',radius:' + circle.radius + ',color:"' + circle.color + '",weight:' + circle.weight + ',opacity:' + circle.opacity + ',fillColor:"' + circle.fillColor + '",fillOpacity:' + circle.fillOpacity + '}');
			}
			aScript.push('	myMap.AddCircles( [' + aCircles.join(',\r\n') + '] );');
		}

		if (this.kmlOverlay)
			aScript.push('	myMap.AddKml("' + this.kmlOverlay + '");');

		aScript.push('}');

		aScript.push('');

		if ( !CKEDITOR.tools.isEmpty( usedIcons ) ) {
			aScript.push('if (!window.googleMaps_Icons) window.googleMaps_Icons = {};');
			for ( var iconName in usedIcons ) {
				if ( usedIcons.hasOwnProperty( iconName ) ) {
					aScript.push('window.googleMaps_Icons["' + iconName + '"] = ' + JSON.stringify( usedIcons[ iconName ] ) + ';');
				}
			}
		}

		aScript.push('if (!window.gmapsLoaders) window.gmapsLoaders = [];');
		aScript.push('window.gmapsLoaders.push(CreateGMap' + this.number  + ');');

		aScript.push('if (generatedType==3)');
		aScript.push('	window.gmapsAutoload=true;');

		if (this.linesData.length || this.areasData.length)
			aScript.push('window.gmapsGeometry=true;');
		if (this.weather)
			aScript.push('window.gmapsWeather=true;');
		if (this.key)
			aScript.push('window.gmapsKey="' + this.key  + '";');

		aScript.push('/*]]>*/');
		aScript.push('</script>');

		return aScript.join('\r\n');
	};


	CKEDITOR.plugins.add( 'googlemaps', {
		requires : [ 'dialog' ],
		// translations
		lang : 'en,ar,cs,de,el,es,fi,fr,it,nl,pl,ru,sk,tr',
		icons: 'googlemaps,gmapsinsertdirections', // %REMOVE_LINE_CORE%

		init : function( editor ) {
			editor.plugins.googleMapsHandler = new GoogleMapsHandler( editor );
			// Add buttons.
			var allowed = 'div[id];img[id,src,style];script';
			if (editor.config.googleMaps_WrapperClass)
				allowed += ';div(' + editor.config.googleMaps_WrapperClass + ')';

			editor.addCommand( 'googlemaps', new CKEDITOR.dialogCommand( 'googlemaps', {
				allowedContent: allowed,
				requiredContent: 'script'
			} ) );

			editor.ui.addButton( 'GoogleMaps', {
				label : editor.lang.googlemaps.toolbar,
				command : 'googlemaps',
				icon : this.path + 'icons/googlemaps.png', // %REMOVE_LINE_CORE%
				toolbar : 'insert'
			} );

			CKEDITOR.dialog.add( 'googlemaps', this.path + 'dialogs/googlemaps.js' );
			CKEDITOR.dialog.add( 'googlemapsMarker', this.path + 'dialogs/marker.js' );
			CKEDITOR.dialog.add( 'googlemapsIcons', this.path + 'dialogs/icons.js' );
			CKEDITOR.dialog.add( 'googlemapsText', this.path + 'dialogs/text.js' );
			CKEDITOR.dialog.add( 'googlemapsLine', this.path + 'dialogs/line.js' );
			CKEDITOR.dialog.add( 'googlemapsArea', this.path + 'dialogs/area.js' );

			// If the "menu" plugin is loaded, register the menu items.
			if ( editor.addMenuItems ) {
				editor.addMenuItems(
					{
						googlemaps :
						{
							label : editor.lang.googlemaps.menu,
							icon : this.path + 'icons/googlemaps.png', // %REMOVE_LINE_CORE%
							command : 'googlemaps',
							group : 'image',
							order : 1
						}
					});
			}

			// If the "contextmenu" plugin is loaded, register the listeners.
			if ( editor.contextMenu ) {
				// check the image

				// We put our listener as the first item
				// The aim of this listener is to mark the images as "fake", but only while the context menu is displayed
				editor.contextMenu._.listeners.unshift( function( element ) {
					if ( !element || !element.is( 'img' ) || !editor.plugins.googleMapsHandler.isStaticImage( element.$ ) )
						return null;

					// Set here the trick
					element.data( 'cke-realelement', '1' );
					return null;
				});

				editor.contextMenu.addListener( function( element ) {
					if ( !element || !element.is( 'img' ) || !editor.plugins.googleMapsHandler.isStaticImage( element.$ ) )
						return null;

					// Now we clear it
					element.data( 'cke-realelement', false );

					// And say that this context menu item must be shown
					return { googlemaps : CKEDITOR.TRISTATE_ON };
				});
			}

			// Open our dialog on double click
			editor.on( 'doubleclick', function( evt ) {
				var element = evt.data.element;

				if ( element.is( 'img' ) && editor.plugins.googleMapsHandler.isStaticImage( element.$ ) ) {
					evt.data.dialog = 'googlemaps';
				// evt.stop();
				}
			}, null, null, 20); // after the default options


			// patch for #13481
			var initialZindex;
			editor.on( 'beforeCommandExec', function( evt ) {
				if (evt.data.name != 'maximize')
					return;

				if (!initialZindex)
					initialZindex = editor.config.baseFloatZIndex;

				// Only the first time to get the initial value
				evt.removeListener();
			} );

			editor.on( 'afterCommandExec', function( evt ) {
				if (evt.data.name != 'maximize')
					return;

				if (evt.data.command.state == CKEDITOR.TRISTATE_ON)
					editor.config.baseFloatZIndex = initialZindex * 2;
				else
					editor.config.baseFloatZIndex = initialZindex;

			} );
			// end patch for #13481

		},
		afterInit : function(editor) {
			// We need to control if the scripts must be added or removed
			processScriptOutput(editor);

			// Expand the editor to provide controlled dialogs.
			editor.openNestedDialog = function( dialogName, callbackOpen, callbackOK ) {
				var onOk = function() {
					releaseHandlers( this );
					if (callbackOK)
						callbackOK( this );
				};
				var onCancel = function() {
					releaseHandlers( this );
				};
				var releaseHandlers = function( dialog ) {
					dialog.removeListener( 'ok', onOk );
					dialog.removeListener( 'cancel', onCancel );
				};
				var bindToDialog = function( dialog ) {
					dialog.on( 'ok', onOk );
					dialog.on( 'cancel', onCancel );
					if (callbackOpen)
						callbackOpen( dialog );
				};

				if ( editor._.storedDialogs[ dialogName ])
					bindToDialog( editor._.storedDialogs[ dialogName ] );
				else {
					CKEDITOR.on( 'dialogDefinition', function( e ) {
						if ( e.data.name != dialogName )
							return;

						var definition = e.data.definition;

						e.removeListener();
						if (typeof definition.onLoad == 'function') {
							definition.onLoad = CKEDITOR.tools.override( definition.onLoad, function( original ) {
								return function() {
									definition.onLoad = original;
									if ( typeof original == 'function' )
										original.call( this );
									bindToDialog( this );
								};
							} );
						} else {
							definition.onLoad = function() {
								bindToDialog( this );
							};
						}

					});
				}

				editor.openDialog( dialogName );
			};


			// Workaround for #9060
			if (CKEDITOR.env.ie)  {
				CKEDITOR.dialog.prototype.hide = CKEDITOR.tools.override( CKEDITOR.dialog.prototype.hide , function( originalFunction ) {
					return function() {
						var parent = this._.parentDialog;

						originalFunction.call( this );

						if (parent && this._.editor != parent._.editor) {
							if ( this._.editor.mode == 'wysiwyg' ) {
								var selection = this._.editor.getSelection();
								if (selection)
									selection.unlock( true );
							}
						}
					};
				});
			}

		}
	});


	// Adjust the output
	function processScriptOutput( editor ) {
		var dataProcessor = editor.dataProcessor,
			htmlFilter = dataProcessor && dataProcessor.htmlFilter,
			dataFilter = dataProcessor && dataProcessor.dataFilter,
			handler = editor.plugins.googleMapsHandler;

		// Avoid that our images are upcasted to widgets
		editor.on( 'toHtml', function( evt ) {

			evt.data.dataValue.forEach( function( element ) {

				// find images and if they belong to us, set an attribute to avoid the conversion by image2
				if (element.name == 'img') {
					var id = element.attributes['id'];
					if (id && (/^gmap\d+$/).test(id))
						element.attributes['data-cke-realelement'] = 'googlemap';
					else {
						var parent = element.parent;
						if (parent && parent.name == 'div') {
							id = parent.attributes['id'];
							if (id && (/^dgmap\d+$/).test(id)) {
								element.attributes['data-cke-realelement'] = 'googlemap';
							}
						}
					}
				}
			});
		// lower priority than the widget system
		}, null, null, 5);


		// dataFilter : conversion from html input to internal data
		dataFilter.addRules(
			{
				comment : function( contents ) {
					// Check if they are part of our scripts. Parse them and remove from the content.
					var protectedSourceMarker = '{cke_protected}';

					if (contents.substr(0, protectedSourceMarker.length) == protectedSourceMarker) {
						var data = contents.substr( protectedSourceMarker.length );
						data = decodeURIComponent( data );

						if ( handler.detectMapScript( data ) ) {
							var oMap = handler.createNew();
							if (oMap.parse( data ) ) {
								if (oMap.requiresImage) {
									// Insert an image with the map data
									return new CKEDITOR.htmlParser.element( 'img',
										{
											id: oMap.number,
											mapnumber: oMap.number,
											src: oMap.generateStaticMap(),
											width: oMap.width,
											height: oMap.height
										} );
								}
								return false;
							}
						} else {
							if ( handler.detectGoogleScript( data ) )
								return false;
						}
					}

					return contents;
				},

				elements :
				{
					'img' : function(element) {
						var id = element.attributes['id'];
						if (id && (/^gmap\d+$/).test(id))
							element.attributes['mapnumber'] = id;
						else {
							var parent = element.parent;
							if (parent && parent.name == 'div') {
								id = parent.attributes['id'];
								if (id && (/^dgmap\d+$/).test(id)) {
									element.attributes['id'] = element.attributes['mapnumber'] = id.substr(1);
								}
							}
						}
						if (element.attributes['data-cke-realelement'] && element.attributes['data-cke-realelement'] == 'googlemap') {
							delete element.attributes['data-cke-realelement'];
						}
					},

					'div' : function(element) {
						// Readjust previous ids.
						var id = element.attributes['id'];
						if (id && (/^gmap\d+$/).test(id)) {
							element.attributes[ 'id' ] = 'd' + id;
						}
					}
				}

			}
		);

		// htmlFilter : conversion from internal data to html output.
		htmlFilter.addRules(
			{
				elements :
				{
					'img' : function( element ) {
						var number = element.attributes.mapnumber;
						if (number) {
							var scriptNode,
								handler = editor.plugins.googleMapsHandler,
								oMap = handler.getMap( number );

							if (oMap && oMap.generatedType > 1) {
								handler.CreatedMapsNames.push( oMap.number );
								// Inject the <script> for this map
								scriptNode = new CKEDITOR.htmlParser.cdata( oMap.BuildScript() );
								element.parent.children.push( scriptNode );
							}
							delete element.attributes.mapnumber;
						}

						return element;
					}
				}
			}, { applyToAll: true } );

		dataProcessor.toDataFormat = CKEDITOR.tools.override( dataProcessor.toDataFormat , function( originalFunction ) {
			return function( html, fixForBody ) {
				// Check if we need to include or not the global maps building script
				var handler = editor.plugins.googleMapsHandler;
				handler.CreatedMapsNames = [];

				// Validate that everything is properly nested and there aren't left-over divs.
				if (!handler.validateDOM()) {
					var config = editor.config,
						fullPage = config.fullPage,
						doc = editor.document;
					// Get the new HTML
					html = fullPage ? doc.getDocumentElement().getOuterHtml() : doc.getBody().getHtml();
				}

				var Result = originalFunction.call(this, html, fixForBody);

				if (handler.CreatedMapsNames.length > 0)
					Result += handler.BuildEndingScript();

				return Result;
			};
		});
	}

	/*
		Compatibility between CKEditor 3 and 4
	*/
	if (CKEDITOR.skins) {
		CKEDITOR.plugins.setLang = CKEDITOR.tools.override( CKEDITOR.plugins.setLang , function( originalFunction ) {
			return function( plugin, lang, obj ) {
				if (plugin != 'devtools' && typeof obj[plugin] != 'object') {
					var newObj = {};
					newObj[ plugin ] = obj;
					obj = newObj;
				}
				originalFunction.call(this, plugin, lang, obj);
			};
		});
	}

})();

