//有思工作室编程类,用于Windows下面的C语言。
//说明：nodejs很多命令默认是在linux下面写的，所以windows平台的信息有些不准确。
//不能调用spawn命令，spawn是最原始的，不能设置超时时间,但spawn可以用数组，这点方便。
//知识要点：CL为编译命令。[]数组中为参数选项
//  /nolog为不输出微软的标志。
//哎，用GCC吧。等以后node稳定了再做windows版本。

var fs = require('fs');


var exec = require('child_process').exec, free;
//var exec = require('child_process').exec, free = exec('CL', ['hello.c', '/nologo']);
//var spawn = require('child_process').spawn, free = spawn('hello.exe', ['hello.c']);
//var spawn = require('child_process').spawn, free = spawn('free', ['-m']);


//------------------------------------------------执行PHP等解释型语言，解释执行的必须加chroot做沙箱------------------------------------------------------------




//PHP语言
exports.exephp = function (res, f,type) {
    //编译指令//生成gcc命令。
    //再执行此执行文件,chroot是让命令执行在沙箱中。沙箱命令需要去除目录标识。
    var cmd = "chroot ./output php ./userfiles/" + f + ".php";
    console.log(cmd);
    //解释性语言异步执行编译生成的命令为空。
    interpreLang(res, cmd , f, type);
}

//Python语言
exports.exepython = function (res, f,type) {
    var cmd = "chroot ./output python ./userfiles/" + f + ".py";
    console.log(cmd);
    interpreLang(res, cmd, f, type); ; //解释性语言异步执行编译生成的命令为空。
}

//Nodejs语言
exports.exenodejs = function (res, f,type) {
    var cmd = "chroot ./output node ./userfiles/" + f + ".js";
    console.log(cmd);
    interpreLang(res, cmd, f, type); ; //解释性语言异步执行编译生成的命令为空。
}

//Ruby语言
exports.exeruby = function (res, f,type) {
    var cmd = "chroot ./output ruby ./userfiles/" + f + ".rb";
    console.log(cmd);
    interpreLang(res, cmd, f, type); ; //解释性语言异步执行编译生成的命令为空。
}

//Perl语言
exports.exeperl = function (res, f,type) {
    var cmd = "chroot ./output perl ./userfiles/" + f + ".pl";
    console.log(cmd);
    interpreLang(res, cmd, f, type); ; //解释性语言异步执行编译生成的命令为空。
}

//Lisp语言
exports.exelisp = function (res, f,type) {
    var cmd = "chroot ./output clisp ./userfiles/" + f + ".lisp";
    console.log(cmd);
    interpreLang(res, cmd, f, type); ; //解释性语言异步执行编译生成的命令为空。
}


//解释性语言统一调用函数
interpreLang = function (res, cmd,f, type) {
    var output = ""; //最终返回的错误信息，警告信息，执行成功信息以及生成的exe的执行信息。但如何区分？
    //解释性语言异步执行编译生成的命令为空。
    usexefile(res, cmd, "", f, output, type, interpreLangAsyn);
}
interpreLangAsyn = function (res, output,f, acmd, type) {
    output = "运行环境Linux+" + type + "语言:\n" + output;
    res.end(output); //php很简单，没C复杂
}




//------------------------------------------------执行Gcc  Net Mono等编译型-----------------------------------------------------
//执行Net和Mono
//mono还需要 /proc才能运行，需要把/proc mount到 chroot目录
//首先在choot目录下建立文件夹  /proc。
//mount proc ./proc -t proc
exports.exenet = function (res, f,type) {
    //Mono编译太慢，需要调整时间为5秒
    f = './output/userfiles/' + f; //给文件加上目录名
    var cmd = 'mcs  ' + f + '.cs';
    //再执行此执行文件,chroot是让命令执行在沙箱中。沙箱命令需要去除目录标识。异步执行的命令
    var acmd = "chroot ./output mono  ./userfiles/" + f.slice(19) + ".exe";
    execomplie(res, f, cmd, acmd,type);
}

//执行Gcc
exports.exegcc = function (res, f,type) {
    //编译指令//生成gcc命令。
    f = './output/userfiles/' + f; //给文件加上目录名
    var cmd = 'gcc  ' + f + '.c' + ' -o ' + f + '.exe';
    //再执行此执行文件,chroot是让命令执行在沙箱中。沙箱命令需要去除目录标识。异步执行的命令
    var acmd = "chroot ./output  ./userfiles/" + f.slice(19) + ".exe";
    execomplie(res, f, cmd, acmd,type);
}


//------------------------------------------------------编译型命令封装-------------------------------------------
//res是传递的http参数，f是文件，c是需要执行的编译命令,as是编译成功后执行可执行的exe文件命令
execomplie = function (res, f,cmd,acmd,type) {
    //编译指令//生成gcc命令。
    var output = ""; //最终返回的错误信息，警告信息，执行成功信息以及生成的exe的执行信息。但如何区分？
    usexefile(res, cmd, acmd, f, output,type, execcompileAsyn);
}
//执行Gcc命令回调函数。参数1为执行gcc的返回数据，参数f为文件名。acmd是执行编译成功后可执行文件的命令
execcompileAsyn = function (res, output, f,acmd,type) {
    //如何判断编译成功呢？暂时采用是否生成了可执行文件的方式。如果采用这种方式，好像windows平台也能做,但有问题，生成成功后再次生成即使错误也会判断成功。
    //对了，同时判断是否有错误输出就OK了。前台判断好还是后台判断好？
    //加Sync表示同步，此处异步好还是同步好呢？
    if (builderr(output)) res.end(output);
    else {
        if (fs.existsSync(f + '.exe')) {
            output += "运行环境Linux+" + type + "语言:\n";
            //再执行此执行文件,把异步命令作为执行命令，原来的异步命令为空。
            usexefile(res, acmd,"", f, output,type, execexeAsyn); //异步执行生成的exe文件。
        }
    }
}
//执行C语言可执行文件回调函数
execexeAsyn = function (res, output, f) {
    //删除执行文件，准备下次运行。此处不好整理逻辑。
    res.end(output);
}

//判断编译是否发生错误，如果发送错误，则输出true，否则输出false
builderr = function (output) {
    var z = output.replace(/\r\n/g, '\n').split('\n'), i;
    for (i = 0; i < z.length; i++) {
        if (z[i].indexOf('标准错误输出') > -1)
            return true;
    }
    return false;
}







//--------------------------------------执行控制台命名封装---------------------------------------
//判断用户是否输出了加大的文字量,如果用户恶意输入，给他提示，结束程序。
largeoutput = function (res,output) {
    if (output.length > 10000)
        res.end("哥们,别开玩笑！\n"+output);
}

//雨云科技执行文件函数,cmd是nycodex.com命令，callback是执行完毕后的回调函数
usexefile = function (res, cmd, acmd, f, output,type, callback) {
    //var output = ""; //输出，因为stderr或者stdout会触发多次，用一个变量循环，为啥要触发多次，以后研究。
    //-Wall 把所有编译警告全部显示。-o指定输出目录
    //free = exec('gcc -Wall ./output/hello.c -o ./output/h.exe',
    free = exec(cmd, {
        encoding: 'utf8', //编码设置
        //encoding: null, //编码设置
        timeout: 8000, //超时时间设置。
        maxBuffer: 200 * 1024,
        killSignal: 'SIGTERM',
        cwd: null,
        env: process.env    //windows环境设置或者linux环境设置，好像不成功
    });
    // 捕获标准输出并将其打印到控制台 
    free.stdout.on('data', function (data) {
        //console.log('标准输出：\n' + data);//data != "\n" ? output += "标准输出: " + data : output += data; //给输出前面加上中文标示，便于学生理解
        output += data;
	//res.write(data);
	//if(output.length>1)	res.end(output);
        //largeoutput(res, output);
	//res.end("AAAAAA");
    });

    // 捕获标准错误输出并将其打印到控制台 //console.log('标准错误输出：\n' + data);
    free.stderr.on('data', function (data) {
        (data != "\n"&&data != "\r") ? output += ("标准错误输出:" + data) : output += data; //给输出前面加上中文标示，便于学生理解。
    });

    // 注册子进程关闭事件 //console.log('子进程已退出，代码：' + code); console.log(output);
    free.on('exit', function (code, signal) {
        largeoutput(res, output);
        if (callback) callback(res, output, f,acmd,type); //如果有定义回调函数，则执行回调函数。
    });

    //free.stdin.write("4\n");

    //C语言处理标准输入，非常麻烦，暂时留在这里。这应该封装为一个函数。首先用resume恢复以前的进程，不过首先需要找到该进程。
    //free.stdin.resume(); //这句话是为了不让控制台推出
    //free.stdin.on('data', function (data) {
        //console.log('标准输出：\n' + data);//data != "\n" ? output += "标准输出: " + data : output += data; //给输出前面加上中文标示，便于学生理解
        //free.stdout.write('标准输入:' + data);
        //free.stdin.emit('end');
        //output += "本系统暂时不支持scanf输入语句，有高手能实现吗？请在意见反馈中联系我们，谢谢";
	//res.end(output);
	//free.stdout.write('data:'+data);
		
    //});
    //free.stdin.on('end', function () {
	//if(output.length>1)	res.end(output);
	//output+="input end";
        //free.stdout.write('end');
        //output += "本系统暂时不支持scanf输入语句，有高手能实现吗？请在意见反馈中联系我们，谢谢";
	
    //});
}




