'use strict';

var cluster = require('cluster');
var fs = require('fs');
var domain = require('domain');
var os = require('os');
var http = require('http');

var websocket = think.require('websocket');

var App = module.exports = think.Class({
  /**
   * send error
   * @param  {Error} err []
   * @return {}     []
   */
  error: function(err){
    think.error(err, this.http);
  },
  /**
   * dispath route
   * @return {} []
   */
  dispatcher: function(){
    var self = this;
    return this.hook('resource_check').then(function(){
      return self.hook('route_parse');
    }).then(function(){
      self.http._config = think.extend({}, think.getModuleConfig(self.http.module));
    })
  },
  /**
   * exec logic
   * @return {Promise} []
   */
  execLogic: function(){
    var name = this.http.module + '/' + think.dirname.logic + '/' + this.http.controller;
    var cls = think._aliasExport[name];
    if (!cls) {
      return Promise.resolve();
    }
    var instance = cls(this.http);
    var action = this.http.action;
    if (think.isFunction(instance[action + think.config('action_suffix')])) {
      return this.action(instance, action);
    }
    //call action
    else if (think.isFunction(instance.__call)) {
      return this.action(instance, '__call');
    }
  },
  /**
   * exec controller
   * @return {Promise} []
   */
  execController: function(){
    var http = this.http;
    var name = http.module + '/' + think.dirname.controller + '/' + http.controller;
    if (name in think._aliasExport) {
      return this.execAction(think._aliasExport[name](http));
    }
    var call = 'call_controller';
    if (call in think._aliasExport) {
      return this.execAction(think._aliasExport[call](http), true);
    }
    var err = new Error(think.message('CONTROLLER_NOT_FOUND', http.controller, http.url));
    return Promise.reject(err);
  },
  /**
   * exec action
   * @param  {Object} controller [controller instance]
   * @param  {Boolean} call       [is call controller]
   * @return {Promise}            []
   */
  execAction: function(controller, isCall){
    var action;
    if (isCall) {
      action = think.config('call_action');
    }else{
      action = this.http.action;
      //action not exist
      if (!think.isFunction(controller[action + think.config('action_suffix')])) {
        //__call not exist
        if (!think.isFunction(controller.__call)) {
          var err = new Error(think.message('ACTION_NOT_FOUND', action, this.http.url));
          return Promise.reject(err);
        }
        action = '__call';
      }
    }
    return this.action(controller, action);
  },
  /**
   * exec 
   * @return {Promise} []
   */
  exec: function(){
    var self = this;
    return this.execLogic().then(function(){
      //http is end
      if (self.http._isEnd) {
        return Promise.defer().promise;
      }
      return self.execController();
    })
  },
  /**
   * run
   * @return {} []
   */
  run: function(){
    var http = this.http;
    http.header('X-Powered-By', 'thinkjs-' + think.version);
    //deny access by ip + port
    if (think.config('proxy_on') && http.host !== http.hostname && !http.websocket) {
      http.res.statusCode = 403;
      http.res.end();
      return;
    }
    var instance = domain.create();
    var self = this;
    instance.on('error', function(err){
      self.error(err);
    });
    instance.run(function(){
      self.dispatcher().then(function(){
        return self.hook('app_begin');
      }).then(function(){
        return self.exec();
      }).then(function(){
        return self.hook('app_end');
      }).catch(function(err){
        self.error(err);
      })
    });
  },
})

/**
 * create server
 * @return {} []
 */
App.createServer = function(){
  var app = this;
  var handle = think.config('create_server');
  var host = think.config('host');
  var port = think.port || think.config('port'); 
  //createServer callback
  var callback = function (req, res) {
    return think.http(req, res).then(function(http){
      app(http).run();
    });
  }
  //define createServer in application
  if (handle) {
    if (!think.isFunction(handle)) {
      handle = global[handle];
    }
    if (!think.isFunction(handle)) {
      throw new Error(think.message('CONFIG_NOT_FUNCTION', 'create_server'));
    }
    handle(callback, port, host, app);
  }else{
    //create server
    var server = http.createServer(callback);
    if (think.config('websocket.on')) {
      websocket(server, this).run();
    }
    server.listen(port, host);
    App.logPid(port);
  }
  console.log('Server running at http://' + (host || '127.0.0.1') + ':' + port + '/');
  if (think.debug) {
    console.log('app debug is open');
  }
}

/**
 * cli mode
 * @return {} []
 */
App.cli = function(){
  think.http(think.url).then(function(http){
    return App(http).listener();
  })
  var timeout = think.config('process_timeout');
  if (timeout) {
    setTimeout(function(){
      process.exit();
    }, timeout * 1000)
  }
}

/**
   * load process id
   * @return {} []
   */
App.logPid = function(port){
  if (!cluster.isMaster || !think.config('log_pid')) {
    return;
  }
  var dir = think.getModulePath() + '/runtime/pid';
  think.mkdir(dir);
  var pidFile = dir + '/' + port + '.pid';
  fs.writeFileSync(pidFile, process.pid);
  //change pid file mode
  think.chmod(pidFile);
  //remove pid file when process exit
  process.on('SIGTERM', function () {
    if (fs.existsSync(pidFile)) {
      fs.unlinkSync(pidFile);
    }
    process.exit(0);
  });
}
/**
 * http mode
 * @return {} []
 */
App.http = function(){
  var nums = think.config('cluster_on');
  if (!nums) {
    return App.createServer();
  }
  if (nums === true) {
    nums = os.cpus().length;
  }
  if (cluster.isMaster) {
    console.log('use cluster, fork nums ' + nums);
    for (var i = 0; i < nums; i++) {
      cluster.fork();
    }
    cluster.on('exit', function(worker) {
      var err = new Error(think.message('WORKER_DIED', worker.process.pid))
      think.log(err, 'worker');
      process.nextTick(function(){
        cluster.fork();
      });
    });
  }else {
    App.createServer();
  }
}
/**
 * run
 * @return {} []
 */
App.run = function(){
  if (think.mode === 'cli') {
    return App.cli();
  }
  return App.http();
}
@@ -880,34 +880,35 @@
 = think.
-_aliasExport%5Bname%5D
+require(name, true)
 ;%0A    if
@@ -1486,38 +1486,56 @@
     
-if (name in think._aliasExport
+var cls = think.require(name, true);%0A    if (cls
 ) %7B%0A
@@ -1567,32 +1567,11 @@
 ion(
-think._aliasExport%5Bname%5D
+cls
 (htt
@@ -1623,38 +1623,52 @@
     
-if (call in think._aliasExport
+cls = think.require(call, true);%0A    if (cls
 ) %7B%0A
@@ -1700,32 +1700,11 @@
 ion(
-think._aliasExport%5Bcall%5D
+cls
 (htt

use think.require replace think._aliasExport in app exec