'use strict';

module.exports.create = function () {
  var id = '0';
  var promiseApp;

  //
  // Worker Mode
  //
  function createAndBind(conf) {
    // NOTE: this callback must return a promise for an express app
    function getOrCreateHttpApp(err, insecserver, webserver/*, newMessage*/) {
      var PromiseA = require('bluebird');

      if (promiseApp) {
        return promiseApp;
      }

      promiseApp = new PromiseA(function (resolve) {
        function initHttpApp(srvmsg) {
          if ('walnut.webserver.onrequest' !== srvmsg.type) {
            console.warn('[Worker] [onrequest] unexpected message:');
            console.warn(srvmsg);
            return;
          }

          process.removeListener('message', initHttpApp);

          if (srvmsg.conf) {
            Object.keys(srvmsg.conf).forEach(function (key) {
              conf[key] = srvmsg.conf[key];
            });
          }

          resolve(require('../lib/worker').create(webserver, conf));
        }

        process.send({ type: 'walnut.webserver.listening' });
        process.on('message', initHttpApp);
      }).then(function (app) {
        console.info('[Worker Ready]');
        return app;
      });

      return promiseApp;
    }

    function serverCallback(err, webserver) {
      if (err) {
        console.error('[ERROR] worker.js');
        console.error(err.stack);
        throw err;
      }

      console.info("#" + id + " Listening on " + conf.protocol + "://" + webserver.address().address + ":" + webserver.address().port, '\n');

      // we are returning the promise result to the caller
      return getOrCreateHttpApp(null, null, webserver, conf);
    }

    // Note the odd use of callbacks (instead of promises) here
    // It's to avoid loading bluebird yet (see sni-server.js for explanation)
    function localServerCreate(port) {
      function initServer(err, server) {
        var app;
        var promiseApp;

        if (err) {
          serverCallback(err);
          return;
        }

        server.on('error', serverCallback);
        server.listen(port, function () {
          // is it even theoritically possible for
          // a request to come in before this callback has fired?
          // I'm assuming this event must fire before any request event
          promiseApp = serverCallback(null, server);
        });
        /*
        server.listen(port, '::::', function () {
          // is it even theoritically possible for
          // a request to come in before this callback has fired?
          // I'm assuming this event must fire before any request event
          promiseApp = serverCallback(null, server);
        });
        */

        // Get up and listening as absolutely quickly as possible
        function onRequest(req, res) {
          // this is a hot piece of code, so we cache the result
          if (app) {
            app(req, res);
            return;
          }

          promiseApp.then(function (_app) {
            console.log('[Server]', req.method, req.host || req.headers['x-forwarded-host'] || req.headers.host, req.url);
            app = _app;
            app(req, res);
          });
        }

        server.on('request', onRequest);
      }

      initServer(null, require('http').createServer());
    }

    // NOTE that message.conf[x] will be overwritten when the next message comes in
    localServerCreate(conf.localPort);
  }

  function waitForConfig(realMessage) {
    console.log('realMessage', realMessage);
    if ('walnut.init' !== realMessage.type) {
      console.warn('[Worker] 0 got unexpected message:');
      console.warn(realMessage);
      return;
    }

    var conf = realMessage.conf;
    process.removeListener('message', waitForConfig);

    createAndBind(conf);
  }

  // we are in cluster mode, as opposed to standalone mode
  id = require('cluster').worker.id.toString();
  // We have to wait to get the configuration from the master process
  // before we can start our webserver
  console.info('[Worker #' + id + '] online!');
  process.on('message', waitForConfig);

  //
  // Debugging
  //
  process.on('exit', function (code) {
    // only sync code can run here
    console.info('uptime:', process.uptime());
    console.info(process.memoryUsage());
    console.info('[exit] process.exit() has been called (or master has killed us).');
    console.info(code);
  });
  process.on('beforeExit', function () {
    // async can be scheduled here
    console.info('[beforeExit] Event Loop is empty. Process will end.');
  });
  process.on('unhandledRejection', function (err) {
    // this should always throw
    // (it means somewhere we're not using bluebird by accident)
    console.error('[caught unhandledRejection]:', err.message || '');
    Object.keys(err).forEach(function (key) {
      console.log('\t'+key+': '+err[key]);
    });
    console.error(err.stack);
  });
  process.on('rejectionHandled', function (msg) {
    console.error('[rejectionHandled]');
    console.error(msg);
  });
};
