/**
 * Main file
 * Pokemon Showdown - http://pokemonshowdown.com/
 *
 * This is the main Pokemon Showdown app, and the file that the
 * `pokemon-showdown` script runs if you start Pokemon Showdown normally.
 *
 * This file sets up our SockJS server, which handles communication
 * between users and your server, and also sets up globals. You can
 * see details in their corresponding files, but here's an overview:
 *
 * Users - from users.ts
 *
 *   Most of the communication with users happens in users.ts, we just
 *   forward messages between the sockets.js and users.ts.
 *
 *   It exports the global tables `Users.users` and `Users.connections`.
 *
 * Rooms - from rooms.ts
 *
 *   Every chat room and battle is a room, and what they do is done in
 *   rooms.ts. There's also a global room which every user is in, and
 *   handles miscellaneous things like welcoming the user.
 *
 *   It exports the global table `Rooms.rooms`.
 *
 * Dex - from .sim-dist/dex.ts
 *
 *   Handles getting data about Pokemon, items, etc.
 *
 * Ladders - from ladders.ts and ladders-remote.ts
 *
 *   Handles Elo rating tracking for players.
 *
 * Chat - from chat.ts
 *
 *   Handles chat and parses chat commands like /me and /ban
 *
 * Sockets - from sockets.js
 *
 *   Used to abstract out network connections. sockets.js handles
 *   the actual server and connection set-up.
 *
 * @license MIT
 */

// NOTE: This file intentionally doesn't use too many modern JavaScript
// features, so that it doesn't crash old versions of Node.js, so we
// can successfully print the "We require Node.js 8+" message.

// Check for version and dependencies
try {
	// I've gotten enough reports by people who don't use the launch
	// script that this is worth repeating here
	[].flatMap(x => x);
} catch (e) {
	throw new Error("We require Node.js version 12 or later; you're using " + process.version);
}

try {
	require.resolve('../.sim-dist/index');
	const sucraseVersion = require('sucrase').getVersion().split('.');
	if (
		parseInt(sucraseVersion[0]) < 3 ||
		(parseInt(sucraseVersion[0]) === 3 && parseInt(sucraseVersion[1]) < 12)
	) {
		throw new Error("Sucrase version too old");
	}
} catch (e) {
	throw new Error("Dependencies are unmet; run `node build` before launching Pokemon Showdown again.");
}

import {FS, Repl} from '../lib';

/*********************************************************
 * Load configuration
 *********************************************************/

import * as ConfigLoader from './config-loader';
global.Config = ConfigLoader.Config;

import {Monitor} from './monitor';
global.Monitor = Monitor;
global.__version = {head: ''};
void Monitor.version().then((hash: any) => {
	global.__version.tree = hash;
});

if (Config.watchconfig) {
	FS(require.resolve('../config/config')).onModify(() => {
		try {
			global.Config = ConfigLoader.load(true);
			Monitor.notice('Reloaded ../config/config.js');
		} catch (e) {
			Monitor.adminlog("Error reloading ../config/config.js: " + e.stack);
		}
	});
}

/*********************************************************
 * Set up most of our globals
 *********************************************************/

import {Dex} from '../sim/dex';
global.Dex = Dex;
global.toID = Dex.toID;

import {Teams} from '../sim/teams';
global.Teams = Teams;

import {LoginServer} from './loginserver';
global.LoginServer = LoginServer;

import {Ladders} from './ladders';
global.Ladders = Ladders;

import {Chat} from './chat';
global.Chat = Chat;

import {Users} from './users';
global.Users = Users;

import {Punishments} from './punishments';
global.Punishments = Punishments;

import {Rooms} from './rooms';
global.Rooms = Rooms;
// We initialize the global room here because roomlogs.ts needs the Rooms global
Rooms.global = new Rooms.GlobalRoomState();

import * as Verifier from './verifier';
global.Verifier = Verifier;
Verifier.PM.spawn();

import {Tournaments} from './tournaments';
global.Tournaments = Tournaments;

import {IPTools} from './ip-tools';
global.IPTools = IPTools;
void IPTools.loadHostsAndRanges();

if (Config.crashguard) {
	// graceful crash - allow current battles to finish before restarting
	process.on('uncaughtException', (err: Error) => {
		Monitor.crashlog(err, 'The main process');
	});

	process.on('unhandledRejection', err => {
		Monitor.crashlog(err as any, 'A main process Promise');
	});
}

/*********************************************************
 * Start networking processes to be connected to
 *********************************************************/

import {Sockets} from './sockets';
global.Sockets = Sockets;

export function listen(port: number, bindAddress: string, workerCount: number) {
	Sockets.listen(port, bindAddress, workerCount);
}

if (require.main === module) {
	// Launch the server directly when app.js is the main module. Otherwise,
	// in the case of app.js being imported as a module (e.g. unit tests),
	// postpone launching until app.listen() is called.
	let port;
	for (const arg of process.argv) {
		if (/^[0-9]+$/.test(arg)) {
			port = parseInt(arg);
			break;
		}
	}
	Sockets.listen(port);
}

/*********************************************************
 * Set up our last global
 *********************************************************/

import * as TeamValidatorAsync from './team-validator-async';
global.TeamValidatorAsync = TeamValidatorAsync;
TeamValidatorAsync.PM.spawn();

/*********************************************************
 * Start up the REPL server
 *********************************************************/

// eslint-disable-next-line no-eval
Repl.start('app', cmd => eval(cmd));

/*********************************************************
 * Fully initialized, run startup hook
 *********************************************************/

if (Config.startuphook) {
	process.nextTick(Config.startuphook);
}

if (Config.ofemain) {
	try {
		require.resolve('node-oom-heapdump');
	} catch (e) {
		if (e.code !== 'MODULE_NOT_FOUND') throw e; // should never happen
		throw new Error(
			'node-oom-heapdump is not installed, but it is a required dependency if Config.ofe is set to true! ' +
			'Run npm install node-oom-heapdump and restart the server.'
		);
	}

	// Create a heapdump if the process runs out of memory.
	global.nodeOomHeapdump = (require as any)('node-oom-heapdump')({
		addTimestamp: true,
	});
}
