"use strict";

/**
 * @file Component for proxy.
 * Generate proxies for rpc client.
 */
const utils = require('../util/utils');
const events = require('../util/events');
const Client = require('@sex-pomelo/sex-pomelo-rpc').client;
const pathUtil = require('../util/pathUtil');
const Constants = require('../util/constants');
const logger = require('@sex-pomelo/sex-pomelo-logger').getLogger('pomelo', __filename);
/**
 * @typedef {import('../application').Application} Application
 */

/**
 * Component factory function
 *
 * @param {Application} app  current application context
 * @param {Object} opts construct parameters
 *                      opts.router: (optional) rpc message route function, route(routeParam, msg, cb),
 *                      opts.mailBoxFactory: (optional) mail box factory instance.
 * @return {Object}     component instance
 */
module.exports = function(app, opts) {
  opts = opts || {};
  // proxy default config
  // cacheMsg is deprecated, just for compatibility here.
  opts.bufferMsg = opts.bufferMsg || opts.cacheMsg || false;
  opts.interval = opts.interval || 30;
  opts.router = genRouteFun();
  opts.context = app;
  opts.routeContext = app;
  if (app.enabled('rpcDebugLog')) {
    opts.rpcDebugLog = true;
    opts.rpcLogger = require('@sex-pomelo/sex-pomelo-logger').getLogger('rpc-debug', __filename);
  }

  return new ComponentProxy(app, opts);
};

/**
 * Proxy component class
 * @class
 * @implements {Component}
 * 
 * @param {Object} app  current application context
 * @param {Object} opts construct parameters
 */
let ComponentProxy = function(app, opts) {
  this.app = app;
  this.opts = opts;
  this.client = genRpcClient(this.app, opts);
  this.app.event.on(events.ADD_SERVERS, this.addServers.bind(this));
  this.app.event.on(events.REMOVE_SERVERS, this.removeServers.bind(this));
  this.app.event.on(events.REPLACE_SERVERS, this.replaceServers.bind(this));
};


ComponentProxy.prototype.name = '__proxy__';

/**
 * Proxy component lifecycle function
 *
 * @param {Function} cb
 * @return {Void}
 */
ComponentProxy.prototype.start = function(cb) {
  if(this.opts.enableRpcLog) {
    logger.warn('enableRpcLog is deprecated in 0.8.0, please use app.rpcFilter(pomelo.rpcFilters.rpcLog())');
  }
  let rpcBefores = this.app.get(Constants.KEYWORDS.RPC_BEFORE_FILTER);
  let rpcAfters = this.app.get(Constants.KEYWORDS.RPC_AFTER_FILTER);
  let rpcErrorHandler = this.app.get(Constants.RESERVED.RPC_ERROR_HANDLER);

  if(!!rpcBefores) {
    this.client.before(rpcBefores);
  } 
  if(!!rpcAfters) {
    this.client.after(rpcAfters);
  }
  if(!!rpcErrorHandler) {
    this.client.setErrorHandler(rpcErrorHandler);
  }
  process.nextTick(cb);
};

/**
 * Component lifecycle callback
 *
 * @param {Function} cb
 * @return {Void}
 */
ComponentProxy.prototype.afterStart = function(cb) {
  let self = this;
  // this.app.__defineGetter__('rpc', function() {
  //   return self.client.proxies.user;
  // });

  Object.defineProperty(self.app, 'rpc', {enumerable: true,
    get: function(){ return self.client.proxies.user},
  });

  // this.app.__defineGetter__('sysrpc', function() {
  //   return self.client.proxies.sys;
  // });
  Object.defineProperty(self.app, 'sysrpc', {enumerable: true,
    get: function(){ return self.client.proxies.sys},
  });
  
  this.app.set('rpcInvoke', this.client.rpcInvoke.bind(this.client), true);
  this.client.start(cb);
};

/**
 * Add remote server to the rpc client.
 *
 * @param {Array} servers server info list, {id, serverType, host, port}
 */
ComponentProxy.prototype.addServers = function(servers) {
  if (!servers || !servers.length) {
    return;
  }

  genProxies(this.client, this.app, servers);
  this.client.addServers(servers);
};

/**
 * Remove remote server from the rpc client.
 *
 * @param  {Array} ids server id list
 */
ComponentProxy.prototype.removeServers = function(ids) {
  this.client.removeServers(ids);
};

/**
 * Replace remote servers from the rpc client.
 *
 * @param  {Array} ids server id list
 */
ComponentProxy.prototype.replaceServers = function(servers) {
  if (!servers || !servers.length) {
    return;
  }

  // update proxies
  this.client.proxies = {};
  genProxies(this.client, this.app, servers);

  this.client.replaceServers(servers);
};

/**
 * Proxy for rpc client rpcInvoke.
 *
 * @param {String}   serverId remote server id
 * @param {Object}   msg      rpc message: {serverType: serverType, service: serviceName, method: methodName, args: arguments}
 * @param {Function} cb      callback function
 */
ComponentProxy.prototype.rpcInvoke = function(serverId, msg, cb) {
  this.client.rpcInvoke(serverId, msg, cb);
};

/**
 * Generate rpc client
 * @access private
 * @param {Object} app current application context
 * @param {Object} opts contructor parameters for rpc client
 * @return {Object} rpc client
 */
let genRpcClient = function(app, opts) {
  opts.context = app;
  opts.routeContext = app;
  if(!!opts.rpcClient) {
    return opts.rpcClient.create(opts);
  } else {
    return Client.create(opts);
  }
};

/**
 * Generate proxy for the server infos.
 * @access private
 * @param  {Object} client rpc client instance
 * @param  {Object} app    application context
 * @param  {Array} sinfos server info list
 */
let genProxies = function(client, app, sinfos) {
  let item;
  for (let i = 0, l = sinfos.length; i < l; i++) {
    item = sinfos[i];
    if (hasProxy(client, item)) {
      continue;
    }
    client.addProxies(getProxyRecords(app, item));
  }
};

/**
 * Check a server whether has generated proxy before
 * @access private
 * @param  {Object}  client rpc client instance
 * @param  {Object}  sinfo  server info
 * @return {Boolean}        true or false
 */
let hasProxy = function(client, sinfo) {
  let proxy = client.proxies;
  return !!proxy.sys && !! proxy.sys[sinfo.serverType];
};

/**
 * Get proxy path for rpc client.
 * Iterate all the remote service path and create remote path record.
 * @access private
 * @param {Object} app current application context
 * @param {Object} sinfo server info, format: {id, serverType, host, port}
 * @return {Array}     remote path record array
 */
let getProxyRecords = function(app, sinfo) {
  let records = [],
    appBase = app.getBase(),
    record;
  // sys remote service path record
  if (app.isFrontend(sinfo)) {
    record = pathUtil.getSysRemotePath('frontend');
  } else {
    record = pathUtil.getSysRemotePath('backend');
  }
  if (record) {
    records.push(pathUtil.remotePathRecord('sys', sinfo.serverType, record));
  }

  // user remote service path record
  record = pathUtil.getUserRemotePath(appBase, sinfo.serverType);
  if (record) {
    records.push(pathUtil.remotePathRecord('user', sinfo.serverType, record));
  }

  return records;
};

let genRouteFun = function() {
  return function(session, msg, app, cb) {
    let routes = app.get('__routes__');

    if (!routes) {
      defaultRoute(session, msg, app, cb);
      return;
    }

    let type = msg.serverType,
      route = routes[type] || routes['default'];

    if (route) {
      route(session, msg, app, cb);
    } else {
      defaultRoute(session, msg, app, cb);
    }
  };
};

let defaultRoute = function(session, msg, app, cb) {
  let list = app.getServersByType(msg.serverType);
  if (!list || !list.length) {
    cb(new Error('can not find server info for type:' + msg.serverType));
    return;
  }

  let uid = session ? (session.uid || '') : '';

  let index = 0;
  if( typeof(uid) === 'number' ){
		index = Math.abs(uid) % list.length;
	}else{
		index = Math.floor(Math.random()*100000) % list.length;
	}

  utils.invokeCallback(cb, null, list[index].id);
};