package compiler;

import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import utils.AyudaDebug;
import exceptions.ErrorNurseException;
import exceptions.ErrorRunException;
import externalExecutor.ExternalExecutor;



/**
 * Clase encargada de ofrecer métodos a ejecutar en terminal de linux
 *
 * @author Enrique M.L
 */
public class Compiler {
  
    /**
     * Método que realiza la creación de una nueva carpeta con nombre 'name' en el path 'path'
     * <p>
     * En caso de que se produzca un error en la creación de la nueva carpeta, el método lanza una excepción del tipo ErrorRunException
     * con el mensaje "Error al crear la carpeta" indicando el comando y el path utilizados
     * @param name
     * @param path
     * @throws ErrorRunException
     */
    public static void newFolder(String name, String path) throws ErrorRunException{
    	AyudaDebug.printMetodo("newFolder:init");
    	ArrayList<String> command = Build.buildFolderCommand(name);
        ArrayList<String> result = run(command, path);
        if (!result.get(1).equals("")){
            System.out.println("Carpeta ya existe");;
        	//throw new ErrorRunException("Error al crear la carpeta", command, path);
        }
        AyudaDebug.printMetodo("newFolder:end");
    }
    
    /**
     * Método que realiza la compilación y, por consiguiente, la creación de un ejecutable del código almacenado en el fichero
     * 'nameFile' en el path 'path', con extension de fichero 'extensionFile'
     * <p>
     * Dicha compilación se realiza con la intrucción 'compileInstruction'
     * @param nameFile
     * @param extensionFile
     * @param compileInstruction
     * @param path
     * @param arguments
     * @return
     */
    public static ArrayList<String> compileExec(String nameFile, String extensionFile, String compileInstruction, String path, String arguments){
    	AyudaDebug.printMetodo("compileExec:init");
		AyudaDebug.print(compileInstruction);
		AyudaDebug.print(path);
		AyudaDebug.print(arguments);
    	ArrayList<String> command;
    	if(extensionFile.equals(".py")){
    		AyudaDebug.print("Compilant Python");
    		command = Build.buildCompileToExecPyCommand(nameFile,extensionFile);
    	}
    	else 
    		command = Build.buildCompileToExecCommand(nameFile,extensionFile, compileInstruction,arguments);
    	ArrayList<String> result = run(command, path);
        AyudaDebug.printMetodo("compileExec:end");
        return result;
    }
    
    /**
     * Método que realiza la eliminación de la carpeta con nombre 'nameFolder' en el path 'path'
     * <p>
     * En caso de que se produzca un error en la eliminación de la carpeta, el método lanza una excepción del tipo ErrorRunException
     * con el mensaje "Error al eliminar la carpeta" indicando el comando y el path utilizados
     * @param nameFolder
     * @param path
     * @throws ErrorRunException
     */
    public static void deleteFolder(String nameFolder, String path) throws ErrorRunException{
    	AyudaDebug.printMetodo("deleteFolder:init");
        ArrayList<String> command_delete = Build.buildDeleteFolderCommand(nameFolder);
        ArrayList<String> result_delete = run(command_delete, path);
        if (!result_delete.get(1).equals("")){
        	throw new ErrorRunException("Error al eliminar la carpeta", command_delete, path);
        }
        AyudaDebug.printMetodo("deleteFolder:end");
    }
    
    /**
     * Método que realiza la eliminación de un test con nombre 'nameFile' en el path 'path'
     * <p>
     * En caso de que se produzca un error en la eliminación del test, el método lanza una excepción del tipo ErrorRunException
     * con el mensaje "Error al eliminar archivo" indicando el comando y el path utilizados
     * @param nameFile
     * @param path
     * @throws ErrorRunException
     */
    public static void deleteTest(String nameFile, String path) throws ErrorRunException{
    	AyudaDebug.printMetodo("deleteTest:init");
        ArrayList<String> command_delete = Build.buildDeleteFileCommand(nameFile);
        ArrayList<String> result_delete = run(command_delete, path);
        if (!result_delete.get(1).equals("")){
        	throw new ErrorRunException("Error al eliminar archivo", command_delete, path);
        }
        AyudaDebug.printMetodo("deleteTest:end");
    }
    
    /**
     * Método que realiza la obtención de los nombres de los archivos (tests) contenidos en la carpeta 'pathTest'
     * <p>
     * En caso de que se produzca un error en la obtención de los nombres de los archivos, el método lanza una excepción del tipo ErrorRunException
     * con el mensaje "Error al obtener la lista de nombres de los tests" indicando el comando y el path utilizados
     * @param pathTest
     * @return
     * @throws ErrorRunException
     */
	public static String getTestList(String pathTest) throws ErrorRunException {
		AyudaDebug.printMetodo("getTestList:init");
        ArrayList<String> command = Build.buildGetListFolderCommand();
        ArrayList<String> result = run(command, pathTest);
        if (!result.get(1).equals("")){
        	throw new ErrorRunException("Error al obtener la lista de nombres de los tests", command, pathTest);
        }
        AyudaDebug.printMetodo("getTestList:end");
        return result.get(0);
	}
	
	
	
	/**
	 * Método que realiza la ejecución del programa 'nameFile' contenido en 'dirTask' en el path 'path' con extension 'fileExtension' en un terminal dentro de nurse(sandbox)
	 * <p>
	 * Si el tipo de test 'typeTest' es igual a 0, se ejecuta el programa sin argumentos
	 * En caso contrario, se ejecuta con los argumentos 'arguments'
	 * <p>
     * En caso de que se produzca un error en la ejecución del programa, el método lanza una excepción del tipo ErrorNurseException
     * 
	 * @param nameFile
	 * @param typeTest
	 * @param arguments
	 * @param dirTask
	 * @param path
	 * @param fileExtension
	 * @return
	 * @throws ErrorNurseException
	 * @throws TimeoutException 
	 */
    public static ArrayList<String> runTestNurse(String nameFile, int typeTest, ArrayList<String> arguments, String dirTask, String path, String fileExtension) throws ErrorNurseException, TimeoutException{
    	AyudaDebug.printMetodo("runTestNurse:init");
        ArrayList<String> command = new ArrayList<String>();
        if (typeTest == 0){
        	command = Build.buildRunNurseCommand(nameFile, dirTask, fileExtension);
        }
        else {
        	AyudaDebug.printArrayList(arguments);
        	command = Build.buildRunNurseTestCommand(nameFile, arguments, dirTask, fileExtension);
        }
        ArrayList<String> result;
        try{
        	result = runNurse(command, path);
        }catch (TimeoutException e) {
        	AyudaDebug.print("timeout");
            throw e;
        }
        AyudaDebug.printMetodo("runTestNurse:end");
        return result;
    }
    
    /**
     * Método que indica si la carpeta con nombre 'find' existe en el path 'path'
     * <p>
     * En caso de que se produzca un error en la comprobación de la existencia de la carpeta, el método lanza una excepción del tipo ErrorRunException
     * con el mensaje "Error al realizar la comprobación de si existe la carpeta" indicando el comando y el path utilizados
     * 
     * @param find
     * @param path
     * @return
     * @throws ErrorRunException
     */
    public static boolean checkExistFolder(String find, String path) throws ErrorRunException{
    	AyudaDebug.printMetodo("checkExistFolder:init");
        boolean ret = false;
        ArrayList<String> command = Build.buildExistCommand(find);
        ArrayList<String> result = run(command, path);
        AyudaDebug.printResult(result);
        
        if (result.get(0).replaceAll("[\n\r]","").equals("./" + find)){
            ret = true;           
        }
        if (!result.get(1).equals("")){
        	throw new ErrorRunException("Error al realizar la comprobación de si existe la carpeta", command, path);
        }
        AyudaDebug.printMetodo("checkExistFolder:end");
        return ret;
    }
    
    /**
     * Método que comprueba si una carpeta está vacia.
     * 
     * <p>
     * En caso de que se produzca un error en la comprobación de si la carpeta está vacia, el método lanza una excepción del tipo ErrorRunException
     * con el mensaje "Error al comprobar si la carpeta está vacia" indicando el comando y el path utilizados
     * @param path
     * @return
     * @throws ErrorRunException
     */
    public static boolean checkEmptyFolder(String path) throws ErrorRunException{
    	AyudaDebug.printMetodo("checkEmptyFolder:init");
    	boolean ret = false;
    	ArrayList<String> command = Build.buildGetListFolderCommand();
        ArrayList<String> result = run(command, path);
        
        if (result.get(0).replaceAll("[\n\r]","").equals("")){
            ret = true;           
        }
        if (!result.get(1).equals("")){
        	throw new ErrorRunException("Error al comprobar si la carpeta está vacia", command, path);
        }
        AyudaDebug.printMetodo("checkEmptyFolder:end");
        return ret;
    }

    /**
     * Método que realiza la ejecución del comando 'command' en el path 'cwd' en un terminal 
     * 
     * @param command
     * @param cwd
     * @return
     */
    public static ArrayList<String> run(ArrayList<String> command, String cwd) {
        
        AyudaDebug.printMetodo("run:init");
        
        AyudaDebug.printCommand(command);
        
        //Run command, first argument is the command, second argument is the current working directory (CWD)
        ExternalExecutor ee = new ExternalExecutor(command, cwd);
        ee.executeCommand();
        StringBuilder stdout = ee.getStandardOutputFromCommand();
        StringBuilder stderr = ee.getStandardErrorFromCommand();

        //Return value is true if there are no compilation errors in stderr
        ArrayList<String> result = new ArrayList<String>();
        result.add(stdout.toString());
        result.add(stderr.toString());
        
        AyudaDebug.printResult(result);
        AyudaDebug.printMetodo("run:end");
        
        return result;
    }
    
    /**
     * Método que realiza la ejecución del comando 'command' dentro de la nurse(sandbox) en el path 'path' en un terminal
     * También limita el tiempo de ejecución del Nurse
     * @param command
     * @param cwd
     * @return
     * @throws ErrorNurseException
     * @throws TimeoutException 
     */
    public static ArrayList<String> runNurse(ArrayList<String> command, String cwd) throws ErrorNurseException, TimeoutException {
        
        AyudaDebug.printMetodo("runNurse:init");
        
        AyudaDebug.printCommand(command);
        AyudaDebug.print("path    = " + cwd);
        //Run command, first argument is the command, second argument is the current working directory (CWD)
        final ExternalExecutor ee = new ExternalExecutor(command, cwd);
        
        ExecutorService executor = Executors.newFixedThreadPool(4);

        Future<?> future = executor.submit(new Runnable() {
            @Override
            public void run() {
                ee.executeCommand();
            }
        });

        executor.shutdown();

        try {
        	//Executa la canonada amb 20 segons com a temps màxim
            future.get(20, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            System.out.println("job was interrupted");
        } catch (ExecutionException e) {
            System.out.println("caught exception: " + e.getCause());
        } catch (TimeoutException e) {
            //Retorna error en cas de superar el temps d'execució
        	future.cancel(true);
            AyudaDebug.print("timeout");
            throw e;
        }
        StringBuilder stdout = ee.getStandardOutputFromCommand();
        StringBuilder stderr = ee.getStandardErrorFromCommand();
        
        //Return value is true if there are no compilation errors in stderr
        ArrayList<String> result = new ArrayList<String>();
        result.add(stdout.toString());
        result.add(stderr.toString());
        
        //AyudaDebug.printResult(result);
        
        if (ee.getExitValue() != 0){
        	AyudaDebug.print("NURSE HA PARADO LA EJECUCION DEL PROGRAMA");
        	AyudaDebug.restartAuxPrint();
        	throw new ErrorNurseException(stderr.toString());
        }
        AyudaDebug.printMetodo("runNurse:end");
        
        return result;
    }   
    

    /*
     * Método no utilizado, que realiza la ejecución de un programa sin utilizar nurse(sandbox)
     public static ArrayList<String> runTest(String nameFile, int typeTest, ArrayList<String> arguments, String path){
         ArrayList<String> command = new ArrayList<String>();
         if (typeTest == 0){
         	command = Build.buildRunCommand(nameFile);
         }
         else {
         	command = Build.buildRunTestCommand(nameFile, arguments);
         }
         ArrayList<String> result = run(command, path);
         return result;
     }
     */
}
