// This is just here to tell JS lint that we need to use a global variable
/*global TVI , document, alert, console, window $ */

/// Declare Namespace

TVI = { version: "0.2" };

// Create function for applying config options to classes
TVI.apply = function(o, c, d){

    // o = object   : The object that is having stuff applied to it.
    // c = config   : The config options that will be applied to the object
    // d = defaults : Any default options that
    
    // See if defaults have been supplied
    if (d) {
        // If they have then call the apply function again with the defaults as the config
        TVI.apply(o, d);
    }

    // Check the object and config are valid objects
    if (o && c && typeof c == 'object') {
        // Loop through the config and add all the properties and methods
        for (var p in c) {
			// Check that we're only dealing with config members, not stuff inherited through the prototype
            if (c.hasOwnProperty(p) && c[p] !== undefined) {
		        o[p] = c[p];
		    }
        }
    }
	
    // Return the original object
    return o;
};


// Closed function - is not global but is instantiated and run immediately
(function() {

        // ********** Set up TVI variable and methods ********** //

        TVI.apply(TVI, {

            /**
            * Counter for creating uniqu ID's for components.
            * @property
            * @type integer
            */
            idSeed: 0,

            /**
            * Function for creating a unique ID for any control
            * @property
            * @type Function
            */
            createID: function(o, prefix) {
                prefix = prefix || "TVI-auto-";
                if (o.id === undefined || o.id === null || o.id === '') {
                    var id = prefix + (++this.idSeed);
                    o.id = id;
                }
                return o;
            },

            /**
            * A reusable empty function
            * @property
            * @type Function
            */
            emptyFn: function() { },

            /**
            * List of field types used in forms
            * @property
            * @type Array
            */
            fieldTypes: [
		        'textBox',
		        'textArea',
		        'dropDownList',
		        'checkBox',
			    'password',
			    'multiRadio',
			    'multiCheckBox'
		    ],

            /**
            * List of allowed data types when communicating with a database
            * @property
            * @type Array
            */
            dataTypes: [
			    'varchar',
			    'text',
			    'boolean',
			    'integer',
			    'decimal',
			    'date'
		    ],

            /**
            * Ajax - wraps up the jQuery Ajax function and adds defaults
            */
            ajax: function(params) {

                // Defaults for ajax calls
                var defaults = {
                    type: 'POST',
                    contentType: 'application/json; charset=utf-8',
                    dataType: 'json',
                    data: '{}'
                }

                var successFunction = params.success;
                var failureFunction = params.failure;

                // Overwrite the default parameters with the ones passed in
                var ajaxParams = TVI.apply(defaults, params);

                // Intercept the success function and add some functionality
                ajaxParams.success = function(data) {

                    // Parse the response
                    var response = JSON.parse(data.d);

                    // Check that the call was successful
                    if (response.success === true) {

                        // Run the success function if it exists
                        if (successFunction) {
                            successFunction.call(this, response);
                        }
                    }
                    else {
                        // Run the failure function if it exists
                        TVI.logWarning(response.errors[0].message);
                        if (failureFunction) {
                            failureFunction.call(this, response);
                        }
                    }

                }

                // Intercept the error function and pass it through to the default ajax error function.
                ajaxParams.error = function() {

                    var errorCode = params.errorCode || '010004';
                    var errorMessage = params.errorMessage || 'Error occured during Ajax call';

                    // Log the error to the console
                    TVI.logWarning(errorMessage);

                    // Run the error function if it exists
                    if (params.error !== undefined && params.error !== null) {
                        params.error.call();
                    }
                };

                // Make the ajax call
                $.ajax(ajaxParams);

            },

            /**
            * Check to see if the Firebug console exists for error logging
            */
            firebugCheck: function() {

                // Attempt top initialise the console if it doesn't exist
                if (typeof console == 'undefined') {
                    if (typeof loadFirebugConsole == 'function') {
                        loadFirebugConsole();
                    }
                }

                // Now check to see if the console exists
                if ($.browser.mozilla && window.console !== null && window.console !== undefined) {
                    return true;
                }
                else {
                    return false;
                }
            },

            /**
            * Log an error to the Firebug console.
            * @param {Object} errorObject - An object containing an error code and error message.
            */
            logError: function(errorObject) {

                // Console only exists in Firebug so check it exists before trying to write to it
                if (TVI.firebugCheck() === true) {

                    // Check to see if there is an errors colleciton
                    if (errorObject.errors) {

                        // Loop through
                        var errorsLength = errorObject.errors.length;
                        for (i = 0; i < errorsLength; i++) {
                            console.error('TVI Error: ' + errorObject.errors[i].code + ' - ' + errorObject.errors[i].message);
                        }
                    }
                    else {
                        console.error('TVI Error: ' + errorObject.code + ' - ' + errorObject.message);
                    }

                }
            },

            /**
            * Log a warning to the Firebug error console.
            * @param {string} warning - The warning to be shown
            */
            logWarning: function(warning) {

                // Console only exists in Firebug so check it exists before trying to write to it
                if (TVI.firebugCheck() === true) {
                    console.warn(warning);
                }
            },

            /**
            * Log a comment to the Firebug console.
            * @param {string} comment - The comment to show
            */
            logComment: function(comment) {

                // Console only exists in Firebug so check it exists before trying to write to it
                if (TVI.firebugCheck() === true) {
                    console.info(comment);
                }
            },

            /**
            * Wraps up Firebug's console.dir function.
            * @param {Object} o - The object to bad added to the console
            */
            logDir: function(o) {

                // Console only exists in Firebug so check it exists before trying to write to it
                if (TVI.firebugCheck() === true) {
                    console.dir(o);
                }
            },

            /**
            * Start a timer in Firebug's console.
            * @param {string} name - The name of the timer. Must match the logTimerEnd name.
            */
            logTimer: function(name) {

                // Console only exists in Firebug so check it exists before trying to write to it
                if (TVI.firebugCheck() === true) {
                    console.time(name);
                }
            },

            /**
            * Stops a timer in Firebug's console.
            * @param {string} name - The name of the timer. Must match the start timer name.
            */
            logTimerEnd: function(name) {

                // Console only exists in Firebug so check it exists before trying to write to it
                if (TVI.firebugCheck() === true) {
                    console.timeEnd(name);
                }
            },

            /**
            * Starts a group of nested comments/warnings/errors in Firebug's console.
            * @param {string} name - The name of the group. Must match the logGroupEnd name.
            */
            logGroup: function(name) {

                // Console only exists in Firebug so check it exists before trying to write to it
                if (TVI.firebugCheck() === true) {
                    console.group(name);
                }
            },

            /**
            * Ends a group of nested comments/warnings/errors in Firebug's console.
            * @param {string} name - The name of the group. Must match the start name.
            */
            logGroupEnd: function(name) {

                // Console only exists in Firebug so check it exists before trying to write to it
                if (TVI.firebugCheck() === true) {
                    console.groupEnd(name);
                }
            },

            /**
            * Starts Firebug's profiler.
            * @param {string} title - Optional title for the profiler report.
            */
            logProfile: function(title) {

                // Console only exists in Firebug so check it exists before trying to write to it
                if (TVI.firebugCheck() === true) {
                    console.profile(title);
                }
            },

            /**
            * Stops Firebug's profiler.
            */
            logProfileEnd: function() {

                // Console only exists in Firebug so check it exists before trying to write to it
                if (TVI.firebugCheck() === true) {
                    console.profileEnd();
                }
            },

            /**
            * Prints an interactive stack trace of JavaScript execution at the point where it is called.
            */
            logTrace: function() {

                // Console only exists in Firebug so check it exists before trying to write to it
                if (TVI.firebugCheck() === true) {
                    console.trace();
                }
            }

        });

    })();



// ********** TVI UTIL ********** //
TVI.util = {};

TVI.apply(TVI.util, {
	
	/**
     * Adds a new option to a select element.
     * @param {Object} e - the select element we are adding the option to.
     * @param {String} v - the value of the option we want to add.
     * @param {String} t - the text of the option we want to add.
     */
	addSelectOption: function(e, v, t){
		var option = document.createElement("option");
	    option.value = v;
	    
		// Set the text to the same as the value if it hasn't been supplied
		if(t === null || t === "" || t === undefined){
			option.text = v;
		}
		else{
			option.text = t;
		}
	
	    // get current options
	    var o = e.options;
	
	    // get number of options
	    var oL = o.length;
	
	    if (!e.cache) {
	        e.cache = {};
	        // loop through existing options, adding to cache
	        for (var l = 0; l < oL; l++) {
	            e.cache[o[l].value] = l;
	        }
	    }
	
	    // add to cache if it isn't already
	    if (typeof e.cache[v] == "undefined") {
	        e.cache[v] = oL;
	    }
	    e.options[e.cache[v]] = option;
	},
	
	/**
     * Selects an item in the jQuery Select list based on it's value.
     * @param {Object} e - the select element we are adding the option to.
     * @param {String} v - the value of the option we want to select.
     */
	selectOption: function(e, v){
		
		// Find the select list
		e.each(function(){

			// Shortcut to options and length of the options
			o = this.options;
			optionsLength = this.options.length;
			
			// Loop through all the options
			for (var i = 0; i < optionsLength; i++) {
				
				// If the option matches then select it
				if(o[i].value == v){
					o[i].selected = true;
				}

			}
		});

	},
	
	
	/**
     * Returns the text of the selected item in the select list passed in
     * @param {Object} e - the jQuery select element we are adding the option to.
     */
	getSelectedOptionText: function(e){
		
		return e[0].options[e[0].selectedIndex].text;
	},
	
	
	/**
     * converts all special characters in a string to HTML entities and returns the string
     * @param {Object} s - the string to entitify.
     */
	stringToEntities: function(s){
		
		return $('<div/>').text(s).html().replace('"',"&quot;");
	},
	
	/**
     * converts all HTML entities in a string to original characters and returns the string
     * @param {Object} s - the string to deEntitify.
     */
	stringFromEntities: function(s){
		
		$('#temp').html(s);
		return $('#temp').html();
	},
	
	/**
     * converts all quotes in a string to their ascii code equivelants
     * @param {Object} s - the string to encode.
     */
	encodeQuotes: function(s){
		
		var output = s;
		s = s.replace(/"/g,'&#34;');
		s = s.replace(/'/g,'&#39;');
		return s;
	},
	
	/**
     * converts all ascii quote codes in a string back into quotes
     * @param {Object} s - the string to encode.
     */
	decodeQuotes: function(s){
		
		var output = s;
		s = s.replace(/&#34;/g,'"');
		s = s.replace(/&#39;/g,"'");
		return s;
	}

	
}); // End of TVI.util



// ***************************************************************** //
// ******** Override and extend default objects and methods ******** //
// ***************************************************************** //

// Add an Array.indexOf method to IE
if(!Array.indexOf){
	Array.prototype.indexOf = function(obj){
		var L = this.length;
			for(var i=0; i<L; i++){
				if(this[i]==obj){
				return i;
			}
		}
		return -1;
	};
}


//*******************************************************//
//                                                       //
//    TVI Component Manager - Last Updated 01/09/2008    //
//                                                       //
//*******************************************************//

// This is just here to tell JS lint that we need to use a global variable
/*global TVI , document, $ , that */

/** 
* COMPONENT MANAGER - A registry for all components created by the TVI JavaScript Framework.
* @author	Jon Hobbs
* @version 1.0
*/
TVI.ComponentManager = function(allowFunctions) {
	
	this.allowFunctions = allowFunctions === true;
	
	return {
		
		/**
		 * Do we allow functions to be stored in this collection? Always yes because we're using it to store components
		 * @property allowFunctions
		 * @type {boolean}
		 */
		allowFunctions: this.allowFunctions,
		/**
		 * The number of components in the registry
		 * @property length
		 * @type {Integer}
		 */
		length:0,
		/**
		 * An Array of objects, each one being a TVI component.
		 * @property items
		 * @type {Array}
		 */
		items: [],
		/**
		 * An Array of keys, which hold the ID of each component.
		 * @property keys
		 * @type {Array}
		 */
		keys: [],
		/**
		 * An object containing a map of keys to components.
		 * @property map
		 * @type {Object}
		 */
		map: {},
		
		/**
		 * add (public)
	     * Adds a new component to the registry
	     * @param {string}   k		The key of the component to add to the key collection.
	     * @param {object}   o		The component to add to the registry.
	     */
		register: function(k, o){
			
			// Check to see if a key has been supplied
	        if(arguments.length == 1){
				// If no key has been supplied then the component is the first parameter, not the second
	            o = arguments[0];
				// And we need to try to get the key for this component
	            k = this.getKey(o);
	        }
			// Check to see if we now have a key
	        if(typeof k == "undefined" || k === null){
				// if we haven't then register the component anyway, with a null key and don't addid to the map
	            this.length++;
	            this.items.push(o);
	            this.keys.push(null);
	        }else{
				// If we have got a key then check to see if the component is already in the gegistry
	            var old = this.map[k];
	            if(old){
					// If the component is already in the registry then call the replace function instead of adding it.
	                return this.replace(k, o);
	            }
				// If we have a key and the component is not already in the registry then add it and add it to the map.
	            this.length++;
	            this.items.push(o);
	            this.map[k] = o;
	            this.keys.push(k);
	        }
	        return o;
	    },
		
		/**
		 * getKey (public)
	     * Returns the ID of a component to use as the key
	     * @param {o} config	The component to get the key for
	     */
		getKey : function(o){
	         return o.id;
	    },
		
		/**
		 * replace (public)
	     * Replaces a component in the registry
	     * @param {string}   k		The key of the component to add to the key collection.
	     * @param {object}   o		The component to add to the registry.
	     */
		replace : function(k, o){
			
			// Check to see if a key has been supplied
	        if(arguments.length == 1){
				// If no key has been supplied then the component is the first parameter, not the second
	            o = arguments[0];
				// And we need to try to get the key for this component
	            k = this.getKey(o);
	        }
			// Get the old item that is being replaced
	        var old = this.item(k);
			// If no key has been supplied, or the old item couldn't be found then call the add function 
			// instead of replacing anything.
	        if(typeof k == "undefined" || k === null || typeof old == "undefined"){
	             return this.add(k, o);
	        }
			// If the old item has been found then replace it in the items array and in the map
	        var index = this.indexOfKey(k);
	        this.items[index] = o;
	        this.map[k] = o;
	        return o;
	    },
		
		
		/**
		* Returns index of the compnent in the registry
		* @param {Object} o The component to find the index of.
		* @return {Integer} index of the component.
		*/
		indexOf : function(o){
			return this.items.indexOf(o);
		},
	
		/**
		* Returns index of the component using the supplied key.
		* @param {String} key 	The key to find the index of.
		* @return {Integer} index of the key.
		*/
		indexOfKey : function(k){
			return this.keys.indexOf(k);
		},
		
		/**
		* Returns the item associated with the passed key OR index. Key has priority over index.
		* @param {String/Number} k The key or index of the item.
		* @return {Object} The item associated with the passed key.
		*/
		item : function(k){
			
			// Create the item. If a key has been supplied then it will find the item in the map and return it.
			// If it isn't found then k must ben an index, so return the item from the items array
		    var item = typeof this.map[k] != "undefined" ? this.map[k] : this.items[k];
			
			// If the item isn't a function, or functions are allowed then return it, otherwise return null
		    return typeof item != 'function' || this.allowFunctions ? item : null;
		}
			
	};
	
}(); 





/// Visual Studio Intellisense reference
/// <reference path="TVI.js" />

/** 
* MAP COORDINATE - An object representing an OSGB1936 longitude/latitude pair
* @param {Object} config	Contains all the config options
*/
TVI.mapCoordinate = function(config) {

	/**
	 * @cfg {string} longitude
	 * An OSGB1936 longitude value
	 */
	/**
	 * @cfg {string} latitude
	 * An OSGB1936 latitude value
	 */
	
	// Apply config
	TVI.apply(this, config);
	
};

/** 
* GMAP - An object representing a google map
* @param {Object} config	Contains all the config options
*/
TVI.GMap = function(config) {

	/**
	 * @cfg {string} id
	 * The unique id of the map
	 */
	/**
	 * @cfg {object} map
	 * The google map object
	 */
	/**
	 * @cfg {string} longitude
	 * An OSGB1936 longitude value
	 */
	/**
	 * @cfg {string} latitude
	 * An OSGB1936 latitude value
	 */
	/**
	 * @cfg {integer} zoomLevel
	 * The zoom level of the map, an integer between 1 and 20.
	 */
	 /**
	 * @cfg {string} view
	 * The view type. Accepts - "normal", "satellite", "hybrid", "physical"
	 */
	/**
	 * @cfg {string} mapControlType
	 * The type of main map control to use - Possible values - large,small
	 */
	/**
	 * @cfg {boolean} showViewButtons
	 * Whether to show the buttos to switch the view type (e.g. map/satellite/hybrid)
	 */
	/**
	 * @cfg {boolean} showViewNormal
	 * Whether to show a button which switches to the normal map view
	 */
	/**
	 * @cfg {boolean} showViewSatellite
	 * Whether to show a button which switches to the satellite view
	 */
	/**
	 * @cfg {boolean} showViewHybrid
	 * Whether to show a button which switches to the hybrid view
	 */
	/**
	 * @cfg {boolean} showViewPhysical
	 * Whether to show a button which switches to the physical view
	 */
	/**
	 * @cfg {string} useCustomMarker
	 * Whether to use a custom marker 
	 */
	/**
	 * @cfg {boolean} customMarkerURL
	 * The URL of the image to use for the custom marker
	 */
	/**
	 * @cfg {integer} customMarkerWidth
	 * The width of the custom marker image in pixels
	 */
	/**
	 * @cfg {integer} customMarkerHeight
	 * The height of the custom marker image in pixels
	 */
	/**
	 * @cfg {integer} useMarkerManager
	 * Whether to use a marker manager. Use this when using a large number of markers
	 */
	/**
	 * @cfg {integer} useClusteredMarkerManager
	 * Whether to use a cluster.js marker manager, designed specifically for clustering.
	 */
	/**
	 * @cfg {boolean} clusterMarkerURL
	 * The URL of the image to use for the cluster marker
	 */
	/**
	 * @cfg {integer} clusterMarkerWidth
	 * The width of the cluster marker image in pixels
	 */
	/**
	 * @cfg {integer} customMarkerHeight
	 * The height of the cluster marker image in pixels
	 */

	// Declare defaults
	var defaults = {
		longitude: '-0.170889',
		latitude: '51.505697',
		zoomLevel: 13,
		view: 'normal',
		mapControlType: 'large',
		showViewButtons: true,
		showViewNormal: true,
		showViewSatellite: true,
		showViewHybrid: true,
		showViewPhysical: false,
		useCustomMarker: false,
		useMarkerManager: false,
		useClusteredMarkerManager: false
	};

	// Apply config
	TVI.apply(this, config, defaults);
	
	// Create unique id if one doesn't already exist
	TVI.createID(this);
	
	// Register with the component manager
	TVI.ComponentManager.register(this.id, this);
	
	this.initialize();

};

TVI.GMap.prototype = {

    /**
    * @property {object} markers
    * An associative array of all the markers belonging to the map
    */
    markers: {},

    /**
    * @property {object} customMarker
    * A google custom marker object to use for markers
    */
    customMarker: {},

    /**
    * @property {object} customMarkerOptions
    * A google custom marker options object to use for markers
    */
    customMarkerOptions: {},

    /**
    * @property {object} clusterMarker
    * A google custom marker object to use for cluster markers
    */
    clusterMarker: {},

    /**
    * @property {object} clusteredMarkerManager
    * A marker manager to use when clustering is necessary - requires clusterer2.js to be loaded.
    */
    clusteredMarkerManager: {},

    /**
    * initialize (public)
    * Creates the google map
    */
    initialize: function() {

        var component = this;

        // Check that the Google maps scripts are loaded
        if (!GBrowserIsCompatible()) {
            TVI.logError({ "code": "080002", "message": "The Google Maps script is not loaded." });
            return;
        }

        // Create the map
        component.map = new GMap2(document.getElementById(component.id));
        component.map.enableScrollWheelZoom();

        // Add the pan/zoom controls if necessary
        switch (component.mapControlType) {
            case 'radius':
                component.map.addControl(new GLargeMapControl());
                break;
            case 'small':
                component.map.addControl(new GSmallMapControl());
                break;
            default:
                // No controls added by default
        }

        // Add the switch view buttons if necessary
        if (component.showViewButtons) {

            component.map.addControl(new GMapTypeControl());

            // Remove the normal view button if it isn't being used
            if (component.showViewNormal === false) {
                component.map.removeMapType(G_NORMAL_MAP);
            }
            // Remove the satellite view button if it isn't being used
            if (component.showViewSatellite === false) {
                component.map.removeMapType(G_SATELLITE_MAP);
            }
            // Remove the hybrid view button if it isn't being used
            if (component.showViewHybrid === false) {
                component.map.removeMapType(G_HYBRID_MAP);
            }
            // Remove the physical view button if it isn't being used
            if (component.showViewPhysical === false) {
                component.map.removeMapType(G_PHYSICAL_MAP);
            }

        }

        // Add a custom marker if necessary
        if (component.useCustomMarker) {

            // Set up the marker
            component.customMarker = new GIcon(G_DEFAULT_ICON);
            component.customMarker.image = component.customMarkerURL;
            component.customMarker.shadow = null;
            component.customMarker.iconSize = new GSize(component.customMarkerWidth, component.customMarkerHeight);

            // Create the marker options to apply to all markers
            component.customMarkerOptions = { icon: component.customMarker };

        }

        // Create a clustered marker manager if necessary
        if (component.useClusteredMarkerManager) {

            // Initialise clustered marker manager
            component.clusteredMarkerManager = new Clusterer(component.map);
            component.clusteredMarkerManager.SetMaxVisibleMarkers(100);

            // Initialise the cluster marker

            var clusterIcon = new GIcon();
            clusterIcon.image = "i/gMapClusterMarker.png";
            clusterIcon.iconSize = new GSize(48, 64);
            clusterIcon.iconAnchor = new GPoint(24, 64);

            component.clusteredMarkerManager.SetIcon(clusterIcon);

        }

        // Position the map
        component.setCentre({
            longitude: component.longitude,
            latitude: component.latitude,
            zoomLevel: component.zoomLevel
        });

        // Change the view if necessary
        if (component.view !== 'normal') {
            component.setView({
                view: component.view
            });
        }


    },
    
    
    getZoomLevel: function(){
    
        return this.map.getZoom()
    
    },
    

    /**
    * setCentre (public)
    * repositions the map with the centre at the supplied coordinates.
    */
    setCentre: function(params) {

        var component = this;

        // Use the default zoom level if one isn't supplied
        if (params.zoomLevel === undefined || params.zoomLevel === null) {
            params.zoomLevel = component.zoomLevel;
        }

        // Set the centre of the map
        if (params.animate){        
            component.map.panTo(new GLatLng(params.latitude, params.longitude), params.zoomLevel);        
        }
        else{
            component.map.setCenter(new GLatLng(params.latitude, params.longitude), params.zoomLevel);
        }

    },


    /**
    * createMarker (public)
    * Creates a new marker but doesn't add it to the map
    * @param {number} id - the ID of the database record that this marker represents
    * @param {number} longitude - the longitude coordinate to place the marker at 
    * @param {number} longitude - the latitude coordinate to place the marker at 
    * @param {function} markerClicked - function to run when the marker is clicked
    * @param {function} markerHovered - function to run when the marker is hovered
    * @param {function} markerUnHovered - function to run when the mouse moves off a marker
    */
    createMarker: function(params) {

        var component = this;

        // Create a new marker using the custom marker if necessary
        var newMarker;
        if (component.useCustomMarker) {
            newMarker = new GMarker(new GLatLng(params.latitude, params.longitude), component.customMarkerOptions);
        }
        else {
            newMarker = new GMarker(new GLatLng(params.latitude, params.longitude));
        }

        // Add the database ID to the marker
        newMarker.id = params.id;

        // Add a click event handler if one has been supplied
        if (params.markerClicked !== undefined && params.markerClicked !== null) {

            GEvent.addListener(newMarker, "click", params.markerClicked);
        }

        // Add a hover event handler if one has been supplied
        if (params.markerHovered !== undefined && params.markerHovered !== null) {

            GEvent.addListener(newMarker, "mouseover", params.markerHovered);
        }

        // Add a mouse out event handler if one has been supplied
        if (params.markerUnHovered !== undefined && params.markerUnHovered !== null) {

            GEvent.addListener(newMarker, "mouseout", params.markerUnHovered);
        }

        return newMarker;
    },


    /**
    * addMarker (public)
    * Adds a marker to the map
    * @param {number} id - the ID of the database record that this marker represents
    * @param {number} longitude - the longitude coordinate to place the marker at 
    * @param {number} longitude - the latitude coordinate to place the marker at 
    * @param {function} markerClicked - function to run when the marker is clicked
    */
    addMarker: function(params) {


        var component = this;

        // Check to see if the marker already exists on the map
        if (component.markers[params.id]) {
            return;
        }

        // Add the ID to the list of items on the map
        component.markers[params.id] = { longitude: params.longitude, latitude: params.latitude };

        // Create the marker using the marker factory function
        var newMarker = component.createMarker(params);

        // Check to see if we're using googles marker manager
        if (component.useMarkerManager === true) {
            // TODO
        }
        // Check to see if we're using a clustered marker manager
        else if (component.useClusteredMarkerManager === true) {
            component.clusteredMarkerManager.AddMarker(newMarker);
        }
        // Else assume we're not using any kind of marker manager
        else {
            component.map.addOverlay(newMarker);
        }


    },
    
    
    getMarker: function(id){
    
        return this.markers[id];
    
    },
    
    
    clearMarkers: function(){
    
        var component = this;
        
        component.markers = {};
        component.map.clearOverlays();
        
        
    
    },


    /**
    * addDragEvent (public)
    * Adds an event which will run when the map is dragged
    */
    mapDragged: function(params) {

        var component = this;

        // Check that a handler function has been passed in
        if (params.handler) {

            // Add the handler to the drag event
            GEvent.addListener(component.map, "moveend", params.handler);
        }
    },


    /**
    * getBounds (public)
    * Gets the upper and lower bounds of the current map viewport in OS coordinates
    */
    getBounds: function() {

        // Set up some variables
        var component = this;
        var bounds = {};

        // Get the bounds
        bounds.longMin = component.map.getBounds().getSouthWest().x;
        bounds.longMax = component.map.getBounds().getNorthEast().x;
        bounds.latMin = component.map.getBounds().getSouthWest().y;
        bounds.latMax = component.map.getBounds().getNorthEast().y;
        
        //calculate centre
        bounds.longitude = bounds.longMin + ((bounds.longMax - bounds.longMin) / 2);
        bounds.latitude = bounds.latMin + ((bounds.latMax - bounds.latMin) / 2);

        // Return the bounds object
        return bounds;

    },

    /**
    * checkResize (public)
    * Gets the map to check if it's container has been resized
    */
    checkResize: function() {

        var component = this;
        component.map.checkResize();

    },


    /**
    * fitMarkers (public)
    * Centres and zooms the map to fit all the markers
    */
    fitMarkers: function() {

        TVI.logTimer('GMap.fitMarkers()');

        var component = this;

        // Set up the variables to hold the bounds
        var lowestLong;
        var highestLong;
        var lowestLat;
        var highestLat;
        var first = true;

        // Loop through all the markers
        for (marker in component.markers) {

            var markerLongitude = parseFloat(component.markers[marker].longitude);
            var markerLatitude = parseFloat(component.markers[marker].latitude);

            // If it's the first one then just set the bounds
            if (first === true) {

                lowestLong = markerLongitude;
                highestLong = markerLongitude;
                lowestLat = markerLatitude;
                highestLat = markerLatitude;

                first = false;

            }
            else { // Otherwise, check to see if it's outside the current bounds and move the bounds if it is.

                if (markerLongitude < lowestLong) {
                    lowestLong = markerLongitude;
                }
                if (markerLongitude > highestLong) {
                    highestLong = markerLongitude;
                }
                if (markerLatitude < lowestLat) {
                    lowestLat = markerLatitude;
                }
                if (markerLatitude > highestLat) {
                    highestLat = markerLatitude;
                }

            }
        }

        // Centre and zoom the map
        var markerBounds = new GLatLngBounds(new GLatLng(lowestLat, lowestLong), new GLatLng(highestLat, highestLong));
        var zoomLevel = component.map.getBoundsZoomLevel(markerBounds);
        var centreLongitude = lowestLong + ((highestLong - lowestLong) / 2);
        var centreLatitude = lowestLat + ((highestLat - lowestLat) / 2) + ((highestLat - lowestLat)*0.14);

        component.setCentre({
            longitude: centreLongitude,
            latitude: centreLatitude,
            zoomLevel: zoomLevel
        });

        TVI.logTimerEnd('GMap.fitMarkers()');
        
    },


    /**
    * setView (public)
    * Changes the view of the map. Accepts - "normal", "satellite", "hybrid", "physical"
    */
    setView: function(params) {

        var component = this;

        // Check which view to switch the map to
        switch (params.view) {
            case 'normal':
                component.map.setMapType(G_NORMAL_MAP);
                break;
            case 'satellite':
                component.map.setMapType(G_SATELLITE_MAP);
                break;
            case 'hybrid':
                component.map.setMapType(G_HYBRID_MAP);
                break;
            case 'physical':
                component.map.setMapType(G_PHYSICAL_MAP);
                break;
            default:
                // Log an error if no valid view has been provided
                TVI.logError({ "code": "080005", "message": "No valid view was provided to Gmap.changeView()" });
        }

    }


}


// ********** TVI MAPPING ********** //
TVI.Mapping = {};

TVI.apply(TVI.Mapping, {

    /**
    * Adds a new option to a select element.
    * @param {string} postcode - The postcode to return co-ordinates for.
    * @param {String} accountCode - The Postcode Anywhere account code to use.
    * @param {String} apiKey - The Postcode Anywhere API key to use.
    * @param {function} success - Function to run when the call to postcode anywhere succeeds.
    * @param {function} failure - function to run when the call to postcode anywhere fails.
    * @param {function} error - function to run when the call to postcode anywhere error.
    * @param {string} paGetLatLongUrl - URL of the handler.
    */
    paGetLatLong: function(params) {

        var that = this;

        // Apply Defaults
        var paGetLatLongUrl = params.paGetLatLongUrl || 'Handlers/Mapping.aspx/paGetLatLong';

        // Create the JSON data
        var jsonData = '';
        jsonData += '{"config":{';
            jsonData += '"postcode":"' + params.postcode + '"';
        jsonData += '}}';


        // Make an ajax call to get the co-ordinates
        TVI.ajax({
            url: paGetLatLongUrl,
            data: jsonData,
            success: params.success,
            failure: params.failure,
            error: params.error,
            errorCode: '080001',
            errorMessage: 'Failed to get Lat & Long coordinates from Postcode Anywhere.'
        });

    },
    
    
    /**
    * Checks for a valid UK postcode.
    * @param {string} postcode - The postcode to validate.
    */
    isValidPostcode: function(postcode){
    
        var regex = /[A-Z]{1,2}[0-9]{1,2} ?[0-9][A-Z]{2}/i;
        
	    return regex.test(postcode);
    
    }


});//**************************************************//
//                                                  //
//     TVI Panel Bar - Last Updated 08/10/2008      //
//                                                  //
//**************************************************//

// This is just here to tell JS lint that we need to use a global variable
/*global TVI , document, $ , that, alert, i, j, k */

/** 
* PANEL BAR - An object representing a TVI panel bar
* @param {Object} config	Contains all the config options
* @author	Jon Hobbs
* @version 1.0
*/

TVI.PanelBar = function(config) {

	/**
	 * @cfg {string} id
	 * The unique id of the PanelBar
	 */
	/**
	 * @cfg {string} openEffect
	 * The effect to use when opening a panel
	 * Allowed values - slide, fade
	 */
	/**
	 * @cfg {decimal} openSpeed
	 * The number speed to use when opening a panel, in miliseconds
	 */
	/**
	 * @cfg {string} closeEffect
	 * The effect to use when opening a panel
	 * Allowed values - slide, fade
	 */
	/**
	 * @cfg {decimal} closeSpeed
	 * The number speed to use when opening a panel, in miliseconds
	 */
	/**
	 * @cfg {boolean} allowMultipleOpenPanels
	 * Whether to allow more than one panel to be open at a time
	 */
	
	// Declare defaults
	var defaults = {
		openEffect: 'slide',
		openSpeed: 300,
		closeEffect: 'slide',
		closeSpeed: 300,
		allowMultipleOpenPanels: true
	};

	// Apply config
	TVI.apply(this, config, defaults);
	
	// Create unique id if one doesn't already exist
	TVI.createID(this);
	
	// Register with the component manager
	TVI.ComponentManager.register(this.id, this);

	
	// Run the initialize method
	this.initialize();
	
};

TVI.PanelBar.prototype = {
	

	/**
	 * initialize (public)
     * initializes the Panel Bar
     */
	initialize: function(){
		
		var component = this;
		
		// Get the root element
		var element = $('#' + component.id);
		
		//Attach the click events
		element.find('.TVI-panelBar-button').click(function(){
		
			//get the item element and make sure we only get the nearest parent
			var itemElement =  $(this).parents('.TVI-panelBar-item');
			itemElement = itemElement.eq(0);
			
			// *******************************************/
			// **************** Closed Items *************/
			// *******************************************/
			
			if(itemElement.hasClass('closed')){
				
				// Check to see whether to close the other panels or not
				if(component.allowMultipleOpenPanels === false){
					
					//Check what type of animation to use
					switch(component.closeEffect)
					{
						case 'slide':
								element.find('.TVI-panelBar-item.open .TVI-panelBar-panel').slideUp(component.closeSpeed);
							break;    
						case 'fade':
								element.find('.TVI-panelBar-item.open .TVI-panelBar-panel').fadeOut(component.closeSpeed);
							break;
					}
					
					// Get the items
					var items = element.find('.TVI-panelBar-item.open');

					//Switch the classes
					items.removeClass('open');
					items.addClass('closed');
					
				}

				//Get the panel element
				panelElement = itemElement.find('.TVI-panelBar-panel');
				
				//Check what type of animation to use
				switch(component.openEffect)
				{
					case 'slide':
							panelElement.slideDown(component.openSpeed);
						break;    
					case 'fade':
							panelElement.fadeIn(component.openSpeed);
						break;
				}
				
				//Switch the classes
				itemElement.removeClass('closed');
				itemElement.addClass('open');

				// Prevent the default behaviour
				return false;
			}
			
			// *******************************************/
			// **************** Open Items ***************/
			// *******************************************/
			
			else if(itemElement.hasClass('open')){

				
				
				//Get the panel element
				panelElement = itemElement.find('.TVI-panelBar-panel');
				
				//Check what type of animation to use
				switch(component.closeEffect)
				{
					case 'slide':
						panelElement.slideUp(component.closeSpeed);
						//Switch the classes
						itemElement.removeClass('open');
						itemElement.addClass('closed');
						break;    
					case 'fade':
						panelElement.fadeOut(component.closeSpeed, function(){
							//Switch the classes
							itemElement.removeClass('open');
							itemElement.addClass('closed');
						});
						break;
				}
				
				

				// Prevent the default behaviour
				return false;
			}
		
		});

	}
	
};



