'use strict';

let saveConfig = (() => {
  var _ref = _asyncToGenerator(function* () {
    function saveFile() {
      return new Promise((resolve, reject) => {
        _fs2.default.writeFile(configName, JSON.stringify(config), 'utf8', err => {
          if (err) {
            log.error(`could not save masterlist config file: ${err}`);
            reject({ status: 'error', message: `could not save local config file: '${err}'` });
            return;
          }
          resolve({ status: 'ok' });
          return;
        });
      });
    }

    // create a diff obj
    const diff = {
      port: serverConfig.port,
      auth_key: config.authKey,
      update: true,
      payload: {}
    };

    let cnt = 0;
    remoteKeys.forEach(function (k) {
      if (_remoteConfig[k] !== config[k]) {
        diff.payload[k] = config[k];
        cnt++;
      }
    });

    if (cnt === 0) {
      return { status: 'ok' };
    }

    try {
      yield saveFile();
      const r = yield (0, _api2.default)('POST', diff, 'server');
      remoteKeys.forEach(function (k) {
        _remoteConfig[k] = config[k];
      });
      return r;
    } catch (e) {
      return e;
    }
  });

  return function saveConfig() {
    return _ref.apply(this, arguments);
  };
})();

let announceServer = (() => {
  var _ref3 = _asyncToGenerator(function* () {
    if (isAnnounced) {
      return { status: 'error', message: 'already announced' };
    }

    if (aliveTimer) {
      clearInterval(aliveTimer);
    }

    const res = validateConfig(config);
    if (!res.result) {
      const r = { status: 'error', message: 'config misconfiguration:\n' };
      res.messages.forEach(function (m) {
        r.message = `${r.message}- ${m}\n`;
      });

      log.debug('masterlist config misconfiguration');
      return r;
    }

    const startAliveTimer = function () {
      let fails = 0;
      aliveTimer = setInterval(_asyncToGenerator(function* () {
        if (!isAnnounced) {
          log.debug(`alive timer is still running even though
          the server is not announced to the masterlist.`);
          clearInterval(aliveTimer);
        }
        try {
          yield (0, _api2.default)('POST', {
            auth_key: config.authKey,
            port: serverConfig.port
          }, 'server');
          fails = 0;
        } catch (e) {
          if (e.message === 'invalid auth_key') {
            log.debug('the masterlist server might have been restarted. ' + 'trying an immediate re-announce.');
            clearInterval(aliveTimer);
            isAnnounced = false;
            const r = yield announceServer();
            if (r.status === 'ok') {
              log.info('re-announced the server.');
            }
            return;
          }

          log.debug(`error in keeping the server alive in the masterlist: ${e.message}`);
          fails++;

          if (fails >= 3) {
            log.warn('too many failures trying to keep connection alive. will try re-announcing.');
            clearInterval(aliveTimer);
            isAnnounced = false;
            tryReannouncing(announceServer, e);
          }
        }
      }), 20000);
    };

    try {
      const r = yield (0, _api2.default)('PUT', {
        name: config.displayName,
        description: config.description,
        max_players: serverConfig.maxPlayers,
        player_count: jcmp.players.length,
        port: serverConfig.port,
        image: config.image,
        auth_key: config.authKey,
        required_dlc: serverConfig.requiredDLC,
        password: serverConfig.password.length > 0,
        network_version: jcmp.networkVersion
      }, 'server');

      _remoteConfig = {
        name: config.displayName,
        description: config.description,
        image: config.image,
        authKey: config.authKey
      };

      isAnnounced = true;
      startAliveTimer();
      return r;
    } catch (e) {
      if (e.message === 'already registered') {
        isAnnounced = true;
        startAliveTimer();
      } else {
        tryReannouncing(announceServer, e);
      }
      return e;
    }
  });

  return function announceServer() {
    return _ref3.apply(this, arguments);
  };
})();

let main = (() => {
  var _ref5 = _asyncToGenerator(function* () {

    // JCMP Events
    let updatePlayerCount = (() => {
      var _ref7 = _asyncToGenerator(function* (value) {
        if (isAnnounced) {
          try {
            yield (0, _api2.default)('POST', {
              update: true,
              auth_key: config.authKey,
              port: serverConfig.port,
              payload: {
                player_count: value
              }
            }, 'server');
          } catch (e) {
            log.error(`could not update player count to masterlist: ${e.message}`);
          }
        }
      });

      return function updatePlayerCount(_x) {
        return _ref7.apply(this, arguments);
      };
    })();

    if (!_fs2.default.existsSync(configName)) {
      _fs2.default.writeFileSync(configName, '{}');
    }

    log.debug('adding event handlers');
    jcmp.events.Add('masterlist.announce', announceServer);
    jcmp.events.Add('masterlist.let-it-go', _asyncToGenerator(function* () {
      if (!isAnnounced) {
        return { status: 'ok', message: '' };
      }

      try {
        const r = yield (0, _api2.default)('DELETE', {
          port: serverConfig.port,
          auth_key: config.authKey
        }, 'server');

        isAnnounced = false;
        clearInterval(aliveTimer);
        log.debug('server has been removed from the masterlist as requested');
        return r;
      } catch (e) {
        return e;
      }
    }));
    jcmp.events.Add('masterlist.config', function () {
      return config;
    });
    jcmp.events.Add('masterlist.config.reload', function () {
      log.debug('reloading masterlist configuration');

      let temp = {};
      try {
        temp = require(configName);
      } catch (e) {
        throw new Error(`could not read masterlist config: ${e}`);
      }

      for (const prop in defaults) {
        if (!temp.hasOwnProperty(prop)) {
          log.debug(`assigning default value for '${prop}'.`);
          temp[prop] = defaults[prop];
        }
      }

      const tempRes = validateConfig(temp);

      if (!tempRes.result) {
        return tempRes;
      }
      config = temp;
      return tempRes;
    });
    jcmp.events.Add('masterlist.config.validate', function () {
      return validateConfig(config);
    });
    jcmp.events.Add('masterlist.config.save', saveConfig);

    jcmp.events.Add('PlayerCreated', function () {
      return updatePlayerCount(jcmp.players.length);
    });
    jcmp.events.Add('PlayerDestroyed', function () {
      return updatePlayerCount(jcmp.players.length - 1);
    });

    const r = yield jcmp.events.Call('masterlist.config.reload')[0];
    if (!r.result) {
      log.error('masterlist config misconfiguration:');
      r.messages.forEach(function (m) {
        return log.error(m);
      });
      return false;
    }

    if (config.announceServer) {
      const neededKeys = ['host', 'requiredDLC'];

      const err = neededKeys.filter(function (k) {
        return typeof serverConfig[k] === 'undefined';
      }).map(function (k) {
        return `'${k}`;
      });

      if (err.length > 0) {
        log.error(`server config error: missing keys ${err.join(', ')}`);
        return false;
      }

      if (!_validator2.default.isIP(serverConfig.host) || serverConfig.host === '127.0.0.1') {
        log.debug('not announcing the server to the masterlist');
        return false;
      }

      log.debug('announcing server to the masterlist.');
      const res = yield jcmp.events.Call('masterlist.announce')[0];

      if (res.status === 'ok') {
        log.info('server has been announced to the masterlist.');
        return true;
      }

      log.error(`could not announce the server to the masterlist: ${res.message}`);
      _errorHelper.ErrorHelper.describe(res.message, 'PUT');
      return false;
    }
    log.info('not announcing the masterlist: announceServer configured to "false"');
    return true;
  });

  return function main() {
    return _ref5.apply(this, arguments);
  };
})();

require('babel-polyfill');

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

var _customLogger = require('custom-logger');

var _customLogger2 = _interopRequireDefault(_customLogger);

var _validator = require('validator');

var _validator2 = _interopRequireDefault(_validator);

var _api = require('./api');

var _api2 = _interopRequireDefault(_api);

var _errorHelper = require('./errorHelper');

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

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

const log = _customLogger2.default.config({ level: process.env.NODE_ENV === 'production' });
global.log = log;

const remoteKeys = ['name', 'description', 'image', 'auth_key'];
const configName = _path2.default.join(__dirname, 'config.json');
const defaults = {
  displayName: 'JC3MP Default Server',
  description: 'This is the JC3MP default server.',
  image: 'banner.png',
  provideImage: true,
  imageProviderPort: 2508
};

const serverConfig = JSON.parse(jcmp.server.config);
let isAnnounced = false;
let aliveTimer = null;
let tryingToReannounce = false;
let config = {};
let _remoteConfig = {};

function validateConfig(c) {
  const retn = {
    result: true,
    messages: []
  };

  const validators = {
    displayName: v => _validator2.default.isLength(v, { min: 6, max: 64 }) ? true : `invalid 'displayName' length: min 6, max 64 chars`,
    description: v => _validator2.default.isLength(v, { min: 0, max: 256 }) ? true : `invalid 'description' length: min 0, max 256 chars`,
    image: v => {
      if (_path2.default.extname(v) !== '.png') {
        return `invalid 'image' filetype: execpted .png`;
      }
      if (!_validator2.default.isURL(v, { require_tld: true, protocols: ['http', 'https'] })) {
        return `invalid 'image': has to be an URL (http(s), *.tld)`;
      }
      return true;
    }
  };

  for (const k in defaults) {
    if (!c.hasOwnProperty(k)) {
      log.debug(`validateConfig: key '${k}' missing`);
      retn.result = false;
      retn.messages.push(`key '${k}' missing`);
      continue;
    }

    if (validators.hasOwnProperty(k)) {
      const res = validators[k](c[k]);

      if (res !== true) {
        retn.result = false;
        retn.messages.push(res);
      }
    }
  }

  return retn;
}

function tryReannouncing(announceFunction, from) {
  if (isAnnounced) {
    log.debug(`skipping tryReannouncing, we are announced`);
    return;
  }
  if (tryingToReannounce) {
    return;
  }

  tryingToReannounce = true;

  let onlySingleRetry = from.message === 'invalid auth_key';
  let interval = null;
  let retries = 0;

  const tryReannounce = (() => {
    var _ref2 = _asyncToGenerator(function* () {
      retries++;
      log.debug('trying to re-announce the server');

      const res = yield announceFunction();
      if (res.status === 'error') {
        if (onlySingleRetry && res.message === 'invalid auth_key') {
          log.error(`fatal: could not re-announce the server after previous failures.
          error message '${res.message}'. 
          this needs to be fixed manually.`);
          log.error('not retrying because we got invalid auth_key both times');
          clearInterval(interval);
          tryingToReannounce = false;
        } else {
          if (res.message === 'invalid auth_key') {
            onlySingleRetry = true;
          }
          log.error(`could not re-announce the server: ${res.message}`);
        }
      } else {
        clearInterval(interval);
        log.info(`server re-announced successfully after ${retries} retries.`);
        retries = 0;
        tryingToReannounce = false;
      }
    });

    return function tryReannounce() {
      return _ref2.apply(this, arguments);
    };
  })();

  interval = setInterval(() => tryReannounce(), 10000);
}

setInterval(() => {}, 500);
main();