package ub.scp2.managedBeans;

import infoObjects.InfoError;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

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 javax.faces.model.SelectItem;

import org.primefaces.context.RequestContext;
import org.primefaces.event.FileUploadEvent;
import org.primefaces.event.SelectEvent;
import org.primefaces.event.UnselectEvent;
import org.primefaces.model.UploadedFile;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.jsf.FacesContextUtils;

import ub.scp2.bo.RoleBo;
import ub.scp2.bo.SubjectBo;
import ub.scp2.bo.UserBo;
import ub.scp2.entities.Role;
import ub.scp2.entities.Subject;
import ub.scp2.entities.User;
import utils.ConstantsErr;
import utils.RMIServices;

/**
 * AdminBean.java - a Bean to control the admin view.
 *
 *
 */
@ManagedBean(name = "adminBean")
@ViewScoped
public class AdminBean implements Serializable, ConstantsErr {

    private User selectedUser;
    private User newUser;
    private User user;

    private Subject selectedSubject;
    private Subject newSubject;

    private List<User> fileUsers;
    private List<User> addedUsers;
    private List<User> availableUsers;
    private List<User> selectedUsers;

    private SubjectBo subjectbo;
    private UserBo userbo;
    private RoleBo rolebo;

    private List<User> userlist;
    private List<Subject> subjectlist;
    private List<User> adminlist;
    
    private List<User> filtereduserlist;
    private List<Subject> filteredsubjectlist;

    private UploadedFile docFile;
    private InputStream contentType;
    
    private SelectItem[] rolesList;

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

    public AdminBean() {
    }

    /**
     * Initialize the parameters required for the class
     */
    @PostConstruct
    public void init() {
        WebApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());
        subjectbo = ctx.getBean(SubjectBo.class);
        userbo = ctx.getBean(UserBo.class);
        rolebo = ctx.getBean(RoleBo.class);
        newUser = new User();
        newSubject = new Subject();
        fileUsers = new ArrayList<User>();
        rolesList = createRolesOptions();
        user = loginbean.getUser();
        getUsers();
        getSubjects();
        getAdmins();

        System.out.println("adminlist size = " + adminlist.size());
        System.out.println("userlist size = " + userlist.size());
    }

    /**
     * Delete the selected user, previously check if have permission.
     */
    public void deleteUser() {
        try {
            if (selectedUser.getRole().getRoleId() != 3) {
                this.deleteUserClass();
            }
            if (userbo.deleteUser(selectedUser, loginbean.getUser())) {
                FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "User deleted.", "User deleted.");
                FacesContext.getCurrentInstance().addMessage(null, message);
            } else {
                FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Permission denied.", "Error deleting user.");
                FacesContext.getCurrentInstance().addMessage(null, message);
            }

        } catch (Exception e) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error deleting user.", "Error deleting user.");
            FacesContext.getCurrentInstance().addMessage(null, message);
        }
        getUsers();
        getAdmins();
    }

    /**
     * Remove the selected user from Class.
     */
    public void deleteUserClass() {
        try {
            addedUsers.remove(selectedUser);
            selectedUser.removeSubject(selectedSubject);
        } catch (Exception e) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error deleting user.", "Error deleting user.");
            FacesContext.getCurrentInstance().addMessage(null, message);
        }
        getUsers();
    }

    /**
     * Create a new User.
     */
    public void saveNewUser() {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (userbo.usernameExists(newUser.getUsername())) {
            newUser = userbo.getUserByUsername(newUser.getUsername());
            String info = "El usuario: " + newUser.getUsername() + " ya existe";
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, info, info);
            facesContext.addMessage(null, message);
            
        } else {
            userbo.saveUser(newUser);
            newUser = new User();
            getUsers();
            RequestContext context = RequestContext.getCurrentInstance();
            context.execute("addUserDialog.hide()");
        }
    }
    
    /**
     * Create a new User, this User is assigned in the selected Class.
     */
    public void saveNewUserToClass() {
        boolean submit = true;
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (userbo.usernameExists(newUser.getUsername())) {
            submit = false;
            newUser = userbo.getUserByUsername(newUser.getUsername());
            String info = "El usuario: " + newUser.getUsername() + " ya existe, no fue creado pero se añadio a la asignatura: " + selectedSubject.getTitle();
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, info, info);
            facesContext.addMessage(null, message);
        }
        newUser.addSubject(selectedSubject);
        addedUsers.add(newUser);
        if (submit) {
            userbo.saveUser(newUser);
        }
        newUser = new User();
        getUsers();
        RequestContext context = RequestContext.getCurrentInstance();
        context.execute("newSubjectUserDialog.hide()");
    }

    /**
     * Create a new User Admin, previously check that this dosen't exist.
     */
    public void saveNewAdmin() {
        boolean submit = true;
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (userbo.usernameExists(newUser.getUsername())) {
            submit = false;
            newUser = userbo.getUserByUsername(newUser.getUsername());
            String info = "El usuario: " + newUser.getUsername() + " ya existe";
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, info, info);
            facesContext.addMessage(null, message);
        }
        if (submit) {
            newUser.setRole(rolebo.getRoleById(3));
            userbo.saveUser(newUser);
        }
        getUsers();
        getAdmins();
        newUser = new User();
        RequestContext context = RequestContext.getCurrentInstance();
        context.execute("newAdminDialog.hide()");
    }

    /**
     * Cancel the create of new User.
     */
    public void cancelNewUser() {
        newUser = new User();
        RequestContext context = RequestContext.getCurrentInstance();
        context.execute("newSubjectUserDialog.hide()");
        context.execute("addUserDialog.hide()");
        context.execute("newAdminDialog.hide()");
    }

    /**
     * Cancel the edit of User.
     */
    public void cancelEditUser() {
        newUser = new User();
        RequestContext context = RequestContext.getCurrentInstance();
        context.execute("editUserDialog.hide()");
        context.execute("editMeDialog.hide()");
    }

    /**
     * Edit the selected User.
     */
    public void editUser() {
        userbo.updateUser(selectedUser);
        RequestContext context = RequestContext.getCurrentInstance();
        context.execute("editUserDialog.hide()");
        newUser = new User();
    }

    /**
     * Edit the login User.
     */
    public void editMe() {
        userbo.updateUser(user);
        RequestContext context = RequestContext.getCurrentInstance();
        context.execute("editMeDialog.hide()");
    }
    

    /**
     * Adds multiple users to a subject.
     */
    public void confirmSelectUsers() {        
        addedUsers = new ArrayList<User>();
        for (User u:selectedUsers){
            u.addSubject(selectedSubject);
            addedUsers.add(u);
        }
       
        RequestContext context = RequestContext.getCurrentInstance();
        context.execute("selectUserClassDialog.hide()");
        
    }
    
    /**
     * Users participating in a subject will be added to this.filtereduserlist
     */
    public void setInitialSelectedUsers(){
        this.filtereduserlist = null;
        
        this.selectedUsers = new ArrayList<User>();
        for (User u:addedUsers){
            this.selectedUsers.add(u);
        }
    }

    /**
     * Retrieve the role list.
     *
     * @return A List of Role type.
     * @see RoleBoImpl.java
     */
    public List<Role> getRoleList() {
        return rolebo.getRoleList();
    }

    /**
     * Assign to userlist a list of Users
     *
     * @see UserBoImpl.java
     */
    public void getUsers() {
        userlist = userbo.getUsers();
    }

    /**
     * Assign to adminlist a list of Admins Users
     *
     * @see UserBoImpl.java
     */
    public void getAdmins() {
        adminlist = userbo.getAdmins();
    }

    /**
     * Assign to subjectlist a list of Subjects.
     *
     * @see SubjectBoImpl.java
     */
    public void getSubjects() {
        subjectlist = subjectbo.getSubjects();
    }

    /**
     * Returns a list of teachers who belong to a class
     *
     * @return A List of User type.
     */
    public List<User> getClassTeachers() {
        List<User> teachers = new ArrayList<User>();
        if (selectedSubject != null) {
            Iterator<User> ite = selectedSubject.getUsers().iterator();
            User u;
            while (ite.hasNext()) {
                u = ite.next();
                if (u.getRole().getName().equals("ROLE_TEACHER")) {
                    teachers.add(u);
                }
            }
        }
        return teachers;
    }

    /**
     * Returns a list of students who belong to a class
     *
     * @return
     */
    public List<User> getClassStudents() {
        List<User> students = new ArrayList<User>();
        if (selectedSubject != null) {
            Iterator<User> ite = selectedSubject.getUsers().iterator();
            User u;
            while (ite.hasNext()) {
                u = ite.next();
                if (u.getRole().getName().equals("ROLE_STUDENT")) {
                    students.add(u);
                }
            }
        }
        return students;
    }

    public void callback() {

        addedUsers = new ArrayList<User>(getClassTeachers());
        addedUsers.addAll(getClassStudents());

        availableUsers = new ArrayList<User>(userlist);
        availableUsers.removeAll(addedUsers);
        Iterator<User> ite = availableUsers.iterator();
        User u;
        List<User> removeUsers = new ArrayList<User>();
        while (ite.hasNext()) {
            u = ite.next();
            System.out.println(u);
            if (u.getRole().getName().equals("ROLE_ADMIN") || addedUsers.contains(u)) {
                removeUsers.add(u);
            }

        }
        availableUsers.removeAll(removeUsers);

    }

    /**
     * Create a new Subject, in this subject add the users that have been loaded
     * with the file.
     */
    public void saveNewSubject() {
        Set<User> s = new HashSet<User>(fileUsers);
        newSubject.setUsers(s);
        subjectbo.saveSubject(newSubject);
        getSubjects();
        newSubject = new Subject();
    }

    /**
     * Cancel the create new Subject.
     */
    public void cancelNewSubject() {
        newSubject = new Subject();
        RequestContext context = RequestContext.getCurrentInstance();
        context.execute("newClassDialog.hide()");
    }

    /**
     * Cancel the Edit Subject.
     */
    public void cancelEditSubject() {
        newSubject = new Subject();
        RequestContext context = RequestContext.getCurrentInstance();
        context.execute("editClassDialog.hide()");
    }

    /**
     * Edit and Update the Subject.
     */
    public void editSubject() {
        Set<User> s = new HashSet<User>(addedUsers);
        selectedSubject.setUsers(s);
        subjectbo.updateSubject(selectedSubject);
        RequestContext context = RequestContext.getCurrentInstance();
        context.execute("editClassDialog.hide()");
        newSubject = new Subject();
    }

    /**
     * Delete the selected Subject.
     */
    public void deleteSubject() {
        try {
            subjectbo.deleteSubject(selectedSubject);
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Class deleted.", "Class deleted.");
            FacesContext.getCurrentInstance().addMessage(null, message);
        } catch (Exception e) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error deleting class.", "Error deleting class.");
            FacesContext.getCurrentInstance().addMessage(null, message);
        }
        getSubjects();
        getUsers();
    }
    
    /**
     * Creates a Subject.
     */
    public void createSubject(){
        if (subjectbo.subjectTitleExist(newSubject.getTitle())) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_FATAL, "The Class already exists!", "The Class already exists!");
            FacesContext.getCurrentInstance().addMessage(null, message);
        } else {
            subjectbo.saveSubject(newSubject);
            getSubjects();
            
            RequestContext context = RequestContext.getCurrentInstance();
            context.execute("loadUserDialog.hide()");
        }
    }
    
    
    /**
     * Shows upload succesful message and saves upload.
     */
    public void uploaded(FileUploadEvent event){
        FacesMessage msg = new FacesMessage("Upload Succesful", event.getFile().getFileName() + " is uploaded.");
        FacesContext.getCurrentInstance().addMessage(null, msg);
        this.setDocFile(event.getFile());
    }
    
   /**
     * Cancel uploaded file.
     */
    public void cancelUploaded(){
        this.setDocFile(null);
    }
    
    /**
     * Reads users from uploaded file. Users won't be created if they already exists
     * Read users will added to the selectedSubject.
     */
    public void addUsersFile2Subject(){
        readUsersFile();
        for (User u : fileUsers) {
            if (!addedUsers.contains(u)){
                u.addSubject(selectedSubject);
                addedUsers.add(u);
            }
        }
    }
    
    /**
     * Reads users from uploaded file. Users won't be created if they already exists
     * Read users will be stored in "this.fileUsers".
     */
    public void readUsersFile(){        
        
        fileUsers = new ArrayList<User>();
        try {
            String line = null;
            boolean primer = true;
            
            FacesContext facesContext = FacesContext.getCurrentInstance();
            contentType = docFile.getInputstream();
            InputStreamReader r = new InputStreamReader(contentType);
            BufferedReader bin = new BufferedReader(r);
            while ((line = bin.readLine()) != null) {
                if (primer) {
                    primer = false;
                } else {
                    newUser = new User();
                    
                    String[] dataUser = line.split(",");
                    newUser.setFirstName(dataUser[0].replaceAll("\\\"", ""));
                    newUser.setLastName(dataUser[1].replaceAll("\\\"", ""));
                    if (dataUser[2].contains("niub")) {
                        newUser.setRole(rolebo.getRoleById(2));
                    } else {
                        newUser.setRole(rolebo.getRoleById(1));
                    }
                    newUser.setUsername(dataUser[2]);
                    newUser.setPassword(dataUser[2] + "2014");
                    System.out.println("NewUser:" + newUser.getFirstName());
                    fileUsers.add(newUser);
                    if (userbo.usernameExists(newUser.getUsername())) {
                        fileUsers.add(userbo.getUserByUsername(newUser.getUsername()));
                        
                        //String info = "The User: " + newUser.getUsername() + " already exists";
                        //FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, info, info);
                        //facesContext.addMessage(null, message);
                    } else {
                        fileUsers.add(newUser);
                        userbo.saveUser(newUser);
                    }
                }
            }
            getUsers();
            newUser = new User();
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "File upload. Passwords: niub<id>2014", "File upload. Passwords: niub<id>2014");
            facesContext.addMessage(null, message);
        } catch (Exception x) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_FATAL, x.getClass().getName(), x.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, message);
        }
    }

    /**
     * Delete all Data Base, also in SSCP. Contain errors
     */
    public boolean cleanDataBase() {

        try {
            userbo.cleanDataBase(loginbean.getUser());
            if (!RMIServices.isConnected()) {
                RMIServices.connectRMI();
            }
            InfoError infoError = RMIServices.securityCleanData();

            boolean cleanOK = (infoError.getError() == ERROR_OK);

            if (!cleanOK) {
                FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error", "");
                FacesContext.getCurrentInstance().addMessage(null, msg);
            } else {
                FacesContext facesContext = FacesContext.getCurrentInstance();
                FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "DataBase Cleaned", "DataBase Cleaned");
                facesContext.addMessage(null, message);
                getSubjects();
            }
        } catch (Exception x) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_FATAL, x.getClass().getName(), x.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, message);
        }
        return false;
    }


    
    /**
     * Creates a SelectItem useful for filtering by roles.
     *
     * @return A list of roles
     */
    private SelectItem[] createRolesOptions()  {
        List<Role> lr = getRoleList();
        SelectItem[] options = new SelectItem[lr.size()+1];  
  
        options[0] = new SelectItem("", "Show all");  
        for(int i = 0; i < lr.size(); i++) {  
            options[i + 1] = new SelectItem(lr.get(i).getWebFriendlyName(), lr.get(i).getWebFriendlyName());  
        }  
  
        return options;  
    } 

    
    // ###################################################
    // ############# MODIFIED GETTERS & SETTERS ##########
    // ###################################################
    
    public String getUserSubjects(){
        String subs = "";
        if (selectedUser!=null){
            boolean first = true;
            for (Subject s:selectedUser.getSubjects()){
                if (first){
                    first = false;
                } else {
                    subs += ", ";
                }
                subs += s.getTitle();
            }
        }
        return subs;
    }
    
    /**
     * Set the List of Selected Users from the website.
     * EDIT: NOT USED DUE TO UNDESIRED ISSUES (needed to avoid errors)
     * addSelectedUsers and remSelectedUsers will be used instead.
     * 
     * Don't delete this method as JSF needs it
     *
     * @param selectedUsers A List of type User
     */
    public void setSelectedUsers(List<User> selectedUsers) {
        //this.selectedUsers = selectedUsers;
    }
    
    /**
     * Event that will add an user to the list
     * @param event Event from DataTable
     */
    public void addSelectedUsers(SelectEvent event) {
        User u = (User) event.getObject();
        if (!this.selectedUsers.contains(u)) {
            this.selectedUsers.add(u);
        }
    }

    /**
     * Event that will remove an user to the list
     * @param event Event from DataTable
     */
    public void remSelectedUsers(UnselectEvent event) {
        User u = (User) event.getObject();
        if (this.selectedUsers.contains(u)) {
            this.selectedUsers.remove(u);
        }
    }
    
    /**
     * Retrieve a List of Selected Users from the website
     *
     * @return A List of type User
     */
    public List<User> getSelectedUsers() {
        return selectedUsers;
    }
    
    // ##########################################
    // ############# GETTERS & SETTERS ##########
    // ##########################################    
    
    /**
     * Set the selectedUser
     *
     * @param user A variable of type User
     */
    public void setSelectedUser(User user) {
        this.selectedUser = user;
    }

    /**
     * Retrieve selectedUser
     *
     * @return A type User
     */
    public User getSelectedUser() {
        return selectedUser;
    }

    /**
     * Retrieve newUser
     *
     * @return A type User
     */
    public User getNewUser() {
        return newUser;
    }

    /**
     * Set the newUser
     *
     * @param newUser A variable of type User
     */
    public void setNewUser(User newUser) {
        this.newUser = newUser;
    }

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

    /**
     * Retrieve selectedSubject
     *
     * @return A type Subject
     */
    public Subject getSelectedSubject() {
        return selectedSubject;
    }

    /**
     * Set the selectedSubject
     *
     * @param selectedSubject A type of Subject
     */
    public void setSelectedSubject(Subject selectedSubject) {
        this.selectedSubject = selectedSubject;
    }
    
    /**
     * Retrieve newSubject
     *
     * @return A type Subject
     */
    public Subject getNewSubject() {
        return newSubject;
    }

    /**
     * Set the newSubject
     *
     * @param subject A type of Subject
     */
    public void setNewSubject(Subject subject) {
        this.newSubject = subject;
    }

    /**
     * Retrieve the list addedUsers
     *
     * @return A List of type User
     */
    public List<User> getAddedUsers() {
        return addedUsers;
    }

    /**
     * Set the list addedUsers
     *
     * @param addedUsers A list of type User
     */
    public void setAddedUsers(List<User> addedUsers) {
        this.addedUsers = addedUsers;
    }

    /**
     * Retrieve the list fileUsers
     *
     * @return A List of type User
     */
    public List<User> getFileUsers() {
        return fileUsers;
    }

    /**
     * Set the list fileUsers
     *
     * @param fileUsers A list of type User
     */
    public void setFileUsers(List<User> fileUsers) {
        this.fileUsers = fileUsers;
    }

    /**
     * Retrieve the list availableUsers
     *
     * @return A List of type User
     */
    public List<User> getAvailableUsers() {
        return availableUsers;
    }

    /**
     * Set the list availableUsers
     *
     * @param availableUsers A list of type User
     */
    public void setAvailableUsers(List<User> availableUsers) {
        this.availableUsers = availableUsers;
    }

    /**
     * Retrieve a List with Users
     *
     * @return A List of type User
     */
    public List<User> getUserlist() {
        return userlist;
    }

    /**
     * Set the list userlist
     *
     * @param userlist A List of type User
     */
    public void setUserlist(List<User> userlist) {
        this.userlist = userlist;
    }

    /**
     * Retrieve a List with Subjects
     *
     * @return A List of type Subject
     */
    public List<Subject> getSubjectlist() {
        return subjectlist;
    }

    /**
     * Set the list subjectlist
     *
     * @param subjectlist A List of type Subject
     */
    public void setSubjectlist(List<Subject> subjectlist) {
        this.subjectlist = subjectlist;
    }

    /**
     * Retrieve a docFile
     *
     * @return A type UploadedFile
     */
    public UploadedFile getDocFile() {
        return docFile;
    }

    /**
     * Set the docFile
     *
     * @param docFile A type of UploadedFile
     */
    public void setDocFile(UploadedFile docFile) {
        this.docFile = docFile;
    }

    /**
     * Retrieve a List of Admins Users
     *
     * @return A List of type User
     */
    public List<User> getAdminlist() {
        return adminlist;
    }

    /**
     * Set the adminlist
     *
     * @param adminlist A List of type User
     */
    public void setAdminlist(List<User> adminlist) {
        this.adminlist = adminlist;
    }
    
    /**
     * Retrieve the user
     *
     * @return A type User
     */
    public User getUser() {
        return user;
    }

    /**
     * Set the user
     *
     * @param user A type of User
     */
    public void setUser(User user) {
        this.user = user;
    }
    
    /**
     * Retrieve a List of Users filtered
     *
     * @return A List of type User
     */
    public List<User> getFiltereduserlist() {
        return filtereduserlist;
    }

    /**
     * Set a List of Users filtered
     *
     * @param filtereduserlist A List of type User
     */
    public void setFiltereduserlist(List<User> filtereduserlist) {
        this.filtereduserlist = filtereduserlist;
    }
    
    /**
     * Set a List of Subjects filtered
     *
     * @return A List of type Subject
     */
    public List<Subject> getFilteredsubjectlist() {
        return filteredsubjectlist;
    }

    /**
     * Set a List of Subjects filtered
     *
     * @param filteredsubjectlist A List of type Subject
     */
    public void setFilteredsubjectlist(List<Subject> filteredsubjectlist) {
        this.filteredsubjectlist = filteredsubjectlist;
    }
    
    /**
     * Return a SelectItem useful for filtering by roles.
     *
     * @return A list of roles
     */
    public SelectItem[] getRolesList() {
        return rolesList;
    }

    /**
     * Set a SelectItem useful for filtering by roles.
     *
     * @param rolesList A List of roles
     */
    public void setRolesList(SelectItem[] rolesList) {
        this.rolesList = rolesList;
    }
}
