text: 'use strict'
  // app
  // external modules
  var express = require('express')
  
  var ejs = require('ejs')
  var passport = require('passport')
  var methodOverride = require('method-override')
  var cookieParser = require('cookie-parser')
  var compression = require('compression')
  var session = require('express-session')
  var SequelizeStore = require('connect-session-sequelize')(session.Store)
  var fs = require('fs')
  var path = require('path')
  
  var morgan = require('morgan')
  var passportSocketIo = require('passport.socketio')
  var helmet = require('helmet')
  var i18n = require('i18n')
  var flash = require('connect-flash')
  
  // core
  var config = require('./lib/config')
  var logger = require('./lib/logger')
  var response = require('./lib/response')
  var models = require('./lib/models')
  
  // generate front-end constants by template
  var constpath = path.join(__dirname, './public/js/lib/common/constant.ejs')
  var data = {
    domain: config.domain,
    urlpath: config.urlpath,
    debug: config.debug,
    version: config.version,
    GOOGLE_API_KEY: config.google.clientSecret,
    GOOGLE_CLIENT_ID: config.google.clientID,
    DROPBOX_APP_KEY: config.dropbox.clientSecret
  }
  
  ejs.renderFile(constpath, data, {}, function (err, str) {
    if (err) throw new Error(err)
    fs.writeFileSync(path.join(__dirname, './public/build/constant.js'), str)
  })
  
  // server setup
  var app = express()
  var server = null
  if (config.usessl) {
    var ca = (function () {
      var i, len, results
      results = []
      for (i = 0, len = config.sslcapath.length; i < len; i++) {
        results.push(fs.readFileSync(config.sslcapath[i], 'utf8'))
      }
      return results
    })()
    var options = {
      key: fs.readFileSync(config.sslkeypath, 'utf8'),
      cert: fs.readFileSync(config.sslcertpath, 'utf8'),
      ca: ca,
      dhparam: fs.readFileSync(config.dhparampath, 'utf8'),
      requestCert: false,
      rejectUnauthorized: false
    }
    server = require('https').createServer(options, app)
  } else {
    server = require('http').createServer(app)
  }
  
  // logger
  app.use(morgan('combined', {
    'stream': logger
  }))
  
  // socket io
  var io = require('socket.io')(server)
  io.engine.ws = new (require('uws').Server)({
    noServer: true,
    perMessageDeflate: false
  })
  
  // others
  var realtime = require('./lib/realtime.js')
  
  // assign socket io to realtime
  realtime.io = io
  
  // methodOverride
  app.use(methodOverride('_method'))
  
  // session store
  var sessionStore = new SequelizeStore({
    db: models.sequelize
  })
  
  // compression
  app.use(compression())
  
  // use hsts to tell https users stick to this
  app.use(helmet.hsts({
    maxAge: 31536000 * 1000, // 365 days
    includeSubdomains: true,
    preload: true
  }))
  
  i18n.configure({
    locales: ['en', 'zh', 'fr', 'de', 'ja', 'es', 'ca', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv', 'eo', 'da'],
    cookie: 'locale',
    directory: path.join(__dirname, '/locales')
  })
  
  app.use(cookieParser())
  
  app.use(i18n.init)
  
  // routes without sessions
  // static files
  app.use('/', express.static(path.join(__dirname, '/public'), { maxAge: config.staticcachetime }))
  
  // session
  app.use(session({
    name: config.sessionname,
    secret: config.sessionsecret,
    resave: false, // don't save session if unmodified
    saveUninitialized: true, // always create session to ensure the origin
    rolling: true, // reset maxAge on every response
    cookie: {
      maxAge: config.sessionlife
    },
    store: sessionStore
  }))
  
  // session resumption
  var tlsSessionStore = {}
  server.on('newSession', function (id, data, cb) {
    tlsSessionStore[id.toString('hex')] = data
    cb()
  })
  server.on('resumeSession', function (id, cb) {
    cb(null, tlsSessionStore[id.toString('hex')] || null)
  })
  
  // middleware which blocks requests when we're too busy
  app.use(require('./lib/web/middleware/tooBusy'))
  
  app.use(flash())
  
  // passport
  app.use(passport.initialize())
  app.use(passport.session())
  
  // check uri is valid before going further
  app.use(require('./lib/web/middleware/checkURIValid'))
  // redirect url without trailing slashes
  app.use(require('./lib/web/middleware/redirectWithoutTrailingSlashes'))
  
  // routes need sessions
  // template files
  app.set('views', path.join(__dirname, '/public/views'))
  // set render engine
  app.engine('ejs', ejs.renderFile)
  // set view engine
  app.set('view engine', 'ejs')
  
  app.use(require('./lib/web/baseRouter'))
  app.use(require('./lib/web/statusRouter'))
  app.use(require('./lib/web/auth'))
  app.use(require('./lib/web/historyRouter'))
  app.use(require('./lib/web/userRouter'))
  app.use(require('./lib/web/imageRouter'))
  app.use(require('./lib/web/noteRouter'))
  
  // response not found if no any route matxches
  app.get('*', function (req, res) {
    response.errorNotFound(res)
  })
  
  // socket.io secure
  io.use(realtime.secure)
  // socket.io auth
  io.use(passportSocketIo.authorize({
    cookieParser: cookieParser,
    key: config.sessionname,
    secret: config.sessionsecret,
    store: sessionStore,
    success: realtime.onAuthorizeSuccess,
    fail: realtime.onAuthorizeFail
  }))
  // socket.io heartbeat
  io.set('heartbeat interval', config.heartbeatinterval)
  io.set('heartbeat timeout', config.heartbeattimeout)
  // socket.io connection
  io.sockets.on('connection', realtime.connection)
  
  // listen
  function startListen () {
    server.listen(config.port, function () {
      var schema = config.usessl ? 'HTTPS' : 'HTTP'
      logger.info('%s Server listening at port %d', schema, config.port)
      realtime.maintenance = false
    })
  }
  
  // sync db then start listen
  models.sequelize.sync().then(function () {
    // check if realtime is ready
    if (realtime.isReady()) {
      models.Revision.checkAllNotesRevision(function (err, notes) {
        if (err) throw new Error(err)
        if (!notes || notes.length <= 0) return startListen()
      })
    } else {
      throw new Error('server still not ready after db synced')
    }
  })
  
  // log uncaught exception
  process.on('uncaughtException', function (err) {
    logger.error('An uncaught exception has occured.')
    logger.error(err)
    logger.error('Process will exit now.')
    process.exit(1)
  })
  
  // install exit handler
  function handleTermSignals () {
    logger.info('hackadoc has been killed by signal, try to exit gracefully...')
    realtime.maintenance = true
    // disconnect all socket.io clients
    Object.keys(io.sockets.sockets).forEach(function (key) {
      var socket = io.sockets.sockets[key]
      // notify client server going into maintenance status
      socket.emit('maintenance')
      setTimeout(function () {
        socket.disconnect(true)
      }, 0)
    })
    var checkCleanTimer = setInterval(function () {
      if (realtime.isReady()) {
        models.Revision.checkAllNotesRevision(function (err, notes) {
          if (err) return logger.error(err)
          if (!notes || notes.length <= 0) {
            clearInterval(checkCleanTimer)
            return process.exit(0)
          }
        })
      }
    }, 100)
  }
  process.on('SIGINT', handleTermSignals)
  process.on('SIGTERM', handleTermSignals)
  process.on('SIGQUIT', handleTermSignals)
  