#!/usr/bin/env node

/**
 * This script parses index.html and sets the version query string of each
 * resource to be the MD5 hash of that resource.
 * It also updates news and the learnsets-g6.js file.
 */

"use strict";

const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const child_process = require('child_process');
const compiler = require('./compiler');

const thisDir = __dirname;
const rootDir = path.resolve(thisDir, '..');
process.chdir(rootDir);

const AUTOCONFIG_START = '/*** Begin automatically generated configuration ***/';
const AUTOCONFIG_END = '/*** End automatically generated configuration ***/';
const UTF8 = { encoding: 'utf8' };

function escapeRegex(string) {
	return string.replace(/[/*.]/g, '\\$&');
}

/*********************************************************
 * Update version number
 *********************************************************/

process.stdout.write("Updating version... ");

let version = require('../package.json').version;
try {
	let commit = child_process.execSync('git rev-parse HEAD', {
		stdio: ['ignore', 'pipe', 'ignore'],
	});
	const head = ('' + commit).trim();
	commit = child_process.execSync('git merge-base origin/master HEAD', {
		stdio: ['ignore', 'pipe', 'ignore'],
	});
	const origin = ('' + commit).trim();
	version += ` (${head.slice(0, 8)}${head !== origin ? `/${origin.slice(0, 8)}` : ''})`;
} catch {}

const routes = JSON.parse(fs.readFileSync('config/routes.json'));
const autoconfigRegex = new RegExp(`${escapeRegex(AUTOCONFIG_START)}[^]+${escapeRegex(AUTOCONFIG_END)}`);
const autoconfig = `${AUTOCONFIG_START}
Config.version = ${JSON.stringify(version)};

Config.routes = {
	root: '${routes.root}',
	client: '${routes.client}',
	dex: '${routes.dex}',
	replays: '${routes.replays}',
	users: '${routes.users}',
	teams: '${routes.teams}',
};
${AUTOCONFIG_END}`;

// remove old automatically generated configuration and add the new one
let configBuf = fs.readFileSync('config/config.js', UTF8);
if (autoconfigRegex.test(configBuf)) {
	configBuf = configBuf.replace(autoconfigRegex, autoconfig);
} else {
	configBuf += autoconfig;
}
fs.writeFileSync('config/config.js', configBuf);
console.log("DONE");

/*********************************************************
 * Compile TS files
 *********************************************************/

process.stdout.write("Compiling TS files... ");

const compileStartTime = process.hrtime();
let compiledFiles = 0;

// Babel can't find babelrc if we try to compile stuff in caches/pokemon-showdown/ fsr
// eslint-disable-next-line no-eval
const compileOpts = Object.assign(eval('(' + fs.readFileSync('.babelrc') + ')'), {
	babelrc: false,
	incremental: true,
	ignore: ['play.pokemonshowdown.com/src/battle-animations.js', 'play.pokemonshowdown.com/src/battle-animations-moves.js'],
});
if (process.argv[2] === 'full') {
	delete compileOpts.ignore;
	compiler.compileToDir(
		['caches/pokemon-showdown/server/chat-formatter.ts', 'caches/pokemon-showdown/sim/teams.ts'],
		'play.pokemonshowdown.com/js/server/',
		compileOpts
	);
} else {
	try {
		fs.statSync('play.pokemonshowdown.com/data/graphics.js');
		// graphics.js exists, recompile it
		delete compileOpts.ignore;
	} catch {}
}

compiledFiles += compiler.compileToDir(`play.pokemonshowdown.com/src`, `play.pokemonshowdown.com/js`, compileOpts);

compiledFiles += compiler.compileToDir(`replay.pokemonshowdown.com/src`, `replay.pokemonshowdown.com/js`, compileOpts);

compiledFiles += compiler.compileToDir(`teams.pokemonshowdown.com/src`, `teams.pokemonshowdown.com/js`, compileOpts);

try {
	fs.statSync('play.pokemonshowdown.com/data/text.js');
	// text.js exists, we can compile battledata
	compiledFiles += compiler.compileToFile(
		[
			'play.pokemonshowdown.com/src/battle-dex.ts',
			'play.pokemonshowdown.com/src/battle-teams.ts',
			'play.pokemonshowdown.com/src/battle-dex-data.ts',
			'play.pokemonshowdown.com/src/battle-log.ts',
			'play.pokemonshowdown.com/src/battle-log-misc.js',
			'caches/pokemon-showdown/server/chat-formatter.ts',
			'play.pokemonshowdown.com/data/text.js',
			'play.pokemonshowdown.com/data/text-afd.js',
			'play.pokemonshowdown.com/src/battle-text-parser.ts',
		],
		'play.pokemonshowdown.com/js/battledata.js',
		compileOpts
	);
} catch {}

if (!compileOpts.ignore) {
	compiledFiles += compiler.compileToFile(
		['play.pokemonshowdown.com/src/battle-animations.ts', 'play.pokemonshowdown.com/src/battle-animations-moves.ts'],
		'play.pokemonshowdown.com/data/graphics.js',
		compileOpts
	);
}

const diff = process.hrtime(compileStartTime);
console.log(
	`(${compiledFiles} ${compiledFiles !== 1 ? "files" : "file"} in ${(diff[0] + diff[1] / 1e9).toFixed(3)}s) DONE`
);

/*********************************************************
 * Update cachebuster and News
 *********************************************************/

process.stdout.write("Updating cachebuster and URLs... ");

const URL_REGEX = /(src=|href=|linkStyle\()"(.*?)(\?[a-z0-9]*?)?"/g;

function addCachebuster(_, attr, url, urlQuery) {
	url = url.replace('/replay.pokemonshowdown.com/', '/' + routes.replays + '/');
	url = url.replace('/dex.pokemonshowdown.com/', '/' + routes.dex + '/');
	url = url.replace('/play.pokemonshowdown.com/', '/' + routes.client + '/');
	url = url.replace('/pokemonshowdown.com/', '/' + routes.root + '/');
	url = url.replace('/teams.pokemonshowdown.com/', '/' + routes.teams + '/');

	if (urlQuery) {
		if (url.startsWith('/')) {
			let hash = Math.random(); // just in case creating the hash fails
			try {
				const filename = url.slice(1).replace('/' + routes.client + '/', '');
				const fstr = fs.readFileSync(filename, UTF8);
				hash = crypto.createHash('md5').update(fstr).digest('hex').substr(0, 8);
			} catch {}

			return attr + '"' + url + '?' + hash + '"';
		} else {
			// TODO: generalize better
			let hash;
			for (const subdir of ['teams', 'replays']) {
				if (hash) break;
				try {
					const fstr = fs.readFileSync(subdir + '.pokemonshowdown.com/' + url, UTF8);
					hash = crypto.createHash('md5').update(fstr).digest('hex').substr(0, 8);
				} catch {}
			}

			return attr + '"' + url + '?' + (hash || 'v1') + '"';
		}
	} else {
		return attr + '"' + url + '"';
	}
}

// add hashes to js and css files and rewrite URLs
let indexContents = fs.readFileSync('play.pokemonshowdown.com/index.template.html', UTF8);
indexContents = indexContents.replace(URL_REGEX, addCachebuster);
let preactIndexContents = fs.readFileSync('play.pokemonshowdown.com/preactalpha.template.html', UTF8);
preactIndexContents = preactIndexContents.replace(URL_REGEX, addCachebuster);
let crossprotocolContents = fs.readFileSync('play.pokemonshowdown.com/crossprotocol.template.html', UTF8);
crossprotocolContents = crossprotocolContents.replace(URL_REGEX, addCachebuster);
let replayEmbedContents = fs.readFileSync('play.pokemonshowdown.com/js/replay-embed.template.js', UTF8);
replayEmbedContents = replayEmbedContents.replace(/play\.pokemonshowdown\.com/g, routes.client);

// add news, only if it's actually likely to exist
process.stdout.write("and news... ");
let stdout = '';
let newsid = 0;
let news = '[failed to retrieve news]';
try {
	stdout = child_process.execSync('php ' + path.resolve(thisDir, 'news-embed.php'), {
		stdio: 'pipe',
	});
} catch (e) {
	console.log("git hook failed to retrieve news (exec command failed):\n" + (e.error + e.stderr + e.stdout));
}
try {
	if (stdout) [newsid, news] = JSON.parse(stdout);
} catch (e) {
	if (stdout.includes("config/news.inc.php): Failed to open stream: No such file or directory")) {
		console.log("news.inc.php unavailable");
	} else {
		console.log("git hook failed to retrieve news (parsing JSON failed):\n\n" + e.message + '\n\n' + stdout);
	}
}

indexContents = indexContents.replace(/<!-- newsid -->/g, newsid);
indexContents = indexContents.replace(/<!-- news -->/g, news);

let indexContents2 = '';
try {
	const indexContentsOld = indexContents;
	indexContents = indexContents.replace(/<!-- head custom -->/g, '' + fs.readFileSync('config/head-custom.html'));
	indexContents2 = indexContentsOld
		.replace(/<!-- head custom -->/g, '' + fs.readFileSync('config/head-custom-test.html'));
} catch {}

fs.writeFileSync('play.pokemonshowdown.com/index.html', indexContents);
if (indexContents2) {
	fs.writeFileSync('play.pokemonshowdown.com/index-test.html', indexContents2);
}
fs.writeFileSync('play.pokemonshowdown.com/preactalpha.html', preactIndexContents);
fs.writeFileSync('play.pokemonshowdown.com/crossprotocol.html', crossprotocolContents);
fs.writeFileSync('play.pokemonshowdown.com/js/replay-embed.js', replayEmbedContents);

let replaysContents = fs.readFileSync('replay.pokemonshowdown.com/index.template.php', UTF8);
replaysContents = replaysContents.replace(URL_REGEX, addCachebuster);
fs.writeFileSync('replay.pokemonshowdown.com/index.php', replaysContents);

let teamsContents = fs.readFileSync('teams.pokemonshowdown.com/index.template.html', UTF8);
teamsContents = teamsContents.replace(URL_REGEX, addCachebuster);
fs.writeFileSync('teams.pokemonshowdown.com/index.html', teamsContents);

console.log("DONE");
