;(function($) {
  'use strict';

  var $win = $(window),
      // $(document)
      $doc = $(document),
      // $(document.body)
      $body,
      // 浏览器地址栏
      location = window.location,
      // 浏览器历史记录
      history = window.history,
      // 是否是ios
      isios = navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad)/),
      // popstate事件state数据中转
      historystate,
      // 框架是否已经启动
      hasBoot = false,
      // 在视图转场过程中，是否阻塞其他转场请求
      isPrevent = false,
      // 是否是首次打开视图
      transitFirst = true,
      // 页面视图定义集合
      routes = {},
      // 面板视图定义集合
      panels = {},
      // 路由请求历史
      hashhistory = [],
      // 视图默认z-index
      pagezIndex = 2000,
      // 前一个视图z-index
      prevPagezIndex = 2001,
      // 当前视图z-index
      curPagezIndex = 2002,
      // 缓存视图数量，默认0缓存全部
      viewcachecount = 0,
      // 页面视图缓存
      pagescache = {},
      // 面板视图缓存
      panelscache = {},
      // 视图顺序缓存
      viewscache = [],
      // 视图数据
      viewsdata = {},
      // 全凭模式监控层
      $fullscreen,
      // 透明遮罩层
      // $cover,
      // loading遮罩层
      $loader,
      // 当前视图
      $curPage,
      // 框架样式
      viewStyle = 'body {position: relative; margin: 0; padding: 0; width: 100%; overflow: hidden;}\
        .spa-fullscreen {position: absolute; left: 0; top: 0; margin: 0; padding: 0; width: 100%; visibility: hidden; overflow: hidden; z-index: -1; }\
        .spa-page {position: absolute; left: 0; top: 0; bottom: 0; right: 0; margin: 0; padding: 0; overflow: hidden; z-index: 2000; -webkit-transform: translateZ(0); -webkit-backface-visibility: hidden; -webkit-transform-style: preserve-3d;}\
        .spa-page-bg {position: absolute; left: 0; top: 0; bottom: 0; right: 0; margin: 0; padding: 0; }\
        .spa-page-body {position: absolute; left: 0; top: 0; bottom: 0; right: 0; margin: 0; padding: 0; overflow: hidden; -webkit-transform: translateZ(0); -webkit-backface-visibility: hidden; -webkit-transform-style: preserve-3d;}\
        .spa-scroll {overflow: auto;}\
        .spa-scroll-touch {-webkit-overflow-scrolling: touch; }\
        .spa-scroll-x {overflow-y: hidden;}\
        .spa-scroll-y {overflow-x: hidden;}\
        .spa-cover {display: none; position: absolute; left: 0; right: 0; top: 0; bottom: 0; text-align: center; z-index: 5000; }\
        .spa-loader {display: none; position: absolute; left: 0; right: 0; top: 0; bottom: 0; text-align: center; overflow: hidden; z-index: 5001; }',
      // loading层元素
      loaderBody = '<div class="spa-loader-animate"><div class="bg"></div><span class="ball"></span><span class="ball"></span></div>',
      // loading层样式
      loaderStyle = '.spa-loader-animate {position: absolute; top: 50%; left: 50%;}\
        .spa-loader-animate .bg {position: absolute; width: 64px; height: 64px; margin: 0 auto; top: -32px; left: -32px; border-radius: 50%; background: #2C3E50; opacity: 0.5;}\
        .spa-loader-animate .ball {display: block; float: left; padding: 8px; margin-top: -8px; margin-left: -10px; -webkit-border-radius: 50%; -moz-border-radius: 50%; -ms-border-radius: 50%; -o-border-radius: 50%; border-radius: 50%;}\
        .spa-loader-animate span:nth-child(2) {background: #16A085; -webkit-animation: move-left 800ms ease-in-out infinite alternate; -moz-animation: move-left 800ms ease-in-out infinite alternate; -ms-animation: move-left 800ms ease-in-out infinite alternate; -animation: move-left 800ms ease-in-out infinite alternate;}\
        .spa-loader-animate .ball:nth-child(3) {background: #E67E22; -webkit-animation: move-right 800ms ease-in-out infinite alternate; -moz-animation: move-right 800ms ease-in-out infinite alternate; -ms-animation: move-right 800ms ease-in-out infinite alternate; animation: move-right 800ms ease-in-out infinite alternate;}\
        @-webkit-keyframes move-left {to {-webkit-transform: translate(20px, 0); transform: translate(20px, 0); background: #e85932;}}\
        @-webkit-keyframes move-right {to {-webkit-transform: translate(-20px, 0); transform: translate(-20px, 0); background: #44bbcc;}}'

  // spa对外接口
  $.spa = {}

  // 获取视图数据
  $.spa.getViewData = function($view) {
    return viewsdata[$view.data('id')]
  }

  // 获取当前视图
  $.spa.getCurPage = function() {
    return $curPage
  }

  // 设置版本号
  $.spa.version = '1.0.4'

  /*
   * 插入样式
   */
  
  $doc.on('spa:addstyle', function(event, css) {
    $('head').append('<style type="text/css">' + css + '</style>')
  })
  
  
  /*
   * 全屏模式
   */

  ;(function() {
    var requestID,
        resizeID,
        winHeight,
        winWidth
    
    var adjust = function() {
      winHeight = Math.max($win.height(), window.innerHeight),
      winWidth = Math.max($win.width(), window.innerWidth)
            
      // 撑高body，收起iphone(ios<7) safari的地址栏
      $fullscreen.css({height: winHeight * 2})
      window.scrollTo(0, 0)
      
      //缓存window全屏时的高度
      $body.data('innerHeight', window.innerHeight)
      
      winHeight = Math.max($win.height(), window.innerHeight)
      
      if($fullscreen.height() != winHeight) {
        
        $body.css({
          width: winWidth,
          height: winHeight
        })
        
        $fullscreen.css({
          width: winWidth,
          height: winHeight
        })

        // 发现在某些安卓浏览器全凭模式和非全屏模式切换后
        // 手势的第一次的动作失效
        // 在这里可以做些处理？
      }
    }
    
    $win.on('spa:adjustfullscreen' + ($.os.ios?' orientationchange':''), function(event) {
      if(requestID !== undefined) {
        cancelAnimationFrame(requestID)
        requestID = undefined
      }
      requestID = requestAnimationFrame(adjust)
    })
    // android下orientationchange触发的时候页面很可能还没有渲染好
    // 所以马上取数据或者短延时取数据都有问题。
    // 暂时没有特别好的思路来处理这个问题
    $.os.android && $win.on('orientationchange', function(event) {
      clearTimeout(resizeID)

      resizeID = setTimeout(adjust, 500)
    })

    $win.on('resize', function(event) {
      clearTimeout(resizeID)
      resizeID = setTimeout(adjust, 200)
    })
  })()
    
  
  /*
   * scrollfix
   */
  
  $doc.on('spa:scroll', function(event, options) {
    var $target = $(event.target),
        direction = (options && options.direction) || ''
    
    $target.addClass('spa-scroll' + (direction ? ' spa-scroll-' + direction : ''))    
  })

  $doc.on('spa:removescroll', function(event, options) {
    var $target = $(event.target)
    
    $target.removeClass('spa-scroll')    
  })

  // ios设备才支持scrollfix
  isios && $doc.on('touchstart', '.spa-scroll, .spa-scroll-x, .spa-scroll-y', function(event) {

    var $target = $(event.currentTarget),
        scrollTop = $target.prop('scrollTop'),
        scrollLeft = $target.prop('scrollLeft'),
        height = $target.height(),
        width = $target.width(),
        scrollHeight = $target.prop('scrollHeight'),
        scrollWidth = $target.prop('scrollWidth')
    
    if($target.hasClass('spa-scroll') || $target.hasClass('spa-scroll-x')) {
      if(scrollLeft < 0) {
        // event.preventDefault()
      }
      if(scrollLeft <= 0) {
        $target.prop('scrollLeft', 1)
      }
      if(scrollLeft + width > scrollWidth) {
        // event.preventDefault()
      }
      if(scrollLeft + width >= scrollWidth) {
        $target.prop('scrollLeft', scrollWidth - width - 1)
      }
    }

    if($target.hasClass('spa-scroll') || $target.hasClass('spa-scroll-y')) {
      if(scrollTop < 0) {
        // event.preventDefault()
      }
      if(scrollTop <= 0) {
        $target.prop('scrollTop', 1)
      }
      if(scrollTop + height > scrollHeight) {
        // event.preventDefault()
      }
      if(scrollTop + height >= scrollHeight) {
        $target.prop('scrollTop', scrollTop - 1)
      }
    }
          
  })

  /*
   * fix body { overflow: hidden }
   * 阻塞body的滚动条
   * 该特性适用于ios6，解决Safari收起地址栏自动全屏的问题，但是会resize后（比如启动输入法）的页面滑动崩溃
   * 鉴于该特性已经不适用于ios7+，并且还带来更严重的bug，所以去掉
   */
  // $win.on('scroll', function(event) {
  //   var innerHeight = $body && $body.data('innerHeight')
    
  //   if(innerHeight && innerHeight !== window.innerHeight) {
  //     $win.trigger('spa:adjustfullscreen')
  //   }
  // })

  
  /*
   * 监听路由
   */
    
  function getHash(url) {
    url = url || location.href
    return url.replace(/^[^#]*#?\/?(.*)\/?$/, '$1')
  }
  
  $win.on('popstate', function(event) {
    // 框架启动前阻止路由请求
    if(!hasBoot) return

    // 在页面加载和转场过程中阻止路由请求
    // if(($cover && $cover.css('display') === 'block') || ($loader && $loader.css('display') === 'block')) {
    //   return false
    // }
    if($loader && $loader.css('display') === 'block') {
      return false
    }
    
    //如果当前页面是面板，则先收起之前的面板，再打开新页面
    if($curPage && $curPage.hasClass('spa-panel')) {
      var curPageId = $curPage.data('id'),
          curPageData = viewsdata[curPageId],
          $prevPage = curPageData.prevPage
      
      $prevPage.trigger('spa:openpage')
      
      return false
    }
    
    var hash = getHash()
    
    // 如果当前路由请求和上一次路由请求不一样
    // 则激活当前路由请求
    if(!hashhistory.length || hashhistory[hashhistory.length - 1] !== hash) {
      hashhistory.push(hash)
      
      var $page = pagescache[hash],
          pushData = event.state || {}
      
      // 如果有中转的state数据，则优先使用，并清除
      if(historystate) {
        pushData = historystate
        historystate = undefined
      }
                     
      if($page) {
        var pageId = $page.data('id'),
            pageData = viewsdata[pageId]

        // 更新pushData
        pageData.oldpushData = pageData.pushData
        pageData.pushData = pushData

        $page.trigger('spa:openpage')
      } else {
        $doc.trigger('spa:createpage', {hash: hash, pushData: pushData})
      }
    }
  })
  
  
  /*
   * 设置路由
   */

  var optionalParam = /\((.*?)\)/g,
      namedParam = /(\(\?)?:\w+/g,
      splatParam = /\*\w+/g,
      escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g

  var defaultPageOptions = {
    route: '',                            //路由规则
    animate: '',                          //入场动画效果
    classname: '',                        //自定义样式名
    view: function() { return {title: '', body: ''} },    //获取页面视图
    init: function() {},                  //初始化回调函数
    beforeopen: function() {},            //打开前回调
    afteropen: function() {},             //打开后回调
    beforeclose: function() {},           //关闭前回调
    afterclose: function() {}             //关闭后回调
  }
  
  //验证是否是正则表达式对象
  function isRegExp(obj) {
    return Object.prototype.toString.call(obj) == '[object RegExp]'
  }
  
  //路由的字符串表达式转正则表达式
  function _routeToRegExp(route) {
    route = route.replace(escapeRegExp, '\\$&')
                 .replace(optionalParam, '(?:$1)?')
                 .replace(namedParam, function(match, optional){
                   return optional ? match : '([^\/]+)'
                 })
                 .replace(splatParam, '(.*?)')
    return '^' + route + '$'
  }
  
  $doc.on('spa:route', function(event, options) {
    var args = Array.prototype.slice.call(arguments, 1)
    if(args.length > 1) {
      $.each(args, function(i, route) {
        $doc.trigger('spa:route', route)
      })
      
      return false
    }
    
    var route = options.route || ''
    
    if(!isRegExp(route)) {
      route = _routeToRegExp(route)
    }
    
    // 页面视图不允许使用面板视图转场动画
    if(options.animate && !$.isFunction(options.animate) && isPanelAnimate(options.animate)) {
      options.animate = ''
    }
    
    routes[route] = $.extend({}, defaultPageOptions, options)
  })
  
  
  /*
   * 页面场景
   */
    
  //解析路由请求参数
  function _extractParameters(route, hash) {
    var params = route.exec(hash).slice(1),
        decodeParams = []
        
    $.each(params, function(i, p) {
      p && decodeParams.push(decodeURIComponent(p))
    })
    
    return decodeParams
  }

  //切换容器的z-index
  function togglePagezIndex($fromPage, $toPage) {
    var fromPageData = viewsdata[$fromPage.data('id')],
        toPageData = viewsdata[$toPage.data('id')]

    // fromPageData && fromPageData.prevPage && fromPageData.prevPage.css({zIndex: pagezIndex})
    // toPageData.prevPage && toPageData.prevPage.css({zIndex: pagezIndex})
    $fromPage.css({zIndex: prevPagezIndex})
    $toPage.css({zIndex: curPagezIndex})
  }
    
  var transitPageAnimates = {},
      transitPageAnimatesName = {},
      transformName,
      transitionName,
      transitionEndEvent

  ;(function() {
    var transitions = {
      '-webkit-transition': ['-webkit-transform', 'webkitTransitionEnd'],
      '-moz-transition': ['-moz-transform', 'transitionend'],
      '-ms-transition': ['-ms-transform', 'msTransitionEnd'],
      '-o-transition': ['-o-transform', 'oTransitionEnd'],
      'transition': ['transform', 'transitionend']
    },
    el = document.createElement('div'),
    t

    for(t in transitions) {
      if(el.style[t] !== undefined) {
        transitionName = t
        transformName = transitions[t][0]
        transitionEndEvent = transitions[t][1]
        break
      }
    }
  })()

  // $el.transition
  $.fn.transition = function(properties, callback) {
    var $el = $(this)

    // css3动画是异步无阻塞的，防止同时重绘
    requestAnimationFrame(function() {
      $el.get(0).offsetWidth
      properties[transitionName] = '0.4s'
      $el.css(properties).emulateTransitionEnd(function() {
        // 过渡动画结束后移除 transition
        properties = {}
        properties[transitionName] = ''
        $el.css(properties)
        callback && callback()
      })
    })


    return $el
  }

  // transitionEnd 回调
  $.fn.emulateTransitionEnd = function(callback, duration) {
    var called = false,
        $el = $(this),
        endtimer

    duration = duration || 500

    // 只执行一次
    $el.one(transitionEndEvent, function() {
      called = true
      clearTimeout(endtimer)
      callback.call($el)
    })

    // 确保transitionend被执行
    var endcallback = function() {
      if(!called) {
        $el.trigger(transitionEndEvent)
      }
    }
    endtimer = setTimeout(endcallback, duration)

    return $el
  }

  //默认转场动画
  transitPageAnimates.defaultInOut = function($toPage, $fromPage, callback) {
    togglePagezIndex($fromPage, $toPage)
    callback()
  }
    
  //添加转场动画
  $doc.on('spa:addTransitPageAnimates', function(event, options) {
    var names = []
    
    $.each(options, function(name, fn) {
      names.push(name)
    })

    $.each(names, function(key, value) {
      if(key % 2 === 0) {
        transitPageAnimatesName[value] = names[key + 1]
      } else {
        transitPageAnimatesName[value] = names[key - 1]
      }
    })
    
    $.extend(transitPageAnimates, options)
  })
    
  $doc.trigger('spa:addTransitPageAnimates', {
    fadeIn: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          toStartCss = {opacity: 0},
          toEndCss = {opacity: 1}

      toStartCss.opacity = 0
      
      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        callback()
      })
    },
    fadeOut: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          fromStartCss = {opacity: 0}

      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        callback()
      })      
    },
    slideInLeft: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          toStartCss = {},
          toEndCss = {}

      toStartCss[transformName] = 'translate(100%, 0)'
      toEndCss[transformName] = 'translate(0%, 0)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        callback()
      })
    },
    slideOutRight: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          fromStartCss = {}

      fromStartCss[transformName] = 'translate(100%, 0)'

      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        callback()
      })
    },
    slideInRight: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          toStartCss = {},
          toEndCss = {}

      toStartCss[transformName] = 'translate(-100%, 0)'
      toEndCss[transformName] = 'translate(0%, 0)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        callback()
      })
    },
    slideOutLeft: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          fromStartCss = {}

      fromStartCss[transformName] = 'translate(-100%, 0)'

      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        callback()
      })
    },
    slideInUp: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          toStartCss = {},
          toEndCss = {}

      toStartCss[transformName] = 'translate(0, 100%)'
      toEndCss[transformName] = 'translate(0, 0%)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        callback()
      })
    },
    slideOutDown: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          fromStartCss = {}

      fromStartCss[transformName] = 'translate(0, 100%)'

      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        callback()
      })
    },
    slideInDown: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          toStartCss = {},
          toEndCss = {}

      toStartCss[transformName] = 'translate(0, -100%)'
      toEndCss[transformName] = 'translate(0, 0%)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        callback()
      })
    },
    slideOutUp: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          fromStartCss = {}

      fromStartCss[transformName] = 'translate(0, -100%)'

      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        callback()
      })
    },
    pushInLeft: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          isFinish = 0,
          toStartCss = {},
          toEndCss = {},
          fromStartCss = {}

      toStartCss[transformName] = 'translate(100%, 0)'
      toEndCss[transformName] = 'translate(0%, 0)'

      fromStartCss[transformName] = 'translate(-100%, 0)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        ;(++isFinish == 2) && callback()
      })
    },
    pushOutRight: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          isFinish = 0,
          toStartCss = {},
          toEndCss = {},
          fromStartCss = {}

      toStartCss[transformName] = 'translate(-100%, 0)'
      toEndCss[transformName] = 'translate(0%, 0)'

      fromStartCss[transformName] = 'translate(100%, 0)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        ;(++isFinish == 2) && callback()
      })
    },
    pushInRight: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          isFinish = 0,
          toStartCss = {},
          toEndCss = {},
          fromStartCss = {}

      toStartCss[transformName] = 'translate(-100%, 0)'
      toEndCss[transformName] = 'translate(0%, 0)'

      fromStartCss[transformName] = 'translate(100%, 0)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        ;(++isFinish == 2) && callback()
      })
    },
    pushOutLeft: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          isFinish = 0,
          toStartCss = {},
          toEndCss = {},
          fromStartCss = {}

      toStartCss[transformName] = 'translate(100%, 0)'
      toEndCss[transformName] = 'translate(0%, 0)'

      fromStartCss[transformName] = 'translate(-100%, 0)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        ;(++isFinish == 2) && callback()
      })
    },
    pushInUp: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          isFinish = 0,
          toStartCss = {},
          toEndCss = {},
          fromStartCss = {}

      toStartCss[transformName] = 'translate(0, 100%)'
      toEndCss[transformName] = 'translate(0%, 0)'

      fromStartCss[transformName] = 'translate(0, -100%)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        ;(++isFinish == 2) && callback()
      })
    },
    pushOutDown: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          isFinish = 0,
          toStartCss = {},
          toEndCss = {},
          fromStartCss = {}

      toStartCss[transformName] = 'translate(0, -100%)'
      toEndCss[transformName] = 'translate(0%, 0)'

      fromStartCss[transformName] = 'translate(0, 100%)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        ;(++isFinish == 2) && callback()
      })
    },
    pushInDown: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          isFinish = 0,
          toStartCss = {},
          toEndCss = {},
          fromStartCss = {}

      toStartCss[transformName] = 'translate(0, -100%)'
      toEndCss[transformName] = 'translate(0%, 0)'

      fromStartCss[transformName] = 'translate(0, 100%)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        ;(++isFinish == 2) && callback()
      })
    },
    pushOutUp: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          isFinish = 0,
          toStartCss = {},
          toEndCss = {},
          fromStartCss = {}

      toStartCss[transformName] = 'translate(0, 100%)'
      toEndCss[transformName] = 'translate(0%, 0)'

      fromStartCss[transformName] = 'translate(0, -100%)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        ;(++isFinish == 2) && callback()
      })
    },
    zoomIn: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          toStartCss = {},
          toEndCss = {}

      toStartCss[transformName] = 'scale(0, 0)'
      toEndCss[transformName] = 'scale(1, 1)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        callback()
      })
    },
    zoomOut: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          fromStartCss = {}

      fromStartCss[transformName] = 'scale(0, 0)'

      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        callback()
      })
    },
    overlayInLeft: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          pageBodyWidth = $toPageBody.children().width(),
          toStartCss = {left: 'auto', width: pageBodyWidth},
          toEndCss = {}
      
      toEndCss[transformName] = 'translate(0px, 0)'

      $toPageBody.css(toStartCss)
      pageBodyWidth = pageBodyWidth * 2 - $toPageBody.prop('clientWidth')
      toStartCss = {width: pageBodyWidth}
      toStartCss[transformName] = 'translate(' + pageBodyWidth + 'px, 0)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        callback()
      })
    },
    overlayOutRight: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          prevPageBodyWidth = $fromPageBody.width(),
          fromStartCss = {},
          fromEndCss = {width: 'auto', left: 0}

      fromStartCss[transformName] = 'translate(' + prevPageBodyWidth + 'px, 0)'

      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        $fromPageBody.css(fromEndCss)
        callback()
      })
    },
    overlayInRight: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          pageBodyWidth = $toPageBody.children().width(),
          toStartCss = {right: 'auto', width: pageBodyWidth},
          toEndCss = {}
      
      toEndCss[transformName] = 'translate(0px, 0)'
      
      $toPageBody.css(toStartCss)
      pageBodyWidth = pageBodyWidth * 2 - $toPageBody.prop('clientWidth')
      toStartCss = {width: pageBodyWidth}
      toStartCss[transformName] = 'translate(' + (0 - pageBodyWidth) + 'px, 0)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        callback()
      })      
    },
    overlayOutLeft: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          prevPageBodyWidth = $fromPageBody.width(),
          fromStartCss = {},
          fromEndCss = {width: 'auto', right: 0}

      fromStartCss[transformName] = 'translate(' + (0 - prevPageBodyWidth) + 'px, 0)'

      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        $fromPageBody.css(fromEndCss)
        callback()
      })
    },
    overlayInUp: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          pageBodyHeight = $toPageBody.children().height(),
          toStartCss = {top: 'auto', height: pageBodyHeight},
          toEndCss = {}
      
      toEndCss[transformName] = 'translate(0, 0px)'
      
      $toPageBody.css(toStartCss)
      pageBodyHeight = pageBodyHeight * 2 - $toPageBody.prop('clientHeight')
      toStartCss = {height: pageBodyHeight}
      toStartCss[transformName] = 'translate(0, ' + pageBodyHeight + 'px)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        callback()
      })
    },
    overlayOutDown: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          prevPageBodyHeight = $fromPageBody.height(),
          fromStartCss = {},
          fromEndCss = {height: 'auto', top: 0}

      fromStartCss[transformName] = 'translate(0, ' + prevPageBodyHeight + 'px)'

      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        $fromPageBody.css(fromEndCss)
        callback()
      })
    },
    overlayInDown: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          pageBodyHeight = $toPageBody.children().height(),
          toStartCss = {bottom: 'auto', height: pageBodyHeight},
          toEndCss = {}
      
      toEndCss[transformName] = 'translate(0, 0px)'
      
      $toPageBody.css(toStartCss)
      pageBodyHeight = pageBodyHeight * 2 - $toPageBody.prop('clientHeight')
      toStartCss = {height: pageBodyHeight}
      toStartCss[transformName] = 'translate(0, ' + (0 - pageBodyHeight) + 'px)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        callback()
      })
    },
    overlayOutUp: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          prevPageBodyHeight = $fromPageBody.height(),
          fromStartCss = {},
          fromEndCss = {height: 'auto', bottom: 0}

      fromStartCss[transformName] = 'translate(0, ' + (0 - prevPageBodyHeight) + 'px)'

      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        $fromPageBody.css(fromEndCss)
        callback()
      })
    },
    revealInLeft: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          pageBodyWidth = $toPageBody.children().width(),
          toStartCss = {left: 'auto', width: pageBodyWidth},
          fromStartCss = {}
            
      toStartCss[transformName] = 'translate(0, 0)'

      $toPageBody.css(toStartCss)
      pageBodyWidth = $toPageBody.width() * 2 - $toPageBody.prop('clientWidth')
      $toPageBody.css({width: pageBodyWidth})
      fromStartCss[transformName] = 'translate(' + (0 - pageBodyWidth) + 'px, 0)'

      togglePagezIndex($toPage, $fromPage)
      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        callback()
      })      
    },
    revealOutRight: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          prevPageBodyWidth = $fromPageBody.width(),
          toStartCss = {}

      toStartCss[transformName] = 'translate(0px, 0)'

      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toStartCss, function() {
        $fromPageBody.css({width: 'auto', left: 0})
        callback()
      })
    },
    revealInRight: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          pageBodyWidth = $toPageBody.children().width(),
          toStartCss = {right: 'auto', width: pageBodyWidth},
          fromStartCss = {}

      toStartCss[transformName] = 'translate(0, 0)'
            
      $toPageBody.css(toStartCss)
      pageBodyWidth = $toPageBody.width() * 2 - $toPageBody.prop('clientWidth')
      $toPageBody.css({width: pageBodyWidth})
      fromStartCss[transformName] = 'translate(' + pageBodyWidth + 'px, 0)'

      togglePagezIndex($toPage, $fromPage)
      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        callback()
      })      
    },
    revealOutLeft: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          prevPageBodyWidth = $fromPageBody.width(),
          toStartCss = {}

      toStartCss[transformName] = 'translate(0px, 0)'

      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toStartCss, function() {
        $fromPageBody.css({width: 'auto', right: 0})
        callback()
      })
    },
    revealInUp: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          pageBodyHeight = $toPageBody.children().height(),
          toStartCss = {top: 'auto', height: pageBodyHeight},
          fromStartCss = {}

      toStartCss[transformName] = 'translate(0, 0)'
      
      $toPageBody.css(toStartCss)
      pageBodyHeight = $toPageBody.height() * 2 - $toPageBody.prop('clientHeight')
      $toPageBody.css({height: pageBodyHeight})
      fromStartCss[transformName] = 'translate(0, ' + (0 - pageBodyHeight) + 'px)'

      togglePagezIndex($toPage, $fromPage)
      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        callback()
      })      
    },
    revealOutDown: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          prevPageBodyHeight = $fromPageBody.height(),
          toStartCss = {}

      toStartCss[transformName] = 'translate(0, 0px)'

      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toStartCss, function() {
        $fromPageBody.css({height: 'auto', top: 0})
        callback()
      })
    },
    revealInDown: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          pageBodyHeight = $toPageBody.children().height(),
          toStartCss = {bottom: 'auto', height: pageBodyHeight},
          fromStartCss = {}

      toStartCss[transformName] = 'translate(0, 0)'
      
      $toPageBody.css(toStartCss)
      pageBodyHeight = $toPageBody.height() * 2 - $toPageBody.prop('clientHeight')
      $toPageBody.css({height: pageBodyHeight})
      fromStartCss[transformName] = 'translate(0, ' + pageBodyHeight + 'px)'

      togglePagezIndex($toPage, $fromPage)
      $fromPageBody.transition(fromStartCss, function() {
        togglePagezIndex($fromPage, $toPage)
        callback()
      })      
    },
    revealOutUp: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          prevPageBodyHeight = $fromPageBody.height(),
          toStartCss = {}

      toStartCss[transformName] = 'translate(0, 0px)'

      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toStartCss, function() {
        $fromPageBody.css({height: 'auto', bottom: 0})
        callback()
      })
    },
    pushPartInLeft: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          pageBodyWidth = $toPageBody.children().width(),
          isFinish = 0,
          toStartCss = {left: 'auto', width: pageBodyWidth},
          toEndCss = {},
          fromStartCss = {}
      
      $toPageBody.css(toStartCss)
      pageBodyWidth = $toPageBody.width() * 2 - $toPageBody.prop('clientWidth')
      
      toStartCss = {width: pageBodyWidth}
      toStartCss[transformName] = 'translate(' + pageBodyWidth + 'px, 0)'

      toEndCss[transformName] = 'translate(0px, 0)'

      fromStartCss[transformName] = 'translate(' + (0 - pageBodyWidth) + 'px, 0)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        ;(++isFinish == 2) && callback()
      })      
    },
    pushPartOutRight: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          prevPageBodyWidth = $fromPageBody.width(),
          isFinish = 0,
          toStartCss = {},
          fromStartCss = {},
          fromEndCss = {width: 'auto', left: 0}

      toStartCss[transformName] = 'translate(0px, 0)'
      fromStartCss[transformName] = 'translate(' + prevPageBodyWidth + 'px, 0)'

      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toStartCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        $fromPageBody.css(fromEndCss)
        ;(++isFinish == 2) && callback()
      })
    },
    pushPartInRight: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          pageBodyWidth = $toPageBody.children().width(),
          isFinish = 0,
          toStartCss = {right: 'auto', width: pageBodyWidth},
          toEndCss = {},
          fromStartCss = {}
      
      $toPageBody.css(toStartCss)
      pageBodyWidth = $toPageBody.width() * 2 - $toPageBody.prop('clientWidth')

      toStartCss = {width: pageBodyWidth}
      toStartCss[transformName] = 'translate(' + (0 - pageBodyWidth) + 'px, 0)'
      fromStartCss[transformName] = 'translate(' + pageBodyWidth + 'px, 0)'

      toEndCss[transformName] = 'translate(0px, 0)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        ;(++isFinish == 2) && callback()
      })      
      $fromPageBody.transition(fromStartCss, function() {
        ;(++isFinish == 2) && callback()
      })      
    },
    pushPartOutLeft: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          prevPageBodyWidth = $fromPageBody.width(),
          isFinish = 0,
          toStartCss = {},
          fromStartCss = {},
          fromEndCss = {width: 'auto', right: 0}

      toStartCss[transformName] = 'translate(0px, 0)'
      fromStartCss[transformName] = 'translate(' + (0 - prevPageBodyWidth) + 'px, 0)'

      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toStartCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        $fromPageBody.css(fromEndCss)
        ;(++isFinish == 2) && callback()
      })
    },
    pushPartInUp: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          pageBodyHeight = $toPageBody.children().height(),
          isFinish = 0,
          toStartCss = {top: 'auto', height: pageBodyHeight},
          toEndCss = {},
          fromStartCss = {}
      
      $toPageBody.css(toStartCss)
      pageBodyHeight = $toPageBody.height() * 2 - $toPageBody.prop('clientHeight')

      toStartCss = {height: pageBodyHeight}
      toStartCss[transformName] = 'translate(0, ' + pageBodyHeight + 'px)'
      toEndCss[transformName] = 'translate(0, 0px)'
      fromStartCss[transformName] = 'translate(0, ' + (0 - pageBodyHeight) + 'px)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        ;(++isFinish == 2) && callback()
      })      
      $fromPageBody.transition(fromStartCss, function() {
        ;(++isFinish == 2) && callback()
      })      
    },
    pushPartOutDown: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          prevPageBodyHeight = $fromPageBody.height(),
          isFinish = 0,
          toStartCss = {},
          fromStartCss = {},
          fromEndCss = {height: 'auto', top: 0}

      toStartCss[transformName] = 'translate(0, 0px)'
      fromStartCss[transformName] = 'translate(0, ' + prevPageBodyHeight + 'px)'

      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toStartCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        $fromPageBody.css(fromEndCss)
        ;(++isFinish == 2) && callback()
      })
    },
    pushPartInDown: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          pageBodyHeight = $toPageBody.children().height(),
          isFinish = 0,
          toStartCss = {bottom: 'auto', height: pageBodyHeight},
          toEndCss = {},
          fromStartCss = {}
      
      $toPageBody.css(toStartCss)
      pageBodyHeight = $toPageBody.height() * 2 - $toPageBody.prop('clientHeight')

      toStartCss = {height: pageBodyHeight}
      toStartCss[transformName] = 'translate(0, ' + (0 - pageBodyHeight) + 'px)'
      toEndCss[transformName] = 'translate(0, 0px)'
      fromStartCss[transformName] = 'translate(0, ' + pageBodyHeight + 'px)'

      $toPageBody.css(toStartCss)
      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toEndCss, function() {
        ;(++isFinish == 2) && callback()
      })      
      $fromPageBody.transition(fromStartCss, function() {
        ;(++isFinish == 2) && callback()
      })      
    },
    pushPartOutUp: function($toPage, $fromPage, callback) {
      var $toPageBody = $('.spa-page-body', $toPage),
          $fromPageBody = $('.spa-page-body', $fromPage),
          prevPageBodyHeight = $fromPageBody.height(),
          isFinish = 0,
          toStartCss = {},
          fromStartCss = {},
          fromEndCss = {height: 'auto', bottom: 0}

      toStartCss[transformName] = 'translate(0, 0px)'
      fromStartCss[transformName] = 'translate(0, ' + (0 - prevPageBodyHeight) + 'px)'

      togglePagezIndex($fromPage, $toPage)
      $toPageBody.transition(toStartCss, function() {
        ;(++isFinish == 2) && callback()
      })
      $fromPageBody.transition(fromStartCss, function() {
        $fromPageBody.css(fromEndCss)
        ;(++isFinish == 2) && callback()
      })
    }
  })

  //判断是否是面板转场动画
  var isPanelAnimate = (function() {
    var panelAnimateReg = /^(overlay|reveal|pushPart).*$/
    return function(animate) {
      return panelAnimateReg.test(animate)
    }
  })()
  
  //页面转场
  function transitPage($toPage, $fromPage, animate, callback) {
    var $toPageBody = $('.spa-page-body', $toPage),
        $fromPageBody = $('.spa-page-body', $fromPage),
        toStartCss = {}
    
    // 如果不支持css3
    !transformName && (animate = 'defaultInOut')

    transitPageAnimatesName[animate] || (animate = 'defaultInOut')

    // 如果是首次打开的视图，强制设置为defaultInOut
    if(transitFirst) {
      transitFirst = false
      animate = 'defaultInOut'
    }

    // 还原之前转场过程中被修改的视图样式
    toStartCss.opacity = 1
    if(!isPanelAnimate(animate) && transformName) {
      toStartCss[transformName] = 'translate(0, 0) scale(1, 1)'
    }
    $toPageBody.css(toStartCss)

    transitPageAnimates[animate].apply($toPage, [$toPage, $fromPage, callback])
  }
  
  //生成唯一id
  var uniqueID = (function() {
    var id = 0
    return function() {
      return id++
    }
  })()
  
  //创建新页面
  $doc.on('spa:createpage', function(event, options) {
    $doc.trigger('spa:openloader')
        
    var hash = options.hash,
        pushData = options.pushData,
        requestData,
        routeRegStr,
        routeReg,
        pageOptions
    
    $.each(routes, function(regStr, opt) {
      routeReg = new RegExp(regStr)
      if(routeReg.test(hash)) {
        routeRegStr = regStr
        pageOptions = opt
        return false
      } else {
        routeReg = false
      }
    })
    
    //页面的hash有匹配的路由规则
    if(isRegExp(routeReg)) {
      var classname = pageOptions.classname ? ' spa-page-' + pageOptions.classname : '',
          $page = $('<div class="spa-page' + classname + '"><div class="spa-page-body"></div></div>'),
          pageId = uniqueID(),
          pageData,
          viewData

      // 设置pageId
      $page.data('id', pageId)

      // 初始化视图数据    
      pageData = {
        id: pageId,
        hash: hash,
        pushData: pushData,
        requestData: _extractParameters(routeReg, hash),
        route: routeRegStr,
        el: $page
      }

      viewsdata[pageId] = pageData

      // 渲染视图
      // $body.append($page)

      // 缓存视图
      $doc.trigger('spa:viewcache', {view: $page})
      
      // 获取视图的渲染数据
      viewData = pageOptions.view.call($page, pageData)
      
      if($.isPlainObject(viewData)) {
        $doc.trigger('spa:initpage', [$page, viewData])
      }      
    }
  })

  //初始化页面
  $doc.on('spa:initpage', function(event, $page, viewData) {
    var pageId = $page.data('id'),
        pageData = viewsdata[pageId],
        pageOptions = routes[pageData.route]
            
    // 重置回调函数
    $.each(['init', 'beforeopen', 'afteropen', 'beforeclose', 'afterclose'], function(i, fn) {
      viewData[fn] && (pageOptions[fn] = viewData[fn])
    })

    pageData.viewData = viewData

    // 关闭loader
    $doc.trigger('spa:closeloader')

    // 渲染视图内容
    $('.spa-page-body', $page).html(viewData.body)
    $body.append($page)

    // 打开页面视图
    $page.trigger('spa:openpage')

    // 初始化视图
    // pageOptions.init.call($page, pageData)
    
  })
  
  //打开页面
  $doc.on('spa:openpage', '.spa-page', function(event, afteropenCallback) {
    var $page = $(event.currentTarget),
        pageId = $page.data('id'),
        pageData = viewsdata[pageId],
        route = routes[pageData.route],
        hash = pageData.hash,
        pushData = pageData.pushData,
        oldpushData = pageData.oldpushData,
        requestData = pageData.requestData,
        viewData = pageData.viewData,
        title = pushData.title || viewData.title,
        // 是否是返回操作
        isBack = false,
        animate = pushData.animate || route.animate
    
    // 第一次没有页面视图的时候
    // 新建一个占位页面视图
    if(!$curPage) {
      $body.append('<div class="spa-page spa-page-empty"><div class="spa-page-body"></div></div>')
      $curPage = $('.spa-page-empty')
    }

    var curPageId = $curPage.data('id'),
        curPageData = viewsdata[curPageId] || {}

    // 如果是返回前一个页面，强制设置为前一个页面的反相动画效果
    // 但是，pushData.animate的优先级更高
    if(!pushData.animate && curPageData.prevPage && curPageData.prevPage.data('id') === pageId) {
      var prevAnimate = curPageData.prevAnimate
      if(prevAnimate && !$.isFunction(prevAnimate)) {
        animate = transitPageAnimatesName[prevAnimate]
      }
      isBack = true
    }
    
    var beforeclose,
        afterclose
    
    // 判断当前视图是面板还是页面
    if($curPage.hasClass('spa-panel')) {
      var panelOptions = panels[$curPage.data('id')]
      
      beforeclose = panelOptions.beforeclose
      afterclose = panelOptions.afterclose
    } else if(curPageData.route) {
      var curPageRoute = routes[curPageData.route]
      
      beforeclose = curPageRoute.beforeclose
      afterclose = curPageRoute.afterclose

      $doc.trigger('spa:navigate', {
        hash: hash,
        title: title,
        pushData: pushData,
        replace: true
      })
    }
        
    // $doc.trigger('spa:opencover')
            
    var callback = function() {
      if(!$page.data('spa:init')) {
        $page.data('spa:init', true)
        route.init.call($page, pageData)

        // 打开之前还原spa-scroll-touch
        if($.os.ios && parseInt($.os.version.slice(0, 1)) > 5) {
          $('.spa-scroll', $page).addClass('spa-scroll-touch')
        }     
      }

      // 关闭之后清除spa-scroll-touch
      if($.os.ios && parseInt($.os.version.slice(0, 1)) > 5) {
        $('.spa-scroll', $curPage).removeClass('spa-scroll-touch')
      }

      afterclose && afterclose.call($curPage, curPageData)
      
      if($curPage.hasClass('spa-panel')) {
        $curPage.css({zIndex: pagezIndex})
        pageData.prevPage && pageData.prevPage.css({zIndex: prevPagezIndex})
      } else {
        // 返回操作不用设置prevPage
        if(!isBack) {
          pageData.prevPage = $curPage
        }
      }

      route.afteropen.call($page, pageData)

      $curPage = $page
      // $doc.trigger('spa:closecover')
      
      // 页面打开后，如果当前路由和当前页面不匹配，触发路由请求
      if(pageData.hash !== getHash()) {
        // 还原isPrevent
        isPrevent = false

        $win.trigger('popstate')
      } else {
        $.isFunction(afteropenCallback) && afteropenCallback.call($page)

        // 还原isPrevent
        isPrevent = false
      }
    }

    beforeclose && beforeclose.call($curPage, curPageData)
    
    route.beforeopen.call($page, pageData)
    
    // 缓存页面最近一次载入的动画（非返回的情况）
    !isBack && (pageData.prevAnimate = animate)

    // 打开之前还原spa-scroll-touch
    if($.os.ios && parseInt($.os.version.slice(0, 1)) > 5) {
      $('.spa-scroll', $page).addClass('spa-scroll-touch')
    }

    // 修改prevPage的zIndex
    if(!isBack) {
      curPageData.prevPage && curPageData.prevPage.css({zIndex: pagezIndex})
    }
    if(!$.isFunction(animate)) {
      transitPage($page, $curPage, animate, callback)
    } else {
      animate($page, $curPage, callback)
    }

    // 更改视图缓存顺序
    $doc.trigger('spa:viewcachesort', {view: $page})
    
  })


  /*
   * 面板
   */
   
  var defaultPanelOptions = {
    id: '',                               //面板ID
    animate: '',                          //入场动画效果
    classname: '',                        //自定义样式名
    view: function() { return {body: ''} },    //获取面板视图
    init: function() {},                  //初始化回调函数
    beforeopen: function() {},            //打开前回调
    afteropen: function() {},             //打开后回调
    beforeclose: function() {},           //关闭前回调
    afterclose: function() {}             //关闭后回调
  }
  
  //定义面板
  $doc.on('spa:panel', function(event, options) {
    var args = Array.prototype.slice.call(arguments, 1)
    if(args.length > 1) {
      $.each(args, function(i, panel) {
        $doc.trigger('spa:panel', panel)
      })
      
      return false
    }
        
    //禁止重复定义ID相同的面板
    if(options.id && !panels[options.id]) {
      panels[options.id] = $.extend({}, defaultPanelOptions, options)
    }
  })

  //创建新面板
  $doc.on('spa:createpanel', function(event, id, pushData) {     
    var panelOptions = panels[id]
    
    if(panelOptions) {
      $doc.trigger('spa:openloader')

      var classname = panelOptions.classname ? ' spa-panel-' + panelOptions.classname : '',
          $panel = $('<div id="spa-panel-' + id + '" class="spa-page spa-panel ' + classname + '"><div class="spa-page-bg"></div><div class="spa-page-body"></div></div>'),
          panelData,
          viewData
      
      // 设置panelId
      $panel.data('id', id)

      panelData = {
        id: id,
        pushData: pushData,
        el: $panel
      }

      viewsdata[id] = panelData

      //初始化页面数据
      // $body.append($panel)  

      //缓存面板
      $doc.trigger('spa:viewcache', {view: $panel})
      
      //获取视图数据
      viewData = panelOptions.view.call($panel, panelData)
            
      if($.isPlainObject(viewData)) {
        $panel.trigger('spa:initpanel', [$panel, viewData])
      }
    }
  })

  //初始化面板
  $doc.on('spa:initpanel', function(event, $panel, viewData) {
    var panelId = $panel.data('id'),
        panelData = viewsdata[panelId],
        pushData = panelData.pushData,
        panelOptions = panels[panelId]
        
    //重置回调函数
    $.each(['init', 'beforeopen', 'afteropen', 'beforeclose', 'afterclose'], function(i, fn) {
      viewData[fn] && (panelOptions[fn] = viewData[fn])
    })

    panelData.viewData = viewData

    // 渲染视图内容
    $('.spa-page-body', $panel).html(viewData.body)
    $body.append($panel)

    // 执行初始化回调函数    
    // panelOptions.init.call($panel, panelData)

    // 关闭loader
    $doc.trigger('spa:closeloader')
    
    //触发打开页面事件
    $panel.trigger('spa:openpanel', [panelId, pushData])
  })

  //打开面板
  $doc.on('spa:openpanel', function(event, id, pushData) {
    //在页面加载和转场过程中阻止面板请求
    // if(($cover && $cover.css('display') === 'block') || ($loader && $loader.css('display') === 'block')) {
    //   return false
    // }
    if($loader && $loader.css('display') === 'block') {
      return false
    }
    
    var $panel = panelscache[id]

    pushData || (pushData = {})
    
    if($panel) {
      // 阻塞转场
      if(isPrevent) {
        return false
      }
      isPrevent = true

      var panelOptions = panels[id],
          animate = pushData.animate || panelOptions.animate
      
      // 如果当前页面是面板，则先收起之前的面板，再打开新面板
      if($curPage.hasClass('spa-panel')) {
        var $prevPage = viewsdata[$curPage.data('id')].prevPage
        
        $prevPage.trigger('spa:openpage', [function() {
          $doc.trigger('spa:openpanel', [id, pushData])
        }])
        
        return false
      }

      var panelData = viewsdata[id]
          
      panelData.oldpushData = panelData.pushData
      panelData.pushData = pushData
          
      // $doc.trigger('spa:opencover')
      
      panelOptions.beforeopen.call($panel, panelData)

      var callback = function() {
        if(!$panel.data('spa:init')) {
          $panel.data('spa:init', true)
          panelOptions.init.call($panel, panelData)

          // 打开之前还原spa-scroll-touch
          if($.os.ios && parseInt($.os.version.slice(0, 1)) > 5) {
            // 带有-webkit-overflow-scrolling: touch
            // 这个属性，如果去修改z-index，会偶现闪屏现象。
            // 不如一帧以后再添加这个class
            setTimeout(function() {
              $('.spa-scroll', $panel).addClass('spa-scroll-touch')
            }, 17)
            
          }

        }

        panelData.prevPage = $curPage
        panelOptions.afteropen.call($panel, panelData)  
        $curPage = $panel
        // $doc.trigger('spa:closecover')

        // 还原isPrevent
        isPrevent = false
      }

      // 缓存页面最近一次载入的动画
      panelData.prevAnimate = animate

      // 打开之前还原spa-scroll-touch
      if($.os.ios && parseInt($.os.version.slice(0, 1)) > 5) {
        $('.spa-scroll', $panel).addClass('spa-scroll-touch')
      }

      // 修改prevPage的zIndex
      var curPageId = $curPage.data('id'),
          curPageData = viewsdata[curPageId] || {}

      curPageData.prevPage && curPageData.prevPage.css({zIndex: pagezIndex})
      
      if(!$.isFunction(animate)) {
        transitPage($panel, $curPage, animate, callback)
      } else {
        animate($panel, $curPage, callback)
      }

      // 更改视图缓存顺序
      $doc.trigger('spa:viewcachesort', {view: $panel})

    } else {
      $doc.trigger('spa:createpanel', [id, pushData])
    }
    
  })
  
  //关闭面板
  $doc.on('spa:closepanel', function(event, options) {
    var $panel = $(event.target),
        panelId = $panel.data('id'),
        panelData = viewsdata[panelId]
    
    options && options.id && ($panel = $('.spa-panel-' + options.id))
    
    // 关闭面板后打开之前的页面
    if($curPage.hasClass('spa-panel') && $curPage.data('id') === panelId) {
      var $page = panelData.prevPage
      
      $page.trigger('spa:openpage')
    }
  })
  
  //点击面板外侧自动关闭面板
  $doc.on('click touchstart', '.spa-panel', function(event) {
    var $panel = $(event.currentTarget),
        $tagert = $(event.target)
    
    if($tagert.hasClass('spa-page-bg') || $tagert.hasClass('spa-panel')) {
      event.stopPropagation()
      event.preventDefault()
      $panel.trigger('spa:closepanel')
    }
  })


  /*
   * 设置清理页面最多缓存的页面数量
   */

  $doc.on('spa:viewcachecount', function(event, options) {
    viewcachecount = options.count
  })


  /*
   * 缓存并清理视图优化内存
   */

  $doc.on('spa:viewcache', function(event, options) {
    var $view = options.view,
        viewId = $view.data('id'),
        type,
        key

    // 先缓存
    if($view.hasClass('spa-panel')) {
      type = 'panle'
      key = viewId
      panelscache[key] = $view
    } else {
      type = 'page'
      key = viewsdata[viewId].hash
      pagescache[key] = $view
    }

    viewscache.unshift(type + ':' + key)

    // 再清理
    if(viewcachecount !== 0 && viewscache.length > viewcachecount) {
      var cleanup = viewscache.splice(viewcachecount),
          cleanupsplit,
          cleanuptype,
          cleanupkey,
          cleanupcache

      $.each(cleanup, function(index, value) {
        cleanupsplit = value.split(':', 2)
        cleanuptype = cleanupsplit[0]
        cleanupkey = cleanupsplit[1]

        cleanupcache = cleanuptype == 'page' ? pagescache : panelscache
        $('img', cleanupcache[cleanupkey]).remove()
        cleanupcache[cleanupkey].html('').remove()
        delete cleanupcache[cleanupkey]
      })
    }
  })


  /*
   * 更新视图缓存顺序
   */

  $doc.on('spa:viewcachesort', function(event, options) {
    var $view = options.view,
        viewId = $view.data('id'),
        type,
        key,
        name,
        index

    if($view.hasClass('spa-panel')) {
      type = 'panle'
      key = viewId
    } else {
      type = 'page'
      key = viewsdata[viewId].hash
    }

    name = type + ':' + key
    index = viewscache.indexOf(name)

    if(index !== -1) {
      viewscache.splice(index, 1)
      viewscache.unshift(name)
    }
  })
  

  /*
   * 路由请求
   */
  
  $doc.on('spa:navigate', function(event, options) {
    var hash = options.hash || '',
        title = options.title || '',
        pushData = options.pushData || {},
        replace = options.replace || false,
        url = options.url || ''

    title && (document.title = title)
    // hack在微信等webview中无法修改document.title的情况
    var $iframe = $('<iframe src="/favicon.ico"></iframe>').on('load', function() {
      setTimeout(function() {
        $iframe.off('load').remove()
      }, 0)
    }).appendTo($body)

    hash = url + '#' + hash
    
    if(replace) {
      history.replaceState(pushData, title, hash)
    } else {
      // 如果在页面视图中，并且路由请求和当前路由重复，则不继续执行
      if(!$curPage.hasClass('spa-panel') && hashhistory.length && '#' + hashhistory[hashhistory.length - 1] === hash) {
        return
      }

      // 阻塞转场
      if(isPrevent) {
        return false
      }
      isPrevent = true

      history.pushState(pushData, title, hash)
      
      //fix: 当$win.trigger('popstate')后,popstate事件对象无法获取到原生事件对象的state属性
      !$.isEmptyObject(pushData) && (historystate = pushData)

      $win.trigger('popstate')
    }
  })
  
  
  /*
   * 场景转换遮罩
   */

  // 阻塞鼠标和手势操作
  function preventEventHandle(evnet) {
    event.stopPropagation()
    event.preventDefault()
  }

  
  /*
   * 数据加载动画
   * http://css-tricks.com/snippets/css/bouncy-animated-loading-animation/
   */

  // 自定义loading层元素和样式
  $doc.on('spa:loader', function(event, options) {
    options.body && (loaderBody = options.body)
    options.style && (loaderStyle = options.style)
  })
  
  // loading显示延迟
  var loaderTimer

  // 显示loading层
  $doc.on('spa:openloader', function(event) {
    loaderTimer = setTimeout(function() {
      loaderTimer = undefined
      $loader.show()
    }, 300)
  })

  // 隐藏loading层
  $doc.on('spa:closeloader', function(event) {
    if(loaderTimer) {
      clearTimeout(loaderTimer)
      loaderTimer = undefined
    } else {
      $loader.hide()
    }
  })
    
  
  /*
   * 应用启动
   */
  
  $doc.on('spa:boot', function(event, options) {
    // 初始化$body
    $body = $('body')

    // 注入样式
    $doc.trigger('spa:addstyle', viewStyle + loaderStyle)

    // 插入全屏模式监控层和loading层
    // $body.append('<div class="spa-fullscreen"></div><div class="spa-cover"></div><div class="spa-loader">' + loaderBody + '</div>')
    $body.append('<div class="spa-fullscreen"></div><div class="spa-loader">' + loaderBody + '</div>')

    // 调整全屏模式
    $fullscreen = $('.spa-fullscreen')
    $doc.trigger('spa:adjustfullscreen')

    // 初始化loading层
    $loader = $('.spa-loader')
    $loader.on('click select mousedown mousemove mouseup touchstart touchmove touchend', preventEventHandle)

    // 启动完成
    hasBoot = true
    // 激活路由请求
    $win.trigger('popstate')

    options && options.callback && options.callback()
  })
  
})(window.Zepto || window.jQuery || window.$)

/*
 * requestAnimationFrame and cancel polyfill
 */
;(function() {
  'use strict';

  var lastTime = 0,
      vendors = ['ms', 'moz', 'webkit', 'o']
  for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']
    window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']
  }

  if (!window.requestAnimationFrame)
    window.requestAnimationFrame = function(callback, element) {
      var currTime = new Date().getTime(),
          timeToCall = Math.max(0, 16 - (currTime - lastTime)),
          id = window.setTimeout(function() { callback(currTime + timeToCall) }, timeToCall)
      lastTime = currTime + timeToCall
      return id
    };

    if (!window.cancelAnimationFrame)
      window.cancelAnimationFrame = function(id) {
        clearTimeout(id)
      }
})()
