/**
 * AutoHistory API:
 *
 * The module AutoHistory has the following public method:
 *   recordHistEvent(id, oldVal, newVal)
 *      id     - the component ID of a ExtJS component which supports the NavState interface,
 *      oldVal - a string representing the previous navigation state
 *      newVal - a string representing the new navigation state.
 *
 * The strings can be anything the caller wants.  They will be passed back as-is.
 *
 * The caller must support the following interface:
 *
 *   getNavState(): navVal
 *      navVal - a string representing the current navigation state
 *   setNavState(navVal)
 *      navVal - a string previously passed to recordHistEvent
 */

Ext.ns('Ext.ux.RapidApp');

Ext.ux.RapidApp.HistoryInit = function() {
	Ext.History.init();
	Ext.History.on('change', Ext.ux.RapidApp.HashNav.handleHashChange);
	Ext.ux.RapidApp.HashNav.INITIALIZED = true;
}

Ext.ux.RapidApp.HashNav = {
	
	INITIALIZED: false,
	INIT_LOCATION_HASH: window.location.hash,
	INIT_TITLE: document.title,
	ignoreHashChange: false,
	
	hashpath_to_autoLoad: function(hashpath) {
		var token = hashpath;
		
		// hashpath with or without leading #
		if(hashpath.search('#') == 0) { token = hashpath.substring(1); }
		
		// valid hashpaths must start with '!/'
		if(token.search('!/') !== 0) { return null; }
		
		var url = token.substring(1); // strip leading !
		var params = {};
		
		// double ?? means base64+json encoded query string (params):
		var parts = url.split('??');
		if(parts.length > 1) {
			url = parts.shift();
			var encP = parts.join('??');
			params = Ext.decode(base64.decode(encP));
		}
		else {
			// else, single ? is a standard urlEncoded query string
			parts = url.split('?');
			if(parts.length > 1) {
				url = parts.shift();
				params = Ext.urlDecode(parts.join('?'));
			}
		}
		
		var autoLoad = {
			url: url, 
			params: params 
		};
		
		return autoLoad;
	},
	
	isParamsUrlSafe: function(params) {
		var safe = true;
		Ext.iterate(params,function(k,v){
			if(v.search && v.search('=') !== -1) { safe = false; }
		});
		return safe;
	},
	
	autoLoad_to_hashpath: function(autoLoad){
		if(Ext.isObject(autoLoad) && Ext.isString(autoLoad.url)) { 
			// We never want to see %2ff type characters (needed for chrome in certain places)
      var url = decodeURIComponent( autoLoad.url );
      
      // Ignore if url doesn't start with /:
      if(url.search('/') !== 0) { return null; }
      var hashpath = '#!' + url;

			if(Ext.ux.RapidApp.HashNav.isParamsUrlSafe(autoLoad.params)) {
				// Use standard url encoded query string:
				var encParams = autoLoad.params ? Ext.urlEncode(autoLoad.params) : '';
				if(encParams.length > 0) { 
					hashpath += '?' + encParams; 
				}
			}
			else {
				// Use special base64+json encoded query string, denoted with double '??'
				var encP = Ext.encode(autoLoad.params || {});
				if(encP !== '{}') {
					hashpath += '??' + base64.encode(encP);
				}
			}
			
			return hashpath;
		}
		return null;
	},
	
	// Set's the hashpath without doing a nav:
	setHashpath: function(load) {
		var autoLoad = Ext.isString(load) ? {url:load,params:{}} : load;
		var hashpath = Ext.ux.RapidApp.HashNav.autoLoad_to_hashpath(autoLoad);
		if(hashpath && decodeURIComponent(window.location.hash) !== decodeURIComponent(hashpath)) {
			// Git Issue #1
      //  This setting was an ugly attempt to track state, but in certain cases it
      //  is not properly reset causing a manual URL change by the user to be ignored.
      //  Furthermore, I don't *think* that this is even needed anymore because the
      //  AppTab is smarter now to do the right thing, but I am not sure. I am turning
      //  this off for now to see if it causes problems and if it doesn't I will come 
      //  back later and remove the rest of the 'ignoreHashChange' checks below
      // UPDATE: ignoreHashChange *was* very much still needed for Chrome. Without it,
      //  infinate tabs can open! -- but, this appears to be caused from improper url %
      //  encode/decode... Updated 'autoLoad_to_hashpath' below to wrap decodeURIComponent
      //  which appears to have fixed this and allowed ignoreHashChange to remain disabled.
      //  will still need to keep an eye and do more testing before removing for good...
      //Ext.ux.RapidApp.HashNav.ignoreHashChange = true;
			window.location.hash = hashpath;
		}
	},
  
  clearHashpath: function() {
    window.location.hash = '';
  },
	
	handleHashChange: function(hashpath) {
    if(Ext.ux.RapidApp.HashNav.ignoreHashChange) {
			Ext.ux.RapidApp.HashNav.ignoreHashChange = false;
			return;
		}
		
		var loadTarget = Ext.getCmp('main-load-target');
		if(!loadTarget) { return; }
		
		var autoLoad = Ext.ux.RapidApp.HashNav.hashpath_to_autoLoad(hashpath);
		if(!autoLoad) {
			// Try to reset the hashpath to the active tab
			var tab = loadTarget.getActiveTab.call(loadTarget);
			autoLoad = tab ? tab.autoLoad : null;
			return Ext.ux.RapidApp.HashNav.setHashpath(autoLoad);
		}
		
		loadTarget.loadContent({ autoLoad: autoLoad });
		Ext.ux.RapidApp.HashNav.ignoreHashChange = false;
	},
	
	/*  Ext.ux.RapidApp.HashNav.formSubmitHandler:
	 Function to be used as 'onsubmit' for any html form tag/element to
	 "submit" the form to a hashpath/loadcontent url instead of doing 
	 an actual GET/POST, directly. 
	 
	 Example:
	
		<form 
		 action="#!/main/explorer/navtree/classicdb_employees" 
		 onsubmit="return Ext.ux.RapidApp.HashNav.formSubmitHandler.apply(this,arguments);"
		>
			<label for="quick_search">Search Employees:</label>
			<input type="text" name="quick_search" />
		</form>
		
	 If 'abc123' were typed into the form/field it would load the following hashpath url:
		
		#!/main/explorer/navtree/classicdb_employees?quick_search=abc123
		
	*/
	formSubmitHandler: function(e) {

		var action = this.getAttribute('action');
		if(!action || action.search('#!/') !== 0) {
			alert("Invalid form action URL: '" + action + 
				'" - HashNav form actions must be valid hashpaths starting with \'#!/\'');
			return false;
		}
		
		var parts = action.split('?');
		if(parts.length > 2) {
			alert("Invalid form action URL: '" + action + 
				'" - multiple question-mark (?) characters are not allowed');
			return false;
		}
		
		var url = action;
		var params = {};
		
		if(parts.length > 1) {
			url = parts.shift();
			params = Ext.urlDecode(parts.join('?'));
		}
		
		for (var i = 0; i < this.elements.length; i++) {
			var name = this.elements[i].name, value = this.elements[i].value;
			if(name && value) {
        // Special element name triggers appending URL:
        if(name == '_append_action_url') {
          url = [url,value].join('/');
        }
        else {
          if(params[name]) {
            alert("duplicate param name '" + name + "' in HashNav form/url - not supported");
            return false;
          }
          params[name] = value;
        }
			}
		}
		
		var hashpath = url;
		var encParams = Ext.urlEncode(params);
		if(encParams.length > 0) {
			hashpath = url + '?' + encParams;
		}
		
		window.location.hash = hashpath;
		
		// Important! the onsubmit handler *must* return false to stop the
		// normal GET/POST browser submit operation (but we also called
		// e.preventDefault() first, so this isn't also needed)
		return false;
	},

  updateTitle: function(title) {
    if(title && Ext.isString(title)) {
      // New: if ita tag, attempt to parse it and use its innerHTML (GitHub Issue #117)
      if(title.search('<') == 0){
        var el = document.createElement( 'div' );
        el.innerHTML = title;
        if(el && el.children.length > 0) {
          return Ext.ux.RapidApp.HashNav.updateTitle(el.children[0].innerHTML);
        }
        else {
          document.title = Ext.ux.RapidApp.HashNav.INIT_TITLE;
        }
      }
      
      // This will have any html entities decoded, and stripped of leading/trailing whitespace:
      var txtarea = document.createElement( 'textarea' );
      txtarea.innerHTML = title;
      title = txtarea.value.replace(/^\s+|\s+$/g,'');
      
      if(title.length > 0) {
        document.title = title + ' - ' + Ext.ux.RapidApp.HashNav.INIT_TITLE;
      }
      else {
        document.title = Ext.ux.RapidApp.HashNav.INIT_TITLE;
      }
    }
    else {
      document.title = Ext.ux.RapidApp.HashNav.INIT_TITLE;
    }
  },
  
  // New: util function - converts a normal URL into a hashpath
  urlToHashPath: function(url) {
    // Return urls that already start with '#' as-is
    if(url.search('#') == 0) { return url; }
    
    //absolute: 
    var hashpath = '#!' + url;
    
    // relative:
    if(url.search('/') !== 0) {
      var parts = window.location.hash.split('/');
      if(parts[0] == '#!') {
        // If we're already at a hashnav path, use it as the base:
        parts.pop();
        parts.push(url);
        hashpath = parts.join('/');
      }
      else {
        // make absolute:
        hashpath = '#!/' + url
      }
    }
    
    return hashpath;
  }
};





/* -- Old component-id-based history code

Ext.ux.RapidApp.HistoryInit = function() {
	Ext.History.init();
	Ext.History.on('change', function(token) { Ext.ux.RapidApp.AutoHistory.handleHistChange(token); });
	Ext.ux.RapidApp.AutoHistory.installSafeguard();
};


Ext.ux.RapidApp.AutoHistory= {
	navIdx: 0,
	wrapIdx: function(idx) { return idx > 99? (idx - 100) : (idx < 0? idx + 100 : idx); },
	isForwardNav: function(oldIdx, newIdx) { var diff= this.wrapIdx(newIdx-oldIdx); return diff < 50; },
	currentNav: '',
	
	// add a fake nav event to prevent the user from backing out of the page
	installSafeguard: function() {
		this.currentNav= ''+this.navIdx+':::';
		Ext.History.add(this.currentNav);
	},
	
	// follow a nav event given to us by the application
	recordHistEvent: function(id, oldval, newval) {
		if (!newval) return;
		
		this.navIdx= this.wrapIdx(this.navIdx+1);
		this.currentNav= ''+this.navIdx+':'+id+':'+oldval+':'+newval;
		//console.log('recordHistEvent '+this.currentNav);
		
		Ext.History.add(this.currentNav);
	},
	
	performNav: function(id, newVal) {
		if (!id) return;
		if (!newVal) return;
		var target= Ext.getCmp(id);
		if (!target) return;
		//console.log('AutoHistory.performNav: '+id+'->setNav '+newVal);
		target.setNavState(newVal);
	},
	
	// respond to user back/forward navigation, but ignore ones generated by recordHistEvent
	handleHistChange: function(navTarget) {
		if (!navTarget) {
			if (this.currentNav) {
				var parts= this.currentNav.split(':');
				this.performNav(parts[1], parts[2]);
			}
			this.installSafeguard();
			return;
		}
		
		// ignore events caused by recordHistEvent
		if (navTarget != this.currentNav) {
			//console.log('AutoHistory.handleHistChange: '+this.currentNav+' => '+navTarget);
			var parts= navTarget.split(':');
			var navTargetIdx= parseInt(parts[0]);
			if (this.isForwardNav(this.navIdx, navTargetIdx)) {
				this.performNav(parts[1], parts[3]);
			}
			else {
				var parts= this.currentNav.split(':');
				this.performNav(parts[1], parts[2]);
			}
			this.currentNav= navTarget;
			this.navIdx= navTargetIdx;
		}
	}
};


Ext.override(Ext.TabPanel, {
	initComponent_orig: Ext.TabPanel.prototype.initComponent,
	initComponent: function() {
		//this.constructor.prototype.initComponent.call(this);
		this.initComponent_orig.apply(this,arguments);
		this.internalTabChange= 0;
		
		this.on('beforetabchange', function(tabPanel, newTab, currentTab) {
			if (newTab && currentTab && !this.internalTabChange) {
				Ext.ux.RapidApp.AutoHistory.recordHistEvent(tabPanel.id, currentTab.id, newTab.id);
			}
			this.internalTabChange= 0;
			return true;
		});
	},
	setNavState: function(navVal) {
		var newTab= Ext.getCmp(navVal);
		if (newTab) {
			this.internalTabChange= 1;
			this.setActiveTab(newTab);
		}
	},
	getNavState: function() { return this.getActiveTab()? this.getActiveTab().id : ""; }
});

*/