var _ = require('lodash');
var jwt = require('jwt-simple');
var debug = require('debug')('4front:apphost:dev-sandbox');
var isStaticAsset = require('../is-static-asset');

require('simple-errors');

var defaultDevOptions = {
  port: '3000', // The localhost port
  buildType: 'debug',
  autoReload: '0'
};

module.exports = function(options) {
  options = _.defaults(options || {}, {
    cookieName: '_dev',
    queryParam: '_dev',
    sandboxFlashCookie: '_sandboxPage',
    showBanner: true
  });

  return function(req, res, next) {
    debug("executing");

    // Bypass simulator if the virtualEnv is not 'dev'
    if (req.ext.virtualEnv !== 'dev')
      return next();

    // Special login path used to initialize the developer sandbox. Just copy
    // the query parameters to the _dev cookie and redirect to the root of the app.
    // Subsequent requests will read from the _dev cookie.
    if (req.path === '/__login') {
      debug("writing devOptions to cookie");

      // Clear the sandbox flash cookie
      res.clearCookie(options.sandboxFlashCookie);

      var devOptions = _.pick(req.query, _.keys(defaultDevOptions).concat('token'));

      var token = validateDevToken(devOptions.token, req.app.settings.jwtTokenSecret);
      if (_.isError(token))
        return next(token);

      res.cookie(options.cookieName, devOptions, {httpOnly: true});
      return res.redirect('/');
    }

    // Load the dev options from the dev cookie
    var devCookie = req.cookies[options.cookieName];

    if (!devCookie) {
      return next(Error.http(401, "No " + options.cookieName + " cookie set. Try running '4front dev' again.", {
        code: "missingDevCookie",
        bypassCustomErrorPage: true
      }));
    }

    // Load the developer's personal options from the _dev cookie
    // Apply the default dev options to the values from the cookie
    var devOptions = _.extend(_.defaults({}, devCookie, defaultDevOptions), {
      cookieName: options.sandboxFlashCookie
    });

    var token = validateDevToken(devOptions.token, req.app.settings.jwtTokenSecret);
    if (_.isError(token))
      return next(token);

    // Store the 4front userId of the current developer
    req.ext.developerId = token.iss;

    // If this is a request for a static asset or an XHR request
    // for an .html file, redirect back to localhost.
    if (isStaticAsset.anyExceptHtml(req) || isStaticAsset.htmlXhr(req)) {
      return res.redirect((req.secure ? 'https' : 'http') + "://localhost:" + devOptions.port + req.path);
    }

    req.ext.clientConfig.sandbox = true;

    // Get the app manifest from cache
    req.app.settings.cache.get(req.ext.developerId + '/' + req.ext.virtualApp.appId + '/_manifest', function(err, manifestJson) {
      if (err) return next(err);

      var manifest;
      try {
        manifest = JSON.parse(manifestJson);
      }
      catch (err) {
        return next(Error.http(400, "Invalid JSON manifest", {
          bypassCustomErrorPage: true,
          help: "Try re-starting the dev sandbox with the command '4front dev'",
          code: 'invalidJsonManifest'
        }));
      }

      _.merge(req.ext, {
        // Specify custom middleware to handle loading the html page stream.
        // This will override the default behavior of loading from the
        // app.settings.storage object.
        loadPageMiddleware: require('./dev-sandbox-page')(devOptions),
        virtualAppVersion: {
          versionId: 'sandbox',
          name: 'sandbox',
          manifest: manifest
        },
        versionAssetPath: '//localhost:' + devOptions.port,
        buildType: devOptions.buildType,
        // Override the cache-control. We never want to cache pages served by the dev sandbox
        cacheControl: 'no-cache',
        htmlOptions: {
          autoReload: devOptions.autoReload === '1',
          // Run autoReload on the same port as the sandbox
          sandboxPort: devOptions.port,
          assetPath: '//localhost:' + devOptions.port,
          inject: {}
        }
      });

      next();
    });
  };

  // Validate the devOptions are valid
  function validateDevToken(encodedToken, tokenSecret) {
    // Validate the auth token
    if (_.isEmpty(encodedToken)) {
      return Error.http(401, "Missing dev token.", {
        bypassCustomErrorPage: true,
        code: "missingDevToken",
        help: "Try re-starting the dev sandbox with '4front dev'"
      });
    }

    var token;
    if (_.isEmpty(encodedToken) === false) {
      debug("decoding token %s", encodedToken);
      try {
        token = jwt.decode(encodedToken, tokenSecret);
      }
      catch (err) {
        return Error.http(401, "Invalid dev token.", {
          bypassCustomErrorPage: true,
          code: "invalidDevToken",
          help: "Try re-starting the dev sandbox with '4front dev'"
        });
      }
    }

    if (token.exp < Date.now()) {
      debug("token expired at %s", token.exp);

      return Error.http(401, "Token expired", {
        bypassCustomErrorPage: true,
        code: "expiredDevToken",
        help: "Please re-start the dev sandbox with the `4front dev` command."
      });
    }

    return token;
  }
};

@@ -665,24 +665,52 @@
 rn next();%0A%0A
+    var token, devOptions;%0A%0A
     // Speci
@@ -1093,28 +1093,24 @@
 ie);%0A%0A      
-var 
 devOptions =
@@ -1175,28 +1175,24 @@
 '));%0A%0A      
-var 
 token = vali
@@ -1621,16 +1621,29 @@
 ie set. 
+%22 +%0A        %22
 Try runn
@@ -1895,20 +1895,16 @@
 kie%0A    
-var 
 devOptio
@@ -2019,28 +2019,24 @@
    %7D);%0A%0A    
-var 
 token = vali
@@ -2499,16 +2499,24 @@
 http') +
+%0A       
  %22://loc
@@ -2651,35 +2651,31 @@
     
-req.app.settings.cache.get(
+var manifestCacheKey = 
 req.
@@ -2737,16 +2737,65 @@
 anifest'
+;%0A    req.app.settings.cache.get(manifestCacheKey
 , functi
@@ -2870,16 +2870,23 @@
 manifest
+ = null
 ;%0A      
@@ -2949,35 +2949,104 @@
  %7D%0A      catch (
-err
+jsonErr) %7B%0A        manifest = null;%0A      %7D%0A%0A      if (manifest === null
 ) %7B%0A        retu

Return error if manifest missing from cache