package managedbean.profile;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
import javax.resource.NotSupportedException;
import javax.validation.constraints.Size;

import org.primefaces.PrimeFaces;

import TO.FamilyDoctorTO;
import TO.MedicalSpecialtyTO;
import TO.PatientTO;
import TO.PrimaryHealthCareCenterTO;
import TO.SpecialistDoctorTO;
import common.Constants;
import common.UserType;
import managedbean.common.ManagedBeanBase;
import managedbean.common.ValidationUtils;

/**
 * ManagedBEan que gestiona el registro de usuarios: Usuarios de tipo "Paciente" Usuarios de tipo "Médico de Familia" usuarios de tipo "Médico especialista"
 * 
 * @author Marcos García Núñez (mgarcianun@uoc.edu)
 *
 */
@Named("RegisterUser")
@ViewScoped
public class RegisterUserMBean extends ManagedBeanBase implements Serializable {

	private static final long serialVersionUID = 1L;
	private String cipCode;
	private String nif;
	private String name;
	private String surname;
	@Size(min = 4, message = "La contraseña debe tener al menos 4 carácteres.")
	private String password;
	private String email;
	private boolean registered;
	private String lastUIQueryPH;
	private String lastUIQueryMS;

	// private HashMap<String, String> userTypes;
	private List<UserType> availableUserTypes;
	private UserType userType;
	private PrimaryHealthCareCenterTO primaryHealthCareCenter;
	private MedicalSpecialtyTO medicalSpecialty;
	private List<MedicalSpecialtyTO> medicalSpecialtiesList;
	private List<PrimaryHealthCareCenterTO> primaryHealthCareCentersList;

	public RegisterUserMBean() {

	}

	/**
	 * Inicializa el managedBean actual.
	 * 
	 * Carga los tipos de usuario disponibles. Carga la lista de CAPs. Carga la lista de especialidades médicas.
	 */
	@PostConstruct
	public void init() {
		this.availableUserTypes = new ArrayList<UserType>();
		this.availableUserTypes.add(UserType.PATIENT);
		this.availableUserTypes.add(UserType.FAMILY_DOCTOR);
		this.availableUserTypes.add(UserType.SPECIALIST_DOCTOR);
		this.registered = false;
		this.lastUIQueryPH = "";
		this.lastUIQueryMS = "";

		this.userType = UserType.PATIENT;

		this.medicalSpecialtiesList = this.getRemoteManagerCommon().listMedicalSpecialtiesPaged(0, Constants.MAX_ITEMS_AUTOCOMPLETE_SEARCH);
		this.primaryHealthCareCentersList = this.getRemoteManagerCommon().listCAPsPaged(0, Constants.MAX_ITEMS_AUTOCOMPLETE_SEARCH);
	}

	public List<UserType> getAvailableUserTypes() {
		return availableUserTypes;
	}

	/**
	 * Gestiona el evento de cambio de tipo de usuario. El usuario que está intentando registrarse en el sistema ha cambiado el tipo de perfil (usuario) con el cual desea
	 * registrarse.
	 * 
	 */
	public void onUserTypeChange() {
		switch (this.userType) {
		case SPECIALIST_DOCTOR:
			// El usuario queire registrarse como médico especialista, lanzamos un evento AJAX de cliente para que la interfaz refleje el cambio en el tipo de usuario
			// seleccionado: Se muestra la lista de especialidades médicas para obligar a seleccionar una.
			PrimeFaces.current().ajax().addCallbackParam("specs", true);
			break;
		case FAMILY_DOCTOR:
			// El usuario queire registrarse como médico especialista, lanzamos un evento AJAX de cliente para que la interfaz refleje el cambio en el tipo de usuario
			// seleccionado: Se muestra la lista de CAPs para obligar a seleccionar uno.
			PrimeFaces.current().ajax().addCallbackParam("caps", true);
			break;
		case ADMINISTRATOR:
		case PATIENT:
			// El usuario queire registrarse como médico especialista, lanzamos un evento AJAX de cliente para que la interfaz refleje el cambio en el tipo de usuario
			// seleccionado: Se ocultará la lista de selección de CAP o Especialidad médica
			PrimeFaces.current().ajax().addCallbackParam("pats", true);
			break;
		}
	}

	public List<MedicalSpecialtyTO> getMedicalSpecialtiesList() {
		return medicalSpecialtiesList;
	}

	public List<PrimaryHealthCareCenterTO> getPhcList() {
		return primaryHealthCareCentersList;
	}

	/**
	 * Método que implementa la búsqueda de un CAP a través del termino tecleado por el usuario en la lista de selección
	 * 
	 * @param query Termino tecleado por el usuario
	 * @return Lista de CAPs que coinciden con el termino búscado por el usuario.
	 */
	public List<PrimaryHealthCareCenterTO> completePrimaryHealCareCenter(String query) {
		if (query != null && query.equals(this.lastUIQueryPH) == false) {
			this.lastUIQueryPH = query;
			// Recuperamos las 200 primeras coincidencias
			this.primaryHealthCareCentersList = this.getRemoteManagerCommon().listCAPsFiltered(query, 0, Constants.MAX_ITEMS_AUTOCOMPLETE_SEARCH);
		}
		return this.primaryHealthCareCentersList;
	}

	/**
	 * Método que implementa la búsqueda de una especialidad a través del termino tecleado por el usuario en la lista de selección
	 * 
	 * @param query Termino tecleado por el usuario
	 * @return Lista de especialidades que coinciden con el termino búscado por el usuario.
	 */
	public List<MedicalSpecialtyTO> completeMedicalSpecialty(String query) {
		if (query != null && query.equals(this.lastUIQueryMS) == false) {
			this.lastUIQueryMS = query;
			// Recuperamos las 200 primeras coincidencias
			this.medicalSpecialtiesList = this.getRemoteManagerCommon().listMedicalSpecialtiesFiltered(query, 0, Constants.MAX_ITEMS_AUTOCOMPLETE_SEARCH);
		}
		return this.medicalSpecialtiesList;
	}

	/**
	 * Búsca si el NIF ya está en uso para otro usuario. Solo se permite que el mismo NIF se refistro como paciente y como médico (especialista o de familia, pero no como ambos).
	 * 
	 * Un Médico puede estar registrado como paciente con el mismo NIF, pero como médico de familia y especialista al mismo tiempo.
	 */
	public boolean checkNIFDuplicated(String nifValue, UserType userType) {
		return ValidationUtils.checkIfNifAlreadyRegistered(this.getRemoteManagerCommon(), userType, nifValue, null);
	}

	public boolean isPatient() {
		return (this.userType == UserType.PATIENT);
	}

	public boolean isFamilyDoctor() {
		return (this.userType == UserType.FAMILY_DOCTOR);
	}

	public boolean isSpecialistDoctor() {
		return (this.userType == UserType.SPECIALIST_DOCTOR);
	}

	public boolean isDoctor() {
		return (isFamilyDoctor() || isSpecialistDoctor());
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getSurname() {
		return surname;
	}

	public void setSurname(String surname) {
		this.surname = surname;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getNif() {
		return nif;
	}

	public void setNif(String nif) {
		this.nif = nif;
	}

	/**
	 * Método que realiza la persistencia de los datos en la base de datos a través de la invocación de los métodos del EJB correspondiente.
	 * 
	 * Se realizan comprobaciones adicionales para asegurar que:
	 * 
	 * Si el tipo de usuario que se está registrando es un méidco de familia, obliga a seleccionar un CAP para el usuario.
	 * 
	 * Si el tipo de usuario que está registrando es un médico especialista, obliga a seleccionar una especialidad médica al usuario.
	 * 
	 * Si el tipo de usuario que está registrando es un paciente, no obliga a seleccionar un médicos de familia (puede ser nulo) será necesario que el usuario lo seleccione a
	 * posteriori, una vez logeado en el sistema, a través de la opción de menú correspondiente.
	 * 
	 * No se permite el registro de administradores a través de la página de registro pública.
	 * 
	 */
	public void addNewUser() throws Exception {
		int error = 0;

		this.nif = ValidationUtils.normalizeNIF(this.nif);

		if (this.isFamilyDoctor() && this.primaryHealthCareCenter == null) {
			this.addFacesMessage("frmRegisterUser:selPHC", FacesMessage.SEVERITY_WARN, "Centro de atención primaria no seleccionado",
					"Por favor, especifique un centro de atención primaria.");
			error++;
		}
		if (this.isSpecialistDoctor() && this.medicalSpecialty == null) {
			this.addFacesMessage("frmRegisterUser:selMS", FacesMessage.SEVERITY_WARN, "Especialidad médica no seleccionada", "Por favor, especifique una especialidad médica.");
			error++;
		}
		if (ValidationUtils.isValid(nif) == false) {
			this.addFacesMessage(FacesMessage.SEVERITY_WARN, "El NIF indicado no es válido", "Por favor, especifique un NIF válido.");
			error++;
		}
		if (this.checkNIFDuplicated(this.nif, this.userType) == true) {
			this.addFacesMessage("frmRegisterUser:nif", FacesMessage.SEVERITY_WARN, "NIF duplicado", "El nif indicado pertenece a otro usuario previamente registrado");
			error++;
		}

		if (error == 0) {
			switch (this.userType) {
			case PATIENT:
				PatientTO pat = this.getRemoteManagerProfile().registerPatient(nif, name, surname, password, email);
				this.cipCode = pat.getPersonalIdentificationCode();

				break;
			case FAMILY_DOCTOR:
				FamilyDoctorTO fd = this.getRemoteManagerProfile().registerFamilyDoctor(nif, name, surname, password, email, this.primaryHealthCareCenter);
				this.cipCode = fd.getProfessionalNumber();

				break;
			case SPECIALIST_DOCTOR:
				SpecialistDoctorTO sd = this.getRemoteManagerProfile().registerSpecialistDoctor(nif, name, surname, password, email, this.medicalSpecialty);
				this.cipCode = sd.getProfessionalNumber();

				break;
			case ADMINISTRATOR:
				throw new NotSupportedException("No se soporta el registro directo de administradores.");
			}

			this.registered = true;
			this.addFacesMessage(FacesMessage.SEVERITY_INFO, "Registro realizado", "El usuario " + name + " " + surname + " se ha registrado correctamente.");
		}
	}

	public UserType getUserType() {
		return userType;
	}

	public void setUserType(UserType value) {
		this.userType = value;
	}

	public MedicalSpecialtyTO getMedicalSpecialty() {
		return medicalSpecialty;
	}

	public void setMedicalSpecialty(MedicalSpecialtyTO value) {
		this.medicalSpecialty = value;
	}

	public PrimaryHealthCareCenterTO getPrimaryHealthCareCenter() {
		return primaryHealthCareCenter;
	}

	public void setPrimaryHealthCareCenter(PrimaryHealthCareCenterTO value) {
		this.primaryHealthCareCenter = value;
	}

	public boolean isRegistered() {
		return registered;
	}

	public String getCipCode() {
		return cipCode;
	}

	public void setCipCode(String cipCode) {
		this.cipCode = cipCode;
	}

}
