/*!
 * jQueryMobile-router v20130527
 * http://github.com/azicchetti/jquerymobile-router
 *
 * Copyright 2011-2013 (c) Andrea Zicchetti
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://github.com/azicchetti/jquerymobile-router/blob/master/MIT-LICENSE.txt
 * http://github.com/azicchetti/jquerymobile-router/blob/master/GPL-LICENSE.txt
 */
(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(['jquery'], factory);
  } else {
    factory(jQuery);
  }
}(this, function($) {

$(document).on("mobileinit", function(){
  /* supports the following configurations:
    $.mobile.jqmRouter.fixFirstPageDataUrl=true
    $.mobile.jqmRouter.firstPageDataUrl="index.html"
      jQM doesn't handle correctly the dataurl of the first page you display
      in a single-page template mode. In fact, the page is fetched again
      from the server the first time you try to access it through a link.
      If this option is set to true, jquery mobile router extensions will
      try to fix this problem. In order to set the data url, you have to
      provide the name of the file containing the first page into the
      "firstPageDataUrl" property (for example: index.html)
  */

  var config = $.extend({
    fixFirstPageDataUrl: false, firstPageDataUrl: "index.html",
    ajaxApp: false, firstMatchOnly: false, defaultArgsRe: false
  }, $.mobile.jqmRouter || {});


  var previousUrl = null, nextUrl = null, ignoreNext = false;

  $(document).on("pagebeforechange", function(e, data) {
    if ( data.options.hasOwnProperty("_jqmrouter_handled") ){ return; }

    var toPage;
    if ( typeof data.toPage === "string" ){
      toPage = data.toPage;
    } else {
      toPage = data.toPage.jqmData("url") || "";
      if (data.toPage.attr("id") == toPage){ toPage = "#" + toPage; }
    }

    data.options._jqmrouter_handled = true;

    // handle form submissions
    if ( data.options.data && (data.options.type+"").toLowerCase() == "get" ){
      toPage += "?" + data.options.data;
    }
    var u = $.mobile.path.parseUrl( toPage );
    previousUrl = nextUrl;
    nextUrl = u;

    if (u.hash.indexOf("?") != -1){
      data.options.dataUrl = u.hash.replace(/^#/,"");
    }

    // conditions to possibly set allowSamePageTransitions: we have a "?" or we're transitioning from
    //  #foo?params to #foo
    var noparamsRE = /^#|\?.*$/g;
    if (  previousUrl && u.hash != previousUrl.hash &&
          u.hash.replace(noparamsRE,"") == previousUrl.hash.replace(noparamsRE,"")
    ){
      data.options.allowSamePageTransition = true && !ignoreNext;
    }
    ignoreNext = false;
    if ( window.location.hash.indexOf("&ui-state=dialog") != -1 ){
      // prevent popups from triggering unwanted pageshow events by forcing allowSamePageTransition=false
      ignoreNext = true;
    }
  });


  if ( config.fixFirstPageDataUrl ){
    $(document).ready(function(){
      if ( !window.location.pathname.match("/$") ){
        return;
      }     
      var page = $(":jqmData(role='page')").first();
      var dataUrl = page.jqmData("url"),
          guessedDataUrl = window.location.pathname
            + config.firstPageDataUrl
            + window.location.search
            + window.location.hash
      ;

      if (dataUrl != guessedDataUrl){
        page.attr( "data-url",guessedDataUrl ).jqmData( "url",guessedDataUrl );
      }
    });
  }

  $.mobile.Router = function(userRoutes,userHandlers,conf){
    /* userRoutes format:
      {
        "regexp": "function name", // defaults to jqm pagebeforeshow event
        "regexp": function(){ ... }, // defaults to jqm pagebeforeshow event
        "regexp": { handler: "function name", events: "bc,c,bs,s,bh,h"  },
        "regexp": { handler: function(){ ... }, events: "bc,c,bs,s,bh,h" }
      }
    */
    this.routes = {
      pagebeforecreate: null, pagecreate: null,
      pagebeforeshow: null, pageshow: null,
      pagebeforehide: null, pagehide: null,
      pageinit: null, pageremove: null,
      pagebeforechange: null, pagebeforeload: null,
      pageload: null,
      popupbeforeposition: null, popupafteropen: null, popupafterclose: null
    };
    this.evtLookup = {
      bC: "pagebeforechange", bl: "pagebeforeload",
      l: "pageload",
      bc: "pagebeforecreate", c: "pagecreate",
      bs: "pagebeforeshow", s: "pageshow",
      bh: "pagebeforehide", h: "pagehide",
      i: "pageinit", rm: "pageremove",
      pbp: "popupbeforeposition", pao: "popupafteropen", pac: "popupafterclose"
    };
    this.routesRex = {};
    this.conf = $.extend({}, config, conf || {});
    this.defaultHandlerEvents = {};
    if (this.conf.defaultHandlerEvents) {
      var evts = this.conf.defaultHandlerEvents.split(",");
      for (var i = 0; i < evts.length; i++) {
        this.defaultHandlerEvents[this.evtLookup[evts[i]]] = evts[i];
      }
    }
    this.add(userRoutes,userHandlers);
  }
  $.extend($.mobile.Router.prototype,{
    documentEvts: { pagebeforechange:1, pagebeforeload:1, pageload:1 },

    debug: function(err){ 
      var conf = this.conf; 
      if (conf.debugHandler){
        conf.debugHandler.apply(this, arguments);
      } else if (conf.debugHandler !== false && typeof(console) != "undefined" && console.log){
	console.log.apply(console, arguments);
        // for webkit
        if (err.stack){ console.log(err.stack); }
      }
    },

    add: function(userRoutes,userHandlers,_skipAttach){
      if (!userRoutes) { return; }

      var _self=this, evtList=[], docEvtList=[];
      if (userRoutes instanceof Array){
        $.each(userRoutes, $.proxy(function(k,v){
          this.add(v,userHandlers,true);
        },this));
      } else {
        $.each(userRoutes,function(r,el){
          if( typeof(el) == "string" || typeof(el) == "function" ){
            if (_self.routes.pagebeforeshow === null){
              _self.routes.pagebeforeshow = {};
            }
	    if (_self.conf.defaultArgsRe){
	      r += '(?:[?](.*))?$';
	    }
            _self.routes.pagebeforeshow[r] = { handler: el, events: 'bs' };
            if ( ! _self.routesRex.hasOwnProperty(r) ){
              _self.routesRex[r] = new RegExp(r);
            }
          } else {
            var i, trig = el.events.split(","), evt;
	    if (el.argsre !== false && (el.argsre || _self.conf.defaultArgsRe) ){
	      r += '(?:[?](.*))?$';
	    }
            for( i = 0; i < trig.length; i++ ){
              evt = _self.evtLookup[trig[i]];
              if ( _self.routes.hasOwnProperty(evt) ){
                if ( _self.routes[evt] === null ){
                  _self.routes[evt] = {};
                }
                _self.routes[evt][r] = el;
                if ( ! _self.routesRex.hasOwnProperty(r) ){
                  _self.routesRex[r] = new RegExp(r);
                }
              } else {
                _self.debug("can't set unsupported route " + trig[i]);
              }
            }
          }
        });
      }
      if ( _skipAttach === true ) { return; }
      if ( !this.userHandlers ) {
        this.userHandlers = userHandlers || {};
      } else {
        $.extend(this.userHandlers,userHandlers || {});
      }
      $.each(_self.routes, function(evt,el){
        if ( el !== null ){
          if ( !_self.documentEvts[evt] ){
            evtList.push(evt);
          } else {
            docEvtList.push(evt);
          }
        }
      });
      this._detachEvents();
      var routeHandler = function(e,ui){ _self._processRoutes(e,ui,this); };
      if ( evtList.length > 0 ){
        this._eventData = {
          events: evtList.join(" "),
          selectors: ":jqmData(role='page'),:jqmData(role='dialog')",
          handler: routeHandler
        };
        $(document).on( this._eventData.events, this._eventData.selectors, this._eventData.handler );
      }
      if ( docEvtList.length > 0 ){
        this._docEventData = { events: docEvtList.join(" "), handler: routeHandler };
        $(document).on( this._docEventData.events, this._docEventData.handler );
      }
    },

    _processRoutes: function(e,ui,page) {
      var _self=this, refUrl, url, retry = 0, bCData = null;
      if (e.type == "pagebeforechange"){
	if (ui.options._jqmrouter_bC){
	  // we must return when _jqmrouter_bC is set to avoid loops
          return;
	}
        bCData = {
          isString: typeof ui.toPage === "string",
          deferred: $.Deferred(),
          toPage: ui.toPage
        };
        // normalizing the "page" reference whenever it's possible, 'cause we want to pass it to the handler
        page = !bCData.isString ? ui.toPage : null;
      }
      if ( e.type in { "pagebeforehide":true, "pagehide":true, "pageremove": true } ){
        refUrl = previousUrl;
      } else {
        refUrl = nextUrl;
      }

      url = !this.conf.ajaxApp ?
        refUrl.hash : (refUrl.pathname + refUrl.search + refUrl.hash)
      ;
      if (url.length == 0){
        // if ajaxApp is false, url may be "" when the user clicks the back button
        // and returns to the first page of the application (which is usually
        // loaded without the hash part of the url). Let's handle this...
        refUrl = null;
        if (!this.documentEvts[e.type] && page){
          refUrl = $(page).jqmData("url");
        } else {
          // mhm, we should be visiting the first page of the app
          refUrl = $.mobile.firstPage.jqmData("url");
          //refUrl = $(":jqmData(role=page):first").jqmData("url");
        }
        if (refUrl){
          refUrl = $.mobile.path.parseUrl("#" + refUrl);
          url = !this.conf.ajaxApp ?
            refUrl.hash : (refUrl.pathname + refUrl.search + refUrl.hash)
          ;
        }
      }
      if (url.length == 0){ return; }

      var bHandled = false;
      $.each(this.routes[e.type], function(route,routeConf){
        var res, handleFn;
        if (bCData && routeConf.step != 'all'){
          routeConf.step = routeConf.step || 'page';  // default step is page
          if ( (bCData.isString && routeConf.step == 'page') || (!bCData.isString && routeConf.step != 'page') ){
            return;
          }
        }
        if ( (res = url.match(_self.routesRex[route])) ) {
          if ( typeof(routeConf.handler) == "function" ){
            handleFn = routeConf.handler;
          } else if ( typeof(_self.userHandlers[routeConf.handler]) == "function" ) {
            handleFn = _self.userHandlers[routeConf.handler];
          }
          if ( handleFn ){
            try {
              if (bCData && ui){
                ui.bCDeferred = bCData.deferred;
              }
              handleFn.apply(_self.userHandlers, [e.type,res,ui,page,e]);
              bHandled = true;
            } catch(err){
              _self.debug(err);
            }
          }
        }
        if ( bHandled && _self.conf.firstMatchOnly ){ return false; }
      });
      //Pass to default if specified and can handle this event type
      if ( !bHandled && this.conf.defaultHandler && this.defaultHandlerEvents[e.type] ) {
        var handleFn;
        if ( typeof(this.conf.defaultHandler) == "function" ) {
          handleFn = this.conf.defaultHandler;
        } else if ( typeof(this.userHandlers[this.conf.defaultHandler]) == "function" ) {
          handleFn = this.userHandlers[this.conf.defaultHandler];
        }
	try {
	  handleFn.apply(this.userHandlers, [e.type, ui, page, e]);
	} catch(err){
          this.debug(err);
        }
      }

      if (bCData && e.isDefaultPrevented()){
        bCData.deferred.done(function(){
	  // if the user re-routed the bC, we shouldn't set _jqmrouter_handled or _jqmrouter_bC
	  var extraOpt = (bCData.toPage === ui.toPage ?
            { _jqmrouter_handled: true, _jqmrouter_bC: true } : null
          );
	  // destination page is refUrl.href, ui.toPage or page.
	  // I'm using ui.toPage so that really crazy users may try to re-route the transition to
	  //   another location by modifying this property from the handler.
          $.mobile.changePage(ui.toPage, $.extend({
            allowSamePageTransition: ui.options.allowSamePageTransition,
            changeHash: ui.options.changeHash,
            data: ui.options.data,
            dataUrl: ui.options.dataUrl,
            pageContainer: ui.options.pageContainer,
            reloadPage: ui.options.reloadPage,
            reverse: ui.options.reverse,
            role: ui.options.role,
            showLoadMsg: ui.options.showLoadMsg,
            transition: ui.options.transition,
            type: ui.options.type
          }, extraOpt));
	});
      }
    },

    _detachEvents: function(){
      if (this._eventData){
        $(document).off( this._eventData.events, this._eventData.selectors, this._eventData.handler );
      }
      if (this._docEventData){
        $(document).off( this._docEventData.events, this._docEventData.handler );
      }
    } ,

    destroy: function(){
      this._detachEvents();
      this.routes = this.routesRex = null;
    } ,

    getParams: function(hashparams) {
      if (!hashparams) { return null; }
      var params = {}, tmp;
      var tokens = hashparams.slice( hashparams.indexOf('?') + 1 ).split("&");
      $.each(tokens, function(k,v){
        tmp = v.split("=");
        tmp[0] = decodeURIComponent(tmp[0]);
        if ( params[tmp[0]] ){
          if ( !(params[tmp[0]] instanceof Array) ) {
            params[tmp[0]] = [ params[tmp[0]] ];
          }
          params[tmp[0]].push( decodeURIComponent(tmp[1]) );
        } else {
          params[tmp[0]] = decodeURIComponent(tmp[1]);
        }
      });
      if ( $.isEmptyObject(params) ) { return null; }
      return params;
    }
  });

});
return {};

}));
