package ub.scp2.managedBeans;

import infoObjects.InfoError;
import infoObjects.InfoResults;
import infoObjects.InfoTarea;
import infoObjects.InfoResult;
import java.io.File;
import java.io.FileOutputStream;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;

import org.apache.commons.io.IOUtils;
import org.primefaces.event.FileUploadEvent;
import org.primefaces.model.UploadedFile;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.jsf.FacesContextUtils;
import ub.scp2.bo.FileSubmissionBo;

import ub.scp2.bo.SubmissionBo;
import ub.scp2.bo.TaskBo;
import ub.scp2.bo.TestResultBo;
import ub.scp2.bo.UserBo;
import ub.scp2.entities.Submission;
import ub.scp2.entities.FileSubmission;
import ub.scp2.entities.FileTask;
import ub.scp2.entities.Task;
import ub.scp2.entities.Test;
import ub.scp2.entities.TestResult;
import ub.scp2.entities.User;
import utils.ConstantsErr;
import utils.RMIServices;

/**
 * SubmissionBean.java -a Bean to control the Submission view.
 *
 *
 */
@ManagedBean(name = "submissionBean")
@ViewScoped
public class SubmissionBean implements ConstantsErr {

    private User user;
    private Task task;
    private Submission submission;
    private FileSubmission fileSubmission;
    private TestResult tr;

    private SubmissionBo submissionbo;
    private TaskBo taskbo;
    private UserBo userbo;
    private FileSubmissionBo filesubmissionbo;
    //private TestResultBo testResultBo;

    private List<TestResult> results;
    //private Set<TestResult> testsResult;

    private List<UploadedFile> docFiles;
    private InputStream docInputStream;

    private boolean rSubmitDisabled;
    private boolean mSubmitDisabled;
    private boolean firstIn;

    private String codigo;
    private String path;
    
    private utils.FileManager fileman;

    @ManagedProperty(value = "#{loginBean}")
    private LoginBean loginbean;

    /**
     * Initialize the parameters required for the class
     */
    @PostConstruct
    public void init() {
        WebApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());
        loginbean.updateUser();
        user = loginbean.getUser();
        task = loginbean.getTask();
        submission = new Submission();
        submission.setFiles(new java.util.HashSet<FileSubmission>());
        System.out.println("INIT");
        fileSubmission = new FileSubmission();
        fileSubmission.setSubmission(submission);

        tr = new TestResult();
        results = new ArrayList<TestResult>();
        docFiles = new ArrayList();
        submissionbo = ctx.getBean(SubmissionBo.class);
        taskbo = ctx.getBean(TaskBo.class);
        userbo = ctx.getBean(UserBo.class);
        filesubmissionbo = ctx.getBean(FileSubmissionBo.class);
        rSubmitDisabled = true;
        mSubmitDisabled = true;
        firstIn = true;
        path = ctx.getServletContext().getRealPath("");

        fileman = new utils.FileManager(path, task.getSubject().getTitle(), task.getTitle(), user.getUsername(), true);
    }

    /**
     * Control function at the beginning of jsf. This control whether a
     * submission has been made ​​or not. If submission has been made, load the
     * code and Test Result.
     */
    public void check() {
        if (task == null) {
            try {
                FacesContext.getCurrentInstance().getExternalContext().redirect("student.xhtml");
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            if (firstIn) {
                submission = submissionbo.getThisSubmission(task, user);
                firstIn = false;
                if (!submission.getFiles().isEmpty()) {
                    results = new ArrayList<TestResult>(submission.getTestsResult());
                }
            }
        }
    }
    
    /**
     * Error print on website.
     * @param text Error message
     */
    private void errorMsg(String text){
        FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, text, "");
        FacesContext.getCurrentInstance().addMessage(null, msg);
    }
    
    
    /**
     * First compile the code, after execute the submission with Test that
     * upload teacher in the Task. And prepare the Test Result Array
     *
     * @throws NotBoundException
     * @throws RemoteException
     * @throws MalformedURLException
     *
     */
    public boolean getTestResult() throws MalformedURLException, RemoteException, NotBoundException {

        if (!RMIServices.isConnected()) {
            RMIServices.connectRMI();
        }

        ArrayList<byte[]> contents = new ArrayList<byte[]>();
        for (FileSubmission f : submission.getFiles()) {
            contents.add(f.getContent());
        }

        InfoTarea infoTarea = new InfoTarea(task.getTaskId(), contents, submission.getFilenameArray(), task.getCompilerArguments(), task.getLanguaje(), task.isHasCodeTeacher(), task.isHasMakefile(), task.getNameFile());
        InfoError infoError = RMIServices.securityCheckCode(infoTarea);

        System.out.println("---------------------------------");
        System.out.println("ID TAREA: " + infoTarea.getIdTarea());
        System.out.println("---------------------------------");

        if (infoError.getError() == ERROR_OK) {

            List<Test> tests = new ArrayList<Test>(task.getTests());
            Iterator<Test> ite = tests.iterator();
            Iterator<TestResult> iteRes = results.iterator();
            Test t;
            int correctCounter = 0;

            // INICIO DEBUG
            System.out.println("\nSECURITYSTUDENTRUNTASK INICIO");
            InfoResults resultado = null;
            try {
                resultado = RMIServices.securityStudentRunTask(infoTarea);
                
                if (resultado.getIdError() == ERROR_OK) {
                    ArrayList<InfoResult> result = resultado.getTests();
                    for (int i = 0; i < result.size(); i++) {
                        InfoResult resultActual = result.get(i);
                        System.out.println("IDTest: " + resultActual.getIdTest());
                        System.out.println("STDOUT:\n" + resultActual.getResultTest().get(0));
                        System.out.println("STDERR:\n" + resultActual.getResultTest().get(1));
                    }

                    ArrayList<InfoResult> resultados = resultado.getTests();

                    while (ite.hasNext()) {
                        t = ite.next();
                        System.out.println("ID TEST: " + t.getTestId());
                        //output = submissionbo.runTest(t, user);

                        if (!iteRes.hasNext()) {
                            tr = new TestResult();
                        } else {
                            tr = iteRes.next();
                        }
                        tr.setSubmission(submission);
                        tr.setArguments(t.getArguments());

                        for (int i = 0; i < resultados.size(); i++) {
                            InfoResult resultActual = resultados.get(i);
                            System.out.println("MATCHING TEST: " + resultActual.getIdTest() + "¿==? " + t.getTestId());
                            if (resultActual.getIdTest().equals(t.getTestId())) {
                                System.out.println("FOUND MATCH TEST: " + resultActual.getIdTest() + "==" + t.getTestId());
                                if (resultActual.getErr().isEmpty()) {
                                    tr.setOutput(resultActual.getOut());
                                    System.out.println(resultActual.getOut());
                                } else {
                                    tr.setOutput(resultActual.getErr());
                                    System.out.println(resultActual.getErr());
                                }
                                if (task.isHasCodeTeacher()) {
                                    tr.setExpectedOutput(t.getOutput());
                                    System.out.println("RESULTADO ESPERADO " + tr.getExpectedOutput());
                                    System.out.println("RESULTADOS.size " + resultados.size());
                                    if (tr.getExpectedOutput().equals(tr.getOutput())) {
                                        tr.setStyle("correct");
                                        correctCounter++;
                                    } else {
                                        tr.setStyle("incorrect");
                                    }
                                }

                                break;
                            }
                        }

                        //results.add(tr);
                        submission.getTestsResult().add(tr);
                    }
                    int m;
                    if (correctCounter == 0) {
                        m = 0;
                    } else {
                        m = (correctCounter * 100) / tests.size();
                    }
                    submission.setMark(m);
                } else {
                    errorMsg("ERROR " + resultado.getErr());
                    return false;
                }
            } catch (RemoteException e) {
                errorMsg("ERROR DE SSCP");
                e.printStackTrace();
                return false;
            }

            System.out.println("\nSECURITYSTUDENTRUNTASK TERMINADA");

        } else if(infoError.getError() == ERROR_LIBRARY){
            errorMsg("Not allowed libraries detected");
            return false;
        }else {
            results = new ArrayList<TestResult>();
            Set<TestResult> auxresults = new HashSet<TestResult>(0);
            submission.setTestsResult(auxresults);
        	errorMsg("Compilation errorssss " + infoError.getErr());
            return false;
        }
        submissionbo.cleanup(user, task);
        results = new ArrayList<TestResult>(submission.getTestsResult());
        //testsResult = new HashSet<TestResult>(results);
        //return results;
        return true;
    }

    /**
     * Handler of the uploaded files. All files will be added to "this.docFiles".
     * No makefiles will be accepted
     * @param event 
     */
    public synchronized void handleFileUpload(FileUploadEvent event) {
        try {
            UploadedFile f = event.getFile();
            if (f.getFileName().toLowerCase().equals("makefile"))
                return;
            
            synchronized (docFiles) {
                docFiles.add(f);
            }
        } catch (Exception x) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_FATAL, x.getClass().getName(), x.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, message);
        }
    }

    /**
     * Set the private variable loginbean
     *
     * @param loginbean A type of LoginBean
     */
    public void setLoginbean(LoginBean loginbean) {
        this.loginbean = loginbean;
    }

    /**
     * Use getTetsResult() and enables the Save Option. Single file raw input.
     *
     * @throws NotBoundException
     * @throws RemoteException
     * @throws MalformedURLException
     */
    public void rawRunTests() throws MalformedURLException, RemoteException, NotBoundException, IOException {

        if (!task.isHasMakefile()) {
            fileSubmission.setFilename(task.getNameFile() + getLangExt());
        }

        String filen = fileSubmission.getFilename();
        if (filen == null || filen.equals("") || filen.equals(getLangExt())) {
            errorMsg("Please, enter a filename");
            rSubmitDisabled = true;
            return;
        }
        if (this.codigo == null || this.codigo.equals("")) {
            errorMsg("Please, enter some code");
            rSubmitDisabled = true;
            return;
        }

        byte[] codigoByte = this.codigo.getBytes();
        fileSubmission.setContent(codigoByte);

        submission.removeAllFiles();
        submission.addFile(fileSubmission);
        
        // Adding shared files
        if (task.isHasMakefile()) {
            String taskPath = path + "/WEB-INF/tasks/" + task.getSubject().getTitle() + "/" + task.getTitle() + "/" + task.getUser().getUsername() + "/";
            for (FileTask f : task.getFiles()) {
                if (f.isShared()) {
                    try {
                        String sharedfilePath = taskPath + f.getFilename();
                        FileSubmission fi = new FileSubmission();
                        fi.setSubmission(submission);
                        fi.setFilename(f.getFilename());
                        fi.setContent(file2ByteArray(sharedfilePath));
                        submission.addFile(fi);
                    } catch (IOException ex) {
                        System.err.println("ERROR LOADING Shared file: "+taskPath + f.getFilename());
                        Logger.getLogger(SubmissionBean.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
        
        fileman.cleanTempPath();
        for (FileSubmission fs : submission.getFiles()){
            if (!fileman.createTempFile(fs.getFilename(),fs.getContent())){
                submission.removeAllFiles();
                fileman.cleanTempPath();
                errorMsg("An error ocurred saving your files");
                rSubmitDisabled = true; return;
            }
        }
        
        fileSubmission.setSubmission(submission);

        rSubmitDisabled = !getTestResult();
    }

    /**
     * Use getTetsResult() and enables the Save Option. Multiple files.
     *
     * @throws NotBoundException
     * @throws RemoteException
     * @throws MalformedURLException
     */
    public void multipleRunTests() throws MalformedURLException, RemoteException, NotBoundException, IOException {

        if (docFiles.isEmpty()) {
            errorMsg("Please, upload at least a file");
            mSubmitDisabled = true; return;
        }

        boolean makefileFound = false, executableAreadyExists = false;
        for (UploadedFile uf : docFiles) {
            if (uf.getFileName().toLowerCase().equals("makefile")) {
                makefileFound = true;
            }
            if (uf.getFileName().toLowerCase().equals(task.getNameFile().toLowerCase())) {
                executableAreadyExists = true;
            }
        }
        if (makefileFound) {
            errorMsg("Don't upload makefile");
            mSubmitDisabled = true; return;
        }
        if (executableAreadyExists) {
            errorMsg("'" + task.getNameFile() + "' will be created after compiling, you mustn't upload this");
            mSubmitDisabled = true; return;
        }
        
        submission.removeAllFiles();
        // Adding makefile
        if (task.isHasMakefile()) {
            String taskPath = path + "/WEB-INF/tasks/" + task.getSubject().getTitle() + "/" + task.getTitle() + "/" + task.getUser().getUsername() + "/";
            for (FileTask f : task.getFiles()) {
                //if (f.getFilename().toLowerCase().equals("makefile")) {
                if (f.isShared()) {
                    try {
                        String sharedfilePath = taskPath + f.getFilename();
                        FileSubmission fi = new FileSubmission();
                        fi.setSubmission(submission);
                        fi.setFilename(f.getFilename());
                        fi.setContent(file2ByteArray(sharedfilePath));
                        submission.addFile(fi);
                    } catch (IOException ex) {
                        System.err.println("ERROR LOADING Shared file: "+taskPath + f.getFilename());
                        Logger.getLogger(SubmissionBean.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }

        for (UploadedFile uf : docFiles) {
            FileSubmission fs = new FileSubmission();
            fs.setSubmission(submission);
            fs.setFilename(uf.getFileName());
            fs.setContent(uf.getContents());

            submission.addFile(fs);
        }
        
        
        System.out.println("CleanTemp: "+fileman.cleanTempPath());
        
        for (FileSubmission fs : submission.getFiles()){
            if (fileman.createTempFile(fs.getFilename(),fs.getContent())){
                String[] mimeType = fileman.getFileMime();
                if (!task.fileIsValid(mimeType)){
                    errorMsg("File '" + fs.getFilename() + "' not admitted: " + mimeType[0]);
                    submission.removeAllFiles();
                    fileman.cleanTempPath();
                    mSubmitDisabled = true; return;
                }
            } else {
                submission.removeAllFiles();
                fileman.cleanTempPath();
                errorMsg("An error ocurred saving your files");
                mSubmitDisabled = true; return;
            }
        }

        mSubmitDisabled = !getTestResult();
    }


    /**
     * Save Submission and update the task and user with this submission.
     *
     * @return A type of String, this is a redirect.
     */
    public String saveSubmission() {
        
        if (!fileman.moveAllTempToSaved()){errorMsg("couldn't move files"); return "";}
        
        java.util.Set<FileSubmission> tmpFiles = submission.getFiles();
        submission.setFiles(null);

        submission.setTask(task);
        submission.setUser(user);
	//submission.setTestsResult(testsResult);

        task.addSubmission(submission);
        
        System.out.println("ID:"+submission.getId());  
        filesubmissionbo.deleteSubmissionsByUser(submission);
        System.out.println("SAVING TASK TO DB");
        taskbo.updateTask(task);

        System.out.println("SAVING FILES TO DB");
        for (FileSubmission sf : tmpFiles) {
            filesubmissionbo.saveFileSubmission(sf);
        }
        
        System.out.println("SETTING FILES BACK");
        submission.setFiles(tmpFiles);

        System.out.println("UPDATEUSER");
        loginbean.updateUser();
        
        submission = new Submission();
        return "student?faces-redirect=true";
    }
    
    /**
     * Remove all uploaded files.
     */
    public void clearufile(){
        docFiles.clear();
        mSubmitDisabled=true;
    }

    /**
     * Disable the Save Option
     */
    public void onCodeChanged() {
        rSubmitDisabled = true;
    }


    /**
     * Redirect to the student view
     *
     * @return a type String, this reference to the redirection instruction.
     */
    public String goBack() {
        return "student?faces-redirect=true";
    }



    public byte[] file2ByteArray(String dir) throws IOException {
        return Files.readAllBytes(new File(dir).toPath());
    }
    
    // ###################################################
    // ############# MODIFIED GETTERS & SETTERS ##########
    // ###################################################
    
    /**
     * Saves to fileSubmission the filename entered by the user + the extension 
     * of the language.
     * @param filename name of the file without extension 
     */
    public void setFilename(String filename) {
        fileSubmission.setFilename(filename + getLangExt());
    }
    
    /**
     * Returns the filename from the fileSubmission excluding the extension.
     * @return String containing the filename with no extensions
     */
    public String getFilename() {
        String fil = fileSubmission.getFilename();
        if (fil == null) {
            return null;
        }
        return fil.substring(0, fil.lastIndexOf("."));
    }
    
    // ##########################################
    // ############# GETTERS & SETTERS ##########
    // ##########################################
    
    /**
     * Retrieve the private variable rSubmitDisabled
     *
     * @return A type boolean
     */
    public boolean isRSubmitDisabled() {
        return rSubmitDisabled;
    }

    /**
     * Set the private variable rSubmitDisabled
     *
     * @param rSubmitDisabled A type of boolean
     */
    public void setRSubmitDisabled(boolean rSubmitDisabled) {
        this.rSubmitDisabled = rSubmitDisabled;
    }

    /**
     * Retrieve the private variable mSubmitDisabled
     *
     * @return A type boolean
     */
    public boolean isMSubmitDisabled() {
        return mSubmitDisabled;
    }

    /**
     * Set the private variable mSubmitDisabled
     *
     * @param mSubmitDisabled A type of boolean
     */
    public void setMSubmitDisabled(boolean mSubmitDisabled) {
        this.mSubmitDisabled = mSubmitDisabled;
    }
    
    /**
     * Retrieve the private variable submission
     *
     * @return A type Submission
     */
    public Submission getSubmission() {
        return submission;
    }

    /**
     * Set the private variable submitDisabled
     *
     * @param submitDisabled A type of boolean
     */
    public void setSubmission(Submission submission) {
        this.submission = submission;
    }
    
    /**
     * Retrieve the private List results
     *
     * @return A type List of TestResult
     */
    public List<TestResult> getResults() {
        return results;
    }

    /**
     * Set the private variable submitDisabled
     *
     * @param submitDisabled A type of boolean
     */
    public void setResults(List<TestResult> results) {
        this.results = results;
    }
    
    /**
     * Retrieve the private variable docInputStream
     *
     * @return A type InputStream
     */
    public InputStream getDocInputStream() {
        return docInputStream;
    }

    /**
     * Set the private variable docInputStream
     *
     * @param docInputStream A type of InputStream
     */
    public void setDocInputStream(InputStream docInputStream) {
        this.docInputStream = docInputStream;
    }

     /**
     * Retrieve the private variable firstIn
     *
     * @return A type boolean
     */
    public boolean isFirstIn() {
        return firstIn;
    }
    
    /**
     * Set the private variable firstIn
     *
     * @param firstIn A type of boolean
     */
    public void setFirstIn(boolean firstIn) {
        this.firstIn = firstIn;
    }
    
    /**
     * Set the private variable codigo
     *
     * @param codigo A type of String
     */
    public void setCodigo(String codigo) {
        this.codigo = codigo;
    }

    public String getCodigo() {
        return codigo;
    }
    
    /**
     * Retrieve the private variable task
     *
     * @return A type Task
     */
    public Task getTask() {
        return task;
    }

    /**
     * Set the private variable task
     *
     * @param task A type of Task
     */
    public void setTask(Task task) {
        this.task = task;
    }   
    
    /**
     * Retrieve the private variable fileSubmission
     *
     * @return A type FileSubmission
     */
    public FileSubmission getFileSubmission() {
        return fileSubmission;
    }

    /**
     * Set the private variable fileSubmission
     *
     * @param fileSubmission A type of FileSubmission
     */
    public void setFileSubmission(FileSubmission fileSubmission) {
        this.fileSubmission = fileSubmission;
    }
    
    /**
     * Retrieve the language file extension: .c, .java or .py.
     * @return String language extension
     */
    public String getLangExt() {
        return task.getLanguaje().getFileExtension();
    }
    
    /**
     * Retrieve the list of uploaded files.
     * @return Uploaded files
     */
    public List<UploadedFile> getDocFiles() {
        return docFiles;
    }

    /**
     * Sets the list of uploaded files.
     *
     * @param docFiles A List of UploadedFile
     */
    public void setDocFiles(List<UploadedFile> docFiles) {
        this.docFiles = docFiles;
    }
}