#!/usr/bin/env node

// -- START HEADER --

// Copyright (c) benja2998. You may redistribute under the terms of the GPL-3.0-or-later.
// bat2js is a Batch to JavaScript compiler.

// -- END HEADER --

// -- START IMPORTS --

import * as fs from 'fs';
import * as path from 'path';
const { randomUUID } = require('crypto');

// -- END IMPORTS --

export type token = {
	type: string;
	value: string;
	argument: string;
}

export class tokenType {
	public static readonly COMMENT = 'COMMENT';
	public static readonly COMMAND = 'COMMAND';
	public static readonly STRING = 'STRING';
	public static readonly VARIABLE = 'VARIABLE';
	public static readonly NUMBER = 'NUMBER';
	public static readonly SETVAR = 'SETVAR';
}

// Define a function to tokenize the file contents

export function tokenize(contents: string): token[] {
    // Parse the contents into an array of lines
    let lines = contents.split('\n');
    let tokens: token[] = [];

	// Iterate over each word

	for (let i = 0; i < lines.length; i++) {
		let line = lines[i];
		let words = line.split(' ');

		if (line.startsWith('@')) {
			continue;
		}

		if (line === '') {
			continue;
		}

		// Check if \r comes at the end of a word or is the only word

		if (words[words.length - 1].endsWith('\r')) {
			// Remove the \r
			words[words.length - 1] = words[words.length - 1].slice(0, -1);
		}

		// Do the same but for \n

		if (words[words.length - 1].endsWith('\n')) {
			// Remove the \n
			words[words.length - 1] = words[words.length - 1].slice(0, -1);
		}

		// Remove empty words

		words = words.filter(word => word !== '');

		// Now that we've cleaned up, let's tokenize

		for (let j = 0; j < words.length; j++) {
			let word = words[j];
			
			let restOfLine = line.slice(line.indexOf(word) + word.length).trim();

			// Check if the word is a comment by checking if it's rem or ::

			if (word.startsWith('rem') || word.startsWith('::')) {
				// Get the comment by looking at the rest of the line
				let comment = line.slice(line.indexOf(word) + word.length).trim();

				// Add the comment to the tokens

				tokens.push({
					type: tokenType.COMMENT,
					value: comment,
					argument: '' // No argument for a comment
				});
				continue;
			} else {
				// Check if the word is a command

				if (word === 'echo') {
					tokens.push({
						type: tokenType.COMMAND,
						value: word,
						argument: restOfLine
					});
				} 
				else if (word === 'set') {
					// Get the rest of the line for the variable name and value

					let variableNameValue = restOfLine.trim();

					// Remove quotes from the variable name and value

					variableNameValue = variableNameValue.replace(/['"]/g, '');

					// Split the variable name and value by the first =

					let variableName = variableNameValue.split('=')[0];
					let variableValue = variableNameValue.split('=')[1];

					// Check if the variableValue is a string

					if (isNaN(Number(variableValue))) {
						// It's a string, so add quotes
						variableValue = `"${variableValue}"`;
					}

					// Add the variable to the tokens

					tokens.push({
						type: tokenType.SETVAR,
						value: variableName,
						argument: variableValue
					});
				}
				else if (word === 'pause') {
					let redirectTo = '';

					// Get the next word
					let nextWord = words[j + 1];

					// Check if the next word contains >

					if (nextWord !== undefined && nextWord.includes('>')) {
						// Redirection

						// Split nextWord by > and get the second part
						redirectTo = nextWord.split('>')[1];

						// Check if redirectTo is empty

						if (redirectTo === '') {
							// Set redirectTo to the rest of the line

							redirectTo = line.slice(line.indexOf(nextWord) + nextWord.length).trim();
						}
					}

					// Push the pause command to the tokens

					tokens.push({
						type: tokenType.COMMAND,
						value: 'pause',
						argument: redirectTo
					});
				}

				continue;
			}
		}
	}

	return tokens;
}

export function tokens_to_javascript(tokens: token[], filePath: string): string {
	// This is the complex part

	let javascript = `#!/usr/bin/env node

// -- START HEADER --

// DO NOT MODIFY.
// This file is automatically generated by bat2js.
// To modify the contents, edit the original .bat file and run bat2js again.
// Compiled from: ${filePath}

// -- END HEADER --

const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
const readline = require('readline');

// -- START FUNCTIONS --

// Make a waitForKeypress function

function waitForKeypress(message) {
	const rl = readline.createInterface({
		input: process.stdin,
		output: process.stdout
	});

	return new Promise((resolve) => {
		rl.question(message, () => {
			rl.close();
			resolve();
		});
	});
}

// -- END FUNCTIONS --

// -- START COMPILED CODE --

`;

	for (let i = 0; i < tokens.length; i++) {
		let token = tokens[i];

		if (token.type === tokenType.COMMENT) {
			javascript += `// ${token.value}\n`;
		}

		if (token.type === tokenType.COMMAND) {
			if (token.value === 'echo') {
				javascript += `console.log(\`${token.argument}\`);\n`;
			}

			if (token.value === 'pause') {
				// Check argument
				if  (token.argument !== 'nul') {
					// Check if argument is a file

					if (token.argument !== undefined) {
						// Append "Press Enter to continue..." to the end of the file
						javascript += `waitForKeypress('');\n`;
						javascript += `fs.appendFileSync('${token.argument}', 'Press Enter to continue...');\n`;
					}
					else {
						javascript += `waitForKeypress('Press Enter to continue...');\n`;
					}
				}
				else {
					javascript += `waitForKeypress('');\n`;
				}
			}
		}

		if (token.type === tokenType.SETVAR) {
			// Check if javascript already contains "let ${token.value} = ${token.argument};"
			if (javascript.includes(`let ${token.value} = ${token.argument};`)) {
				javascript += `${token.value} = ${token.argument};\n`;
			} else {
				javascript += `let ${token.value} = ${token.argument};\n`;
			}
		}
	}

	javascript = javascript.replace(/%([^%]+)%/g, (_match, p1) => `\${${p1}}`);

	return javascript;
}

export function isPercentVariable(str: string): boolean {
  return /^%.*%$/.test(str);
}

if (require.main === module) {
	let filePath = process.argv[2];

	if (!filePath) {
		console.error('Usagee: bat2js <file>');;
		process.exit(1);
	}

	if (!fs.existsSync(filePath)) {
		console.error(`File '${filePath}' does not exist.`);
		process.exit(1);
	}

	let fileContents = fs.readFileSync(filePath, 'utf8');

	console.time('Compilation Time');

	let tokens = tokenize(fileContents);
	let javascript = tokens_to_javascript(tokens, filePath);
	console.timeEnd('Compilation Time');

	// Write the compiled JavaScript to a file

	const fileName = path.parse(filePath).name;

	const compiledFilePath = path.join(path.dirname(filePath), `${fileName}.js`);

	fs.writeFileSync(compiledFilePath, javascript);
}