'use strict';

var _keys = require('babel-runtime/core-js/object/keys');

var _keys2 = _interopRequireDefault(_keys);

var _typeof2 = require('babel-runtime/helpers/typeof');

var _typeof3 = _interopRequireDefault(_typeof2);

var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');

var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);

var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');

var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);

var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

var _createClass2 = require('babel-runtime/helpers/createClass');

var _createClass3 = _interopRequireDefault(_createClass2);

var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');

var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);

var _inherits2 = require('babel-runtime/helpers/inherits');

var _inherits3 = _interopRequireDefault(_inherits2);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var Channel = require('./channel');
var Handler = require('./handler');
var Emitter = require('./emitter');
var Message = require('./message');

var _require = require('../../../util/logger'),
    logger = _require.logger;

var _routerInstances = {};

var Router = function (_Emitter) {
  (0, _inherits3.default)(Router, _Emitter);

  function Router(id) {
    (0, _classCallCheck3.default)(this, Router);

    var _this = (0, _possibleConstructorReturn3.default)(this, (Router.__proto__ || (0, _getPrototypeOf2.default)(Router)).call(this));

    if (_routerInstances[id]) {
      var _ret;

      return _ret = _routerInstances[id], (0, _possibleConstructorReturn3.default)(_this, _ret);
    }
    _this.hubs = {};
    _this.id = id;
    _this.channelMap = {};
    _this.handlerList = [];
    _routerInstances[id] = _this;
    return _this;
  }

  (0, _createClass3.default)(Router, [{
    key: 'link',
    value: function link(hub) {
      this.hubs[hub.id] = hub;
      hub.router = this;
    }
  }, {
    key: 'reply',
    value: function reply(message, payload) {
      this.pushMessage(message._from.hubId, message._from.terminalId, payload);
    }
  }, {
    key: 'newChannel',
    value: function newChannel(mode) {
      var channel = new Channel(mode);
      this.channelMap[channel.id] = channel;
      return channel.id;
    }
    // fixme

  }, {
    key: 'pushMessageByChannelId',
    value: function pushMessageByChannelId(hubId, channelId, payload) {
      var message = new Message(payload, 'unknown', 'unknown', channelId);
      message.to(hubId);
      message.route();
      this._fetchMessage(message);
    }
  }, {
    key: 'pushMessage',
    value: function pushMessage(hubId, terminalId, payload) {
      if (arguments.length === 2) {
        payload = terminalId;
        if (typeof hubId === 'string') {
          var _hubId$split = hubId.split('@');

          var _hubId$split2 = (0, _slicedToArray3.default)(_hubId$split, 2);

          hubId = _hubId$split2[0];
          terminalId = _hubId$split2[1];
        } else if ((typeof hubId === 'undefined' ? 'undefined' : (0, _typeof3.default)(hubId)) === 'object') {
          hubId = hubId.hubId;
          terminalId = hubId.terminalId;
        } else {
          logger.error('the first argument of pushMessage must be a string or object');
        }
      }
      var message = new Message(payload, 'unknown', 'unknown');
      message.to(hubId, terminalId);
      message.route();
      this._pushMessage(message);
    }
  }, {
    key: '_pushMessage',
    value: function _pushMessage(message) {
      var _this2 = this;

      message.destination.forEach(function (dest) {
        if (_this2.hubs[dest.hubId]) {
          if (dest.terminalId) {
            _this2.hubs[dest.hubId].pushToTerminal(dest.terminalId, message);
          } else {
            _this2.hubs[dest.hubId].broadcast(message);
          }
        } else {
          logger.error('Hub [' + dest.hubId + '] not found!');
        }
      });
      message.destroy();
    }
  }, {
    key: '_dispatchMessage',
    value: function _dispatchMessage(message) {
      var _this3 = this;

      if (message.isAlive()) {
        message.route(function (from, to, channelId) {
          if (channelId) {
            var channel = _this3.channelMap[channelId];
            if (channel) {
              var others = channel.findOthers(from.hubId, from.terminalId, to.hubId);
              if (others.length === 0) channel.pushCache(message.selectOne(to));
              return others;
            } else {
              logger.warn('There should be a connection that request to a close device, invalid channelId: ' + channelId);
            }
          } else {
            logger.error('invalid message no channelId');
          }
        });
        this._pushMessage(message);
      }
    }
  }, {
    key: '_fetchMessage',
    value: function _fetchMessage(message) {
      var _this4 = this;

      // Handler.run(this.handlerList)
      this.emit(Router.Event.MESSAGE_RECEIVED, message._from.hubId + '.' + message._from.terminalId, message);
      return Handler.run(this.handlerList, message).then(function (data) {
        // todo 如何保证消息的顺序
        _this4._dispatchMessage(message);
      }).catch(function (e) {
        console.error(e);
      });
    }
  }, {
    key: 'registerHandler',
    value: function registerHandler(handler) {
      var currentHandler = new Handler(handler, this);
      this.handlerList.push(currentHandler);
      return currentHandler;
    }
  }, {
    key: '_event',
    value: function _event(signal) {
      var _this5 = this;

      switch (signal.type) {
        case Router.Event.TERMINAL_JOINED:
          logger.verbose('[terminal]' + signal.hubId + '-' + signal.terminalId + ' joined');
          if (signal.channelId) {
            var channel = this.channelMap[signal.channelId];
            if (channel) {
              logger.verbose('[channel]' + signal.hubId + '-' + signal.terminalId + ' join channel ' + signal.channelId);
              channel.join(signal.hubId, signal.terminalId, signal.forced);
            } else {
              logger.warn('There should be a connection that is not closed properly, the channel [' + signal.channelId + '] of terminal [' + signal.terminalId + '] is not found!');
            }
            var cacheMessages = channel.getCache(signal.hubId, signal.terminalId);
            cacheMessages.forEach(function (m) {
              return _this5._dispatchMessage(m);
            });
          }
          this.emit(Router.Event.TERMINAL_JOINED, signal.hubId + '.' + signal.terminalId, signal);
          break;
        case Router.Event.TERMINAL_LEAVED:
          logger.verbose('[terminal]' + signal.hubId + '-' + signal.terminalId + ' leaved');
          if (signal.channelId) {
            var _channel = this.channelMap[signal.channelId];
            if (_channel) {
              logger.verbose('[channel]' + signal.hubId + '-' + signal.terminalId + ' leave channel ' + signal.channelId);
              _channel.leave(signal.hubId, signal.terminalId);
            }
          }
          this.emit(Router.Event.TERMINAL_LEAVED, signal.hubId + '.' + signal.terminalId, signal);
          break;
      }
    }
  }], [{
    key: 'check',
    value: function check() {
      (0, _keys2.default)(_routerInstances).forEach(function (id) {
        if ((0, _keys2.default)(_routerInstances[id].hubs).length === 0) {
          console.warn('[Mlink Warning] Router[' + id + '] do not has any hub.make sure your id is correct');
        }
      });
    }
  }, {
    key: 'get',
    value: function get(id) {
      return _routerInstances[id] || new Router(id);
    }
  }, {
    key: 'dump',
    value: function dump() {
      (0, _keys2.default)(_routerInstances).forEach(function (id) {
        var router = _routerInstances[id];
        for (var hid in router.hubs) {
          console.log('Router[' + id + ']' + '<---->' + 'Hub[' + hid + ']');
        }
      });
    }
  }]);
  return Router;
}(Emitter);

Router.Event = {
  TERMINAL_JOINED: 0,
  TERMINAL_LEAVED: 1,
  MESSAGE_RECEIVED: 2
};
module.exports = Router;