import { writeFile, readFile, unlink } from 'fs';

export class Coder {
    constructor(agent) {
        this.agent = agent;
        this.current_code = '';
        this.file_counter = 0;
        this.fp = './agent_code/';
        this.agent.bot.interrupt_code = false;
        this.executing = false;
        this.agent.bot.output = '';
        this.code_template = '';

        readFile(this.fp+'template.js', 'utf8', (err, data) => {
            if (err) throw err;
            console.log('Template str:', data);
            this.code_template = data;
        });
    }

    queueCode(code) {
        this.current_code = this.santitizeCode(code);
    }

    santitizeCode(code) {
        const remove_strs = ['javascript', 'js']
        for (let r of remove_strs) {
            if (code.startsWith(r)) {
                code = code.slice(r.length);
                return code;
            }
        }
        // this may cause problems in callback functions
        code = code.replaceAll(';\n', '; if(bot.interrupt_code) {log(bot, "Code interrupted.");return;}\n');
        return code;
    }

    hasCode() {
        return this.current_code.length > 0;
    }

    writeFilePromise(filename, src) {
        // makes it so we can await this function
        return new Promise((resolve, reject) => {
            writeFile(filename, src, (err) => {
                if (err) {
                    reject(err);
                } else {
                    resolve();
                }
            });
        });
    }


    // returns {success: bool, message: string, interrupted: bool}
    async execute() {
        if (!this.current_code) return {success: false, message: "No code to execute.", interrupted: false};
        if (!this.code_template) return {success: false, message: "Code template not loaded.", interrupted: false};
        let src = '';
        for (let line of this.current_code.split('\n')) {
            src += `    ${line}\n`;
        }
        src = this.code_template.replace('/* CODE HERE */', src);

        console.log("writing to file...", src)

        let filename = this.fp + this.file_counter + '.js';
        // if (this.file_counter > 0) {
        //     let prev_filename = this.fp + (this.file_counter-1) + '.js';
        //     unlink(prev_filename, (err) => {
        //         console.log("deleted file " + prev_filename);
        //         if (err) console.error(err);
        //     });
        // } commented for now, useful to keep files for debugging
        this.file_counter++;

        let write_result = await this.writeFilePromise(filename, src);
        
        if (write_result) {
            console.error('Error writing code execution file: ' + result);
            return {success: false, message: result, interrupted: false};
        }

        try {
            console.log('executing code...\n');
            let execution_file = await import('.'+filename);
            await this.stop();
            
            this.executing = true;
            await execution_file.main(this.agent.bot);
            this.executing = false;

            this.agent.bot.emit('finished_executing');
            let output = this.formatOutput(this.agent.bot);
            let interrupted = this.agent.bot.interrupt_code;
            this.clear();
            return {success:true, message: output, interrupted};
        } catch (err) {
            this.executing = false;
            this.agent.bot.emit('finished_executing');
            console.error("Code execution triggered catch:" + err);
            let message = this.formatOutput(this.agent.bot);
            message += '!!Code threw exception!!  Error: ' + err;
            let interrupted = this.agent.bot.interrupt_code;
            await this.stop();
            return {success: false, message, interrupted};
        }
    }

    formatOutput(bot) {
        if (bot.interrupt_code) return '';
        let output = bot.output;
        const MAX_OUT = 1000;
        if (output.length > MAX_OUT) {
            output = `Code output is very long (${output.length} chars) and has been shortened.\n
                First outputs:\n${output.substring(0, MAX_OUT/2)}\n...skipping many lines.\nFinal outputs:\n ${output.substring(output.length - MAX_OUT/2)}`;
        }
        else {
            output = 'Code output:\n' + output;
        }
        return output;
    }

    async stop() {
        while (this.executing) {
            this.agent.bot.interrupt_code = true;
            this.agent.bot.collectBlock.cancelTask();
            this.agent.bot.pathfinder.stop();
            console.log('waiting for code to finish executing... interrupt:', this.agent.bot.interrupt_code);
            await new Promise(resolve => setTimeout(resolve, 1000));
        }
        this.clear();
    }

    clear() {
        this.current_code = '';
        this.agent.bot.output = '';
        this.agent.bot.interrupt_code = false;
    }
}