// TODO: Expose this like webpack-dev-server middleware
var stacked = require('stacked')
var serveStatic = require('serve-static')
var defaultIndex = require('simple-html-index')
var logger = require('./simple-http-logger')
var urlLib = require('url')
var xtend = require('xtend')
var pushState = require('connect-pushstate')
var liveReload = require('inject-lr-script')
var urlTrim = require('url-trim')
var escapeHtml = require('escape-html')

var fs = require('fs')
var browserify = require('browserify')
var path = require('path')
var liveReloadClientFile = path.resolve(__dirname, 'reload', 'client.js')
var bundledReloadClientFile = path.resolve(__dirname, '..', 'build', 'bundled-livereload-client.js')

module.exports = budoMiddleware
function budoMiddleware (entryMiddleware, opts) {
  opts = opts || {}
  var staticPaths = [].concat(opts.dir).filter(Boolean)
  if (staticPaths.length === 0) {
    staticPaths = [ process.cwd() ]
  }

  var entrySrc = opts.serve
  var live = opts.live
  var cors = opts.cors
  var handler = stacked()
  var middlewares = [].concat(opts.middleware).filter(Boolean)

  // Everything is logged except favicon.ico
  var logHandler = logger({
    ignores: [ '/favicon.ico' ]
  })
  handler.use(function (req, res, next) {
    if (cors) {
      res.setHeader('Access-Control-Allow-Headers', 'Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With')
      res.setHeader('Access-Control-Allow-Methods', 'GET, PUT, POST')
      res.setHeader('Access-Control-Allow-Origin', '*')
    }
    logHandler(req, res, next)
  })

  // User middleware(s) can override others
  middlewares.forEach(function (middleware) {
    if (typeof middleware !== 'function') {
      throw new Error('middleware options must be functions')
    }
    handler.use(function (req, res, next) {
      logHandler.type = 'middleware'
      middleware(req, res, next)
    })
  })

  // Re-route for pushState support
  if (opts.pushstate) {
    if (typeof opts.pushstate === 'string') {
      throw new Error('--pushstate is a string, you shouold use subarg options instead')
    }
    var pushStateOpts = xtend(typeof opts.pushstate === 'boolean' ? {} : opts.pushstate)
    delete pushStateOpts._ // added by subarg
    handler.use(pushState(pushStateOpts))
  }

  // Inject liveReload snippet on response
  var liveInjector = liveReload({
    local: true
  })
  // this is lazily set and cannot be changed dynamically
  var liveScriptUrl
  // By default, attempt to optimize the response
  var shouldUseBundledLiveReload = true
  // Cache the client by default to optimize the response
  var liveReloadClient
  handler.use(liveReloadHandler)

  // Entry (watchify) middleware
  if (entryMiddleware) {
    var entryRoute = '/' + urlLib.parse(entrySrc).pathname
    handler.use(function (req, res, next) {
      if (urlTrim(req.url) === urlTrim(entryRoute)) {
        entryMiddleware(req, res, next)
      } else {
        next()
      }
    })
  }

  // Ignore favicon clutter
  handler.mount('/favicon.ico', favicon)

  // If the user wishes to *always* serve
  // a generated index instead of a static one.
  if (opts.forceDefaultIndex) {
    handler.use(indexHandler)
  }

  // Static assets (html/images/etc)
  staticPaths.forEach(function (rootFile) {
    var staticOpts = xtend({
      cacheControl: false
    }, opts.staticOptions)
    delete staticOpts._ // from subarg
    var staticHandler = serveStatic(rootFile, staticOpts)
    handler.use(function (req, res, next) {
      logHandler.type = 'static'
      staticHandler(req, res, next)
    })
  })

  // Generates a default index.html
  // when none is found locally.
  handler.use(indexHandler)

  // Handle errors
  handler.use(function (req, res) {
    res.statusCode = 404
    res.end('404 not found: ' + escapeHtml(req.url))
  })

  // Allow live options to be changed at runtime
  handler.setLiveOptions = setLiveOptions
  return handler

  function setLiveOptions (opts) {
    live = opts
  }

  function favicon (req, res) {
    var maxAge = 345600 // 4 days
    res.setHeader('Cache-Control', 'public, max-age=' + Math.floor(maxAge / 1000))
    res.setHeader('Content-Type', 'image/x-icon')
    res.statusCode = 200
    res.end()
  }

  function indexHandler (req, res, next) {
    if (urlLib.parse(req.url).pathname === '/' || /\/index.html?/i.test(req.url)) {
      // If we reach this, our response will be generated
      // (not static from local file system)
      logHandler.type = 'generated'
      res.setHeader('content-type', 'text/html')

      var stream = opts.defaultIndex || defaultIndex
      stream({
        entry: entrySrc,
        title: opts.title,
        css: opts.css,
        base: opts.base === true ? '/' : (opts.base || null)
      }, req).pipe(res)
    } else {
      next()
    }
  }

  function serveBundledLiveReload (res, successCallback) {
    if (liveReloadClient) {
      res.end(liveReloadClient)
      successCallback(true)
    } else {
      fs.readFile(bundledReloadClientFile, function (err, src) {
        if (err) {
          if (shouldUseBundledLiveReload) {
            console.error([
              'NOTE: The bundled LiveReload client could not be found, so budo will',
              'generate this on the fly.',
              'This is most likely because you are using a git cloned version of budo',
              'instead of installing it from npm.',
              'You can run this from your cloned budo folder to create a build:',
              '  npm run bundle-live-client'
            ].join('\n'))
          }
          shouldUseBundledLiveReload = false
          successCallback(false)
        } else {
          liveReloadClient = src
          res.end(src)
          successCallback(true)
        }
      })
    }
  }

  function serveBrowserifyLiveReload (cache, debug, liveScripts, res) {
    // Browserify the client file, e.g. if user has a script to include
    if (cache && liveReloadClient) {
      res.end(liveReloadClient)
    } else {
      var b = browserify({ debug: debug })
      b.add(liveReloadClientFile)
      if (live.expose) {
        b.require(liveReloadClientFile, { expose: 'budo-livereload' })
      }

      liveScripts.forEach(function (file) {
        b.add(path.resolve(file))
      })
      b.bundle(function (err, src) {
        if (err) {
          console.error('Error bundling LiveReload client:\n' + err.message)
          res.statusCode = 500
          res.end('Error bundling LiveReload client: ' + err)
        } else {
          liveReloadClient = src
          res.end(src)
        }
      })
    }
  }

  function liveReloadHandler (req, res, next) {
    if (!live || live.plugin) return next()
    if (!liveScriptUrl) {
      liveScriptUrl = live.path || '/budo/livereload.js'
      logHandler.ignores.push(liveScriptUrl)
    } else if (liveScriptUrl && live.path && liveScriptUrl !== live.path) {
      var errMessage = 'Error: The LiveReload path field cannot be changed dynamically.\n' +
          'Please open an issue in budo if you have a specific use case for this.'
      console.error(errMessage)
      res.statusCode = 500
      res.end(errMessage)
      return
    }

    if (req.url === liveScriptUrl) {
      res.statusCode = 200
      res.setHeader('Content-Type', 'application/javascript')
      var liveScripts = (Array.isArray(live.include) ? live.include : [ live.include ]).filter(Boolean)
      var cache = live.cache !== false
      var debug = live.debug

      // Default setup - use a bundled JS file for LiveReload client
      if (shouldUseBundledLiveReload && cache && !debug && liveScripts.length === 0) {
        serveBundledLiveReload(res, function (success) {
          // fall back to browserify on the fly
          if (!success) serveBrowserifyLiveReload(cache, debug, liveScripts, res)
        })
      } else {
        serveBrowserifyLiveReload(cache, debug, liveScripts, res)
      }
    } else {
      liveInjector.path = liveScriptUrl
      liveInjector(req, res, next)
    }
  }
}
