package org.msh.tb.cases;


import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.core.Events;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.international.StatusMessage;
import org.msh.etbm.commons.transactionlog.mapping.LogInfo;
import org.msh.tb.adminunits.AdminUnitSelection;
import org.msh.tb.application.App;
import org.msh.tb.cases.exams.ExamCultureHome;
import org.msh.tb.cases.exams.ExamMicroscopyHome;
import org.msh.tb.cases.exams.MedicalExaminationHome;
import org.msh.tb.cases.treatment.StartTreatmentHome;
import org.msh.tb.entities.*;
import org.msh.tb.entities.enums.*;
import org.msh.tb.login.UserSession;
import org.msh.tb.tbunits.TBUnitSelection;
import org.msh.tb.tbunits.TBUnitType;
import org.msh.utils.date.DateUtils;

import javax.persistence.EntityManager;
import java.util.Date;
import java.util.HashMap;
import java.util.List;


/**
 * Handle TB and DR-TB cases editing and new notification
 * @author Ricardo Memoria
 *
 */
@Name("caseEditingHome")
@Scope(ScopeType.CONVERSATION)
@LogInfo(roleName = "CASE_DATA", entityClass = TbCase.class)
public class CaseEditingHome {

    @In(create = true)
    CaseHome caseHome;

    @In(create = true)
    PatientHome patientHome;

    @In
    EntityManager entityManager;

    @In(create = true)
    PrevTBTreatmentHome prevTBTreatmentHome;

    @In(create = true)
    MedicalExaminationHome medicalExaminationHome;

    @In(create = true)
    FacesMessages facesMessages;

    @In(required = false)
    StartTreatmentHome startTreatmentHome;

    @In(required = false)
    ExamMicroscopyHome examMicroscopyHome;

    @In(required = false)
    ExamCultureHome examCultureHome;

    private TBUnitSelection tbunitselection;
    private AdminUnitSelection notifAdminUnit;
    private AdminUnitSelection currentAdminUnit;
    private boolean initialized;

    /*patientid: Identify the id of the patient DRTB case used to search its BMU Tb case
    * regdate: identify the registration date used to search its BMU Tb case
    * BMUTbcase: The TBcase found*/
    private HashMap<String, Object> BMUcase;

    /**
     * 1 - Standard, 2 - Individualized
     */
    protected int regimenType;


    public String selectPatientData() {
        initialized = false;
        if (initializeNewNotification().equals("initialized")) {
            return "/cases/casenew.xhtml";
        }
        return "error";
    }

    /**
     * Initialize a new notification
     */
    public String initializeNewNotification() {
        if (initialized) {
            return "initialized";
        }

        Patient p = patientHome.getInstance();

        patientHome.setTransactionLogActive(false);
        patientHome.setDisplayMessage(false);

        if (caseHome.getInstance().getClassification() == null) {
            return "/cases/index.xhtml";
        }

        if ((!patientHome.isManaged()) &&
                (p.getName() == null) && (p.getMiddleName() == null) && (p.getLastName() == null) &&
                (p.getBirthDate() == null)) {
            return "patient-searching";
        }

        caseHome.getInstance().setPatient(patientHome.getInstance());

        if (p.getBirthDate() != null) {
            updatePatientAge();
        }

        // initialize default values
        CasesViewController ctrl = (CasesViewController) App.getComponent("casesViewController");
        if (ctrl != null && ctrl.getSelectedUnit() != null) {
            getTbunitselection().setSelected(ctrl.getSelectedUnit());
            getTbunitselection().setReadOnly(true);
        }

        UserWorkspace userWorkspace = (UserWorkspace)Component.getInstance("userWorkspace");
        if (userWorkspace != null) {
            AdministrativeUnit au = userWorkspace.getAdminUnit();
            if (au == null)
                au = userWorkspace.getTbunit().getAdminUnit();

            if (au != null) {
                au = entityManager.find(AdministrativeUnit.class, au.getId());

                getNotifAdminUnit().setSelectedUnit(au);

                if (getTbunitselection().getSelected() == null) {
                    List<AdministrativeUnit> lst = getTbunitselection().getAdminUnits();

                    if (lst != null)
                        for (AdministrativeUnit adminUnit: lst) {
                            if (adminUnit.isSameOrChildCode(au.getCode())) {
                                getTbunitselection().setAdminUnit(adminUnit);
                            }
                        }
                }
            }

            updateBMUinfo();
        }

        Events.instance().raiseEvent("new-notification");

        initialized = true;
        return "initialized";
    }


    /**
     * Update the patient's age according to his birth date and the diagnosis date
     */
    public void updatePatientAge() {
        Date dtBirth = caseHome.getInstance().getPatient().getBirthDate();
        if (dtBirth == null)
            return;

        Date dtDiag = caseHome.getInstance().getDiagnosisDate();
        if (dtDiag == null)
            dtDiag = new Date();

        int age = DateUtils.yearsBetween(dtBirth, dtDiag);
        caseHome.getInstance().setAge(age);
    }



    /**
     * Initialize the caseHome object for editing
     */
    public void initializeEditing() {
        if (initialized)
            return;

        prevTBTreatmentHome.setEditing(true);

        TbCase tbcase = caseHome.getInstance();
        Address addr = tbcase.getCurrentAddress();
        if (addr == null)
            tbcase.setCurrentAddress(new Address());
        addr = tbcase.getNotifAddress();
        if (addr == null)
            tbcase.setNotifAddress(new Address());

        if (getTbunitselection().getSelected() == null)
            tbunitselection.setSelected(tbcase.getNotificationUnit());

        if (getNotifAdminUnit().getSelectedUnit() == null)
            notifAdminUnit.setSelectedUnit(tbcase.getNotifAddress().getAdminUnit());
        if (getCurrentAdminUnit().getSelectedUnit() == null)
            currentAdminUnit.setSelectedUnit(tbcase.getCurrentAddress().getAdminUnit());

        updatePatientAge();

        initialized = true;
    }



    /**
     * Check if the register date is valid. If not, return the register date of the case that is already
     * registered in the period
     * @return Register date of the case that is in the date registered
     */
    public Date registerDateValid() {
        if (caseHome.getInstance().getPatient().getId() == null)
            return null;

        Date dt = caseHome.getInstance().getRegistrationDate();

        try {
            // uses a range of one week
            Date dt2 = DateUtils.incDays(dt, -7);
            Date dt3 = DateUtils.incDays(dt, 7);

            return (Date)entityManager.createQuery("select max(registerDate) from TbCase c " +
                    "where c.registerDate = :dt or (c.registerDate <= :dt2 and c.treatmentPeriod.endDate >= :dt2) " +
                    "and c.paitent.id = #{tbcase.patient.id}")
                    .setParameter("dt", dt)
                    .setParameter("dt2", dt2)
                    .setParameter("dt3", dt3)
                    .getSingleResult();
        } catch (Exception e) {
            return null;
        }
    }



    /**
     * Save changes made to a case
     * @return
     */
    public String saveEditing() {
        if (!validateData())
            return "error";

        TbCase tbcase = caseHome.getInstance();

        tbcase.setNotificationUnit(tbunitselection.getSelected());
        tbcase.getNotifAddress().setAdminUnit(notifAdminUnit.getSelectedUnit());
        tbcase.getCurrentAddress().setAdminUnit(currentAdminUnit.getSelectedUnit());

        if (!tbcase.isNotifAddressChanged()) {
            tbcase.getCurrentAddress().copy(tbcase.getNotifAddress());
        }

        prevTBTreatmentHome.persist();

        updatePatientAge();

        String s = caseHome.persist();

        if ("persisted".equals(s)) {
            caseHome.updateCaseTags();
            OwnerUnitChecker.checkOwnerId(tbcase);
        }

        return s;
    }

    /**
     * Save changes made to a case
     * @return
     */
    public String saveEditingWithoutValidation() {
        TbCase tbcase = caseHome.getInstance();

        tbcase.setNotificationUnit(tbunitselection.getSelected());
        tbcase.getNotifAddress().setAdminUnit(notifAdminUnit.getSelectedUnit());
        tbcase.getCurrentAddress().setAdminUnit(currentAdminUnit.getSelectedUnit());

        if (!tbcase.isNotifAddressChanged()) {
            tbcase.getCurrentAddress().copy(tbcase.getNotifAddress());
        }

        prevTBTreatmentHome.persist();

        updatePatientAge();

        String s = caseHome.persist();

        if ("persisted".equals(s)) {
            caseHome.updateCaseTags();
            OwnerUnitChecker.checkOwnerId(tbcase);
        }

        return s;
    }


    /**
     * Start a standard regimen for a case
     */
    protected void startTreatment() {
        if (startTreatmentHome == null)
            return;

        TbCase tbcase = caseHome.getInstance();
        if (startTreatmentHome.getIniTreatmentDate() == null) {
            tbcase.setState(CaseState.WAITING_TREATMENT);
            return;
        }

        startTreatmentHome.getTbunitselection().setSelected(tbcase.getNotificationUnit());
        startTreatmentHome.setUseDefaultDoseUnit(true);
        startTreatmentHome.updatePhases();
        startTreatmentHome.startStandardRegimen();
    }




    /**
     * Register a new TB or MDR-TB case
     * @return "persisted" if successfully registered
     */
    @Transactional
    public String saveNew() {
        if (!validateData())
            return "error";

        TbCase tbcase = caseHome.getInstance();

        // is this a suspect case?
        if (tbcase.getDiagnosisType() == DiagnosisType.SUSPECT) {
            tbcase.setSuspectClassification(tbcase.getClassification());
        }

        // save the patient's data
        patientHome.persist();

        // get notification unit
        tbcase.setNotificationUnit(getTbunitselection().getSelected());
        tbcase.getNotifAddress().setAdminUnit(notifAdminUnit.getSelectedUnit());

        Address notifAddress = tbcase.getNotifAddress();
        Address curAddress = tbcase.getCurrentAddress();
        //curAddress.copy(notifAddress);

        tbcase.setNotifAddress(notifAddress);
        tbcase.setCurrentAddress(curAddress);

        if (tbcase.getValidationState() == null)
            tbcase.setValidationState(ValidationState.WAITING_VALIDATION);

        if (tbcase.getState() == null)
            tbcase.setState(CaseState.WAITING_TREATMENT);

        updatePatientAge();

        //if it is a new case need to set owner before save to avoid error.
        if (tbcase.getId() == null)
            tbcase.setOwnerUnit(tbcase.getNotificationUnit());

        // treatment was defined ?
        caseHome.setTransactionLogActive(true);
        if (!caseHome.persist().equals("persisted"))
            return "error";

        caseHome.setTransactionLogActive(false);

        // define the treatment regimen if it's not individualized (==2)
        if (regimenType != 2)
            startTreatment();

        if (medicalExaminationHome != null) {
            MedicalExamination medExa = medicalExaminationHome.getInstance();
            if (medExa.getDate() != null) {
                medExa.setAppointmentType(MedAppointmentType.SCHEDULLED);
                medExa.setUsingPrescMedicines(YesNoType.YES);
                medicalExaminationHome.persist();
            }
        }

        // save additional information
        if (prevTBTreatmentHome != null)
            prevTBTreatmentHome.persist();

        caseHome.updateCaseTags();
        OwnerUnitChecker.checkOwnerId(tbcase);

        return (regimenType == 2? "individualized": "persisted");
    }

    /**
     * Register a new TB or MDR-TB case
     * @return "persisted" if successfully registered
     */
    @Transactional
    public String saveNewWithoutValidation() {
        TbCase tbcase = caseHome.getInstance();

        // is this a suspect case?
        if (tbcase.getDiagnosisType() == DiagnosisType.SUSPECT) {
            tbcase.setSuspectClassification(tbcase.getClassification());
        }

        // save the patient's data
        patientHome.persist();

        // get notification unit
        tbcase.setNotificationUnit(getTbunitselection().getSelected());
        tbcase.getNotifAddress().setAdminUnit(notifAdminUnit.getSelectedUnit());

        Address notifAddress = tbcase.getNotifAddress();
        Address curAddress = tbcase.getCurrentAddress();
        //curAddress.copy(notifAddress);

        tbcase.setNotifAddress(notifAddress);
        tbcase.setCurrentAddress(curAddress);

        if (tbcase.getValidationState() == null)
            tbcase.setValidationState(ValidationState.WAITING_VALIDATION);

        if (tbcase.getState() == null)
            tbcase.setState(CaseState.WAITING_TREATMENT);

        updatePatientAge();

        // treatment was defined ?
        caseHome.setTransactionLogActive(true);
        if (!caseHome.persist().equals("persisted"))
            return "error";

        caseHome.setTransactionLogActive(false);

        // define the treatment regimen if it's not individualized (==2)
        if (regimenType != 2)
            startTreatment();

        if (medicalExaminationHome != null) {
            MedicalExamination medExa = medicalExaminationHome.getInstance();
            if (medExa.getDate() != null) {
                medExa.setAppointmentType(MedAppointmentType.SCHEDULLED);
                medExa.setUsingPrescMedicines(YesNoType.YES);
                medicalExaminationHome.persist();
            }
        }

        // save additional information
        if (prevTBTreatmentHome != null)
            prevTBTreatmentHome.persist();

        caseHome.updateCaseTags();
        OwnerUnitChecker.checkOwnerId(tbcase);

        return (regimenType == 2? "individualized": "persisted");
    }

    /**
     * Checks if information entered or modified is valid to be recorded
     * @return true if successfully validated, otherwise returns false if fail
     */
    public boolean validateData() {
        TbCase tbcase = caseHome.getInstance();

        if (tbcase.getRegistrationDate() == null) {
            facesMessages.addToControlFromResourceBundle("edtregdate", "javax.faces.component.UIInput.REQUIRED");
            return false;
        }

        if ((tbcase.getDiagnosisType() == DiagnosisType.CONFIRMED) && (tbcase.getDiagnosisDate() == null)) {
            facesMessages.addToControlFromResourceBundle("diagdateedt", "javax.faces.component.UIInput.REQUIRED");
            return false;
        }

        if ((regimenType == 1) && ((startTreatmentHome != null) && (startTreatmentHome.getRegimen() == null))) {
            facesMessages.addToControlFromResourceBundle("cbregimen", "javax.faces.component.UIInput.REQUIRED");
            return false;
        }

        //Checks if the treatment iniDate is before the diagnosis date
        //Workspace configuration allows it?
        if (tbcase.getDiagnosisDate() != null) {
            //treatment has been defined?
            Date iniTreatmentDate = null;
            if(tbcase.getTreatmentPeriod() != null)
                iniTreatmentDate = tbcase.getTreatmentPeriod().getIniDate();
            else if (startTreatmentHome != null && startTreatmentHome.getIniTreatmentDate() != null)
                iniTreatmentDate = startTreatmentHome.getIniTreatmentDate();

            Workspace ws = UserSession.getWorkspace();

            //Validates if defined
            if ((!ws.isAllowDiagAfterTreatment()) && (iniTreatmentDate  != null) && (tbcase.getDiagnosisDate().after(iniTreatmentDate))) {
                facesMessages.addToControlFromResourceBundle("diagdateedt", "cases.treat.inidatemsg");
                return false;
            }

            if ((!ws.isAllowRegAfterDiagnosis()) && (tbcase.getRegistrationDate().after(tbcase.getDiagnosisDate()))) {
                facesMessages.addToControlFromResourceBundle("diagdateedt", "cases.details.valerror1");
                return false;
            }
        }

        if(tbcase.getPatientType() != null && !tbcase.getPatientType().equals(PatientType.PREVIOUSLY_TREATED)){
            tbcase.setPreviouslyTreatedType(null);
        }

        if(tbcase.getClassification().equals(CaseClassification.DRTB)){
            if(tbcase.getLastBmuDateTbRegister() != null && tbcase.getRegistrationDate() != null
                    && tbcase.getLastBmuDateTbRegister().after(tbcase.getRegistrationDate())){
                facesMessages.addToControlFromResourceBundle("bmudateInputDate", StatusMessage.Severity.FATAL ,"cases.details.valerror2");
                return false;
            }
        }

        if(tbcase.getClassification().equals(CaseClassification.TB) && tbcase.getPatientType() != null && tbcase.getPatientType().equals(PatientType.PREVIOUSLY_TREATED)
                && tbcase.getPreviouslyTreatedType() == null){
            facesMessages.addToControlFromResourceBundle("previouslyTreatedType", "javax.faces.component.UIInput.REQUIRED");
            return false;
        }

        return true;
    }

    /**
     * Solve redirecting problem when the user changes diagnosis type but cancel the form
     */
    public String cancel(){
        caseHome.setInstance(null);
        return "cancelcaseediting";
    }


    /**
     * @return the currentAdminUnit
     */
    public AdminUnitSelection getCurrentAdminUnit() {
        if (currentAdminUnit == null)
            currentAdminUnit = new AdminUnitSelection(false);
        return currentAdminUnit;
    }


    /**
     * @return the notifAdminUnit
     */
    public AdminUnitSelection getNotifAdminUnit() {
        if (notifAdminUnit == null)
            notifAdminUnit = new AdminUnitSelection(false);
        return notifAdminUnit;
    }



    public TbCase getTbcase() {
        return caseHome.getInstance();
    }



    public TBUnitSelection getTbunitselection() {
        if (tbunitselection == null) {
            tbunitselection = new TBUnitSelection("newuaid", true, TBUnitType.NOTIFICATION_UNITS);
        }
        return tbunitselection;
    }


    public int getRegimenType() {
        return regimenType;
    }


    public void setRegimenType(int regimenType) {
        this.regimenType = regimenType;
    }

    public PatientHome getPatientHome() {
        return patientHome;
    }

    public PrevTBTreatmentHome getPrevTBTreatmentHome() {
        return prevTBTreatmentHome;
    }

    public MedicalExaminationHome getMedicalExaminationHome() {
        return medicalExaminationHome;
    }

    /**
     * @param tbunitselection the tbunitselection to set
     */
    public void setTbunitselection(TBUnitSelection tbunitselection) {
        this.tbunitselection = tbunitselection;
    }

    public HashMap<String, Object> getBMUcase() {
        if(this.BMUcase == null){
            updateBMUTbCase();
        }else {
            Integer patientId = (Integer) this.BMUcase.get("patientid");
            Date regDate = (Date) this.BMUcase.get("regdate");
            TbCase DRTBcase = caseHome.getInstance();
            Patient DRTBpatient = patientHome.getInstance();

            if (DRTBcase == null)
                return null;

            if (!isSamePatientAndRegDate(DRTBpatient, DRTBcase.getRegistrationDate(), patientId, regDate))
                updateBMUTbCase();
        }
        return BMUcase;
    }

    private boolean isSamePatientAndRegDate(Patient patient, Date regDate, Integer patientIdSearched, Date regDateSearched){
        if(patient.getId() == null)
            return false;

        boolean isSamePatient = patient.getId().equals(patientIdSearched);

        boolean isSameRegDate = false;

        if(regDate == null && regDateSearched == null)
            isSameRegDate = true;
        else if (regDate != null)
            isSameRegDate = regDate.equals(regDateSearched);

        return isSamePatient && isSameRegDate;
    }

    public void setBMUcase(HashMap<String, Object> BMUcase) {
        this.BMUcase = BMUcase;
    }

    public TbCase getBMUTbCaseObject(){
        TbCase BMUcase = (TbCase) getBMUcase().get("BMUTbcase");

        return BMUcase;
    }

    public void updateBMUTbCase(){
        if(BMUcase == null)
            BMUcase = new HashMap<String, Object>();

        //If instance is null, patient is null, or it is not DRTB, no sense to calculate BMUTbcase
        if(caseHome.getInstance()==null || (!caseHome.getInstance().getClassification().equals(CaseClassification.DRTB))
                || caseHome.getInstance().getPatient() == null || caseHome.getInstance().getPatient().getId() == null || caseHome.getInstance().getPatient().getId().equals(0)){
            BMUcase.put("patientid", null);
            BMUcase.put("regdate", null);
            BMUcase.put("BMUTbcase", null);
            return;
        }

        BMUcase.put("patientid", caseHome.getInstance().getPatient().getId());
        BMUcase.put("regdate", caseHome.getInstance().getRegistrationDate());

        Date queryDate = caseHome.getInstance().getRegistrationDate();
        if(queryDate == null)
            queryDate = new Date();

        List<TbCase> cases = (List<TbCase>) entityManager.createQuery("from TbCase c where c.patient.id = :patientId and c.classification = :TB  and c.diagnosisType = :CONFIRMED " +
                " and c.registrationDate < :DRTBRegDate order by c.registrationDate desc")
                .setParameter("patientId", caseHome.getInstance().getPatient().getId())
                .setParameter("TB", CaseClassification.TB)
                .setParameter("CONFIRMED", DiagnosisType.CONFIRMED)
                .setParameter("DRTBRegDate", queryDate)
                .getResultList();

        if(cases == null || cases.size() < 1)
            BMUcase.put("BMUTbcase", null);
        else
            BMUcase.put("BMUTbcase", cases.get(0));
    }

    public void updateBMUinfo(){
        TbCase BMUcase = this.getBMUTbCaseObject();
        if(caseHome.getInstance().getClassification().equals(CaseClassification.DRTB) && BMUcase != null && caseHome.getId() == null){
            caseHome.getInstance().setLastBmuTbRegistNumber(BMUcase.getDisplayCaseNumber());
            caseHome.getInstance().setLastBmuDateTbRegister(BMUcase.getRegistrationDate());
        }
    }
}
