/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import es from 'event-stream';
import gulp from 'gulp';
import * as path from 'path';
import fancyLog from 'fancy-log';
import ansiColors from 'ansi-colors';
import * as cp from 'child_process';
import { tmpdir } from 'os';
import { existsSync, mkdirSync, rmSync } from 'fs';
import * as task from './lib/task.ts';
import watcher from './lib/watch/index.ts';
import { debounce, untar } from './lib/util.ts';
import { createReporter } from './lib/reporter.ts';
import gunzip from 'gulp-gunzip';

const root = 'cli';
const rootAbs = path.resolve(import.meta.dirname, '..', root);
const src = `${root}/src`;

const platformOpensslDirName =
	process.platform === 'win32' ? (
		process.arch === 'arm64'
			? 'arm64-windows-static-md'
			: 'x64-windows-static-md')
		: process.platform === 'darwin' ? (
			process.arch === 'arm64'
				? 'arm64-osx'
				: 'x64-osx')
			: (process.arch === 'arm64'
				? 'arm64-linux'
				: process.arch === 'arm'
					? 'arm-linux'
					: 'x64-linux');
const platformOpensslDir = path.join(rootAbs, 'openssl', 'package', 'out', platformOpensslDirName);

const hasLocalRust = (() => {
	let result: boolean | undefined = undefined;
	return () => {
		if (result !== undefined) {
			return result;
		}

		try {
			const r = cp.spawnSync('cargo', ['--version']);
			result = r.status === 0;
		} catch (e) {
			result = false;
		}

		return result;
	};
})();

const compileFromSources = (callback: (err?: string) => void) => {
	const proc = cp.spawn('cargo', ['--color', 'always', 'build'], {
		cwd: root,
		stdio: ['ignore', 'pipe', 'pipe'],
		env: existsSync(platformOpensslDir) ? { OPENSSL_DIR: platformOpensslDir, ...process.env } : process.env
	});

	const stdoutErr: Buffer[] = [];
	proc.stdout.on('data', d => stdoutErr.push(d));
	proc.stderr.on('data', d => stdoutErr.push(d));
	proc.on('error', callback);
	proc.on('exit', code => {
		if (code !== 0) {
			callback(Buffer.concat(stdoutErr).toString());
		} else {
			callback();
		}
	});
};

const acquireBuiltOpenSSL = (callback: (err?: unknown) => void) => {
	const dir = path.join(tmpdir(), 'vscode-openssl-download');
	mkdirSync(dir, { recursive: true });

	cp.spawnSync(
		process.platform === 'win32' ? 'npm.cmd' : 'npm',
		['pack', '@vscode/openssl-prebuilt'],
		{ stdio: ['ignore', 'ignore', 'inherit'], cwd: dir }
	);

	gulp.src('*.tgz', { cwd: dir })
		.pipe(gunzip())
		.pipe(untar())
		.pipe(gulp.dest(`${root}/openssl`))
		.on('error', callback)
		.on('end', () => {
			rmSync(dir, { recursive: true, force: true });
			callback();
		});
};

const compileWithOpenSSLCheck = (reporter: import('./lib/reporter.ts').IReporter) => es.map((_, callback) => {
	compileFromSources(err => {
		if (!err) {
			// no-op
		} else if (err.toString().includes('Could not find directory of OpenSSL installation') && !existsSync(platformOpensslDir)) {
			fancyLog(ansiColors.yellow(`[cli]`), 'OpenSSL libraries not found, acquiring prebuilt bits...');
			acquireBuiltOpenSSL(err => {
				if (err) {
					callback(err as Error);
				} else {
					compileFromSources(err => {
						if (err) {
							reporter(err.toString());
						}
						callback(undefined, '');
					});
				}
			});
		} else {
			reporter(err.toString());
		}
		callback(undefined, '');
	});
});

const warnIfRustNotInstalled = () => {
	if (!hasLocalRust()) {
		fancyLog(ansiColors.yellow(`[cli]`), 'No local Rust install detected, compilation may fail.');
		fancyLog(ansiColors.yellow(`[cli]`), 'Get rust from: https://rustup.rs/');
	}
};

const compileCliTask = task.define('compile-cli', () => {
	warnIfRustNotInstalled();
	const reporter = createReporter('cli');
	return gulp.src(`${root}/Cargo.toml`)
		.pipe(compileWithOpenSSLCheck(reporter))
		.pipe(reporter.end(true));
});


const watchCliTask = task.define('watch-cli', () => {
	warnIfRustNotInstalled();
	return watcher(`${src}/**`, { read: false })
		.pipe(debounce(compileCliTask as task.StreamTask));
});

gulp.task(compileCliTask);
gulp.task(watchCliTask);
