define(["dojo/_base/lang",
    	"dojo/_base/array", // array.forEach array.map
    	"dojo/_base/declare", // declare
    	"dojo/router/RouterBase",
    	"dojo/when",
    	"dojo/on",
    	"dojo/hash",
    	"dojo/io-query",
    	"dojo/json",
    	"dojo/_base/config",
    	"obno/core/safejson"
    	],
function(lang, array, declare, RouterBase, when, on, hash, ioQuery, json, config, safejson){
	// summary:
	//  manages routing and history in an obno app
	//
	//holds the state history for a specific route so that it can be applied 
	//to a viewmodel when navigating back and forward through the application history
	//state is held in terms view model instances as they were found at the time of route
	//transitioning
	
	

	var router = declare("obno.app.router", [RouterBase], {
		
		isStateful: (config.app && config.app.stateful && config.app.stateful === true),
		
		_handlePathChange: function(newPath){
			
			if (!this.isStateful){
				this.inherited(arguments);
				return;
			}
			
			console.log("path changed to - " + newPath);	
			//ensure that we have a state id so that we can take restore view via back/forward button
			if (newPath && newPath.length > 0){
				if (newPath.indexOf('?') > 0){
					//we have args so lets see if we have the state uid in there
					pathArgs = ioQuery.queryToObject(newPath.slice(newPath.indexOf('?') + 1));
					if ("__state-uid" in pathArgs){
					}  else {
						hash(this._addSuid(newPath), true);
						return;
					}
				} else {
					//we need to add it
					hash(this._addSuid(newPath), true);
					return;
				}				
			}
			
			this.inherited(arguments);
		},
		
		back: function(){
			window.history.go(-1);
		},
		
		fwd: function(){
			window.history.go(1);			
		},
		
		/** 
		 * Stores current router state to session store so that we can reload when user navigates back 
		 * 
		 */
		saveRouterState: function(storageKey){
			if (!this.isStateful){
				return;
			}
			if (window.sessionStorage){
				console.log("Saving state");
				storageKey = storageKey || "obno.router";
				var routerState = safejson.stringify({'stateHistory': router.stateHistory, 'stateStack': router.stateStack, 'stateIndex':router.stateIndex});
				try{
					window.sessionStorage.setItem(storageKey, routerState);
					console.log("Saved state");
				}
				catch (e) {
					if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {
						//ipad bug, sometimes u need to unset before setting
						if (window.sessionStorage.length) {
							try{
								window.sessionStorage.removeItem(storageKey);
								window.sessionStorage.setItem(storageKey, routerState);
							}
							catch (ignore){
								//nothing we can do
							}
						} else {
							//in private mode? cannot do anything
						}
					}
				}
			}
		},
		
		/**
		 * Load router state from session store
		 */
		loadRouterState: function(storageKey){
			if (!this.isStateful){
				return;
			}
			if (window.sessionStorage){
				storageKey = storageKey || "obno.router";
				try{
					var routerStateString = window.sessionStorage.getItem(storageKey);
					var routerState;
					if (routerStateString){
						routerState = json.parse(routerStateString);
					}
					if (routerState){
						console.log("Loaded state");
						router.stateHistory = routerState.stateHistory || {};
						router.stateStack = routerState.stateStack || [];
						router.stateIndex = routerState.stateIndex || -1;
					}
				} catch (ignore){
					
				}
			}
		},
		
		
		/**
		 * push the route path and data to the state history
		 */
		pushState: function(path, data){
			if (!this.isStateful){
				return;
			}
			//1. extract route
			var route = path.slice(0, path.indexOf('?'));
			//2. extract state id
			var stateUID;
			if(path.indexOf('?') > 0){
				pathArgs = ioQuery.queryToObject(path.slice(path.indexOf('?') + 1));
				if ("__state-uid" in pathArgs){
					stateUID = pathArgs['__state-uid'];
				}
			}
			if (route && stateUID){
				var routeHistory;
				if (route in router.stateHistory){
					routeHistory = router.stateHistory[route];
				} else {
					routeHistory = {};
					router.stateHistory[route] = routeHistory;
				}
				if (!(stateUID in routeHistory)){
					//get hold of the current state if it exists and purge anything after it
					if (router.stateIndex >= 0 && router.stateStack.length > router.stateIndex + 1){
						var spliced = router.stateStack.splice(router.stateIndex + 1, router.stateStack.length);
						array.forEach(spliced, function(r){
							if (router.stateHistory[r.route] && router.stateHistory[r.route][r.stateUID]){
								delete router.stateHistory[r.route][r.stateUID];
							}
						});
					}
					//a new state so push the route/stateUID key into the history stack so that we can then easily purge dead history entries
					router.stateStack.push({'route': route, 'stateUID': stateUID});
					router.stateIndex = router.stateStack.length - 1;
					routeHistory[stateUID] = {};
					routeHistory[stateUID]['index'] = router.stateIndex;
				} 
				routeHistory[stateUID]['data'] = data;
				//this.saveRouterState();
				console.log("history: currentIndex= " + router.stateIndex + ", states= " + safejson.stringify(router.stateStack));
			} else {
				//nothing to do
				return;
			}			
		},
		
		/**
		 * loads the state from history if it exists
		 */
		loadState: function(path){
			if (!this.isStateful){
				return null;
			}
			//1. extract route
			var route = path.slice(0, path.indexOf('?'));
			//2. extract state id
			var stateUID;
			if(path.indexOf('?') > 0){
				pathArgs = ioQuery.queryToObject(path.slice(path.indexOf('?') + 1));
				if ("__state-uid" in pathArgs){
					stateUID = pathArgs['__state-uid'];
				}
			}
			if (route && stateUID){
				var routeHistory;
				if (route in router.stateHistory){
					routeHistory = router.stateHistory[route];
					if (routeHistory && routeHistory[stateUID]){
						var data = routeHistory[stateUID]['data'];
						//this is a move into history we need to find our index in the history stack and set it as the current index
						router.stateIndex = routeHistory[stateUID]['index'];
						if (data){
							return data;
						} else {
							return null;
						}						
					} else {
						return null;
					}
				}
			} else {
				return null;
			}
		},
		
		_addSuid: function(path){
			var suid = ((new Date()).getTime()).toString(36) + (Math.floor(Math.random() * (10000000 - 1)) + 1).toString(36);
			if (path.indexOf('?') > 0){
				return path + "&__state-uid=" + suid;
			} else {
				return path + "?__state-uid=" + suid;
			}
		}
	});
	
	
	//map of routes to map of states
	router.stateHistory = {};	
	//the history stack
	router.stateStack = [];
	//the current index in the history stack
	router.stateIndex = -1;
	return new router({});
});