/******************************************************
 * Simple way to test if device is touch enabled ?
 * 
 * @returns {Boolean}
 */
function isTouchDevice() {
    // FIXME: is this the best sollution ?
	return ('ontouchstart' in window);
	//try {
    //    document.createEvent("TouchEvent");
    //    return true;
    //} catch (e) {
    //    return false;
    //}
}



/**********************************************************************************
 *  TODO: update docs
 *  IMPRV: Add fancy animations/eye candy to the idControlsArea
 *  
 *  idControlsArea - container class for the map's 'category' filter section and search area
 *  
 *  HTML is loaded from /advertisements/adcategories/?output=inc.html which
 *  is dynamically generated on the server side. 
 *  
 *  OPTIONS:
 *  
 *  ``categories_selected`` - optional
 *  The ``pk`` list of categories that are active if this is undefined than defaults
 *  to all categories. Format is categories=[3,7,5,<pk> ...]
 *  ``fncCategoryOnClick`` - optional a function that gets attached as an event and fired whenever
 *  					   - a category is clicked
 *  
 *  METHODS:
 *  
 *  ``<class instance>.getCanvas()`` - Returns a pointer to the DOM canvas element  
 *  ``<class instance>.getCategorises()`` Returns an array of 'active/selected' categorises
 *  ``<class instance>.toggleCategory()`` toggles a category by adding/removing CSS .clsLiActive
 *  ``<class instance>.showSubCategories()`` shows subcategories if they exist
 *  ``<class instance>.hideSubCategories()`` hides subcategories
 *  ``<class instance>.initCategories()`` called when the class is first created
 *
 **********************************************************************************/
var ControlsArea = new Class({
	Implements: [Options, Events],
	Binds: ['getCategories'],
	/*** global variables **/
	CANVAS: undefined, // holds reference to the html canvas element
	ID_PREFIX: 'adcategory_', // prefix for the CSS ID selector i.e. id='category_<number>' as it must begin with a letter as per HTML
	CSS_CLASS_ACTIVE: 'clsLiActive',
	CSS_CLASS_INACTIVE: 'clsLiInactive', 
	
	/*** mootools class options ***/
	options: {
		categories_selected: undefined, // categories selected - if undefined than all categorises selected
		fncAllOnClick: undefined, // on click call back function when the all button is clicked
		fncCategoryOnClick: undefined, // onClick event function
		fncOnLocationClick: undefined,  // when a location is clicked in the idMapSettings FIXME: move this into it's own control
		fncSaveLocationOnClick: undefined //function that is called when on click link is clicked
	},
	
	/*** initialise function ***/
    initialize: function(options){
    	this.setOptions(options);
    	
    	/*** create element dom ***/
    	this.CANVAS = new Element('div', {id: 'idControlsArea', html: ''}); 
    	this.CANVAS.index = -1;
    	
    	var objHTMLRequest = new Request.HTML({
    		url: '/advertisements/adcategories/?output=inc.html',
    		onSuccess: function(responseTree, responseElements, responseHTML) {
    			this.CANVAS.set('html', responseHTML);
    			this.initAllButton();
    			this.initCategories();
    			this.initSettings();
    			this.initSearch();
    			}.bind(this)
    	}).get();
    	
    	
    	/*** end create element dom ***/
    },
    /*** end initialise ***/
	
    /********************************************************************************
    * returns the filters HTML dom element
    ********************************************************************************/
    getCanvas: function() {
       	return this.CANVAS
    },	
	
    /********************************************************************************
     * toggles the category click by adding ACTIVE_CSS_CLASS class to the element and applying 
     * ACTIVE_CSS_STYLE style
     * 
     * IF AND ONLY if all categories are active then all are de-selected and one clicked becomes
     * active 
     * 
     * Any subcategories are all toggled as well
     * 
     * i.e. <a href=... class='clsLiActive'>...</a>
     * 
     * id is just the number of category without the prefix
     ********************************************************************************/
    toggleCategory: function(id) {
    	// check if all categories selected and if so deselect all before proceeding
    	// with the toggle
    	var self = this; // store reference to this class
    	var clsLiInactive = self.CANVAS.getElements('.clsLiInactive')
    	//trace('clsLiInactive.length: ' + !clsLiInactive.length);
    	if (!clsLiInactive.length) {
    		//trace('here');
    		self.CANVAS.getElements('.clsCategoryLink').removeClass(self.CSS_CLASS_ACTIVE).addClass(self.CSS_CLASS_INACTIVE);
    		//clsLiInactive.addClass(this.CSS_CLASS_ACTIVE);
    	}
    	
    	
    	var category = this.CANVAS.getElementById(this.ID_PREFIX + id);
    	var subCategory = category.getParent('li').getElement('ul.clsSubCategory');
    	if (category.hasClass(this.CSS_CLASS_ACTIVE)) {
    		category.removeClass(this.CSS_CLASS_ACTIVE);
    		category.addClass(this.CSS_CLASS_INACTIVE);
    		if (subCategory) {
    			subCategory.getElements('a').removeClass(this.CSS_CLASS_ACTIVE);
    			subCategory.getElements('a').addClass(this.CSS_CLASS_INACTIVE);
    		}
    	}
    	else if (category.hasClass(this.CSS_CLASS_INACTIVE)) {
    		category.removeClass(this.CSS_CLASS_INACTIVE);
    		category.addClass(this.CSS_CLASS_ACTIVE);
    		if (subCategory) {
    			subCategory.getElements('a').removeClass(this.CSS_CLASS_INACTIVE);
    			subCategory.getElements('a').addClass(this.CSS_CLASS_ACTIVE);
    		}
    	}
    	
    },
    
    toggleAllButton: function() {
    	// init vars
    	var self = this;
    	var $clsLiInactive = self.CANVAS.getElements('.clsLiInactive')
    	var $idShowAll = self.CANVAS.getElementById('idShowAll');
    	
    	//hide all button as all categories active
    	if (!$clsLiInactive.length) {
    		$idShowAll.fade('out');
    	}
    	else {
    		$idShowAll.fade('in');
    	}
    },
    
    /*******************************************************************************
     * shows the settings form for map
     * 
     * FIXME: move this out of this class and make it it's own map control ?
     */
    showSettings: function() {
    	var idMapSettingsArea = this.CANVAS.getElementById('idMapSettingsArea');
    	var idMapSettingsButton = this.CANVAS.getElementById('idMapSettingsButton');
    	var topoffest = idMapSettingsButton.getElement('img').getSize().y + 10;
		idMapSettingsArea.setStyle('top', topoffest);
    	//idMapSettingsArea.show();
		//idMapSettingsArea.tween('height', 144)
		idMapSettingsArea.reveal();
    },
    
    /********************************************************************************
     * hides the settings for map
     * FIXME: move this out of this class and make it it's own map control ?
     */
    hideSettings: function() {
    	var idMapSettingsArea = this.CANVAS.getElementById('idMapSettingsArea');
    	//idMapSettingsArea.hide();
    	//idMapSettingsArea.tween('height', 0)
    	tempFunction = function () {
    		idMapSettingsArea.dissolve();
    	};
    	tempFunction.delay(1000);
    },
    
    /********************************************************************************
     * shows the subCategories if they exist 
     * 
     * takes the <li>...</li> element wrapper of the parent category
     * 
     * IMPRV: Add nice eye candy and perhaps written text out sub category titles 
     ********************************************************************************/
    showSubCategories: function(objHTML) {
    	/*** do some formating ***/
    	this.CANVAS.setStyle('z-index', '999'); //hack: google maps keeps want to reset it back where our control is below theirs
    	
    	//var category = this.CANVAS.getElementById(this.ID_PREFIX + id);
    	objSubCategory = objHTML.getElement('ul.clsSubCategory');
    	
    	
    	
    	// if subCategory found
    	if (objSubCategory) {
    		// style elements before we display - we always need to check if view size has changed
    		objSubCategory.getElements('img').setStyles({
    			'height': objHTML.getElement('img').getSize().y/1.5,
    			'width': objHTML.getElement('img').getSize().x/1.5
    		});

    		screen_width =  $(document).getSize().x; // screen width
    		el_width = objSubCategory.getDimensions().width; // el_width
    		xpos = objHTML.getElement('img').getPosition().x; // x starting postion
    		newxpos = Math.min(xpos, screen_width-el_width);  // adjusted for right viewing edge (shifts subcategories left if needed)
    		objSubCategory.setStyles({
    			'left': newxpos,
    			'top':  objHTML.getElement('img').getPosition().y + objHTML.getElement('img').getSize().y + 1
    		});
    		
    		objSubCategory.reveal({'duration': 'short'});
    	}
    },
    /********************************************************************************
     * hides the subCategories 
     * 
     * IMPRV: Add nice fadeout eye candy 
     ********************************************************************************/
    hideSubCategories: function(objHTML) {
    	objSubCategory = objHTML.getElement('ul.clsSubCategory');
    	if (objSubCategory) {
    		objSubCategory.dissolve({'duration': 'short'});
    	}
    },
    
    
    /********************************************************************************
     * returns an array containing PK of all categories - derived 
     * from the IDs of elements by removing the prefix
     * 
     * Output: [1,3,4,5,<pk>,<pk>...]
     ********************************************************************************/
    getCategories: function() {
    	var elLis = this.CANVAS.getElements('a'); //grab all categories
    	
    	var categories = []; // our result
    	elLis.each(function(item, index) {
    		categories.push(item.getProperty('id').replace(this.ID_PREFIX, '').toInt());
    	}.bind(this));
    	return categories;
    },

    /********************************************************************************
     * returns an array containing PK of selected categories 
     * Output: [1,3,4,5,<pk>,<pk>...]
     ********************************************************************************/
    getSelectedCategories: function() {
    	var elLis = this.CANVAS.getElements('a.clsCategoryLink'); //grab all categories
    	var categories = []; // our result
    	elLis.each(function(item, index) {
    		if (item.hasClass('clsLiActive')) {
    			categories.push(item.getProperty('id').replace(this.ID_PREFIX, '').toInt());
    		}
    	}.bind(this));
    	return categories;
    },
    
    /********************************************************************************
     * inits categories
     ********************************************************************************/
    initCategories: function() {
    	// initialize vars
    	var self = this;
		var objsSubCategory = this.CANVAS.getElements('.clsSubCategory')
    	var $clsCategoryLink = this.CANVAS.getElements('a.clsCategoryLink'); //grab all categories
    	var $elsLi = this.CANVAS.getElements('li'); //grab all categories
		
    	
    	// set initial style 
    	this.CANVAS.setStyle('z-index', '999'); //hack: make appear over google controls - more info in function showSubCategories
    	objsSubCategory.dissolve({'duration': 'short'}) // hide the sub categories on init
    	
    	/* add toggle event to all categories and sub categories*/
		$clsCategoryLink.addEvent('click', function(event) {
			// toggle the category first
    		var id = event.target.getParent().getProperty('id').replace(self.ID_PREFIX, '');
    		self.toggleCategory(id);

    		// toggle ALL button if needed 
    		self.toggleAllButton()
    		
    		// call the chained function
			self.options.fncCategoryOnClick(event);
		});
		
		/* add mousein event to show subcategories - works better with touch device than onmouseover */
		//var elLis = this.CANVAS.getElements('li'); //grab all categories
    	$elsLi.addEvent('mouseenter', function(event) {
			self.showSubCategories(this);
		});
		/* add mouseout to the subcategories - works better with touch device than onmouseleave */
    	$elsLi.addEvent('mouseleave', function(event) {
			self.hideSubCategories(this); // this will reffer to the li element
		});
		
    },
    /******************************************************************************
     * init ALL button
     ******************************************************************************/
    initAllButton: function() {
    	// init vars
    	var self = this;
    	var $idShowAll = self.CANVAS.getElementById('idShowAll');
    	$idShowAll.setStyle('opacity', 0); // hide ALL button if not already hidden
    	$idShowAll.addEvent('click', function(event) {
    		self.CANVAS.getElements('.clsCategoryLink').removeClass(self.CSS_CLASS_INACTIVE).addClass(self.CSS_CLASS_ACTIVE); //makes all categories active
    		self.toggleAllButton() // this will hide the button since all cetegories active
    		self.options.fncAllOnClick(event); // execute the passed on function
		});
    	
    },
    
    /******************************************************************************
     * initilizes the search area by adding events and such
     * 
     * Options:
     * ``fncOnSearch`` - required - a call back that is called when user starts a search
     * ``fncOnSearchClear - required - executed when user clears search 
     *****************************************************************************/
    initSearch: function(fncOnSearch, fncOnSearchClear) {
    	var idSearchArea = this.CANVAS.getElement('#idSearchArea');
    	var idCancel = idSearchArea.getElement('#idCancel');
    	var idSubmit = idSearchArea.getElement('#idSubmit');
    	// init the display first
    	idCancel.dissolve({'display': 'inline-block', 'mode': 'horizontal'}); //setting options once stores them
    	idSearchArea.setStyle('opacity', '0.65');
    	
    	// add events
    	//idSearchArea.addEvent('click', function(event) {idSearchArea.setStyle('opacity', 1);});
    	
    	idCancel.addEvent('click', function(event) {
    		idSearchArea.setStyle('opacity', '0.65');
    		idCancel.dissolve();
    		event.preventDefault();
    	}.bind(this));
    	
    	
    	// When user clicks search than add cancel button and make highlight the area
    	// this shows that search is currently active
    	
    	idSubmit.addEvent('click', function(event) {
    		idSearchArea.setStyle('opacity', '1');
    		idCancel.reveal(); // show cancell button
    		event.preventDefault();
    	}.bind(this));
    	
    	// do the flyer/locations search
    	//idSubmit.addEvent('click', function(event) {
    	//	
    	//}.bind(this));
    },
    
    /***********************************************************************************
     * initSettings - prototype - adds the location searching and
     * selection to map
     * FIXME: this prob should live in it's own module?
     **********************************************************************************/
    initSettings: function() {
    	var idMapSettingsButton = this.CANVAS.getElementById('idMapSettingsButton');
    	var idMapSettingsArea = this.CANVAS.getElementById('idMapSettingsArea');
    	var arrRadioButtons = idMapSettingsArea.getElements('input[name=location_choices]');
    	var idMapSaveLocation = this.CANVAS.getElementById('idMapSaveLocation');
    	
    	// change FX.Reveal to use inline-block so that when it's visiable 
    	// the width is limited to what's seen and map can be interacted with
    	//idMapSettingsArea.set('reveal', {'display': 'inline-block'}); 
    	
    	// add onclick to save location link
    	idMapSaveLocation.addEvent('click', fncSaveLocationOnClick);
    	
    	// add onclick and geocoding to the radio buttons
    	arrRadioButtons.addEvent('click', fncOnLocationClick);
    	
    	// add show settings to the button
    	idMapSettingsButton.addEvent('mouseover', function(event) {
    		
    		this.showSettings();
    	}.bind(this));
    	
    	// add show event when over settings
    	//idMapSettingsArea.addEvent('mouseover', function(event) {
    	//	this.showSettings();
    	//}.bind(this));
    	// hide settings when mouse out
    	idMapSettingsArea.addEvent('click', function(event) {
    		this.hideSettings();
    	}.bind(this));
    }
});


/**********************************************************************************
 *  IMPRV: re-code this so only one DB hit rather than a hit each for output inc.html and json  
 *  FIXME: rename CONSTANTS that are not constants: i.e. ADS to arrAds etc.
 *  
 *  ListArea - container class for the maps idListArea
 *  
 *  HTML is loaded from /advertisements/ads/?output=inc.html which
 *  is dynamically generated on the server side. 
 *  
 *  OPTIONS:
 *  
 *  ``categories_selected`` - optional
 *  The ``pk`` list of categories that are active if this is undefined than defaults
 *  to all categories. Format is categories=[3,7,5,<pk> ...]
 *  ``latlngbounds`` - required
 *  The lat lng bounding box for which to load up the entries. Format <south-lat>,<west-lng>,<north-lat>,<north-lng>
 *  ``onclickfunction - required - the onclickfunction that is added to each 'clsPromotion' during init or update
 *  ``onmouseoverfunction - required - the onclickfunction that is added to each 'clsPromotion'
 *  ``onmouseoutfunction - required - the onclickfunction that is added to each 'clsPromotion'
 *  
 *  METHODS:
 *  
 *  ``<class instance>.getCanvas()`` - Returns a pointer to the DOM canvas element  
 *  ``<class instance>.getAds()`` 	- Returns a HASHED array containing references to individual ad objects
 *  								- output format {<pk>: <ad object>, <pk>: <ad object>, ...}
 *  ``<class instance>.focusAds([<pk>, <pk>, ...])`` - focuses flyer matching a list of pks 
 *  												 - if no pks are passed than focus is cleared (all ads shown) 
**********************************************************************************/
var ListAreaDef = {
	Implements: [Options, Events],
	/*** global variables **/
	CANVAS: undefined, // holds reference to the html canvas element
	ID_PREFIX: 'ad_', // prefix for the CSS ID selector i.e. id='category_<number>' as it must begin with a letter as per HTML specs
	ADS: {}, // holds the ads in HASHED format
	TEMPLATE_PROMOTION: "<div style='display: {display}' id='ad_{pk}' class='clsPromotionWrapper'>"
		+	"<div class='clsPromotion'>"
		+		"<a href='{extras.get_absolute_url}'>"
		+			"<img alt='{fields.description}' src='{extras.get_firstmedia_url}'>"
		+		"</a>"
		+	"</div>"
		+	"<iframe class='clsLikeItButton'"
		+	"src='http://www.facebook.com/plugins/like.php?href=http://www.flyermail.ca{extras.get_absolute_url}&amp;layout=standard&amp;show_faces=true&amp;width=100%&amp;action=like&amp;font=tahoma&amp;colorscheme=light&amp;height=25'"
		+	"scrolling='no' frameborder='0' style='border:none; overflow:hidden;' allowTransparency='false'>"
		+	"</iframe>"
		+"</div>",
	TEMPLATE_CANVAS:  "<br><div id='idListArea' class='clsClickable'>"
		+ "</div><br><br>",
//	    + "<div id='idListAreaControls'>"
//		+ "<ul class='clsHorizontalNav'>" 
//		+ "<li><a id='idActionUp' target='_blank' href='#'><img class='clsClickable' alt='Scroll Up' src='/static/template/icons/up_48.png'></a></li>"
//		+ "<li><a id='idActionDown' href='#'><img class='clsClickable' alt='Scroll Down' src='/static/template/icons/down_48.png'></a></li>"
//		+ "</ul>" 
//		+ "</div>",
	JSONREQUEST: undefined, // will hold our request object that we can reffer to and canell if it's already running
	/*** mootools class options ***/
	options: {
		categories_selected: undefined, // categories selected - if undefined than all categorises selected
		latlngbounds: undefined, 
		onclickfunction: undefined, 
		onmouseoverfunction: undefined,
		onmouseoutfunction: undefined
	},
	
	/*** initialise function ***/
    initialize: function(options){
    	var self = this; 
    	self.setOptions(options);
    	self.CANVAS = new Element('div', {
    				id: 'idListAreaWrapper', 
    				html: self.TEMPLATE_CANVAS
    		}); 
    	self.CANVAS.index = -1;
    	
    	if (isTouchDevice()) {
	    	var touch = new Touch(self.CANVAS);
	    	var scrollOffset = 0;
	    	touch.addEvent('start', function(dx, dy) {
	    		scrollOffset = self.CANVAS.scrollTop;
	    	});
	    	touch.addEvent('move', function(dx, dy) {
	    		self.CANVAS.scrollTop = scrollOffset + (-1 * dy);
	    	});
    	}
    	
    	//self.CANVAS.addEvent('click', function() {self.focusAdsClearAll()});
    	//self.drawAds();
    	
    },
    /*** end initialise ***/
    /********************************************************************************
     * draws the ads list based - takes the latlngbounds arguement and optional fnc_selectedcategorires
     * 
     * This fnc_selectedcategories is a function that returns PKS of selected categorires to filter by.
     * 
     * We pass this as a functio so the actuall categories selected are used when Request completes 
	 * other wise during request the vistor might click and change the selected categories
     * 
     * if arr_selectedcategorires specified this will be passed to the view which will hide the ads based on categories
     * FIXME: make only P.JSON and use templating
     * IMPRV: make it so it queries excluding any ads that are alerady hashed - we can do this because we can know the ads ids
     * IMPRV: we would than need to use googles in bounds checker for the locations of the ads to display them or not on left side
     * IMPRV: doing this would minimize trafic as the session progresses!
     ********************************************************************************/
    drawAds: function(latlngbounds, fnc_selectedcategories) {
    	/*** populate the ADS hashed array ***/
    	arr_selectedcategories = (fnc_selectedcategories) ? fnc_selectedcategories() : [];
    	var self = this;
    	if (self.JSONREQUEST) {
    		self.JSONREQUEST.cancel(); // cancell any request if it is defined
    	}
    	
    	
    	self.JSONREQUEST = new Request.JSON({
    		url: '/advertisements/ads/', //FIXME: make this so it returns based on categories and latlngbounds
    		onSuccess: function(responseJSON, responseText) {
    			var arr_selectedcategories = (fnc_selectedcategories) ? fnc_selectedcategories() : []; // IMPRV: make this so it rechecks categorires on each loop ???
    			var arrActiveAds = [];
    			var canvasContent = '';
    			responseJSON.each(function(item, index) { 
    				//trace("item['fields']['categories']: " + item['fields']['locations']);
    				//trace('arr_selectedcategories:' + arr_selectedcategories);
    				//trace("item['fields']['categories'].intersect(arr_selectedcategories): " + item['fields']['categories'].intersect(arr_selectedcategories));
    				item['display'] = (item['fields']['categories'].intersect(arr_selectedcategories).length > 0) ? 'inline-block' : 'none'; 
    				if (item['fields']['categories'].intersect(arr_selectedcategories).length > 0) {
    					arrActiveAds.push(item); //FIXME: this is a hack how to fix it?
    				}
    				item['content'] = self.TEMPLATE_PROMOTION.substitute(item);
    				self.ADS[item.pk] = item;
    				canvasContent += item['content'];
    			})
    			canvasContent += '';
    			
    			self.CANVAS.getElementById('idListArea').set('html', canvasContent );
    			self.CANVAS.getElements('.clsPromotion a').addEvent('click', self.options.onclickfunction);
    			
    			self.CANVAS.getElements('.clsPromotion').addEvent('mouseenter', self.options.onmouseoverfunction);
    			self.CANVAS.getElements('.clsPromotion').addEvent('mouseleave', self.options.onmouseoutfunction);
		
    			
    			// touch gestures
    			if (isTouchDevice()) {
	    			self.CANVAS.getElements('.clsPromotion').each(function(item) {
	    				var touch = new Touch(item);
	        	    	var scrollOffset = 0;
	        	    	touch.addEvent('start', function(dx, dy) {
	        	    		scrollOffset = self.CANVAS.scrollTop;
	        	    	});
	        	    	touch.addEvent('move', function(dx, dy, event) {
	        	    		self.CANVAS.scrollTop = scrollOffset + (-1 * dy);
	        	    	});
	        	    	touch.addEvent('cancel', function(event) {
	        	    		self.options.onclickfunction(event); // open the details area for this promo
	        	    	});
	    			})
	    		}
    			
    			glb_idMapCanvas.showLocationsByAds(arrActiveAds); //FIXME: this is a hack how to fix it?
    		}
    	})
    	self.JSONREQUEST.get({'output': 'serializer_json', 'latlngbounds': latlngbounds});
    	
    	
    	
    	
//    	/*** load the canvas and click function ***/
//    	var objHTMLRequest = new Request.HTML({
//    		url: '/advertisements/ads/', 
//    		//update: this.CANVAS,
//    		async: false,
//    		onSuccess: function(responseTree, responseElements, responseHTML, responseJavaScript) {
//    			this.CANVAS.set('html', responseHTML);
//    			this.CANVAS.getElements('.clsPromotion').addEvent('click', this.options.onclickfunction);
//    			this.CANVAS.getElements('.clsPromotion').addEvent('mouseover', this.options.onmouseoverfunction);
//    			this.CANVAS.getElements('.clsPromotion').addEvent('mouseout', this.options.onmouseoutfunction);
//    			
//    			//fnc_onsuccess()
//    			if (glb_idMapCanvas) {
//    			//trace('getSelectedCategories: ' + glb_idMapCanvas.CONTROLSAREA.getSelectedCategories());
//    			//trace('CONTROLSAREA.getSelectedCategories(): ' + glb_idMapCanvas);
//    			var arrActiveAds = this.focusAdsByCategories(glb_idMapCanvas.CONTROLSAREA.getSelectedCategories()); // FIXME: this is a hack so that if selected categories changed while the request was being executed
//    			//trace('arrActiveAds: ' + arrActiveAds);
//    			
//    			//glb_idMapCanvas.showLocationsByAds(arrActiveAds) // FIXME: this is a hack
//    			}
//    		}.bind(this)
//    	
//    	})
//    	
//    	if (arr_selectedcategorires) {
//    		objHTMLRequest.get({'output': 'inc.html', 'latlngbounds': latlngbounds, 'selected_categories':arr_selectedcategorires.toString(',')});
//    	}
//    	else {
//    		objHTMLRequest.get({'output': 'inc.html', 'latlngbounds': latlngbounds});
//    	}
    },
    
    /********************************************************************************
     * returns a HASHED array of ads 
     * 
     * Output format: {<pk>: <ad object>, <pk>: <ad object>, ...}
     ********************************************************************************/
    getAds: function() {
    	return this.ADS;
    },
    
    
    /********************************************************************************
     * Returns an array containing pks of active ads
     * 
     * IMPRV: make it so it utilizes the ADS hash rather than dom element picking
     * 
     */
    getAdsFocused: function () {
    	var self = this;
    	var elements = self.CANVAS.getElements('.clsPromotionWrapper[style*="inline-block"]');
    	var arr_result = [];
    	elements.each(function (item, key) {
    		//trace(item.getProperty('id').replace('ad_', '').toInt());
    		arr_result.include(item.getProperty('id').replace('ad_', '').toInt());
    	});
    	return arr_result;
    },
    
    /********************************************************************************
     * returns the canvas HTML dom element
     ********************************************************************************/
    getCanvas: function() {
    	return this.CANVAS
    },	
    
    /********************************************************************************
     * clear all ad focuses - i.e. shows all
     ********************************************************************************/
    focusAdsClearAll: function() {
    	this.CANVAS.getElements('.clsPromotionWrapper').reveal();
    },
    
    /********************************************************************************
     * focuses ads by Categories - takes arrCategories: [1,2,4, <pk>, <pk>, ...]
     * 
     * Returns: - an array of Ad objects
     ********************************************************************************/
    focusAdsByCategories: function(arrCategory) {
    	var self = this;
    	var arrAdPKs = [];
    	var arrAds = [];
    	
    	arrCategory.each(function(pkCategory, index) {
    		Object.each(self.ADS, function(ad, index) {
    			if (ad.fields.categories.contains(pkCategory)) {
    				if (!arrAdPKs.contains(ad.pk)) {
    					arrAdPKs.push(ad.pk);
    					arrAds.push(ad);
		    		}
	    		}
	    	});
    	});
    	//trace('arrAdPKs: ' + arrAdPKs);
    	self.focusAdsByPk(arrAdPKs);
    	return arrAds;
    },
    
    /********************************************************************************
     * focuses ads by Locations
     ********************************************************************************/
    focusAdsByLocation: function(objLocation) {
    	var self = this;
    	var arrAdPKs = [];
    	Object.each(self.ADS, function(item, index) {
    		if (item.fields.locations.contains(objLocation.pk)) {
    			arrAdPKs.push(item.pk);
    		}
    	});
    	self.focusAdsByPk(arrAdPKs);
    },
    
    /********************************************************************************
     * focuses ads that match the pks
     * 
     * ``arrPks`` - required - [1, 3, <pk>, <pk> ... ]
     ********************************************************************************/
    focusAdsByPk: function(arrPks) {
    	var self = this;
    	var fxOptions = {'duration': 'short', 'display': 'inline-block'};
    	Object.each(self.ADS ,function(item, key) {
    		var objAd = self.CANVAS.getElementById(self.ID_PREFIX + key); // grab the ad DOM 
    		// if the objAd exists in the DOM than perform the action otherwise skip as it's hashed
    		if (objAd) {	
				if (arrPks.contains(key) || arrPks.contains(key.toInt())) {
	    			
					objAd.reveal(fxOptions);
	    		}
	    		else {
	    			
	    			objAd.dissolve(fxOptions);
	    		}
	    	}
    	});
    }	
},
ListArea = new Class(ListAreaDef);

/**************************************************************************************************************
 *  IMPRV: have map center default to 'geocoded' location based on ip or gps provided by browser
 *  
 *  Map Class - google map container class
 *  
 *  Options:
 *  
 *  ``idMapCanvas`` (str) optional - The element ID string to draw the map onto - defaults to 'idMapCanvas'
 *  ``mapCenter`` (array) optional - the [lat, lng] for center of map - defaults to Kingston city
 *  
 *  Methods:
 *  
 *  ``<class instance>.updateMarkers()`` loads more markers based on current map bounding region
 *  
 *************************************************************************************************************/

var MapCanvas = new Class({
	Implements: [Options, Events],
	/*** global variables **/
	CONTROLSAREA: undefined, // reference to the filter control area
	LISTAREA: undefined, //reference to the list area
	MAP: undefined, // holds reference to the map
	MAPDEFAULTS: {
		zoom: 14,
		center: undefined, 
		panControl: true,
		zoomControl: true,
		zoomControlOptions: {
			position: google.maps.ControlPosition.TOP_LEFT
		},
		scaleControl: false,
		streetViewControl: true,
		streetViewControlOptions: {
			position: google.maps.ControlPosition.TOP_LEFT
		},
		mapTypeControl: true,
		mapTypeControlOptions: {
	      position: google.maps.ControlPosition.RIGHT_TOP
		},
		mapTypeId: google.maps.MapTypeId.ROADMAP,
		minZoom: 12
		
	},
	MARKER_CSS_STYLE_UNFOCUSED: {
			'position': 'relative',
			'height': '50px',
			'width': '50px',
			'top': '0px',
			'left': '0px',
			'opacity': '1'
		},
	MARKER_CSS_STYLE_UNFOCUSED_TRANSPARENT: {
			'position': 'relative',
			'height': '50px',
			'width': '50px',
			'top': '0px',
			'left': '0px',
			'opacity': '0.35'
			
		},
	MARKER_CSS_STYLE_FOCUSED: {
			'height': '120px',
			'width': '120px',
			'font-size': '1.25em',
			'top': '-72px', // FIXME: why is this -2px extra ?
			'left': '-35px',
			'opacity': '1'
		},
		
	arrMapLocations: [], // holds reference to map (locations) in a HASH array based on location.id
	arrAdvertisers: [], // caches advertiser JSON objects
	
	/*** mootools class default options ***/
	options: {
		
		mapCenter: [44.243554,-76.540296],
		idMapCanvas: 'idMapCanvas' //required
	},
	
	/*** initialise function ***/
    initialize: function(options){
    	var self = this;
    	
    	/*** initialize the map ***/
    	this.setOptions(options);
    	
    	/*** if cookie stored that use that for map center otherwise use what server passed ***/
    	var savedlatlng = Cookie.read('savedlatlng');
    	if (savedlatlng && savedlatlng.split(',') && savedlatlng.split(',').length == 2) {
    		this.options.mapCenter = savedlatlng.split(',');
    	}
    	this.MAPDEFAULTS.center = new google.maps.LatLng(this.options.mapCenter[0], this.options.mapCenter[1]);
    	this.MAP = new google.maps.Map($(this.options.idMapCanvas), this.MAPDEFAULTS);
    	
    	/*** load up markers and add list once map loaded ***/
    	
    	google.maps.event.addListener(this.MAP, 'idle', function() {
    		// grab the map bounds but take into consideration the ListArea covering some of the map
    		var mapBounds = this.MAP.getBounds();
    		var southwest = mapBounds.getSouthWest();
    		var northeast = mapBounds.getNorthEast();
    		var lngspan = mapBounds.toSpan().lng();
    		var mapwidth = this.MAP.getDiv().getSize().x;
    		var listwidth = this.LISTAREA.CANVAS.getSize().x;
    		var ratiowidth = listwidth/mapwidth;
    		var lngspanadjustment = lngspan*ratiowidth;
    		
    		var adjustedsouthwest = new google.maps.LatLng(southwest.lat(), southwest.lng()+lngspanadjustment);
    		var adjustedMapBounds = new google.maps.LatLngBounds(adjustedsouthwest, northeast);
    		//trace(adjustedMapBounds.toUrlValue());
    		 
    		this.LISTAREA.drawAds(adjustedMapBounds.toUrlValue(), this.CONTROLSAREA.getSelectedCategories.bind(this.CONTROLSAREA));
    		//google.maps.event.removeListener(tempEvent);
    		//trace('tilesloaded');
    		
    		// defocus any map markers
    		this.focusLocationsClearAll();
        }.bind(this));
    	
    	/*** load up markers as map is being dragged as that looks good ***/
    	google.maps.event.addListener(this.MAP, 'bounds_changed', function() {
    		this.addMarkers();
        }.bind(this));
    	
    	
    	
    	
    	/*** start CONTROLSAREA (idFiltersArea) ***/
    	fncCategoryOnClick = function (event) {
    		// toggle the category first
    		//var id = event.target.getParent().getProperty('id').replace(this.CONTROLSAREA.ID_PREFIX, '');
    		//this.CONTROLSAREA.toggleCategory(id);

    		// grab active categories and filter LIST area as needed
    		// IMPRV: perhaps add a global arr hasing locations and ads agains categories for performance
    		//trace('focusedAds: ' + this.CONTROLSAREA.getSelectedCategories());
    		var arrActiveAds = this.LISTAREA.focusAdsByCategories(this.CONTROLSAREA.getSelectedCategories());
    		//trace(JSON.stringify(arrActiveAds));
    		this.showLocationsByAds(arrActiveAds);
    		// we need to extract the locations now from the focused adds
    		//event.stopPropagation();
    		//trace('here');
    	}.bind(this); // this refers to this class
    	fncAllOnClick = function (event) {
    		// a function fired one ALL is selected in control area
    		var arrActiveAds = self.LISTAREA.focusAdsByCategories(self.CONTROLSAREA.getSelectedCategories());
    		self.showLocationsByAds(arrActiveAds);
    	}; // this refers to this class
    	
    	fncOnLocationClick = function (event) {
    		var geocoder = new google.maps.Geocoder();
			var queryString = event.target.get('value');
			geocoder.geocode( { 'address': queryString}, function(results, status) {
				if (status == google.maps.GeocoderStatus.OK) {
					// parse the result and pass it to a google marker 
					// this way we honor the google API if the JSON format changes
					this.MAP.panTo(results[0].geometry.location);
					this.MAP.setZoom(12);
				} 
				else {
			        alert("Geocode was not successful for the following reason: " + status);
			    }
			}.bind(this));
    	}.bind(this);
    	
    	fncSaveLocationOnClick = function (event) {
    		var mapCenter = this.MAP.getCenter();
    		var cookie = Cookie.write('savedlatlng', mapCenter.toUrlValue()); //stores it as "lat,lng" string
    	}.bind(this);
    	
    	var objControlsArea = new ControlsArea({'fncAllOnClick': fncAllOnClick,
    											'fncCategoryOnClick': fncCategoryOnClick,
    											'fncOnLocationClick': fncOnLocationClick,
    											'fncSaveLocationOnClick': fncSaveLocationOnClick});
    	this.CONTROLSAREA = objControlsArea;
    	this.MAP.controls[google.maps.ControlPosition.TOP_RIGHT].push(this.CONTROLSAREA.getCanvas());
    	// searching functions
    	
    	/*** end idControls Area ***/
    	
    	
    	/*** start idListArea ***/
    	// we need to pass the events as options so it gets assigned property during init of idListArea
    	// trying to do: this.LISTAREA.getCanvas().getElements()...addEvent wont work as it is empty
    	// untill request.HTML completes the dynamic HTML loading
    	//
    	// the below anonymous function grabs the locations associated with the ad
    	// and focuses them on the map
    	var onmouseoverfunction = function (event) {
    		var intAdId = event.target.getParent('.clsPromotionWrapper').get('id').replace('ad_', '').toInt();
    		var arrLocationsPks = this.LISTAREA.ADS[intAdId].fields.locations;
    		this.focusLocationsClearAll();
    		this.focusLocationsByPk(arrLocationsPks);
    		event.preventDefault();
    		event.stopPropagation();
    	}.bind(this);
    	var onmouseoutfunction = function (event) {
    		// IMPRV: when hovering out over a filtered area do not clear focus from locations
    		this.focusLocationsClearAll();
    		event.preventDefault();
    		event.stopPropagation();
    		
    	}.bind(this);
    	// We must pass the events functions here as we don't know when the LISTAREA will finish rendering - this is handled in initlize 
    	var onclickfunction = function (event) {
    		var intAdId = event.target.getParent('.clsPromotionWrapper').get('id').replace('ad_', '').toInt();
    		var adPk = self.LISTAREA.ADS[intAdId].pk;
    		var adName = self.LISTAREA.ADS[intAdId].fields.name;
    		var adUrl = '/advertisements/ads/' + adPk + '-' + adName + '/?output=inc.html'; //IMPRV: somehow just grab the url from get_absolute_url?
    		//trace(idDetailsArea);
    		// show idDetailsArea which has already been created on dom ready along with the map
    		// toggle visiblity if not visible (SHOW IT), or same list element clicked (HIDE IT)
    		if (glb_idDetailsArea.bolVisible == false || glb_idDetailsArea.urlPromotionsSource == adUrl) {
    			glb_idDetailsArea.toggleVisibility();
    		}
    		glb_idDetailsArea.addPromotions(adUrl);
    		//idDetailsArea.addPromotions('http://dev.klinsight.com:204/static/ex_idPromotions.html');
    		event.preventDefault();
    		event.stopPropagation(); // so we don't triger the 'resets' the focus state - see DetailArea initilize function
    		//return false;
    	};
    	
    	// create the list area object
    	var objListArea = new ListArea({
    		'onmouseoverfunction': onmouseoverfunction,
    		'onmouseoutfunction': onmouseoutfunction,
    		'onclickfunction': onclickfunction
    		});
        	
    	this.LISTAREA = objListArea;
    	this.MAP.controls[google.maps.ControlPosition.TOP_LEFT].push(this.LISTAREA.getCanvas());
    	// add events to the list area
    	
    	/*** start idMapPromoBanner ***/
    	//var nodePromo = new Promo();
    	//this.MAP.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(nodePromo.getContent());
    },
    /*** end initialise ***/


    /************************************************************************
	 * Helper function that queries the server to grab the markers icon
	 * and assings it to the passed location's marker
	 * 
	 * IMPRV: do not download the full logo size - add resizing view on server
	 ************************************************************************/
	addMarkerRichContent: function(location) {
		var advertiserPK = location.fields.advertiser;
		var loccopy = location;
		// we have the advertiser cached
		if (this.arrAdvertisers[advertiserPK]) {
			var iconurl = '/media/' + this.arrAdvertisers[advertiserPK].fields.logo;
			//trace('advertiserPK ' + advertiserPK);
			//trace(iconurl);
			//trace('this.arrAdvertisers ' + JSON.stringify(this.arrAdvertisers));
			var html = "<img class='clsClickable clsRichMarkerBG' alt='BG' src='/static/template/imgs/marker_pin_bg-175r.png'>"
					 + "<div class='clsRichMarkerCanvas'>" 
					 + "<img class='clsMapPinLogo' alt='" + this.arrAdvertisers[advertiserPK].fields.name  + "' src='" + iconurl + "'>"
					 + "</div>";
			var htmlNode = new Element('div', {
				id: "location_" + loccopy.pk,
				'class': 'clsRichMarkerWrapper',
				html: html
			})	
				
			location.marker.setContent(htmlNode);
			location.marker.getContent().setStyles(this.MARKER_CSS_STYLE_UNFOCUSED);
			
		}
		// it is not hashed so we need to fetch it
		else {
			//trace('url: ' + '/advertisements/advertiser/' + advertiserPK + '-gui-js-request/');
			var objJSONRequest = new Request.JSON({
	    		url: '/advertisements/advertiser/' + advertiserPK + '-gui-js-request/', 
	    		onSuccess: function(responseJSON, responseText) {
	    			this.arrAdvertisers[advertiserPK] = responseJSON[0];
	    			//trace('advertiserPK ' + advertiserPK);
	    			//trace('responseJSON: ' + JSON.stringify(responseJSON));
	    			
	    			//trace('this.arrAdvertiser[advertiserPK] ' + JSON.stringify(this.arrAdvertisers[advertiserPK]));
	    			//trace('this.arrAdvertisers ' + JSON.stringify(this.arrAdvertisers));
	    			this.addMarkerRichContent(loccopy); // we can now just call ourself as advertiser will be hashed
	    		}.bind(this),
	    		onError: function(text, error) {
	    			trace(text);
	    			trace(error);
	    		}
	    	}).get({'output': 'serializer_json'});
		}
		
    },
    
    
    /************************************************************************
	 * When this function is called new markers are loaded as
	 * needed based on the current latlng bounding box of map
	 * 
	 * IMPRV: add GET variable that excludes locations (IDs) already chached
	 ************************************************************************/
	addMarkers: function() {
		/*** load markers ***/
		var objJSONRequest = new Request.JSON({
    		url: '/advertisements/advertiserlocation/', 
    		onSuccess: function(responseJSON, responseText) {
    			responseJSON.each(function(item, index) { 
    				// add the marker if not already cached in our hashed array
    				if (this.arrMapLocations[item.pk] == undefined) { 
    					var objNewLocation = item;
    					//trace(JSON.stringify(objNewLocation));
    					var marker = new RichMarker({
    						position: new google.maps.LatLng(item.fields.geopoint.coordinates[1],item.fields.geopoint.coordinates[0]),
    						map: this.MAP,
    						flat: true,
    						zIndex: item.fields.order,
    						anchor: RichMarkerPosition.BOTTOM,
    						content: '' // we load this dynamicly later
    					})
    					objNewLocation.focused = false;
    					objNewLocation.marker = marker;
    					this.arrMapLocations[item.pk] = objNewLocation; //cache it
    					// now add the marker icon to the location
    					// we must do it in a seperate JSON request
    					this.addMarkerRichContent(objNewLocation)
    					
    					// add focusing events
    					google.maps.event.addListener(marker, 'click', function() {
    						// location not focused so focus
    						if (objNewLocation.focused == false) { //the objNewLocation is the right one due to scope binded to .this
    							this.focusLocationsClearAll();
    							//this.focusLocationsByPk([item.pk]);
    							//objNewLocation.focused = true;
    							this.focusLocationsByPk([objNewLocation.pk]);
    							this.LISTAREA.focusAdsByLocation(objNewLocation); // show ads relating to this location
    						}
    						// location is already focused so defocus
    						// IMPRV: FIXME: should we defocus location or open details area???
    						else {
    							//objNewLocation.focused = false;
    							this.focusLocationsClearAll();
    							//this.LISTAREA.focusAdsClearAll();
    							 // refocus ads based on categories
    							this.LISTAREA.focusAdsByCategories(this.CONTROLSAREA.getSelectedCategories());
    							
    						}
    					}.bind(this));
    				}
    			//trace('latlngbounds' + this.MAP.getBounds().toUrlValue());
    			}.bind(this))
    		}.bind(this)
    	});
		
		// send the request only if .getBounds() is defined
		if (this.MAP.getBounds()) {
			objJSONRequest.get({'output': 'serializer_geojson', 'latlngbounds': this.MAP.getBounds().toUrlValue()}); 
		}
	},
    

	/***********************************************************************
	 * Hide all markers/locations
	 * 
	 * IMPRV: improve speed by hashing this information?
	 * IMPRV: improve speed by perhaps doing one global tween with Fx.Elements ?
	 **********************************************************************/
	hideLocationsAll: function() {
		
		this.arrMapLocations.each(function(item, index) {
			var markerFx = new Fx.Tween(item['marker'].getContent());
			markerFx.start('opacity', 0).chain(
					function() {item['marker'].setVisible(false)}
					);
    	});
	},
	
	/***********************************************************************
	 * Hides specified marker
	 * 
	 * ``Options`` - numLocationPk - ex 1, 2 etc
	 **********************************************************************/
	hideLocationsByPk: function(numLocationPk) {
		var marker = this.arrMapLocations[numLocationPk]['marker']
		if (typeof(marker.getContent()) == 'string') { // somehow getContent() is string sometimes 
			return false; 
		};
		var markerFx = new Fx.Tween(marker.getContent());
		
		markerFx.start('opacity', 0).chain(
				function() {marker.setVisible(false)}
				);
	},
	
	/***********************************************************************
	 * Show specified marker
	 * 
	 * ``Options`` - numLocationPk - ex 1, 2 etc
	 **********************************************************************/
	showLocationsByPk: function(numLocationPk) {
		var marker = this.arrMapLocations[numLocationPk]['marker']
		if (typeof(marker.getContent()) == 'string') { // somehow getContent() is string sometimes 
			return false; 
		};
		
		marker.setVisible(true)
		var markerFx = new Fx.Tween(marker.getContent());
		markerFx.start('opacity', 1);
	},
	
	/***********************************************************************
	 * Show specified markers while hiding others
	 * 
	 * ``Options`` - arrMarkerPks - [1, 3, 4, <pk> ...]
	 **********************************************************************/
	showLocationsByPks: function(arrLocationPks) {
		arrLocationPks = arrLocationPks || [];
		var self = this;
		//trace('arrLocationPks: ' + JSON.stringify(arrLocationPks));
		self.arrMapLocations.each(function(item, key) {
			if (arrLocationPks.contains(key)) {
				self.showLocationsByPk(key);
			}
			else {
				self.hideLocationsByPk(key);
			}
		});
		
    	
	},
	
	/***********************************************************************
	 * Show specified markers while hiding others based on ADS
	 * 
	 * So first we extract the locations than call an internal showLocationsByPks
	 * 
	 * ``Options`` - arrMarkerPks - [1, 3, 4, <pk> ...]
	 **********************************************************************/
	showLocationsByAds: function(arrAds) {
		var self = this; // store class instance referance here
		var arrLocationPks = []
		//trace('arrAds: ' + JSON.stringify(arrAds));
		arrAds.each(function(item, key) {
			arrLocationPks.append(item.fields.locations);
		
		})
		//trace('arrLocationPks: ' + JSON.stringify(arrLocationPks.unique()));
		self.showLocationsByPks(arrLocationPks.unique());
    	
	},
	
	/***********************************************************************
	 * reset focus on all markers
	 * 
	 * IMPRV: improve this function efficiency by storing 'focused' markers
	 * or factoring it out into focusLocationsByPk
	 **********************************************************************/
	focusLocationsClearAll: function() {
		this.arrMapLocations.each(function(location, index) {
    		//item['marker'].setAnimation(null);
    		// FIXME: this needs to be called so that we reset focus z-index back to normal
    		//         we can't keep doing zindex + 1 as clicking on one marker a few times makes it the MOST top and other markers can't catchup
			var marker = location['marker'];
			//trace(location.focused);
			if (location.focused == true) {
				//var zIndex = this.arrMapLocations[item]['marker'].getZIndex();
				var zIndex = marker.getZIndex();
				//trace('defocused zIndex to: ' + ( zIndex - 100));
				marker.setZIndex(zIndex - 100);
				location.focused = false;
			}
			
			if (marker.getContent()) { // getContent() might be null still
				marker.getContent().morph(this.MARKER_CSS_STYLE_UNFOCUSED);
			}
    	}.bind(this));
	},
	
	/***********************************************************************
	 * focuses locations on the map based on their PK
	 *
	 * IMPRV: the zIndex focused should be the max zIndex+1 of a marker on map
	 * 
	 * Options:
	 * ``arrPks`` - required - [1, 3, <pk>, <pk>, ... ] - array of PK
	 ***********************************************************************/
	focusLocationsByPk: function(arrPks) {
		this.arrMapLocations.each(function(location, index) {
    		//item['marker'].setAnimation(null);
    		// FIXME: this needs to be called so that we reset focus z-index back to normal
    		//         we can't keep doing zindex + 1 as clicking on one marker a few times makes it the MOST top and other markers can't catchup
			if (arrPks.indexOf(location.pk) == -1) { // fade out not focused locations
				var marker = location['marker'];
				marker.getContent().morph(this.MARKER_CSS_STYLE_UNFOCUSED_TRANSPARENT);
				
			}
		}.bind(this));
		//this.focusLocationsClearAll();
		arrPks.each(function(location_pk, index) {
    		var location = this.arrMapLocations[location_pk];
    		var marker = location['marker'];
    		//marker.setAnimation(google.maps.Animation.BOUNCE);
    		marker.getContent().morph(this.MARKER_CSS_STYLE_FOCUSED);
    		if (location.focused == false) {
    			var zIndex = marker.getZIndex();
    			marker.setZIndex(zIndex  + 100);
    			//trace('focused zIndex to: ' + (zIndex+100));
    			location.focused = true;
    		}
    	}.bind(this));
	}
    
});

