package controller;

import infoObjects.InfoError;
import infoObjects.InfoResults;
import infoObjects.InfoTarea;
import infoObjects.InfoTest;

import compiler.Compiler;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.TimeoutException;

import exceptions.ErrorNurseException;
import exceptions.ErrorRunException;

import utils.AyudaDebug;
import utils.ConstantsErr;
import utils.UtilsFile;


/**
 * Esta clase es la encargada de controlar la aplicación.
 * <p>
 * Además, es la encargada de guardar los diferentes path correspondientes a las carpetas donde se almacena la información y se encuentra el nurse.
 *
 * @author Enrique M.L
 */
public class Controller implements ConstantsErr{

	/**
	 * PATH_ROOT guarda el path root
	 */
    private static String PATH_ROOT;
	/**
	 * PATH_TMP_CODE guarda el path de los archivos de código temporales
	 */
    private static String PATH_TMP_CODE = "tmp/code/";
    /**
     * PATH_TMP_FILES guarda el path de los ficheros temporales
     */
    private static String PATH_TMP_FILES = "tmp/files/";
    /**
     * PATH_TASK guarda el path del directorio de las tareas
     */
    private static String PATH_TASK = "tasks/";
    /**
     * PATH_NURSE guarda el path del directorio del nurse
     */
    private static String PATH_NURSE = "nurse/";
    /**
     * DIR_TESTS guarda el nombre del directorio donde se guardan los test asociados a la tarea
     */
    private static final String DIR_TESTS = "/tests/";
    /**
     * DIR_INFO guarda el nombre del directorio donde se guarda la información asoaciada a la tarea
     */
    private static final String DIR_INFO = "/info/";
    /**
     * idTmpCode guarda el siguiente nombre para la carpeta temporal asociada a código temporal
     */
    private static int idTmpCode = 1;
	/**
	 * idTmpFiles guarda el siguiente nombre para la carpeta temporal asociada a ficheros temporales 
	 */
    private static int idTmpFiles = 1;

	/**
	 * Método que comprueba que la existencia de todas las carpetas necesarias en el path 'ruteRoot' para la ejecución correcta del SecuritySCP
	 * <p>
	 * Éstas son:
	 * <p>
	 * ·SecuritySCP
	 * <p>
	 * ·tasks
	 * <p>
	 * ·tmp
	 * <p>
	 * ·tmp/code
	 * <p>
	 * ·tmp/files
	 * <p>
	 * ·nurse
	 * @param ruteRoot
	 */
	public static void init(String ruteRoot){
		AyudaDebug.printMetodo("init:init");
		
		AyudaDebug.print("Rute root= " + ruteRoot);
		
		try {
			if (!Compiler.checkExistFolder("SecuritySCP", ruteRoot)){
				Compiler.newFolder("SecuritySCP",ruteRoot);
			}
			ruteRoot += "/SecuritySCP/";
			if (!Compiler.checkExistFolder("tasks", ruteRoot)){
				Compiler.newFolder("tasks",ruteRoot);
			}
			if (!Compiler.checkExistFolder("tmp", ruteRoot)){
				Compiler.newFolder("tmp", ruteRoot);
				Compiler.newFolder("code", ruteRoot+"/tmp/");
				Compiler.newFolder("files", ruteRoot+"/tmp/");
			}
			if (!Compiler.checkExistFolder("nurse", ruteRoot)){
				System.out.println("Copia nurse en el directorio SecuritySCP");
				while (!Compiler.checkExistFolder("nurse", ruteRoot)){

					System.out.println("Aviso: Copia nurse en el directorio SecuritySCP");
					Thread.sleep(5000);
				}
			}
			
			PATH_ROOT = ruteRoot;
			PATH_TMP_CODE = ruteRoot + PATH_TMP_CODE;
			PATH_TMP_FILES = ruteRoot + PATH_TMP_FILES;
			PATH_TASK = ruteRoot + PATH_TASK;
			PATH_NURSE = ruteRoot + PATH_NURSE;
			
		} catch (ErrorRunException | InterruptedException e) {
			e.printStackTrace();
		}
		AyudaDebug.printMetodo("init:end");
	}
	
	/**
	 * Recupera una lista de tests (InfoTest) del path 'pathTest'
	 *
	 * @param pathTest
	 * @return 
	 * @throws ErrorRunException
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
    private static ArrayList<InfoTest> getTestsInFolder(String pathTest) throws ErrorRunException, ClassNotFoundException, IOException{
    	AyudaDebug.printMetodo("getTestsInFolder:init");
        String listTest;
        ArrayList<InfoTest> tempInfoTest = new ArrayList<InfoTest>();
		
	listTest = Compiler.getTestList(pathTest);
        
        if (listTest.length()<1) 
            return tempInfoTest;
        
        String[] temp = listTest.split("\n");
	    
    	for(int i =0; i < temp.length ; i++){
    	    String testName = temp[i];
    	    tempInfoTest.add(getInfoTestInFolder(testName, pathTest));
	    }
		
    	AyudaDebug.printMetodo("getTestsInFolder:end");
        return tempInfoTest;
    }
    
    /**
     * Recupera el objeto InfoTest asociado al id de test 'idTest' en el path 'pathTest'
     * 
     * @param idTest
     * @param pathTest
     * @return 
     * @throws ClassNotFoundException
     * @throws IOException
     */
    private static InfoTest getInfoTestInFolder(String idTest, String pathTest) throws ClassNotFoundException, IOException{
    	AyudaDebug.printMetodo("getInfoTestInFolder:init");
    	
        String tempfile = pathTest;
        InfoTest infoTestRecuperada = (InfoTest) UtilsFile.getObject(tempfile,idTest);
        
        AyudaDebug.printMetodo("getInfoTestInFolder:end");
        return infoTestRecuperada;
    }

    /**
     * Recupera el objeto InfoTarea asociado al id de tarea 'idTarea'
     * 
     * @param idTarea
     * @param path
     * @return
     * @throws ClassNotFoundException
     * @throws IOException
     */
    private static InfoTarea getInfoTarea(Long idTarea) throws ClassNotFoundException, IOException{
    	AyudaDebug.printMetodo("getInfoTarea:init");
    	
        String tempfile = PATH_TASK + idTarea + DIR_INFO;
        InfoTarea infoTareaRecuperada = (InfoTarea) UtilsFile.getObject(tempfile, "infoTarea");
        
        AyudaDebug.printMetodo("getInfoTarea:end");
        return infoTareaRecuperada;
    }
    
    /**
     * Aumenta el contador del nombre de la siguiente carpeta temporal de código
     * 
     * Método synchronized puesto que idTmpCode es un recurso compartido entre varios threads
     * @return
     */
    private static synchronized int increaseTmpCode(){
    	AyudaDebug.printMetodo("increaseTmpCode:init");
    	int tmp = idTmpCode;
    	idTmpCode++;
    	AyudaDebug.printMetodo("increaseTmpCode:end");
    	return tmp;
    }
    
    /**
     * Aumenta el contador del nombre de la siguiente carpeta temporal de ficheros
     * 
     * Método synchronized puesto que idTmpCode es un recurso compartido y modificado por varios threads
     * @return
     */
    private static synchronized int increaseTmpFiles(){
    	AyudaDebug.printMetodo("increaseTmpFiles:init");
    	int tmp = idTmpFiles;
    	idTmpFiles++;
    	AyudaDebug.printMetodo("increaseTmpFiles:end");
    	return tmp;
    }
    
	/**
	 * Método que comprueba la existencia de la carpeta "tests" en el path 'path'
	 * 
	 * @param path
	 * @return
	 */
    @SuppressWarnings("unused")
	private static boolean existTest(String path) {
    	AyudaDebug.printMetodo("existeTest:init");
    	boolean ret = false;
		try {
			if (Compiler.checkExistFolder("tests", path)){
				ret = !Compiler.checkEmptyFolder(path+DIR_TESTS);
			}
		} catch (ErrorRunException e) {
			e.printError();
		}
		AyudaDebug.printMetodo("existeTest:end");
		return ret;
	}
	
    /**
     * Método que indica la existencia de la tarea con nombre 'task' en el path 'path'
     * @param task
     * @param path
     * @return
     */
	private static boolean existTask(String task, String path) {
		AyudaDebug.printMetodo("existTask:init");
		boolean ret = false;
		try {
			ret = Compiler.checkExistFolder(task, path);
		} catch (ErrorRunException e) {
			e.printError();
		}
		AyudaDebug.printMetodo("existTask:end");
		return ret;
	}
    
    /**
     * Método que realiza la ejecución del programa asociado a 'infoTarea' en el directorio 'tempDirTask' con el test asociado a 'infoTest'
     * 
     * @param infoTest
     * @param infoTarea
     * @param tempDirTask
     * @return
     * @throws ClassNotFoundException
     * @throws IOException
     * @throws ErrorRunException
     * @throws ErrorNurseException
     */
        // TODO REVISAR:
    private static ArrayList<String> runTestPrivate(InfoTest infoTest, InfoTarea infoTarea, String tempDirTask) throws ClassNotFoundException, IOException, ErrorRunException, ErrorNurseException {
        AyudaDebug.printMetodo("runTestPrivate:init");

        ArrayList<String> result = new ArrayList<String>();

        if (infoTest.getTipo() == 0) {
        	try{
        		result = Compiler.runTestNurse(infoTarea.getFicheroMain(), infoTest.getTipo(), null, tempDirTask, PATH_NURSE, infoTarea.getFileExtension());
        	}  catch (TimeoutException e) {
        		throw new ErrorNurseException("timeout");
            }
        } else if (infoTest.getTipo() == 1) {
            try{
            	result = Compiler.runTestNurse(infoTarea.getFicheroMain(), infoTest.getTipo(), infoTest.getArgumentos(), tempDirTask, PATH_NURSE, infoTarea.getFileExtension());
            }catch (TimeoutException e) {
            	throw new ErrorNurseException("timeout");
	        }
        } else if (infoTest.getTipo() > 1 && infoTest.getTipo() < 5) {
            int idTmpF = increaseTmpFiles();
            String pathTmpFolder = PATH_TMP_FILES + idTmpF + "/";
            Compiler.newFolder(pathTmpFolder, PATH_TMP_FILES);
            ArrayList<String> argumentos = new ArrayList<String>(infoTest.getArgumentos());

            for (int i = 0; i < infoTest.getTipo() - 1; i++) {
                UtilsFile.codeToFile(infoTest.getArgumentos().get(i), pathTmpFolder, Integer.toString(i), "");
                argumentos.set(i, pathTmpFolder + Integer.toString(i));
            }
            try {
                result = Compiler.runTestNurse(infoTarea.getFicheroMain(), infoTest.getTipo(), argumentos, tempDirTask, PATH_NURSE, infoTarea.getFileExtension());
            } catch (ErrorNurseException e) {
                throw new ErrorNurseException("error");
            } catch (TimeoutException e) {
            	throw new ErrorNurseException("timeout");
            } finally {
                Compiler.deleteFolder(pathTmpFolder, PATH_TMP_FILES);
            }
        }
        /*
         // NO FUNCIONA 
         else if (infoTest.getTipo() == 5){
         int idTmpF = aumentarContTmpFiles();
         String pathTmpFolder = PATH_TMP_FILES + idTmpF + "/";
         Compiler.newFolder(pathTmpFolder, PATH_TMP_FILES);
         CodeFile.codeToFile(infoTest.getArgumentos().get(0), pathTmpFolder, "0", "");
         	
         ArrayList<String> argumentos = new ArrayList<String> ();
         argumentos.add("<");
         argumentos.add(pathTmpFolder + Integer.toString(0));
         result = Compiler.runTest(infoTarea.getNombreFichero(), infoTest.getTipo(), argumentos, tempDirTask);
         	
         Compiler.deleteFolder(pathTmpFolder, PATH_TMP_FILES);
         	
         }*/

        AyudaDebug.printMetodo("runTestPrivate:end");
        return result;
    }
    
    /** 
     * Método que compila el código contenido en el objeto 'infoTarea'
     * 
     * @param infoTarea
     * @return InfoError
     */
    public static InfoError securityCheckCode(InfoTarea infoTarea) {
        AyudaDebug.printMetodo("securityCheckCode:init");

        InfoError infoError = new InfoError();

        String pathTmpFolder = "";
        String pathTmpLang = "";
        int idTmpLang;

        try {

            pathTmpLang = PATH_TMP_CODE;
            idTmpLang = increaseTmpCode();
            pathTmpFolder = pathTmpLang + idTmpLang + "/";

            Compiler.newFolder(Integer.toString(idTmpLang), pathTmpLang);

            // Crea ficheros
            ArrayList<byte[]> fichs = infoTarea.getFicheros();
            ArrayList<String> filn = infoTarea.getNombreFicheros();
            for (int i = 0; i < fichs.size(); i++) {
                UtilsFile.codeToFile(fichs.get(i), pathTmpFolder, filn.get(i));
            }

            // Compila y ejecuta
            ArrayList<String> result = Compiler.compileExec(infoTarea.getFicheroMain(), infoTarea.getLanguaje().getFileExtension(), (infoTarea.isTieneMakefile())? "make" : infoTarea.getLanguaje().getCompileInstruction(), pathTmpFolder, infoTarea.getArgumentos());

            if (!result.get(0).isEmpty() && infoTarea.getFileExtension().equals(".py")) {
                infoError.setInfoError(ERROR_LIBRARY, result);
            }else if (result.get(1).isEmpty()) {
                infoError.setInfoError(ERROR_OK, result);
            }else {
                infoError.setInfoError(ERROR_CHECKCODE, result);
            }
            Compiler.deleteFolder(Integer.toString(idTmpLang), pathTmpLang);

        } catch (ErrorRunException e) {
            e.printError();
            infoError.setError(ERROR_INTERN);
        } catch (IOException e) {
            e.printStackTrace();
            infoError.setError(ERROR_INTERN);
        }
    
        AyudaDebug.printMetodo("securityCheckCode:end");
        return infoError;
    }

    /**
     * Método que crea una nueva tarea con los datos de 'infoTarea'
     *
     * @param infoTarea
     * @return InfoError
     */
    public static InfoError securityNewTask(InfoTarea infoTarea) {

        AyudaDebug.printMetodo("securityNewTask:init");
        InfoError infoError = new InfoError();

        String tempDirTask = PATH_TASK + infoTarea.getIdTarea();
        String tempDirTaskInfo = tempDirTask + DIR_INFO;
        String tempDirTaskTests = tempDirTask + DIR_TESTS;
        tempDirTask += "/";

        try {

            Compiler.newFolder(tempDirTask, PATH_TASK);

            if (infoTarea.tieneCodigo()) {
                
                // Crea ficheros
                ArrayList<byte[]> fichs = infoTarea.getFicheros();
                ArrayList<String> filn = infoTarea.getNombreFicheros();
                for (int i = 0; i < fichs.size(); i++) {
                    UtilsFile.codeToFile(fichs.get(i), tempDirTask, filn.get(i));
                }

                
                if (!infoTarea.getFileExtension().equals(".py")) {
                    Compiler.compileExec(infoTarea.getFicheroMain(), infoTarea.getLanguaje().getFileExtension(), (infoTarea.isTieneMakefile())? "make" : infoTarea.getLanguaje().getCompileInstruction(), tempDirTask, infoTarea.getArgumentos());
                }
            }

            Compiler.newFolder(tempDirTaskInfo, tempDirTask);
            UtilsFile.saveObject(tempDirTaskInfo, "infoTarea", infoTarea);
            Compiler.newFolder(tempDirTaskTests, tempDirTask);

            infoError.setError(ERROR_OK);
        } catch (ErrorRunException e) {
            e.printError();
            infoError.setError(ERROR_INTERN);
        } catch (IOException e) {
            e.printStackTrace();
            infoError.setError(ERROR_INTERN);
        }

        AyudaDebug.printMetodo("securityNewTask:end");

        return infoError;
    }
  
    /**
     * Método que ejecuta la tarea asociada de 'infoTest' con los argumentos de 'infoTest'
     * 
     * @param infoTest
     * @return 
     */
    public static InfoError securityRunTest(InfoTest infoTest) {
        
        AyudaDebug.printMetodo("securityRunTest:init");
        InfoError infoError = new InfoError();

        InfoTarea infoTarea;
		try {
			String tempDirTask = PATH_TASK + infoTest.getIdTareaAsociada() +"/";
			infoTarea = getInfoTarea(infoTest.getIdTareaAsociada());
			ArrayList<String>  result = runTestPrivate(infoTest, infoTarea, tempDirTask);
			if (result.get(1).isEmpty()){
				infoError.setInfoError(ERROR_OK, result);
			}else{
				infoError.setInfoError(ERROR_EXECUTION, result);
			}
		} catch (ClassNotFoundException | IOException e) {
			infoError.setError(ERROR_NO_EXIST_TASK);
		} catch (ErrorRunException e) {
			infoError.setError(ERROR_INTERN);
			e.printError();
		} catch (ErrorNurseException e) {
			infoError.setError(ERROR_NURSE);
		}
		
        AyudaDebug.printMetodo("securityRunTest:end");
        
        return infoError;    
    }   
    
    /**
     * Método que graba el test 'infoTest' en la tarea asociada
     * 
     * @param infoTest
     * @return 
     */
    public static InfoError securitySaveTest(InfoTest infoTest){
        AyudaDebug.printMetodo("securitySaveTest:init");
        InfoError infoError = new InfoError();
        
        if (existTask(infoTest.getIdTareaAsociada().toString(), PATH_TASK)){
	        String tempDirTaskTest = PATH_TASK + infoTest.getIdTareaAsociada() + DIR_TESTS;
	        String tempNameTest = "infoTest"+infoTest.getIdTest();
	        
	        AyudaDebug.print(infoTest.toString());
	        
	        try {
				UtilsFile.saveObject(tempDirTaskTest, tempNameTest, infoTest);
				infoError.setError(ERROR_OK);
			} catch (IOException e) {
				e.printStackTrace();
				infoError.setError(ERROR_INTERN);
			}
        }
        else{
        	infoError.setError(ERROR_NO_EXIST_TASK);
        }
        
        AyudaDebug.printMetodo("securitySaveTest:end");
        return infoError;
    }
    
    /**
     * Método que ejecuta el código asociado a 'infoTarea' del alumno con todos los test 
     * que el profesor ha introducido para esta tarea
     * 
     * @param infoTarea
     * @return 
     */
    // TODO REVISAR
    public static InfoResults securityStudentRunTask(InfoTarea infoTareaAlumno) {
        AyudaDebug.printMetodo("securityStudentRunTask:init");
        InfoResults infoResults = new InfoResults();

        String pathTmpFolder;
        String pathTmpLang;
        int idTmpLang;

        idTmpLang = increaseTmpCode();
        pathTmpLang = PATH_TMP_CODE;
        pathTmpFolder = pathTmpLang + idTmpLang + "/";


        try {

            Compiler.newFolder(Integer.toString(idTmpLang), pathTmpLang);
            
            // Crea ficheros
            ArrayList<byte[]> fichs = infoTareaAlumno.getFicheros();
            ArrayList<String> filn = infoTareaAlumno.getNombreFicheros();
            for (int i = 0; i < fichs.size(); i++) {
                UtilsFile.codeToFile(fichs.get(i), pathTmpFolder, filn.get(i));
            }
            
            if (!infoTareaAlumno.getFileExtension().equals(".py")) {                
                Compiler.compileExec(infoTareaAlumno.getFicheroMain(), infoTareaAlumno.getLanguaje().getFileExtension(), (infoTareaAlumno.isTieneMakefile())? "make" : infoTareaAlumno.getLanguaje().getCompileInstruction(), pathTmpFolder, infoTareaAlumno.getArgumentos());
            }

            ArrayList<InfoTest> listaTests = new ArrayList<InfoTest>();
            String tempDirTaskTest = PATH_TASK + infoTareaAlumno.getIdTarea() + DIR_TESTS;
            listaTests = getTestsInFolder(tempDirTaskTest);

            Iterator<InfoTest> ite = listaTests.iterator();
            InfoTest testActual = new InfoTest();
            while (ite.hasNext()) {
                testActual = ite.next();
                AyudaDebug.print("TestActual\n" + testActual.toString());
                infoResults.addResultTest(testActual.getIdTest(), runTestPrivate(testActual, infoTareaAlumno, pathTmpFolder));
            }
            infoResults.setError(ERROR_OK);

        } catch (ErrorRunException e) {
            infoResults.setError(ERROR_INTERN);
            e.printError();
        } catch (IOException e) {
            infoResults.setError(ERROR_INTERN);
        } catch (ClassNotFoundException e) {
            infoResults.setError(ERROR_INTERN);
        } catch (ErrorNurseException e) {
            infoResults.setError(ERROR_NURSE);
        }

        try {
            Compiler.deleteFolder(Integer.toString(idTmpLang), pathTmpLang);
        } catch (ErrorRunException e) {
            e.printError();
        }

        AyudaDebug.printMetodo("securityStudentRunTask:end");

        return infoResults;
    }
    
    /**
     * Método que realiza la eliminación del test asociado a 'infoTest'
     * 
     * @param infoTest
     * @return
     */
    public static InfoError securityDeleteTest(InfoTest infoTest){
        AyudaDebug.printMetodo("securityDeletTest:init");
        InfoError infoError = new InfoError();
        
        if (existTask(infoTest.getIdTareaAsociada().toString(), PATH_TASK)){
	        String tempDirTaskTest = PATH_TASK + infoTest.getIdTareaAsociada() + DIR_TESTS;
	        String tempNameTest = "infoTest"+infoTest.getIdTest();
	        try {
				Compiler.deleteTest(tempNameTest, tempDirTaskTest);
				infoError.setError(ERROR_OK);
			} catch (ErrorRunException e) {
				e.printError();
			}
        }
        else{
        	infoError.setError(ERROR_NO_EXIST_TASK);
        }
        
        AyudaDebug.printMetodo("securityDeleteTest:end");
        
        return infoError;
    }
    
    /** 
     * Método que realiza la eliminación de la tarea asociada a 'infoTarea'
     *  
     * @param infoTarea
     * @return
     */
	public static InfoError securityDeleteTask(InfoTarea infoTarea) {
		AyudaDebug.printMetodo("securityDeleteTask:init");
		
        InfoError infoError = new InfoError();
        
        if (existTask(infoTarea.getIdTarea().toString(), PATH_TASK)){
	        String tempDirTask = PATH_TASK;
	        String tempNameTask = infoTarea.getIdTarea().toString();
	        
	        try {
				Compiler.deleteFolder(tempNameTask, tempDirTask);
				infoError.setError(ERROR_OK);
			} catch (ErrorRunException e) {
				e.printError();
			}
	        
	        
        }
        else{
        	infoError.setError(ERROR_NO_EXIST_TASK);
        }
        
        AyudaDebug.printMetodo("securityDeleteTask:end");
        
        return infoError;
	}

	/**
	 * Método que elimina todas las tareas y tests almacenados en el SSCP
	 * @return
	 */
	public static InfoError securityCleanData() {
		AyudaDebug.printMetodo("securityCleanData:init");
		
		InfoError infoError = new InfoError();
		
        try {
			Compiler.deleteFolder("tasks", PATH_ROOT);
			Compiler.newFolder("tasks", PATH_ROOT);
			infoError.setError(ERROR_OK);
		} catch (ErrorRunException e) {
			e.printError();
		}
		
		AyudaDebug.printMetodo("securityCleanData:end");
		return infoError;
	}
	
	/** 
	 * Método que cambia el estado de mostrar la ayuda de debug
	 * <p>
	 * Muestra ayuda debug si 'show' es true. No la muestra si 'show' es false
	 * @param show
	 */
	public static void setShowHelpDebug(boolean show) {
		AyudaDebug.setMostrarPrint(show);
	}
	
}
