function Spot(asGlobals)
{
	self = this;
	this.consts = asGlobals.consts;
	this.consts.hash_sep = '-';
	this.consts.title = 'Spotty';
	this.consts.default_page = 'project';
	this.consts.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || this.consts.default_timezone;
	
	/* Initialization */
	
	this.init = function()
	{
		//Variables & constants from php
		self.vars('tmp', 'object');
		self.vars('page', 'string');
		self.updateVars(asGlobals.vars);
		
		//page elem
		self.elem = {};
		self.elem.container = $('#container');
		self.elem.main = $('#main');
		
		self.resetTmpFunctions();
		
		//On Key down
		$('html').on('keydown', function(oEvent){self.onKeydown(oEvent);});
		
		//on window resize
		$(window).on('resize', function(){self.onResize();});
		
		//Setup menu
		//self.initMenu();
		
		//Hash management
		$(window)
			.bind('hashchange', self.onHashChange)
			.trigger('hashchange');
	};
	
	this.updateVars = function(asVars)
	{
		$.each(asVars, function(sKey, oValue){self.vars(sKey, oValue)});
	};
	
	/* Variable Management */
	
	this.vars = function(oVarName, oValue)
	{
		var asVarName = (typeof oVarName == 'object')?oVarName:[oVarName];
		
		//Set, name & type / default value (init)
		if(typeof oValue !== 'undefined') setElem(self.vars, copyArray(asVarName), oValue);

		//Get, only name parameter
		return getElem(self.vars, asVarName);
	};
	
	this.tmp = function(sVarName, oValue)
	{
		var asVarName = (typeof sVarName == 'object')?sVarName:[sVarName];
		asVarName.unshift('tmp');
		return self.vars(asVarName, oValue);
	};
	
	/* Interface with server */
	
	this.get = function(sAction, fOnSuccess, oVars, fOnError, fonProgress)
	{
		if(!oVars) oVars = {};
		fOnError = fOnError || function(sError) {console.log(sError);};
		fonProgress = fonProgress || function(sState){};
		fonProgress('start');
		
		oVars['a'] = sAction;
		oVars['t'] = self.consts.timezone;
		return $.ajax(
			{
				url: self.consts.process_page,
				data: oVars,
				dataType: 'json'
			})
			.done(function(oData)
			{
				fonProgress('done');
				if(oData.desc.substr(0, self.consts.lang_prefix.length)==self.consts.lang_prefix) oData.desc = self.lang(oData.desc.substr(5));
				
				if(oData.result==self.consts.error) fOnError(oData.desc);
				else fOnSuccess(oData.data, oData.desc);
			})
			.fail(function(jqXHR, textStatus, errorThrown)
			{
				fonProgress('fail');
				fOnError(textStatus+' '+errorThrown);
			});
	};
	
	this.lang = function(sKey, asParams) {
		var sParamType = $.type(asParams);
		if(sParamType == 'undefined') asParams = [];
		else if($.type(asParams) != 'array') asParams = [asParams];
		var sLang = '';
		
		if(sKey in self.consts.lang) {
			sLang = self.consts.lang[sKey];
			for(i in asParams) sLang = sLang.replace('$'+i, asParams[i]);
		}
		else console.log('missing translation: '+sKey);
		
		return sLang;
	};
	
	/* Page Switch - Trigger & Event catching */
	
	this.onHashChange = function()
	{
		var asHash = self.getHash();
		if(asHash.hash !='' && asHash.page != '') self.switchPage(asHash); //page switching
		else if(self.vars('page')=='') self.setHash(self.consts.default_page); //first page
	};
	
	this.getHash = function()
	{
		var sHash = self.hash();
		var asHash = sHash.split(self.consts.hash_sep);
		var sPage = asHash.shift() || '';
		return {hash:sHash, page:sPage, items:asHash};
	};
	
	this.setHash = function(sPage, asItems, bReboot)
	{
		bReboot = bReboot || false;
		sPage = sPage || '';
		asItems = asItems || [];
		if(typeof asItems == 'string') asItems = [asItems];
		if(sPage != '')
		{
			var sItems = (asItems.length > 0)?self.consts.hash_sep+asItems.join(self.consts.hash_sep):'';
			self.hash(sPage+sItems, bReboot);
		}
	};
	
	this.hash = function(hash, bReboot)
	{
		bReboot = bReboot || false;
		if(!hash) return window.location.hash.slice(1);
		else window.location.hash = '#'+hash;
		
		if(bReboot) location.reload();
	};
	
	/* Page Switch - DOM Replacement */
	
	this.getActionLink = function(sAction, oVars)
	{
		if(!oVars) oVars = {};
		sVars = '';
		for(i in oVars)
		{
			sVars += '&'+i+'='+oVars[i];
		}
		return self.consts.process_page+'?a='+sAction+sVars;
	};
	
	this.resetTmpFunctions = function()
	{
		self.pageInit = function(asHash){console.log('no init for the page: '+asHash.page)};
		self.onSamePageMove = function(asHash){return false};
		self.onQuitPage = function(){return true};
		self.onResize = function(){};
		self.onFeedback = function(sType, sMsg){};
		self.onKeydown = function(oEvent){};
	};
	
	this.switchPage = function(asHash)
	{
		var sPageName = asHash.page;
		var bSamePage = self.vars('page')==sPageName;
		if(self.onQuitPage(bSamePage) && !bSamePage || self.onSamePageMove(asHash))
		{
			//Delete tmp variables
			self.vars('tmp', {});
			
			//disable tmp functions
			self.resetTmpFunctions();
			
			//Officially a new page
			var bFirstPage = self.vars('page')=='';
			self.vars('page', sPageName);
			
			//Update Page Title
			this.setPageTitle(sPageName+' '+(asHash.items[0] || ''));
			
			//Replacing DOM
			var $Dom = $(self.consts.pages[sPageName]);
			if(bFirstPage)
			{
				self.splash($Dom, asHash, bFirstPage); //first page
			}
			else
			{
				self.elem.main.stop().fadeTo('fast', 0, function(){self.splash($Dom, asHash, bFirstPage);}); //Switching page
			}
		}
	};
	
	this.setPageTitle = function(sTitle) {
		document.title = self.consts.title+' - '+sTitle;
	};
	
	this.splash = function($Dom, asHash, bFirstPage)
	{
		//Switch main content
		self.elem.main.empty().html($Dom);

		//Page Bootstrap
		self.pageInit(asHash, bFirstPage);
		
		//Show main
		var $FadeInElem = bFirstPage?self.elem.container:self.elem.main;
		$FadeInElem.hide().fadeTo('slow', 1);
	};
}

/* Common Functions */

function copyArray(asArray)
{
	return asArray.slice(0); //trick to copy array
}

function getElem(aoAnchor, asPath)
{
	return (typeof asPath == 'object' && asPath.length > 1)?getElem(aoAnchor[asPath.shift()], asPath):aoAnchor[(typeof asPath == 'object')?asPath.shift():asPath];
}

function setElem(aoAnchor, asPath, oValue)
{
	var asTypes = {boolean:false, string:'', integer:0, int:0, array:[], object:{}};
	if(typeof asPath == 'object' && asPath.length > 1)
	{
		var nextlevel = asPath.shift();
		if(!(nextlevel in aoAnchor)) aoAnchor[nextlevel] = {}; //Creating a new level
		if(typeof aoAnchor[nextlevel] !== 'object') debug('Error - setElem() : Already existing path at level "'+nextlevel+'". Cancelling setElem() action');
		return setElem(aoAnchor[nextlevel], asPath, oValue);
	}
	else
	{
		var sKey = (typeof asPath == 'object')?asPath.shift():asPath;
		return aoAnchor[sKey] = (!(sKey in aoAnchor) && (oValue in asTypes))?asTypes[oValue]:oValue;
	}
}

function getTimeZoneDesc(sTimeZoneName) {
	if(sTimeZoneName.indexOf('/')==-1) return sTimeZoneName;
	else {
		var sCity = sTimeZoneName.split('/')[1].replace('_', ' ') || '';
		return oSpot.lang('city_time', sCity);
	}
}

$.prototype.addInput = function(sType, sName, sValue, aoEvents)
{
	aoEvents = aoEvents || [];
	var $Input = $('<input>', {type: sType, name: sName, value: sValue}).data('old_value', sValue);
	$.each(aoEvents, function(iIndex, aoEvent) {
		$Input.on(aoEvent.on, aoEvent.callback);
	});
	return $(this).append($Input);
};

$.prototype.addButton = function(sIcon, sText, sName, fOnClick, sClass)
{
	sText = sText || '';
	sClass = sClass || '';
	var $Btn = $('<button>', {name: sName, 'class':sClass})
		.addIcon('fa-'+sIcon, (sText != ''))
		.append(sText)
		.click(fOnClick);
		
	return $(this).append($Btn);
};

$.prototype.addIcon = function(sIcon, bMargin, sStyle)
{
	bMargin = bMargin || false;
	sStyle = sStyle || '';
	return $(this).prepend($('<i>', {'class':'fa'+sStyle+' '+sIcon+(bMargin?' push':'')}));
};

$.prototype.defaultVal = function(sDefaultValue)
{
	$(this)
		.data('default_value', sDefaultValue)
		.val(sDefaultValue)
		.addClass('defaultText')
		.focus(function()
		{
			var $This = $(this);
			if($This.val() == $This.data('default_value')) $This.val('').removeClass('defaultText');
		})
		.blur(function()
		{
			var $This = $(this);
			if($This.val() == '') $This.val($This.data('default_value')).addClass('defaultText');
		});
};

$.prototype.checkForm = function(sSelector)
{
	sSelector = sSelector || 'input[type="text"], textarea';
	var $This = $(this);
	var bOk = true;
	$This.find(sSelector).each(function()
	{
		$This = $(this);
		bOk = bOk && $This.val()!='' && $This.val()!=$This.data('default_value');
	});
	return bOk;
};

$.prototype.cascadingDown = function(sDuration)
{
	return $(this).slideDown(sDuration, function(){$(this).next().cascadingDown(sDuration);});
};

$.prototype.hoverSwap = function(sDefault, sHover)
{
	return $(this)
		.data('default', sDefault)
		.data('hover', sHover)
		.hover(function(){
			var	$This = $(this),
				sHover = $This.data('hover');
				sDefault = $This.data('default');
			
			if(sDefault!='' && sHover != '') {
				$This.fadeOut('fast', function() {
					var $This = $(this);
					$This.text((sDefault==$This.text())?sHover:sDefault).fadeIn('fast');
			    });
			}				
		})
		.text(sDefault);
};

$.prototype.onSwipe = function(fCallBack){
	return $(this)
		.on('mousedown touchstart', function(e) {
			var $This = $(this);
			$This.data('x-down', e.pageX);
			$This.data('y-down', e.pageY);
		})
		.on('mouseup touchend',function (e) {
			var $This = $(this);
			var iDeltaX = e.pageX - $This.data('x-down');
			var iDeltaY = e.pageY - $This.data('y-down');
			fCallBack({x:iDeltaX, y:iDeltaY});
		});
};
