// $E Development H.D$
// $D http://wiki.newsonline.tc.nca.bbc.co.uk/wiki/moin.cgi/HieuLuuDanh/MappingHelper (under Framework)

/**
 * @fileOverview This is the mapping framework used in parallel with Microsoft's Virtual Earth Mapping SDK
 * @author Hieu Luu Danh &lt;hieu.danh (at) bbc.co.uk&gt;
 * @version 1.1.0-dev
 */

/**
 * @ignore
 */
(function () {
	createObject("bbc.fmtj.apps");
	
	bbc.fmtj.apps.createObject = createObject;
	
	/**
	 * @ignore
	 */
	function createObject(strName) {
		var nameParts = strName.split("."),
		i = 0,
		len = nameParts.length,
		obj = window;
		
		for (; i < len; i++){
			if (obj[nameParts[i]] === undefined) {
				obj[nameParts[i]] = {};
			}
			
			obj = obj[nameParts[i]];
		}
	}
	
})();

/**
 * @ignore
 */
bbc.fmtj.apps.createObject("bbc.fmtj.csd.fwkmap");


/**
 * @constructor
 * @param {String} id The htmlId of where the map will sit
 * @param {Object} [glow] Glow instance
 * @property {String} elmId The htmlId passed
 * @requires >= VEMap 6.2
 * @requires >= Glow 1.1
 * @author Hieu Luu Danh &lt;hieu.danh (at) bbc.co.uk&gt;
 * @class Helps developer set up a map via framework helper methods
 * @throws ArgumentNotFound
 */
bbc.fmtj.csd.fwkmap = function(id, glow) {
	
	// checking arguments
	
	if (!id) {
		throw 'You must provide a htmlId for the map.';
	} else {
		var elmId = id;
	}
	
	// public
	
	/**
	 * @name _vemap
	 * @public
	 * @description Stores the map instance passed by MSVE Map
	 * @fieldOf bbc.fmtj.csd.fwkmap.prototype
	 * @type Object
	 */
	this._vemap = null;
	
	// private
	
		/**
		 * @name currentState
		 * @private
		 * @description The current state the map is in, see {@link mapState}
		 * @fieldOf bbc.fmtj.csd.fwkmap.prototype
		 * @type Number
		 * @default 0 (via <strong>this.mapState.<em>rawMap</em></strong>)
		 */
	var currentState = this.mapState.rawMap,
		/**
		 * @name holder
		 * @private
		 * @description Serves as a general purpose controlled and hashed storage
		 * @fieldOf bbc.fmtj.csd.fwkmap.prototype
		 * @type Dictionary
		 */
		holder = {},
		/**
		 * @name pinCompilation
		 * @private
		 * @description Compilation of pins (hashed)
		 * @fieldOf bbc.fmtj.csd.fwkmap.prototype
		 * @type Dictionary
		 */
		pinCompilation = {},
		/**
		 * @name layerCompilation
		 * @private
		 * @description Compilation of layers (hashed)
		 * @fieldOf bbc.fmtj.csd.fwkmap.prototype
		 * @type Dictionary
		 */
		layerCompilation = {},
		/**
		 * @name shapeCompilation
		 * @private
		 * @description Compilation of shapes (hashed)
		 * @fieldOf bbc.fmtj.csd.fwkmap.prototype
		 * @type Dictionary
		 */
		shapeCompilation = {};
	
	/**
	 * @name setCurrentState
	 * @description attr_writer wrapper for currentState
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 */
	this.setCurrentState = function setCurrentState(s) {
		if ((s - 1) != currentState) {
			return;
		} else {
			currentState = s;
		}
	}
	/**
	 * @name getCurrentState
	 * @description attr_reader wrapper for currentState
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @return {Number} {@link currentState}
	 */
	this.getCurrentState = function getCurrentState() {
		return currentState;
	}
	
	/**
	 * @name hold
	 * @description attr_writer wrapper for {@link holder}
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @return 0 if successful
	 * @param {String} name The object's identifier
	 * @param {Object} what The object itself
	 * @param {Hash} opts Optional settings
	 * @throws NoOverwriteButExistingValue
	 */
	this.hold = function hold(name, what, opts) {
		opts = opts || {overwrite: true, extend: false};
		if (opts.overwrite == false && typeof(holder[name]) != 'undefined') {
			throw 'overwrite and extend is false but ' + name + ' already exists!';
		} else if (opts.extend == true && typeof(holder[name]) != 'undefined') {
			glow.lang.apply(holder[name], what);
		} else {
			holder[name] = what;
			return 0;
		}
	}
	/**
	 * @name retrieve
	 * @description attr_reader wrapper for {@link holder}
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @return {Object} The requested object from {@link holder}
	 * @param {String} name The requested object's identifier
	 * @throws StructDoesNotExist
	 */
	this.retrieve = function retrieve(name) {
		if (holder[name]) {
			return holder[name];
		} else {
			throw 'the struct ' + name + ' does not exist inside the holder.';
		}
	}
	
	/**
	 * @name pushPin
	 * @description attr_writer wrapper for {@link pinCompilation}
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @return 0 if successful
	 * @param {String} name The pin's identifier
	 * @param {VEShape} pinObj The pin itself
	 * @param {Boolean} [overwrite] If true, will overwrite if already existing object. Defaults to true.
	 * @throws NoOverwriteButExistingValue
	 */
	this.pushPin = function pushPin(name, pinObj, overwrite) {
		if (typeof(overwrite) == 'undefined') {
			overwrite = true;
		}
		if (overwrite == false && typeof(pinCompilation[name]) != 'undefined') {
			throw 'overwrite is false but ' + name + ' already exists!';
		} else {
			pinCompilation[name] = pinObj;
			return 0;
		}
	};
	/**
	 * @name getPin
	 * @description attr_reader wrapper for {@link pinCompilation}
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @return {VEShape} The requested pin from {@link pinCompilation}
	 * @param {String} name The requested pin's identifier
	 * @throws StructDoesNotExist
	 */
	this.getPin = function getPin(name) {
		if (pinCompilation[name]) {
			return pinCompilation[name];
		} else {
			throw 'the pin ' + name + ' does not exist inside the compilation.';
		}
	};
	
	/**
	 * @name makeLayer
	 * @description attr_writer wrapper for {@link layerCompilation}
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @return 0 if successful
	 * @param {String} name The layer's identifier
	 * @throws ExistingValue
	 */
	this.makeLayer = function makeLayer(name) {
		if (typeof(layerCompilation[name]) != 'undefined') {
			throw 'the layer ' + name + ' already exists.';
		} else {
			layerCompilation[name] = new VEShapeLayer();
			return 0;
		}
	};
	/**
	 * @name getLayer
	 * @description attr_reader wrapper for {@link layerCompilation}
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @return {VELayer} The requested layer from {@link layerCompilation}
	 * @param {String} name The requested layer's identifier
	 * @throws StructDoesNotExist
	 */
	this.getLayer = function getLayer(name) {
		if (typeof(layerCompilation[name]) == 'undefined') {
			throw 'you are trying to get ' + name + ' which does not exist inside the cmpl.';
		} else {
			return layerCompilation[name];
		}
	};
	/**
	 * @name instantiateLayer
	 * @description adds the layer to the map (draws it)
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @return 0 if successful
	 * @param {String} name The layer's identifier
	 * @throws StructDoesNotExist
	 */
	this.instantiateLayer = function instantiateLayer(layer) {
		if (typeof(layerCompilation[layer]) == 'undefined') {
			throw 'you are trying to instantiate ' + layer + ' which does not exist.';
		} else {
			this._vemap.AddShapeLayer(layerCompilation[layer]);
			return 0;
		}
	};
	
	/**
	 * @name makeShape
	 * @description attr_writer wrapper for {@link shapeCompilation}
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @return 0 if successful
	 * @param {String} name The shape's identifier
	 * @param {Hash} args A hash containing the following members
	 * @param {VEShapeType} args.type The shape's type
	 * @param {VELatLong[]} args.points An array of points forming the shape
	 * @param {Boolean} [overwrite] If true, will overwrite if already existing object. Defaults to true.
	 * @throws NoOverwriteButExistingValue
	 */
	this.makeShape = function makeShape(name, args, overwrite) {
		if (typeof(overwrite) == 'undefined') {
			overwrite = true;
		}
		if (overwrite == false && typeof(shapeCompilation[name]) != 'undefined') {
			throw 'overwrite is false but ' + name + ' already exists!';
		} else {
			shapeCompilation[name] = new VEShape(args.type, args.points);
			return 0;
		}
	};
	/**
	 * @name getShape
	 * @description attr_reader wrapper for {@link shapeCompilation}
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @return {VEShape} The requested shape from {@link shapeCompilation}
	 * @param {String} name The requested shape's identifier
	 * @param {Hash} [name.debug] If you pass to this method a hash with debug == true, it will return the whole shapeCompilation
	 * @throws StructDoesNotExist
	 */
	this.getShape = function getShape(name) {
		if (typeof(shapeCompilation[name]) == 'undefined') {
			throw 'you are trying to get ' + name + ' which does not exist inside the shape cmpl.';
		}
		if (name.debug == true) {
			return shapeCompilation;
		} else {
			return shapeCompilation[name];
		}
	};
	
	/**********
	Make Map
	***********/
	/**
	 * @name pre
	 * @description Prepares the map for usage
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @throws StateFlowNotFollowed
	 * @throws VEMapNotIncluded
	 */
	this.pre = function pre() {
		if (!VEMap) {
			throw "MSVE Map not included";
		} else if (!glow) {
			throw "Glow instance not found or not named glow";
		} else {
			this._vemap = new VEMap(elmId);
			this._vemap.LoadMap();
		}
	}
	
	//Privil Methods
	
	/**
	 * @name contextualize
	 * @description Advances the map's state into Contextualized Map
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @throws StateFlowNotFollowed
	 * @throws VEMapNotIncluded
	 * @param {Dictionary} dirv A context directive
	 * @returns 0 if successful
	 */
	this.contextualize = function contextualize(dirv) {
		if (dirv == 'nameOf') {
			return 'contextualize';
		}
		
		var _dirv = dirv;
		var _aim = this.mapState.get(arguments.callee('nameOf'));
		
		if ((_aim - 1) != this.getCurrentState()) {
			throw "State flow not followed";
		}
		if (!VEMap) {
			throw "MSVE Map not included";
		}
		
		/*--
		Directive structure -
		
		{
			startingPositionLat: int,
			startingPositionLong: int,
			startingPositionZoom: int,
			kmSwitch: bool, [defaults: true],
			perfOverQuality: bool [defaults: false],
			terrainMode: bool [defaults: true],
			aerialOnly: bool [defaults: false]
		}	
		
		--*/
		var startPos = new VELatLong(_dirv.startingPositionLat,_dirv.startingPositionLong);
		var zoomLvl = parseInt(_dirv.startingPositionZoom);
		this._vemap.SetCenterAndZoom(startPos, zoomLvl);
		this.hold('startPos', startPos);
		this.hold('zoomLvl', zoomLvl);
		if (typeof(_dirv.kmSwitch) == 'undefined') {
			_dirv.kmSwitch = true;
		}
		if (_dirv.kmSwitch) {
			this._vemap.SetScaleBarDistanceUnit(VEDistanceUnit.Kilometers);
		}
		if (typeof(_dirv.perfOverQuality) == 'undefined') {
			_dirv.perfOverQuality = false;
		}
		if (_dirv.perfOverQuality) {
			this._vemap.EnableShapeDisplayThreshold(true);
		} else {
			this._vemap.EnableShapeDisplayThreshold(false);
		}

		if (typeof(_dirv.terrainMode) == 'undefined') {
			_dirv.terrainMode = true;
		}
		if (_dirv.terrainMode) {
			this._vemap.SetMapStyle(VEMapStyle.Hybrid);
		} else {
			this._vemap.SetMapStyle(VEMapStyle.Road);
		}
		if (typeof(_dirv.aerialOnly) == 'undefined') {
			_dirv.aerialOnly = false;
		}
		if (_dirv.aerialOnly) {
			this._vemap.SetMapStyle(VEMapStyle.Aerial);
		} else {
			// do nothing
		}
		
		this.setCurrentState(this.getCurrentState()+1); // _aim
		return 0;
	}
	/**
	 * @name initialize
	 * @description Advances the map's state into Initialized Map
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @throws StateFlowNotFollowed
	 * @throws VEMapNotIncluded
	 * @param {Dictionary} dirv A context directive
	 * @returns 0 if successful
	 */
	this.initialize = function initialize(dirv) {
		if (dirv == 'nameOf') {
			return 'initialize';
		}
		
		var _dirv = dirv;
		var _aim = this.mapState.get(arguments.callee('nameOf'));
		
		if ((_aim - 1) != this.getCurrentState()) {
			throw "State flow not followed";
		}
		if (!VEMap) {
			throw "MSVE Map not included";
		}
		
		/*--
		Directive structure -
		
		{
			bareMinimum: bool, [defaults: true]
			ctrlStruct: {
				ctrl_glow_ref: {
					css: {
						property: value, [...]
					},
					futureopts: val, [...]
				}, [...]
			},
			evntStruct: {
				ctrl_glow_ref: {
					type: (special|normal),
					action: (reset|evFx), [...]
				}, [...]
			},
			metadata: {
				global: String
			}
		}
		
		evFx is a function
		
		Using a glow ref & making it global will help with
		interactivity - other scripts meddling with the
		map can just call the control(s) via their
		reference variable and modify them on-the-fly
		without having to resort to this class
		
		--*/
		
		if (typeof(_dirv.bareMinimum) == 'undefined') {
			_dirv.bareMinimum = true;
		}
		if (_dirv.bareMinimum) {
			this._vemap.HideMiniMap();
			this._vemap.HideDashboard();
		} else {
			this._vemap.ShowMiniMap();
			this._vemap.ShowDashBoard();
		}
		for (glowref in _dirv.ctrlStruct) {
			htmlElm = eval(glowref);
			this._vemap.AddControl(htmlElm.item(0));
			for (opt in _dirv.ctrlStruct[glowref]) {
			 if (opt == "css") {
				for (prop in _dirv.ctrlStruct[glowref][opt]) {
					if (
					 prop == 'left' ||
					 prop == 'right' ||
					 prop == 'top' ||
					 prop == 'bottom'
					) {
						cval = parseInt(glow.dom.get(htmlElm).css(prop));
						cval = 0; // hack
						glow.dom.get(htmlElm).css(prop, cval + _dirv.ctrlStruct[glowref].css[prop] + "px");
					} else {
						glow.dom.get(htmlElm).css(prop, _dirv.ctrlStruct[glowref].css[prop]);
					}
				}
			 } else {
				// no other opts yet
			 }
			}
			htmlElm.appendTo(glow.dom.get(_dirv.metadata.global));
		}
		if (_dirv.evntStruct != null) {
		for (glowref in _dirv.evntStruct) {
			htmlElm = eval(glowref);
			for (fx in _dirv.evntStruct[glowref]) {
				if (fx == 'type' && _dirv.evntStruct[glowref][fx] == 'map_control') {
					/*--
					_dirv.evntStruct[glowref].buttons = Array
					--*/
					var moveSpeed = 8;
					var moveDelta = 25;
					for (i = _dirv.evntStruct[glowref].buttons.length - 1; i >= 0; i--) {
						switch (_dirv.evntStruct[glowref].buttons[i]) {
							case 'up':
								//htmlElm.up
								glow.events.addListener(
									htmlElm.up,
									'click',
									function() {
										this._vemap.Pan(0,-moveDelta);
									},
									this
								);
								glow.events.addListener(
									htmlElm.up,
									'mousedown',
									function() {
										this._vemap.StartContinuousPan(0, -moveSpeed);
									},
									this
								);
								glow.events.addListener(
									htmlElm.up,
									'mouseup',
									function() {
										this._vemap.EndContinuousPan();
									},
									this
								);
								break;
							case 'down':
								glow.events.addListener(
									htmlElm.down,
									'click',
									function() {
										this._vemap.Pan(0, moveDelta);
									},
									this
								);
								glow.events.addListener(
									htmlElm.down,
									'mousedown',
									function() {
										this._vemap.StartContinuousPan(0, moveSpeed);
									},
									this
								);
								glow.events.addListener(
									htmlElm.down,
									'mouseup',
									function() {
										this._vemap.EndContinuousPan();
									},
									this
								);
								break;
							case 'left':
								glow.events.addListener(
									htmlElm.left,
									'click',
									function() {
										this._vemap.Pan(-moveDelta, 0);
									},
									this
								);
								glow.events.addListener(
									htmlElm.left,
									'mousedown',
									function() {
										this._vemap.StartContinuousPan(-moveSpeed, 0);
									},
									this
								);
								glow.events.addListener(
									htmlElm.left,
									'mouseup',
									function() {
										this._vemap.EndContinuousPan();
									},
									this
								);
								break;
							case 'right':
								glow.events.addListener(
									htmlElm.right,
									'click',
									function() {
										this._vemap.Pan(moveDelta, 0);
									},
									this
								);
								glow.events.addListener(
									htmlElm.right,
									'mousedown',
									function() {
										this._vemap.StartContinuousPan(moveSpeed, 0);
									},
									this
								);
								glow.events.addListener(
									htmlElm.right,
									'mouseup',
									function() {
										this._vemap.EndContinuousPan();
									},
									this
								);
								break;
							case 'reset':
								glow.events.addListener(
									htmlElm.reset,
									'click',
									function() {
										this._vemap.SetCenterAndZoom(
										 this.retrieve('startPos'),
										 this.retrieve('zoomLvl')
										);
									},
									this
								);
								break;
							case 'zin':
								glow.events.addListener(
									htmlElm.zin,
									'click',
									function() {
										this._vemap.ZoomIn();
									},
									this
								);
								break;
							case 'zout':
								glow.events.addListener(
									htmlElm.zout,
									'click',
									function() {
										this._vemap.ZoomOut();
									},
									this
								);
								break;
							default:
								// not happening
								break;
						}
					}
					// make it draggable?
					// let's not
				} else {
					// future
				}
				
			}
		}
		} //endIf
		
		this.setCurrentState(this.getCurrentState()+1); // _aim
		return 0;
	}
	/**
	 * @name canvasify
	 * @description Advances the map's state into Canvasified Map
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @throws StateFlowNotFollowed
	 * @throws VEMapNotIncluded
	 * @throws DirectiveError
	 * @param {Dictionary} dirv A context directive
	 * @returns 0 if successful
	 */
	this.canvasify = function canvasify(dirv) {
		if (dirv == 'nameOf') {
			return 'canvasify';
		}
		
		var _dirv = dirv;
		var _aim = this.mapState.get(arguments.callee('nameOf'));
		
		if ((_aim - 1) != this.getCurrentState()) {
			throw "State flow not followed";
		}
		if (!VEMap) {
			throw "MSVE Map not included";
		}
		
		/*--
		Directive structure -
		
		{
			layer1 : {
				pointId: {
					x: xcoord, or lat: latcoord,
					y: ycoord, or lon: longcoord,
					type: PointTypeDirective,
					event: PointEventDirective,
					metadata: PointDescription
				}, [...]
			}, [...]
		}
		
		PointTypeDirective -
		
		{
			format: (pin|geom),
			customization: {
				customIcon: string,
			} |
			customization: {
				size: int |
				size: { l : int, h : int } |
				size : { r : int },
				color : {
					fill : VEColor,
					line : VEColor
				}
				linewidth: int
			}
		}
		
		PointEventDirective -
		
		{
			zoomRestriction: [minZoom, maxZoom],
			domEvent : {
				type : 'click' | 'mouseover' [...],
				event : evFx
			}
		}
		// PtEvntDirv should be filled out manually, so it'll be inserted instead.
		
		PointDescription -
		
		{
			title: string,
			description: string,
			name : string + nospace,
		}
		
		--*/
		
		for (layer in _dirv) {
			this.makeLayer(layer);
			this.instantiateLayer(layer);
			
			// define i_decision to see whether
			// we are dealing with pins or geom shapes
			// and call _c_process with correct type
			// for each point
			for (ptId in _dirv[layer]) {
				this._c_process(_dirv[layer][ptId], _dirv[layer][ptId].type.format, layer);
			}
			
			// now we should have all the shapes and pins inside their Compilations
			// let's draw'em'all
			for (drawId in _dirv[layer]) {
				if (_dirv[layer][drawId].type.format == 'pin') {
					this.getLayer(layer).AddShape(this.getPin(_dirv[layer][drawId].metadata.name));
					//console.log(this.getPin(_dirv[layer][drawId].metadata.name).GetID());
					// Stores the ID into the holder
					this.hold(
					 drawId,
					 {'internalId' : this.getPin(_dirv[layer][drawId].metadata.name).GetID()},
					 {extend: true}
					);
					// min/maxzoomlvl?
					if (typeof(_dirv[layer][drawId].type.customization.minZoomLevel) != 'undefined') {
						this.getPin(_dirv[layer][drawId].metadata.name).SetMinZoomLevel(_dirv[layer][drawId].type.customization.minZoomLevel);
					}
				} else if (_dirv[layer][drawId].type.format == 'geom') {
					// borders first, if any
					if (_dirv[layer][drawId].type.customization.border) {
						this.getLayer(layer).AddShape(this.getShape(_dirv[layer][drawId].metadata.name + '_border'));
					}
					// then the shape
					this.getLayer(layer).AddShape(this.getShape(_dirv[layer][drawId].metadata.name));
					
				}
			}
		}
		
		
		this.setCurrentState(this.getCurrentState()+1); // _aim
		return 0;
	}
	/**
	 * @name _c_process
	 * @inner
	 * @description Internal function to help with drawing on the map
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @throws DirectiveError
	 * @param {Dictionary} dirv A portion of the old directive having only the point
	 * @param {String} i_decision Either pin or geom, passed from ::type > format
	 * @param {String} i_layer A layer that exists inside {@link layerCompilation}
	 */
	this._c_process = function _c_process(dirv, i_decision, i_layer) {
		var _dirv = dirv;
		if (i_decision == 'pin') {
		// deal with pin
		// _dirv = old_dirv[pointId]
			/*--
			shp5 = new VEShape(VEShapeType.Pushpin, pt5setLL.c);
			_dirv.x && _dirv.y
			
			shp5.SetCustomIcon("<div class='vepin' id='airplane'></div>");
			shp5.SetTitle("03 Sep 07");
			shp5.SetDescription("Fossett leaves airship");
			
			--*/
			var llcoord, // are we dealing with x, y or lat, lon?
				tmp_center, // center of the pin
				tmp_pinshp, // pin shape
				tmp_construct; // construction of the pin (html)
			if (typeof(_dirv.x) == 'undefined' && typeof(_dirv.lat) != 'undefined') {
				llcoord = true;
			} else {
				llcoord = false;
			}
			if (llcoord) {
				tmp_center = new VELatLong(_dirv.lat, _dirv.lon);
			} else {
				tmp_center = this._vemap.PixelToLatLong(new VEPixel(_dirv.x, _dirv.y));
			}
			tmp_pinshp = new VEShape(VEShapeType.Pushpin, tmp_center);
			tmp_construct = "<div class='vepin' id='" + _dirv.metadata.name + "'></div>";
			tmp_pinshp.SetCustomIcon(tmp_construct);
			if(typeof(_dirv.metadata.title)!='undefined'){tmp_pinshp.SetTitle(_dirv.metadata.title);}
			if(typeof(_dirv.metadata.description)!='undefined'){tmp_pinshp.SetDescription(_dirv.metadata.description);}
			this.pushPin(_dirv.metadata.name, tmp_pinshp);
			
		} else if (i_decision == 'geom') {
		// deal with geometric shapes
		// _dirv = old_dirv[pointId]
			/*--
			
			type > customization > shape : {rectangular, circular}
			type > customization > size > l, h : int | type > customization > size > r : int
			xypos : {center, topleft}
			
			pt4setLL = mhelp.llPlotter(
			 mhelp.ptPlotter("rect", "pt4", {
				 size : { h : 132, l : 216 } 
				}
			 )
			);
			shp4 = mhelp.shapeDrawer(pt4setLL);
			mDraw(shp4, "transparent");
			
			--*/
			// step one, determine rectangular or circular
			if (_dirv.type.customization.shape == 'rectangular') {
				// step two, build the four corners + center struct
				// Center?
				if (_dirv.xypos == 'center') {
					
					var tmpPtSet = {
						nw : {
						 x : _dirv.x - _dirv.type.customization.size.l/2,
						 y : _dirv.y - _dirv.type.customization.size.h/2
						},
						ne : {
						 x : _dirv.x + _dirv.type.customization.size.l/2,
						 y : _dirv.y - _dirv.type.customization.size.h/2
						},
						sw : {
						 x : _dirv.x - _dirv.type.customization.size.l/2,
						 y : _dirv.y + _dirv.type.customization.size.h/2
						},
						se : {
						 x : _dirv.x + _dirv.type.customization.size.l/2,
						 y : _dirv.y + _dirv.type.customization.size.h/2
						},
						c: {
						 x: _dirv.x,
						 y: _dirv.y
						}
					};
				// Top Left?
				} else if (_dirv.xypos = 'topleft') {
					// not implemented yet
				} else {
					throw _dirv.metadata.name + "::xypos - Unexpected value (center? topleft?)";
				}
				// step three, map all corners to VEPixel
				var tmpLlSet = {
					nw : {},
					ne : {},
					sw : {},
					se : {},
					c: {}
				};
				for (corner in tmpPtSet) {
					tmpLlSet[corner] = this._vemap.PixelToLatLong(
					 new VEPixel(tmpPtSet[corner].x, tmpPtSet[corner].y)
					);
				}
				// step four, create the shape and store it
				// see shapeCompilation
				this.makeShape(
				 _dirv.metadata.name,
				 {type : VEShapeType.Polygon, points : [tmpLlSet.nw, tmpLlSet.ne, tmpLlSet.se, tmpLlSet.sw]}
				);
				if (_dirv.type.customization.border) {
					this.makeShape(
					 _dirv.metadata.name + '_border',
					 {type : VEShapeType.Polygon, points : [tmpLlSet.nw, tmpLlSet.ne, tmpLlSet.se, tmpLlSet.sw]}
					);
					this.draw.processShape(
					 this.getShape(_dirv.metadata.name + '_border'),
					  {
					  lw : _dirv.type.customization.linewidth + 2,
					  cl : _dirv.type.customization.color.border
					  }
					);
				} // end if border
				// step five, process the shape (coloring and customizing)
				this.draw.processShape(
				 this.getShape(_dirv.metadata.name),
				  {
				  lw : _dirv.type.customization.linewidth,
				  cl : _dirv.type.customization.color.line,
				  cf : _dirv.type.customization.color.fill
				  }
				);
				// step six, drawing, resides in the upper level
				// END RECTANGULAR
			} else if (_dirv.type.customization.shape == 'circular') {
				if (_dirv.xypos == 'topleft') {
					throw _dirv.metadata.name + "::xypos - Circular shapes cannot have topleft as xypos";
				}
				// step one, plot the center to VEPixel then to LatLong
				var tmpCenterLl = this._vemap.PixelToLatLong(
				 new VEPixel(_dirv.x, _dirv.y)
				);
				// step two, draw the circle
				var tmpPoints =	this.draw.circle(
				 tmpCenterLl.Latitude, tmpCenterLl.Longitude, _dirv.type.customization.size.r
				);
				this.makeShape(
				 _dirv.metadata.name,
				 {type : VEShapeType.Polyline, points : tmpPoints}
				);
				if (_dirv.type.customization.border) {
					this.makeShape(
					 _dirv.metadata.name + '_border',
					 {type : VEShapeType.Polygon, points : tmpPoints}
					);
					this.draw.processShape(
					 this.getShape(_dirv.metadata.name + '_border'),
					  {
					  lw : _dirv.type.customization.linewidth + 2,
					  cl : _dirv.type.customization.color.border
					  }
					);
				} // end if border
				this.draw.processShape(
				 this.getShape(_dirv.metadata.name),
				  {
				  lw : _dirv.type.customization.linewidth,
				  cl : _dirv.type.customization.color.line,
				  cf : _dirv.type.customization.color.fill
				  }
				);
			} else {
				throw _dirv.metadata.name + "::type > customization > shape - Unexpected value";
			}
		} else {
			throw _dirv.metadata.name + '::type > format - (_c_process call) Unexpected value';
		}
	}
	/**
	 * @name finalize
	 * @description Advances the map's state into Finalized Map
	 * @public
	 * @methodOf bbc.fmtj.csd.fwkmap.prototype
	 * @throws StateFlowNotFollowed
	 * @throws VEMapNotIncluded
	 * @throws DirectiveError
	 * @param {Dictionary} dirv A context directive
	 * @returns 0 if successful
	 */
	this.finalize = function finalize(dirv) {
		if (dirv == 'nameOf') {
			return 'finalize';
		}
		
		var _dirv = dirv;
		var _aim = this.mapState.get(arguments.callee('nameOf'));
		
		if ((_aim - 1) != this.getCurrentState()) {
			throw "State flow not followed";
		}
		if (!VEMap) {
			throw "MSVE Map not included";
		}
		
		/*--
		Directive structure -
		
		--*/
		
		// content
		// work with _dirv
		
		var linksArray = [];
		
		for (pin in _dirv.events) {
			var piid, // internal id
				ipiid, // tmp for split adding
				elm; // glow elm
			
			
			piid = this.retrieve(pin).internalId;
			
			ipiid = piid.split("_")[2];
			
			ipiid = piid + "_1000" + ipiid.slice(-1);
			
			elm = glow.dom.get("a#" + ipiid + " div#" + pin);
			
			if (_dirv.events[pin].eventfx.internal && _dirv.events[pin].eventfx.onclick == 'link') {
				linksArray.push({id: pin, iid: ipiid, link: _dirv.events[pin].content.morelink});
			}
		}
		
		if (_dirv.metadata.internal && _dirv.metadata.type == 'linkonly') {
			this._vemap.AttachEvent("onclick", linker);
		}
		
		function linker(o) {
			if (o.elementID) {
				
				for (var i = 0; i < linksArray.length; i++) {
					if (linksArray[i].iid == o.elementID) {
						//console.log(glow.dom.get("div.vepin#"+linksArray[i].id));
						//window.location = linksArray[i].link;
						//anchor down
						window.location.hash = linksArray[i].link.split('#')[1];
						//console.log(linksArray[i].link.split('#')[1]);
						//console.log(linksArray[i].link);
					}
				}
				
				//console.log(glow.dom.get("a#" + o.elementID).children().children());
			}
		}
		
		
		this.setCurrentState(this.getCurrentState()+1); // _aim
		return 0;
	}
};

	/*--
	Ref & Struct
	--*/
/**
 * @description Contains all map states and various functions to help with state processing
 * @public
 * @type Hash
 */
bbc.fmtj.csd.fwkmap.prototype.mapState = {
	/**
	 * @methodOf bbc.fmtj.csd.fwkmap
	 * @description Returns the corresponding state Integer when passed the right state String
	 * @public
	 * @function
	 * @param {String} state String describing the state
	 */
	get : function (state) {
		switch (state) {
			case 'rawMap':
			return 0;
			case 'contextualize':
			return 1;
			case 'initialize':
			return 2;
			case 'canvasify':
			return 3;
			case 'finalize':
			return 4;
			case 'work':
			return 5;
			default:
			return -1;
		}
	},
	rawMap : 0,
	contextualizedMap : 1,
	initializedMap : 2,
	canvasifiedMap : 3,
	finalizedMap : 4,
	working : 5
};

	/*--
	Public methods
	--*/
/**
 * @description Contains various drawing helper functions
 * @public
 * @type Hash
 */
bbc.fmtj.csd.fwkmap.prototype.draw = {
	/**
	 * @methodOf bbc.fmtj.csd.fwkmap
	 * @description Draws a circle shape
	 * @public
	 * @function
	 * @param {Latitude|Number} latin Latitude of circle's centre
	 * @param {Longitude|Number} lonin Longitude of circle's centre
	 * @param {Number} radius Radius of circle
	 */
	circle : function (latin, lonin, radius) {
		var R = 6371; // earth's mean radius in km (Uses Km as default)
	    var lat = (latin * Math.PI) / 180; //rad
	    var lon = (lonin * Math.PI) / 180; //rad
	    var d = parseFloat(radius)/R;  // d = angular distance covered on earth's surface
	    var locs = new Array();
	    for (x = 0; x <= 360; x++) { 
	        var p2 = new VELatLong(0,0);
	        brng = x * Math.PI / 180; //rad
	        p2.Latitude = Math.asin(Math.sin(lat)*Math.cos(d) + Math.cos(lat)*Math.sin(d)*Math.cos(brng));
	        p2.Longitude = ((lon + Math.atan2(Math.sin(brng)*Math.sin(d)*Math.cos(lat), Math.cos(d)-Math.sin(lat)*Math.sin(p2.Latitude))) * 180) / Math.PI;
	        p2.Latitude = (p2.Latitude * 180) / Math.PI;
	        locs.push(p2);
	    }
	    return locs;
	},
	/**
	 * @methodOf bbc.fmtj.csd.fwkmap
	 * @description Draws shapes
	 * @public
	 * @function
	 * @param {VEShape} o The shape in question
	 * @param {Hash} args An associative array containing the following keys
	 * @param {Number} args.lw Line width
	 * @param {VEColor|String} [args.cl] Line color, defaults to transparent if none. If you pass 'border', sets color to white
	 * @param {VEColor} [args.cf] Fill color, defaults to transparent if none.
	 */
	processShape : function (o, args) {
		// if (fill ..) new VEColor(255,255,255,0)
		o.HideIcon();
		o.SetLineWidth(args.lw);
		if (args.cl == null || typeof(args.cl) == 'undefined') {
			args.cl = new VEColor(255,255,255,1);
		} else if (args.cl == 'border') {
			args.cl = new VEColor(255,255,255,1);
		}
		o.SetLineColor(args.cl);
		if (args.cf == null || typeof(args.cf) == 'undefined') {
			args.cf = new VEColor(255,255,255,0);
		}
		o.SetFillColor(args.cf);
	}
};