/**
 * Battle Simulator multi random runner.
 * Pokemon Showdown - http://pokemonshowdown.com/
 *
 * @license MIT
 */

import { PRNG, type PRNGSeed } from '../prng';
import { Runner, type RunnerOptions } from './runner';

export interface MultiRandomRunnerOptions extends Partial<RunnerOptions> {
	totalGames: number;
	prng?: PRNG | PRNGSeed | null;
	format?: string;
	cycle?: boolean;
	all?: boolean;
	async?: boolean;
}

export class MultiRandomRunner {
	static readonly FORMATS = [
		'gen8randombattle', 'gen8randomdoublesbattle', 'gen8battlefactory',
		'gen7randombattle', 'gen7battlefactory',
		'gen6randombattle', 'gen6battlefactory',
		'gen5randombattle',
		'gen4randombattle',
		'gen3randombattle',
		'gen2randombattle',
		'gen1randombattle',
	];

	private readonly options: Partial<RunnerOptions>;
	private readonly totalGames: number;
	private readonly prng: PRNG;
	private readonly format: string | undefined;
	private readonly cycle: boolean;
	private readonly all: boolean;
	private readonly isAsync: boolean;

	private formatIndex: number;
	private numGames: number;

	constructor(options: MultiRandomRunnerOptions) {
		this.options = { ...options };

		this.totalGames = options.totalGames;

		this.prng = PRNG.get(options.prng);
		this.options.prng = this.prng;

		this.format = options.format;
		this.cycle = !!options.cycle;
		this.all = !!options.all;

		this.isAsync = !!options.async;

		this.formatIndex = 0;
		this.numGames = 0;
	}

	async run() {
		let games = [];
		let format: string | false;
		let lastFormat: string | false = false;
		let failures = 0;
		while ((format = this.getNextFormat())) {
			if (this.all && lastFormat && format !== lastFormat) {
				if (this.isAsync) await Promise.all(games);
				games = [];
			}

			const seed = this.prng.getSeed();
			const game = new Runner({ format, ...this.options }).run().catch(err => {
				failures++;
				console.error(
					`Run \`node tools/simulate multi 1 --format=${format} --seed=${seed}\` ` +
					`to debug (optionally with \`--output\` and/or \`--input\` for more info):\n`,
					err
				);
			});

			if (!this.isAsync) await game;
			games.push(game);
			lastFormat = format;
		}

		if (this.isAsync) await Promise.all(games);
		return failures;
	}

	private getNextFormat() {
		const FORMATS = MultiRandomRunner.FORMATS;
		if (this.formatIndex > FORMATS.length) return false;

		if (this.numGames++ < this.totalGames) {
			if (this.format) {
				return this.format;
			} else if (this.all) {
				return FORMATS[this.formatIndex];
			} else if (this.cycle) {
				const format = FORMATS[this.formatIndex];
				this.formatIndex = (this.formatIndex + 1) % FORMATS.length;
				return format;
			} else {
				return this.prng.sample(FORMATS);
			}
		} else if (this.all) {
			this.numGames = 1;
			this.formatIndex++;
			return FORMATS[this.formatIndex];
		}

		return false;
	}
}
